diff --git a/.gitignore b/.gitignore index 39723909c71c28974ce56903c9c52fc1386c3d24..aa62a9fad7104de144269ef1150df344eb638322 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ _testmain.go /src/cmd/internal/objabi/zbootstrap.go /src/go/build/zcgo.go /src/go/doc/headscan +/src/internal/buildcfg/zbootstrap.go /src/runtime/internal/sys/zversion.go /src/unicode/maketables /test.out diff --git a/AUTHORS b/AUTHORS index 4828ba36cc6e3e9de3c18ca81b37687eacbee819..95d3158d204ff0cc73238867aeba27e0d70b0e34 100644 --- a/AUTHORS +++ b/AUTHORS @@ -41,7 +41,7 @@ Aeneas Rekkas (arekkas) Afanasev Stanislav Agis Anastasopoulos Agniva De Sarker -Ahmed Wahed +Ahmed W. Mones Ahmet Soormally Ahmy Yulrizka Aiden Scandella @@ -145,7 +145,7 @@ Andy Davis Andy Finkenstadt Andy Lindeman Andy Maloney -Andy Pan +Andy Pan Andy Walker Anfernee Yongkun Gui Angelo Bulfone @@ -195,7 +195,7 @@ Ayanamist Yang Aymerick Jéhanne Azat Kaumov Baiju Muthukadan -Baokun Lee +Baokun Lee Bartosz Grzybowski Bastian Ike Ben Burkert @@ -1425,6 +1425,7 @@ Wèi Cōngruì Wei Fu Wei Guangjing Weichao Tang +Weixie Cui <523516579@qq.com> Wembley G. Leach, Jr Will Faught Will Storey diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ccbe4627f38753b1b6d212e2aa65e81fb81e5fc7..1984d44c53773624a686c0af48bc1055d4fb0e5a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -33,6 +33,7 @@ Aaron Jacobs Aaron Jensen Aaron Kemp Aaron Patterson +Aaron Sheah Aaron Stein Aaron Torres Aaron Zinman @@ -47,6 +48,7 @@ Adam Harvey Adam Kisala Adam Langley Adam Medzinski +Adam Mitha Adam Shannon Adam Shelton Adam Sindelar @@ -54,6 +56,8 @@ Adam Thomason Adam Williams Adam Woodbeck Adarsh Ravichandran +Adel Rodríguez +Adin Scannell Aditya Harindar Aditya Mukerjee Adrian Hesketh @@ -67,7 +71,8 @@ Aeneas Rekkas (arekkas) Afanasev Stanislav Agis Anastasopoulos Agniva De Sarker -Ahmed Wahed +Ahmed W. Mones +Ahmet Aktürk Ahmet Alp Balkan Ahmet Soormally Ahmy Yulrizka @@ -92,11 +97,13 @@ Alberto Bertogli Alberto Donizetti Alberto García Hierro Alec Benzer +Alejandro García Montoro Aleksa Sarai Aleksandar Dezelin Aleksandr Lukinykh Aleksandr Razumov Alekseev Artem +Aleksei Tirman Alessandro Arzilli Alessandro Baffa Alex A Skinner @@ -165,6 +172,7 @@ Ali Rizvi-Santiago Aliaksandr Valialkin Alice Merrick Alif Rachmawadi +Allan Guwatudde Allan Simon Allen Li Alok Menghrajani @@ -172,6 +180,7 @@ Alwin Doss Aman Gupta Amarjeet Anand Amir Mohammad Saied +Amit Kumar Amr Mohammed Amrut Joshi An Long @@ -185,6 +194,7 @@ André Carvalho André Martins Andre Nathan Andrea Nodari +Andrea Simonini Andrea Spadaccini Andreas Auernhammer Andreas Jellinghaus @@ -240,10 +250,11 @@ Andy Davis Andy Finkenstadt Andy Lindeman Andy Maloney -Andy Pan +Andy Pan Andy Walker Andy Wang Andy Williams +Andy Zhao Andzej Maciusovic Anfernee Yongkun Gui Angelo Bulfone @@ -269,6 +280,7 @@ Anton Kuklin Antonin Amand Antonio Antelo Antonio Bibiano +Antonio Garcia Antonio Huete Jimenez Antonio Murdaca Antonio Troina @@ -292,8 +304,10 @@ Artem Khvastunov Artem Kolin Arthur Fabre Arthur Khashaev +Artur M. Wolff Artyom Pervukhin Arvindh Rajesh Tamilmani +Ashish Bhate Ashish Gandhi Asim Shankar Assel Meher @@ -321,10 +335,11 @@ Azat Kaumov Baiju Muthukadan Balaram Makam Balazs Lecz -Baokun Lee +Baokun Lee Barnaby Keene Bartosz Grzybowski Bartosz Oler +Bassam Ojeil Bastian Ike Ben Burkert Ben Cartwright-Cox @@ -332,6 +347,7 @@ Ben Eitzen Ben Fried Ben Haines Ben Hoyt +Ben Hutchings Ben Kraft Ben Laurie Ben Lubar @@ -430,6 +446,7 @@ Carl Henrik Lunde Carl Jackson Carl Johnson Carl Mastrangelo +Carl Menezes Carl Shapiro Carlisia Campos Carlo Alberto Ferraris @@ -443,6 +460,7 @@ Carlos Iriarte Carlos Souza Carolyn Van Slyck Carrie Bynon +Carson Hoffman Cary Hull Case Nelson Casey Callendrello @@ -462,11 +480,12 @@ Charles Kenney Charles L. Dorian Charles Lee Charles Weill +Charlie Moog Charlotte Brandhorst-Satzkorn Chauncy Cullitan Chen Zhidong Chen Zhihan -Cherry Zhang +Cherry Mui Chew Choon Keat Chiawen Chen Chirag Sukhala @@ -516,6 +535,7 @@ Christopher Nelson Christopher Nielsen Christopher Redden Christopher Swenson +Christopher Thomas <53317512+chrisssthomas@users.noreply.github.com> Christopher Wedgwood Christos Zoulas Christy Perez @@ -541,6 +561,8 @@ Cosmos Nicolaou Costin Chirvasuta Craig Citro Cristian Staretu +Cristo García +cui fliter Cuihtlauac ALVARADO Cuong Manh Le Curtis La Graff @@ -560,6 +582,7 @@ Dan Callahan Dan Harrington Dan Jacques Dan Johnson +Dan McArdle Dan Peterson Dan Pupius Dan Scales @@ -611,6 +634,7 @@ Dave Russell David Anderson David Barnett David Benjamin +David Black David Bond David Brophy David Bürgin <676c7473@gmail.com> @@ -654,6 +678,7 @@ Davor Kapsa Dean Eigenmann <7621705+decanus@users.noreply.github.com> Dean Prichard Deepak Jois +Deepak S Denis Bernard Denis Brandolini Denis Isaev @@ -676,8 +701,10 @@ Dhiru Kholia Dhruvdutt Jadhav Di Xiao Didier Spezia +Diego Medina Diego Siqueira Dieter Plaetinck +Dilyn Corner Dimitri Sokolyuk Dimitri Tcaciuc Dina Garmash @@ -714,6 +741,7 @@ Doug Fawley Douglas Danger Manley Drew Flower Drew Hintz +Drew Richardson Duco van Amstel Duncan Holm Dustin Carlino @@ -735,6 +763,7 @@ Egon Elbre Ehren Kret Eitan Adler Eivind Uggedal +El Mostafa Idrassi Elbert Fliek Eldar Rakhimberdin Elena Grahovac @@ -742,6 +771,7 @@ Eli Bendersky Elias Naur Elliot Morrison-Reed Ellison Leão +Elvina Yakubova Emerson Lin Emil Bektimirov Emil Hessman @@ -767,6 +797,7 @@ Eric Rescorla Eric Roshan-Eisner Eric Rutherford Eric Rykwalder +Eric Wang Erick Tryzelaar Erik Aigner Erik Dubbelboer @@ -778,6 +809,7 @@ Ernest Chiang Erwin Oegema Esko Luontola Ethan Burns +Ethan Hur Ethan Miller Euan Kemp Eugene Formanenko @@ -818,6 +850,7 @@ Felix Cornelius <9767036+fcornelius@users.noreply.github.com> Felix Geisendörfer Felix Kollmann Ferenc Szabo +Fernandez Ludovic Filip Gruszczyński Filip Haglund Filip Stanis @@ -858,6 +891,7 @@ Gabriel Nelle Gabriel Nicolas Avellaneda Gabriel Rosenhouse Gabriel Russell +Gabriel Vasile Gareth Paul Jones Garret Kelly Garrick Evans @@ -891,6 +925,8 @@ Gianguido Sora` Gideon Jan-Wessel Redelinghuys Giles Lean Giovanni Bajo +GitHub User @180909 (70465953) <734461790@qq.com> +GitHub User @6543 (24977596) <6543@obermui.de> GitHub User @aca (50316549) GitHub User @ajnirp (1688456) GitHub User @ajz01 (4744634) @@ -904,10 +940,12 @@ GitHub User @bontequero (2674999) GitHub User @cch123 (384546) GitHub User @chainhelen (7046329) GitHub User @chanxuehong (3416908) +GitHub User @Cluas (10056928) GitHub User @cncal (23520240) GitHub User @DQNEO (188741) GitHub User @Dreamacro (8615343) GitHub User @dupoxy (1143957) +GitHub User @EndlessCheng (7086966) GitHub User @erifan (31343225) GitHub User @esell (9735165) GitHub User @fatedier (7346661) @@ -916,12 +954,15 @@ GitHub User @geedchin (11672310) GitHub User @GrigoriyMikhalkin (3637857) GitHub User @hengwu0 (41297446) <41297446+hengwu0@users.noreply.github.com> GitHub User @hitzhangjie (3725760) +GitHub User @hqpko (13887251) GitHub User @itchyny (375258) GitHub User @jinmiaoluo (39730824) GitHub User @jopbrown (6345470) GitHub User @kazyshr (30496953) GitHub User @kc1212 (1093806) +GitHub User @komisan19 (18901496) GitHub User @Kropekk (13366453) +GitHub User @lhl2617 (33488131) GitHub User @linguohua (3434367) GitHub User @LotusFenn (13775899) GitHub User @ly303550688 (11519839) @@ -936,10 +977,14 @@ GitHub User @OlgaVlPetrova (44112727) GitHub User @pityonline (438222) GitHub User @po3rin (29445112) GitHub User @pokutuna (57545) +GitHub User @povsister (11040951) GitHub User @pytimer (17105586) +GitHub User @qcrao (7698088) GitHub User @ramenjuniti (32011829) GitHub User @saitarunreddy (21041941) +GitHub User @SataQiu (9354727) GitHub User @shogo-ma (9860598) +GitHub User @sivchari (55221074) GitHub User @skanehira (7888591) GitHub User @soolaugust (10558124) GitHub User @surechen (7249331) @@ -947,9 +992,12 @@ GitHub User @tatsumack (4510569) GitHub User @tell-k (26263) GitHub User @tennashi (10219626) GitHub User @uhei (2116845) +GitHub User @uji (49834542) +GitHub User @unbyte (5772358) GitHub User @uropek (39370426) GitHub User @utkarsh-extc (53217283) GitHub User @witchard (4994659) +GitHub User @wolf1996 (5901874) GitHub User @yah01 (12216890) GitHub User @yuanhh (1298735) GitHub User @zikaeroh (48577114) @@ -962,6 +1010,7 @@ Glenn Brown Glenn Lewis Gordon Klaus Gordon Tyler +Grace Han Graham King Graham Miller Grant Griffiths @@ -977,10 +1026,12 @@ Guilherme Caruso Guilherme Garnier Guilherme Goncalves Guilherme Rezende +Guilherme Souza <32180229+gqgs@users.noreply.github.com> Guillaume J. Charmes Guillaume Sottas Günther Noack Guobiao Mei +Guodong Li Guoliang Wang Gustav Paul Gustav Westling @@ -995,6 +1046,7 @@ HAMANO Tsukasa Han-Wen Nienhuys Hang Qian Hanjun Kim +Hanlin He Hanlin Shi Haoran Luo Haosdent Huang @@ -1026,18 +1078,19 @@ Herbie Ong Heschi Kreinick Hidetatsu Yaginuma Hilko Bengen +Himanshu Kishna Srivastava <28himanshu@gmail.com> Hiroaki Nakamura Hiromichi Ema Hironao OTSUBO Hiroshi Ioka Hitoshi Mitake Holden Huang -Songlin Jiang Hong Ruiqi Hongfei Tan Horacio Duran Horst Rutter Hossein Sheikh Attar +Hossein Zolfi Howard Zhang Hsin Tsao Hsin-Ho Yeh @@ -1054,11 +1107,14 @@ Ian Haken Ian Kent Ian Lance Taylor Ian Leue +Ian Mckay Ian Tay +Ian Woolf Ian Zapolsky Ibrahim AshShohail Icarus Sparry Iccha Sethi +Ichinose Shogo Idora Shinatose Ignacio Hagopian Igor Bernstein @@ -1068,6 +1124,7 @@ Igor Vashyst Igor Zhilianin Ikko Ashimine Illya Yalovyy +Ilya Chukov <56119080+Elias506@users.noreply.github.com> Ilya Sinelnikov Ilya Tocar INADA Naoki @@ -1122,6 +1179,7 @@ James Cowgill James Craig Burley James David Chalfant James Eady +James Fennell James Fysh James Gray James Hartig @@ -1178,6 +1236,7 @@ Jason Wangsadinata Javier Kohen Javier Revillas Javier Segura +Jay Chen Jay Conrod Jay Lee Jay Taylor @@ -1200,6 +1259,7 @@ Jeff Johnson Jeff R. Allen Jeff Sickel Jeff Wendling +Jeff Widman Jeffrey H Jelte Fennema Jens Frederich @@ -1210,6 +1270,7 @@ Jeremy Faller Jeremy Jackins Jeremy Jay Jeremy Schlatter +Jero Bado Jeroen Bobbeldijk Jeroen Simonetti Jérôme Doucet @@ -1251,6 +1312,8 @@ Joe Richey Joe Shaw Joe Sylve Joe Tsai +Joel Courtney +Joel Ferrier Joel Sing Joël Stemmer Joel Stemmer @@ -1260,7 +1323,9 @@ Johan Euphrosine Johan Jansson Johan Knutzen Johan Sageryd +Johannes Huning John Asmuth +John Bampton John Beisley John C Barstow John DeNero @@ -1269,6 +1334,7 @@ John Gibb John Gilik John Graham-Cumming John Howard Palevich +John Jago John Jeffery John Jenkins John Leidegren @@ -1320,6 +1386,7 @@ Josa Gesell Jose Luis Vázquez González Joseph Bonneau Joseph Holsten +Joseph Morag Josh Baum Josh Bleecher Snyder Josh Chorlton @@ -1327,12 +1394,14 @@ Josh Deprez Josh Goebel Josh Hoak Josh Holland +Josh Rickmar Josh Roppo Josh Varga Joshua Bezaleel Abednego Joshua Boelter Joshua Chase Joshua Crowgey +Joshua Harshman Joshua M. Clulow Joshua Rubin Josselin Costanzi @@ -1353,6 +1422,7 @@ Julie Qiu Julien Kauffmann Julien Salleyron Julien Schmidt +Julien Tant Julio Montes Jun Zhang Junchen Li @@ -1419,10 +1489,12 @@ Kenta Mori Kerollos Magdy Ketan Parmar Kevan Swanberg +Kevin Albertson Kevin Ballard Kevin Burke Kévin Dunglas Kevin Gillette +Kevin Herro Kevin Kirsche Kevin Klues Kevin Malachowski @@ -1457,6 +1529,7 @@ Koya IWAMURA Kris Kwiatkowski Kris Nova Kris Rousey +Krishna Birla Kristopher Watts Krzysztof Dąbrowski Kshitij Saraogi @@ -1480,6 +1553,7 @@ Lajos Papp Lakshay Garg Lann Martin Lanre Adelowo +Lapo Luchini Larry Clapp Larry Hosken Lars Jeppesen @@ -1496,6 +1570,7 @@ Leigh McCulloch Leo Antunes Leo Rudberg Leon Klingele +Leonard Wang Leonardo Comelli Leonel Quinteros Lev Shamardin @@ -1506,7 +1581,9 @@ Lily Chung Lingchao Xin Lion Yang Liz Rice +Lize Cai Lloyd Dewolf +Lluís Batlle i Rossell Lorenz Bauer Lorenz Brun Lorenz Nickel @@ -1531,6 +1608,7 @@ Lukasz Milewski Luke Champine Luke Curley Luke Granger-Brown +Luke Shumaker Luke Young Luna Duclos Luuk van Dijk @@ -1550,6 +1628,7 @@ Mal Curtis Manfred Touron Manigandan Dharmalingam Manish Goregaokar +Manlio Perillo Manoj Dayaram Mansour Rahimi Manu Garg @@ -1646,6 +1725,8 @@ Matt Joiner Matt Jones Matt Juran Matt Layher +Matt Masurka +Matt Pearring Matt Reiferson Matt Robenolt Matt Strong @@ -1659,9 +1740,12 @@ Matthew Denton Matthew Holt Matthew Horsnell Matthew Waters +Matthias Frei Matthieu Hauglustaine Matthieu Olivier Matthijs Kooijman +Mattias Appelgren +Mauricio Alvarado Max Drosdo.www Max Riveiro Max Schmitt @@ -1677,9 +1761,11 @@ Máximo Cuadros Ortiz Maxwell Krohn Maya Rashish Mayank Kumar +Mehrad Sadeghi <2012.linkinpark@gmail.com> Meir Fischer Meng Zhuo Mhd Sulhan +Mia Zhu Micah Stetson Michael Anthony Knyszek Michael Brandenburg @@ -1730,8 +1816,10 @@ Michal Franc Michał Łowicki Michal Pristas Michal Rostecki +Michal Stokluska Michalis Kargakis Michel Lespinasse +Michel Levieux Michele Di Pede Mickael Kerjean Mickey Reiss @@ -1790,7 +1878,9 @@ Muir Manders Mukesh Sharma Mura Li Mykhailo Lesyk +Nahum Shalman Naman Aggarwal +Naman Gera Nan Deng Nao Yonashiro Naoki Kanatani @@ -1818,6 +1908,7 @@ Neven Sajko Nevins Bartolomeo Niall Sheridan Nic Day +Nicholas Asimov Nicholas Katsaros Nicholas Maniscalco Nicholas Ng @@ -1847,6 +1938,7 @@ Nik Nyby Nikhil Benesch Nikita Gillmann Nikita Kryuchkov +Nikita Melekhin Nikita Vanyasin Niklas Schnelle Niko Dziemba @@ -1858,6 +1950,7 @@ Niranjan Godbole Nishanth Shanmugham Noah Campbell Noah Goldman +Noah Santschi-Cooney Noble Johnson Nodir Turakulov Noel Georgi @@ -1894,6 +1987,7 @@ Pablo Rozas Larraondo Pablo Santiago Blum de Aguiar Padraig Kitterick Pallat Anchaleechamaikorn +Pan Chenglong <1004907659@qq.com> Panos Georgiadis Pantelis Sampaziotis Paolo Giarrusso @@ -1947,6 +2041,7 @@ Paulo Casaretto Paulo Flabiano Smorigo Paulo Gomes Pavel Paulau +Pavel Watson Pavel Zinovkin Pavlo Sumkin Pawel Knap @@ -1954,6 +2049,8 @@ Pawel Szczur Paweł Szulik Pei Xian Chee Pei-Ming Wu +Pen Tree +Peng Gao Percy Wegmann Perry Abbott Petar Dambovaliev @@ -1992,6 +2089,7 @@ Philip Brown Philip Hofer Philip K. Warren Philip Nelson +Philipp Sauter Philipp Stephani Phillip Campbell <15082+phillc@users.noreply.github.com> Pierre Carru @@ -2007,6 +2105,7 @@ Poh Zi How Polina Osadcha Pontus Leitzler Povilas Versockas +Prajwal Koirala <16564273+Prajwal-Koirala@users.noreply.github.com> Prasanga Siripala Prasanna Swaminathan Prashant Agrawal @@ -2027,11 +2126,13 @@ Quim Muntal Quinn Slack Quinten Yearsley Quoc-Viet Nguyen +Rabin Gaire Radek Simko Radek Sohlich Radu Berinde Rafal Jeczalik Raghavendra Nagaraj +Rahul Bajaj Rahul Chaudhry Rahul Wadhwani Raif S. Naffah @@ -2041,12 +2142,14 @@ Rajender Reddy Kompally Ralph Corderoy Ramazan AYYILDIZ Ramesh Dharan +Randy Reddig Raph Levien Raphael Geronimi Raul Silvera Ravil Bikbulatov RaviTeja Pothana Ray Tung +Ray Wu Raymond Kazlauskas Rebecca Stambler Reilly Watson @@ -2066,6 +2169,7 @@ Richard Eric Gavaletz Richard Gibson Richard Miller Richard Musiol +Richard Pickering Richard Ulmer Richard Wilkes Rick Arnold @@ -2124,6 +2228,7 @@ Rowan Worth Rudi Kramer Rui Ueyama Ruixin Bao +Ruslan Andreev Ruslan Nigmatullin Russ Cox Russell Haering @@ -2141,6 +2246,7 @@ Ryan Seys Ryan Slade Ryan Zhang Ryoichi KATO +Ryoya Sekino Ryuji Iwata Ryuma Yoshida Ryuzo Yamamoto @@ -2176,8 +2282,10 @@ Sardorbek Pulatov Sascha Brawer Sasha Lionheart Sasha Sobol +Satoru Kitaguchi Scott Barron Scott Bell +Scott Cotton Scott Crunkleton Scott Ferguson Scott Lawrence @@ -2191,6 +2299,7 @@ Sean Chittenden Sean Christopherson Sean Dolphin Sean Harger +Sean Harrington Sean Hildebrand Sean Liao Sean Rees @@ -2212,6 +2321,7 @@ Sergey Dobrodey Sergey Frolov Sergey Glushchenko Sergey Ivanov +Sergey Kacheev Sergey Lukjanov Sergey Mishin Sergey Mudrik @@ -2223,6 +2333,7 @@ Serhat Giydiren Serhii Aheienko Seth Hoenig Seth Vargo +Shaba Abhiram Shahar Kohanim Shailesh Suryawanshi Shamil Garatuev @@ -2250,9 +2361,13 @@ Shivakumar GN Shivani Singhal Shivansh Rai Shivashis Padhi +Shoshin Nikita +Shota Sugiura Shubham Sharma +Shuhei Takahashi Shun Fan Silvan Jegen +Simão Gomes Viana Simarpreet Singh Simon Drake Simon Ferquel @@ -2267,13 +2382,16 @@ Sina Siadat Sjoerd Siebinga Sokolov Yura Song Gao +Song Lim Songjiayang +Songlin Jiang Soojin Nam Søren L. Hansen Sparrow Li Spencer Kocot Spencer Nelson Spencer Tung +Spenser Black Spring Mc Srdjan Petrovic Sridhar Venkatakrishnan @@ -2324,6 +2442,7 @@ Suyash Suzy Mueller Sven Almgren Sven Blumenstein +Sven Lee Sven Taute Sylvain Zimmer Syohei YOSHIDA @@ -2406,12 +2525,14 @@ Tiwei Bie Tobias Assarsson Tobias Columbus Tobias Klauser +Tobias Kohlbau Toby Burress Todd Kulesza Todd Neal Todd Wang Tom Anthony Tom Bergan +Tom Freudenberg Tom Heng Tom Lanyon Tom Levy @@ -2440,6 +2561,7 @@ Toshiki Shima Totoro W Travis Bischel Travis Cline +Trevor Dixon Trevor Strohman Trey Lawrence Trey Roessig @@ -2463,6 +2585,7 @@ Tzach Shabtay Tzu-Chiao Yeh Tzu-Jung Lee Udalov Max +Uddeshya Singh Ugorji Nwoke Ulf Holm Nielsen Ulrich Kunitz @@ -2475,6 +2598,7 @@ Vadim Grek Vadim Vygonets Val Polouchkine Valentin Vidic +Vaughn Iverson Vee Zhang Vega Garcia Luis Alfonso Venil Noronha @@ -2491,6 +2615,7 @@ Vincent Batts Vincent Vanackere Vinu Rajashekhar Vish Subramanian +Vishal Dalwadi Vishvananda Ishaya Visweswara R Vitaly Zdanevich @@ -2526,6 +2651,7 @@ Wei Guangjing Wei Xiao Wei Xikai Weichao Tang +Weixie Cui <523516579@qq.com> Wembley G. Leach, Jr Wenlei (Frank) He Wenzel Lowe @@ -2541,6 +2667,7 @@ Willem van der Schyff William Chan William Chang William Josephson +William Langford William Orr William Poussier Wisdom Omuya @@ -2549,6 +2676,7 @@ Xi Ruoyao Xia Bin Xiangdong Ji Xiaodong Liu +Xing Gao <18340825824@163.com> Xing Xing Xingqang Bai Xu Fei @@ -2570,6 +2698,7 @@ Yasha Bubnov Yasser Abdolmaleki Yasuharu Goto Yasuhiro Matsumoto +Yasutaka Shinzaki Yasuyuki Oka Yazen Shunnar Yestin Sun @@ -2582,14 +2711,18 @@ Yorman Arias Yoshiyuki Kanno Yoshiyuki Mineo Yosuke Akatsuka +Youfu Zhang Yu Heng Zhang Yu Xuan Zhang +Yu, Li-Yu Yuichi Kishimoto Yuichi Nishiwaki Yuji Yaginuma +Yuki Ito Yuki OKUSHI Yuki Yugui Sonoda Yukihiro Nishinaka <6elpinal@gmail.com> +YunQiang Su Yury Smolsky Yusuke Kagiwada Yuusei Kuwana @@ -2598,6 +2731,7 @@ Yves Junqueira Zac Bergquist Zach Bintliff Zach Gershman +Zach Hoffman Zach Jones Zachary Amsden Zachary Gershman @@ -2616,6 +2750,7 @@ Zhou Peng Ziad Hatahet Ziheng Liu Zorion Arrizabalaga +Zvonimir Pavlinovic Zyad A. Ali Максадбек Ахмедов Максим Федосеев diff --git a/VERSION b/VERSION index ea60ca03a04bcd36650e71e9aa0910f8312a7130..dd2329269cab0d37fac9f13bc5fd73172f7a9abb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -go1.16.3 \ No newline at end of file +go1.17 \ No newline at end of file diff --git a/api/except.txt b/api/except.txt index 6f6f839ba604355c0841fac5d71e26d1937d5782..14fe7785fa54d3bc2a484c992113edf1d13c2d1f 100644 --- a/api/except.txt +++ b/api/except.txt @@ -1,4 +1,7 @@ pkg encoding/json, method (*RawMessage) MarshalJSON() ([]uint8, error) +pkg math, const MaxFloat64 = 1.79769e+308 // 179769313486231570814527423731704356798100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +pkg math, const SmallestNonzeroFloat32 = 1.4013e-45 // 17516230804060213386546619791123951641/12500000000000000000000000000000000000000000000000000000000000000000000000000000000 +pkg math, const SmallestNonzeroFloat64 = 4.94066e-324 // 4940656458412465441765687928682213723651/1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 pkg math/big, const MaxBase = 36 pkg math/big, type Word uintptr pkg net, func ListenUnixgram(string, *UnixAddr) (*UDPConn, error) diff --git a/api/go1.17.txt b/api/go1.17.txt new file mode 100644 index 0000000000000000000000000000000000000000..48505381f1e41f961684e0df5f68b74b7766d902 --- /dev/null +++ b/api/go1.17.txt @@ -0,0 +1,195 @@ +pkg archive/zip, method (*File) OpenRaw() (io.Reader, error) +pkg archive/zip, method (*Writer) Copy(*File) error +pkg archive/zip, method (*Writer) CreateRaw(*FileHeader) (io.Writer, error) +pkg compress/lzw, method (*Reader) Close() error +pkg compress/lzw, method (*Reader) Read([]uint8) (int, error) +pkg compress/lzw, method (*Reader) Reset(io.Reader, Order, int) +pkg compress/lzw, method (*Writer) Close() error +pkg compress/lzw, method (*Writer) Reset(io.Writer, Order, int) +pkg compress/lzw, method (*Writer) Write([]uint8) (int, error) +pkg compress/lzw, type Reader struct +pkg compress/lzw, type Writer struct +pkg crypto/tls, method (*CertificateRequestInfo) Context() context.Context +pkg crypto/tls, method (*ClientHelloInfo) Context() context.Context +pkg crypto/tls, method (*Conn) HandshakeContext(context.Context) error +pkg database/sql, method (*NullByte) Scan(interface{}) error +pkg database/sql, method (*NullInt16) Scan(interface{}) error +pkg database/sql, method (NullByte) Value() (driver.Value, error) +pkg database/sql, method (NullInt16) Value() (driver.Value, error) +pkg database/sql, type NullByte struct +pkg database/sql, type NullByte struct, Byte uint8 +pkg database/sql, type NullByte struct, Valid bool +pkg database/sql, type NullInt16 struct +pkg database/sql, type NullInt16 struct, Int16 int16 +pkg database/sql, type NullInt16 struct, Valid bool +pkg debug/elf, const SHT_MIPS_ABIFLAGS = 1879048234 +pkg debug/elf, const SHT_MIPS_ABIFLAGS SectionType +pkg encoding/csv, method (*Reader) FieldPos(int) (int, int) +pkg go/build, type Context struct, ToolTags []string +pkg go/parser, const SkipObjectResolution = 64 +pkg go/parser, const SkipObjectResolution Mode +pkg image, method (*Alpha) RGBA64At(int, int) color.RGBA64 +pkg image, method (*Alpha) SetRGBA64(int, int, color.RGBA64) +pkg image, method (*Alpha16) RGBA64At(int, int) color.RGBA64 +pkg image, method (*Alpha16) SetRGBA64(int, int, color.RGBA64) +pkg image, method (*CMYK) RGBA64At(int, int) color.RGBA64 +pkg image, method (*CMYK) SetRGBA64(int, int, color.RGBA64) +pkg image, method (*Gray) RGBA64At(int, int) color.RGBA64 +pkg image, method (*Gray) SetRGBA64(int, int, color.RGBA64) +pkg image, method (*Gray16) RGBA64At(int, int) color.RGBA64 +pkg image, method (*Gray16) SetRGBA64(int, int, color.RGBA64) +pkg image, method (*NRGBA) RGBA64At(int, int) color.RGBA64 +pkg image, method (*NRGBA) SetRGBA64(int, int, color.RGBA64) +pkg image, method (*NRGBA64) RGBA64At(int, int) color.RGBA64 +pkg image, method (*NRGBA64) SetRGBA64(int, int, color.RGBA64) +pkg image, method (*NYCbCrA) RGBA64At(int, int) color.RGBA64 +pkg image, method (*Paletted) RGBA64At(int, int) color.RGBA64 +pkg image, method (*Paletted) SetRGBA64(int, int, color.RGBA64) +pkg image, method (*RGBA) RGBA64At(int, int) color.RGBA64 +pkg image, method (*RGBA) SetRGBA64(int, int, color.RGBA64) +pkg image, method (*Uniform) RGBA64At(int, int) color.RGBA64 +pkg image, method (*YCbCr) RGBA64At(int, int) color.RGBA64 +pkg image, method (Rectangle) RGBA64At(int, int) color.RGBA64 +pkg image, type RGBA64Image interface { At, Bounds, ColorModel, RGBA64At } +pkg image, type RGBA64Image interface, At(int, int) color.Color +pkg image, type RGBA64Image interface, Bounds() Rectangle +pkg image, type RGBA64Image interface, ColorModel() color.Model +pkg image, type RGBA64Image interface, RGBA64At(int, int) color.RGBA64 +pkg image/draw, type RGBA64Image interface { At, Bounds, ColorModel, RGBA64At, Set, SetRGBA64 } +pkg image/draw, type RGBA64Image interface, At(int, int) color.Color +pkg image/draw, type RGBA64Image interface, Bounds() image.Rectangle +pkg image/draw, type RGBA64Image interface, ColorModel() color.Model +pkg image/draw, type RGBA64Image interface, RGBA64At(int, int) color.RGBA64 +pkg image/draw, type RGBA64Image interface, Set(int, int, color.Color) +pkg image/draw, type RGBA64Image interface, SetRGBA64(int, int, color.RGBA64) +pkg io/fs, func FileInfoToDirEntry(FileInfo) DirEntry +pkg math, const MaxFloat64 = 1.79769e+308 // 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368 +pkg math, const MaxInt = 9223372036854775807 +pkg math, const MaxInt ideal-int +pkg math, const MaxUint = 18446744073709551615 +pkg math, const MaxUint ideal-int +pkg math, const MinInt = -9223372036854775808 +pkg math, const MinInt ideal-int +pkg math, const SmallestNonzeroFloat32 = 1.4013e-45 // 1/713623846352979940529142984724747568191373312 +pkg math, const SmallestNonzeroFloat64 = 4.94066e-324 // 1/202402253307310618352495346718917307049556649764142118356901358027430339567995346891960383701437124495187077864316811911389808737385793476867013399940738509921517424276566361364466907742093216341239767678472745068562007483424692698618103355649159556340810056512358769552333414615230502532186327508646006263307707741093494784 +pkg net, method (*ParseError) Temporary() bool +pkg net, method (*ParseError) Timeout() bool +pkg net, method (IP) IsPrivate() bool +pkg net/http, func AllowQuerySemicolons(Handler) Handler +pkg net/url, method (Values) Has(string) bool +pkg reflect, func VisibleFields(Type) []StructField +pkg reflect, method (Method) IsExported() bool +pkg reflect, method (StructField) IsExported() bool +pkg reflect, method (Value) CanConvert(Type) bool +pkg runtime/cgo (darwin-amd64-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (darwin-amd64-cgo), method (Handle) Delete() +pkg runtime/cgo (darwin-amd64-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (darwin-amd64-cgo), type Handle uintptr +pkg runtime/cgo (freebsd-386-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (freebsd-386-cgo), method (Handle) Delete() +pkg runtime/cgo (freebsd-386-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (freebsd-386-cgo), type Handle uintptr +pkg runtime/cgo (freebsd-amd64-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (freebsd-amd64-cgo), method (Handle) Delete() +pkg runtime/cgo (freebsd-amd64-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (freebsd-amd64-cgo), type Handle uintptr +pkg runtime/cgo (freebsd-arm-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (freebsd-arm-cgo), method (Handle) Delete() +pkg runtime/cgo (freebsd-arm-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (freebsd-arm-cgo), type Handle uintptr +pkg runtime/cgo (linux-386-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (linux-386-cgo), method (Handle) Delete() +pkg runtime/cgo (linux-386-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (linux-386-cgo), type Handle uintptr +pkg runtime/cgo (linux-amd64-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (linux-amd64-cgo), method (Handle) Delete() +pkg runtime/cgo (linux-amd64-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (linux-amd64-cgo), type Handle uintptr +pkg runtime/cgo (linux-arm-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (linux-arm-cgo), method (Handle) Delete() +pkg runtime/cgo (linux-arm-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (linux-arm-cgo), type Handle uintptr +pkg runtime/cgo (netbsd-386-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (netbsd-386-cgo), method (Handle) Delete() +pkg runtime/cgo (netbsd-386-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (netbsd-386-cgo), type Handle uintptr +pkg runtime/cgo (netbsd-amd64-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (netbsd-amd64-cgo), method (Handle) Delete() +pkg runtime/cgo (netbsd-amd64-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (netbsd-amd64-cgo), type Handle uintptr +pkg runtime/cgo (netbsd-arm-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (netbsd-arm-cgo), method (Handle) Delete() +pkg runtime/cgo (netbsd-arm-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (netbsd-arm-cgo), type Handle uintptr +pkg runtime/cgo (netbsd-arm64-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (netbsd-arm64-cgo), method (Handle) Delete() +pkg runtime/cgo (netbsd-arm64-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (netbsd-arm64-cgo), type Handle uintptr +pkg runtime/cgo (openbsd-386-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (openbsd-386-cgo), method (Handle) Delete() +pkg runtime/cgo (openbsd-386-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (openbsd-386-cgo), type Handle uintptr +pkg runtime/cgo (openbsd-amd64-cgo), func NewHandle(interface{}) Handle +pkg runtime/cgo (openbsd-amd64-cgo), method (Handle) Delete() +pkg runtime/cgo (openbsd-amd64-cgo), method (Handle) Value() interface{} +pkg runtime/cgo (openbsd-amd64-cgo), type Handle uintptr +pkg strconv, func QuotedPrefix(string) (string, error) +pkg sync/atomic, method (*Value) CompareAndSwap(interface{}, interface{}) bool +pkg sync/atomic, method (*Value) Swap(interface{}) interface{} +pkg syscall (netbsd-386), const SYS_WAIT6 = 481 +pkg syscall (netbsd-386), const SYS_WAIT6 ideal-int +pkg syscall (netbsd-386), const WEXITED = 32 +pkg syscall (netbsd-386), const WEXITED ideal-int +pkg syscall (netbsd-386-cgo), const SYS_WAIT6 = 481 +pkg syscall (netbsd-386-cgo), const SYS_WAIT6 ideal-int +pkg syscall (netbsd-386-cgo), const WEXITED = 32 +pkg syscall (netbsd-386-cgo), const WEXITED ideal-int +pkg syscall (netbsd-amd64), const SYS_WAIT6 = 481 +pkg syscall (netbsd-amd64), const SYS_WAIT6 ideal-int +pkg syscall (netbsd-amd64), const WEXITED = 32 +pkg syscall (netbsd-amd64), const WEXITED ideal-int +pkg syscall (netbsd-amd64-cgo), const SYS_WAIT6 = 481 +pkg syscall (netbsd-amd64-cgo), const SYS_WAIT6 ideal-int +pkg syscall (netbsd-amd64-cgo), const WEXITED = 32 +pkg syscall (netbsd-amd64-cgo), const WEXITED ideal-int +pkg syscall (netbsd-arm), const SYS_WAIT6 = 481 +pkg syscall (netbsd-arm), const SYS_WAIT6 ideal-int +pkg syscall (netbsd-arm), const WEXITED = 32 +pkg syscall (netbsd-arm), const WEXITED ideal-int +pkg syscall (netbsd-arm-cgo), const SYS_WAIT6 = 481 +pkg syscall (netbsd-arm-cgo), const SYS_WAIT6 ideal-int +pkg syscall (netbsd-arm-cgo), const WEXITED = 32 +pkg syscall (netbsd-arm-cgo), const WEXITED ideal-int +pkg syscall (netbsd-arm64), const SYS_WAIT6 = 481 +pkg syscall (netbsd-arm64), const SYS_WAIT6 ideal-int +pkg syscall (netbsd-arm64), const WEXITED = 32 +pkg syscall (netbsd-arm64), const WEXITED ideal-int +pkg syscall (netbsd-arm64-cgo), const SYS_WAIT6 = 481 +pkg syscall (netbsd-arm64-cgo), const SYS_WAIT6 ideal-int +pkg syscall (netbsd-arm64-cgo), const WEXITED = 32 +pkg syscall (netbsd-arm64-cgo), const WEXITED ideal-int +pkg syscall (openbsd-386), const MSG_CMSG_CLOEXEC = 2048 +pkg syscall (openbsd-386), const MSG_CMSG_CLOEXEC ideal-int +pkg syscall (openbsd-386-cgo), const MSG_CMSG_CLOEXEC = 2048 +pkg syscall (openbsd-386-cgo), const MSG_CMSG_CLOEXEC ideal-int +pkg syscall (openbsd-amd64), const MSG_CMSG_CLOEXEC = 2048 +pkg syscall (openbsd-amd64), const MSG_CMSG_CLOEXEC ideal-int +pkg syscall (openbsd-amd64-cgo), const MSG_CMSG_CLOEXEC = 2048 +pkg syscall (openbsd-amd64-cgo), const MSG_CMSG_CLOEXEC ideal-int +pkg syscall (windows-386), type SysProcAttr struct, AdditionalInheritedHandles []Handle +pkg syscall (windows-386), type SysProcAttr struct, ParentProcess Handle +pkg syscall (windows-amd64), type SysProcAttr struct, AdditionalInheritedHandles []Handle +pkg syscall (windows-amd64), type SysProcAttr struct, ParentProcess Handle +pkg testing, method (*B) Setenv(string, string) +pkg testing, method (*T) Setenv(string, string) +pkg testing, type TB interface, Setenv(string, string) +pkg text/template/parse, const SkipFuncCheck = 2 +pkg text/template/parse, const SkipFuncCheck Mode +pkg time, const Layout = "01/02 03:04:05PM '06 -0700" +pkg time, const Layout ideal-string +pkg time, func UnixMicro(int64) Time +pkg time, func UnixMilli(int64) Time +pkg time, method (Time) GoString() string +pkg time, method (Time) IsDST() bool +pkg time, method (Time) UnixMicro() int64 +pkg time, method (Time) UnixMilli() int64 diff --git a/codereview.cfg b/codereview.cfg new file mode 100644 index 0000000000000000000000000000000000000000..9d5ebe82eb1ff710dd454dc32286d12b61258f7e --- /dev/null +++ b/codereview.cfg @@ -0,0 +1,2 @@ +branch: release-branch.go1.17 +parent-branch: master diff --git a/doc/asm.html b/doc/asm.html index 7173d9bd514fb2c5ecc3417cffa1a6d84feba4f2..51f85eb94822a4a900bfbb205f5cbcc709e5a2f8 100644 --- a/doc/asm.html +++ b/doc/asm.html @@ -166,7 +166,7 @@ jumps and branches.
  • -SP: Stack pointer: top of stack. +SP: Stack pointer: the highest address within the local stack frame.
  • @@ -216,7 +216,7 @@ If a Go prototype does not name its result, the expected assembly name is The SP pseudo-register is a virtual stack pointer used to refer to frame-local variables and the arguments being prepared for function calls. -It points to the top of the local stack frame, so references should use negative offsets +It points to the highest address within the local stack frame, so references should use negative offsets in the range [−framesize, 0): x-8(SP), y-4(SP), and so on.

    @@ -409,7 +409,7 @@ The linker will choose one of the duplicates to use. (For TEXT items.) Don't insert the preamble to check if the stack must be split. The frame for the routine, plus anything it calls, must fit in the -spare space at the top of the stack segment. +spare space remaining in the current stack segment. Used to protect routines such as the stack splitting code itself.
  • @@ -460,7 +460,7 @@ Only valid on functions that declare a frame size of 0. TOPFRAME = 2048
    (For TEXT items.) -Function is the top of the call stack. Traceback should stop at this function. +Function is the outermost frame of the call stack. Traceback should stop at this function.
  • @@ -827,10 +827,6 @@ The other codes are -> (arithmetic right shift),

    ARM64

    -

    -The ARM64 port is in an experimental state. -

    -

    R18 is the "platform register", reserved on the Apple platform. To prevent accidental misuse, the register is named R18_PLATFORM. diff --git a/doc/go1.16.html b/doc/go1.16.html deleted file mode 100644 index 0beb62d160e0b98e9b0bcfd87168ce58966c931d..0000000000000000000000000000000000000000 --- a/doc/go1.16.html +++ /dev/null @@ -1,1220 +0,0 @@ - - - - - - -

    Introduction to Go 1.16

    - -

    - The latest Go release, version 1.16, arrives six months after Go 1.15. - Most of its changes are in the implementation of the toolchain, runtime, and libraries. - As always, the release maintains the Go 1 promise of compatibility. - We expect almost all Go programs to continue to compile and run as before. -

    - -

    Changes to the language

    - -

    - There are no changes to the language. -

    - -

    Ports

    - -

    Darwin and iOS

    - -

    - Go 1.16 adds support of 64-bit ARM architecture on macOS (also known as - Apple Silicon) with GOOS=darwin, GOARCH=arm64. - Like the darwin/amd64 port, the darwin/arm64 - port supports cgo, internal and external linking, c-archive, - c-shared, and pie build modes, and the race - detector. -

    - -

    - The iOS port, which was previously darwin/arm64, has - been renamed to ios/arm64. GOOS=ios - implies the - darwin build tag, just as GOOS=android - implies the linux build tag. This change should be - transparent to anyone using gomobile to build iOS apps. -

    - -

    - Go 1.16 adds an ios/amd64 port, which targets the iOS - simulator running on AMD64-based macOS. Previously this was - unofficially supported through darwin/amd64 with - the ios build tag set. See also - misc/ios/README for - details about how to build programs for iOS and iOS simulator. -

    - -

    - Go 1.16 is the last release that will run on macOS 10.12 Sierra. - Go 1.17 will require macOS 10.13 High Sierra or later. -

    - -

    NetBSD

    - -

    - Go now supports the 64-bit ARM architecture on NetBSD (the - netbsd/arm64 port). -

    - -

    OpenBSD

    - -

    - Go now supports the MIPS64 architecture on OpenBSD - (the openbsd/mips64 port). This port does not yet - support cgo. -

    - -

    - On the 64-bit x86 and 64-bit ARM architectures on OpenBSD (the - openbsd/amd64 and openbsd/arm64 ports), system - calls are now made through libc, instead of directly using - the SYSCALL/SVC instruction. This ensures - forward-compatibility with future versions of OpenBSD. In particular, - OpenBSD 6.9 onwards will require system calls to be made through - libc for non-static Go binaries. -

    - -

    386

    - -

    - As announced in the Go 1.15 release notes, - Go 1.16 drops support for x87 mode compilation (GO386=387). - Support for non-SSE2 processors is now available using soft float - mode (GO386=softfloat). - Users running on non-SSE2 processors should replace GO386=387 - with GO386=softfloat. -

    - -

    RISC-V

    - -

    - The linux/riscv64 port now supports cgo and - -buildmode=pie. This release also includes performance - optimizations and code generation improvements for RISC-V. -

    - -

    Tools

    - -

    Go command

    - -

    Modules

    - -

    - Module-aware mode is enabled by default, regardless of whether a - go.mod file is present in the current working directory or a - parent directory. More precisely, the GO111MODULE environment - variable now defaults to on. To switch to the previous behavior, - set GO111MODULE to auto. -

    - -

    - Build commands like go build and go - test no longer modify go.mod and go.sum - by default. Instead, they report an error if a module requirement or checksum - needs to be added or updated (as if the -mod=readonly flag were - used). Module requirements and sums may be adjusted with go - mod tidy or go get. -

    - -

    - go install now accepts arguments with - version suffixes (for example, go install - example.com/cmd@v1.0.0). This causes go - install to build and install packages in module-aware mode, - ignoring the go.mod file in the current directory or any parent - directory, if there is one. This is useful for installing executables without - affecting the dependencies of the main module. -

    - -

    - go install, with or without a version suffix (as - described above), is now the recommended way to build and install packages in - module mode. go get should be used with the - -d flag to adjust the current module's dependencies without - building packages, and use of go get to build and - install packages is deprecated. In a future release, the -d flag - will always be enabled. -

    - -

    - retract directives may now be used in a go.mod file - to indicate that certain published versions of the module should not be used - by other modules. A module author may retract a version after a severe problem - is discovered or if the version was published unintentionally. -

    - -

    - The go mod vendor - and go mod tidy subcommands now accept - the -e flag, which instructs them to proceed despite errors in - resolving missing packages. -

    - -

    - The go command now ignores requirements on module versions - excluded by exclude directives in the main module. Previously, - the go command used the next version higher than an excluded - version, but that version could change over time, resulting in - non-reproducible builds. -

    - -

    - In module mode, the go command now disallows import paths that - include non-ASCII characters or path elements with a leading dot character - (.). Module paths with these characters were already disallowed - (see Module paths and versions), - so this change affects only paths within module subdirectories. -

    - -

    Embedding Files

    - -

    - The go command now supports including - static files and file trees as part of the final executable, - using the new //go:embed directive. - See the documentation for the new - embed - package for details. -

    - -

    go test

    - -

    - When using go test, a test that - calls os.Exit(0) during execution of a test function - will now be considered to fail. - This will help catch cases in which a test calls code that calls - os.Exit(0) and thereby stops running all future tests. - If a TestMain function calls os.Exit(0) - that is still considered to be a passing test. -

    - -

    - go test reports an error when the -c - or -i flags are used together with unknown flags. Normally, - unknown flags are passed to tests, but when -c or -i - are used, tests are not run. -

    - -

    go get

    - -

    - The go get -insecure flag is - deprecated and will be removed in a future version. This flag permits - fetching from repositories and resolving custom domains using insecure - schemes such as HTTP, and also bypasses module sum validation using the - checksum database. To permit the use of insecure schemes, use the - GOINSECURE environment variable instead. To bypass module - sum validation, use GOPRIVATE or GONOSUMDB. - See go help environment for details. -

    - -

    - go get example.com/mod@patch now - requires that some version of example.com/mod already be - required by the main module. - (However, go get -u=patch continues - to patch even newly-added dependencies.) -

    - -

    GOVCS environment variable

    - -

    - GOVCS is a new environment variable that limits which version - control tools the go command may use to download source code. - This mitigates security issues with tools that are typically used in trusted, - authenticated environments. By default, git and hg - may be used to download code from any repository. svn, - bzr, and fossil may only be used to download code - from repositories with module paths or package paths matching patterns in - the GOPRIVATE environment variable. See - go - help vcs for details. -

    - -

    The all pattern

    - -

    - When the main module's go.mod file - declares go 1.16 or higher, the all - package pattern now matches only those packages that are transitively imported - by a package or test found in the main module. (Packages imported by tests - of packages imported by the main module are no longer included.) This is - the same set of packages retained - by go mod vendor since Go 1.11. -

    - -

    The -toolexec build flag

    - -

    - When the -toolexec build flag is specified to use a program when - invoking toolchain programs like compile or asm, the environment variable - TOOLEXEC_IMPORTPATH is now set to the import path of the package - being built. -

    - -

    The -i build flag

    - -

    - The -i flag accepted by go build, - go install, and go test is - now deprecated. The -i flag instructs the go command - to install packages imported by packages named on the command line. Since - the build cache was introduced in Go 1.10, the -i flag no longer - has a significant effect on build times, and it causes errors when the install - directory is not writable. -

    - -

    The list command

    - -

    - When the -export flag is specified, the BuildID - field is now set to the build ID of the compiled package. This is equivalent - to running go tool buildid on - go list -exported -f {{.Export}}, - but without the extra step. -

    - -

    The -overlay flag

    - -

    - The -overlay flag specifies a JSON configuration file containing - a set of file path replacements. The -overlay flag may be used - with all build commands and go mod subcommands. - It is primarily intended to be used by editor tooling such as gopls to - understand the effects of unsaved changes to source files. The config file - maps actual file paths to replacement file paths and the go - command and its builds will run as if the actual file paths exist with the - contents given by the replacement file paths, or don't exist if the replacement - file paths are empty. -

    - -

    Cgo

    - -

    - The cgo tool will no longer try to translate - C struct bitfields into Go struct fields, even if their size can be - represented in Go. The order in which C bitfields appear in memory - is implementation dependent, so in some cases the cgo tool produced - results that were silently incorrect. -

    - -

    Vet

    - -

    New warning for invalid testing.T use in -goroutines

    - -

    - The vet tool now warns about invalid calls to the testing.T - method Fatal from within a goroutine created during the test. - This also warns on calls to Fatalf, FailNow, and - Skip{,f,Now} methods on testing.T tests or - testing.B benchmarks. -

    - -

    - Calls to these methods stop the execution of the created goroutine and not - the Test* or Benchmark* function. So these are - required to be called by the goroutine - running the test or benchmark function. For example: -

    - -
    -func TestFoo(t *testing.T) {
    -    go func() {
    -        if condition() {
    -            t.Fatal("oops") // This exits the inner func instead of TestFoo.
    -        }
    -        ...
    -    }()
    -}
    -
    - -

    - Code calling t.Fatal (or a similar method) from a created - goroutine should be rewritten to signal the test failure using - t.Error and exit the goroutine early using an alternative - method, such as using a return statement. The previous example - could be rewritten as: -

    - -
    -func TestFoo(t *testing.T) {
    -    go func() {
    -        if condition() {
    -            t.Error("oops")
    -            return
    -        }
    -        ...
    -    }()
    -}
    -
    - -

    New warning for frame pointer

    - -

    - The vet tool now warns about amd64 assembly that clobbers the BP - register (the frame pointer) without saving and restoring it, - contrary to the calling convention. Code that doesn't preserve the - BP register must be modified to either not use BP at all or preserve - BP by saving and restoring it. An easy way to preserve BP is to set - the frame size to a nonzero value, which causes the generated - prologue and epilogue to preserve the BP register for you. - See CL 248260 for example - fixes. -

    - -

    New warning for asn1.Unmarshal

    - -

    - The vet tool now warns about incorrectly passing a non-pointer or nil argument to - asn1.Unmarshal. - This is like the existing checks for - encoding/json.Unmarshal - and encoding/xml.Unmarshal. -

    - -

    Runtime

    - -

    - The new runtime/metrics package - introduces a stable interface for reading - implementation-defined metrics from the Go runtime. - It supersedes existing functions like - runtime.ReadMemStats - and - debug.GCStats - and is significantly more general and efficient. - See the package documentation for more details. -

    - -

    - Setting the GODEBUG environment variable - to inittrace=1 now causes the runtime to emit a single - line to standard error for each package init, - summarizing its execution time and memory allocation. This trace can - be used to find bottlenecks or regressions in Go startup - performance. - The GODEBUG - documentation describes the format. -

    - -

    - On Linux, the runtime now defaults to releasing memory to the - operating system promptly (using MADV_DONTNEED), rather - than lazily when the operating system is under memory pressure - (using MADV_FREE). This means process-level memory - statistics like RSS will more accurately reflect the amount of - physical memory being used by Go processes. Systems that are - currently using GODEBUG=madvdontneed=1 to improve - memory monitoring behavior no longer need to set this environment - variable. -

    - -

    - Go 1.16 fixes a discrepancy between the race detector and - the Go memory model. The race detector now - more precisely follows the channel synchronization rules of the - memory model. As a result, the detector may now report races it - previously missed. -

    - -

    Compiler

    - -

    - The compiler can now inline functions with - non-labeled for loops, method values, and type - switches. The inliner can also detect more indirect calls where - inlining is possible. -

    - -

    Linker

    - -

    - This release includes additional improvements to the Go linker, - reducing linker resource usage (both time and memory) and improving - code robustness/maintainability. These changes form the second half - of a two-release project to - modernize the Go - linker. -

    - -

    - The linker changes in 1.16 extend the 1.15 improvements to all - supported architecture/OS combinations (the 1.15 performance improvements - were primarily focused on ELF-based OSes and - amd64 architectures). For a representative set of - large Go programs, linking is 20-25% faster than 1.15 and requires - 5-15% less memory on average for linux/amd64, with larger - improvements for other architectures and OSes. Most binaries are - also smaller as a result of more aggressive symbol pruning. -

    - -

    - On Windows, go build -buildmode=c-shared now generates Windows - ASLR DLLs by default. ASLR can be disabled with --ldflags=-aslr=false. -

    - -

    Core library

    - -

    Embedded Files

    - -

    - The new embed package - provides access to files embedded in the program during compilation - using the new //go:embed directive. -

    - -

    File Systems

    - -

    - The new io/fs package - defines the fs.FS interface, - an abstraction for read-only trees of files. - The standard library packages have been adapted to make use - of the interface as appropriate. -

    - -

    - On the producer side of the interface, - the new embed.FS type - implements fs.FS, as does - zip.Reader. - The new os.DirFS function - provides an implementation of fs.FS backed by a tree - of operating system files. -

    - -

    - On the consumer side, - the new http.FS - function converts an fs.FS to an - http.FileSystem. - Also, the html/template - and text/template - packages’ ParseFS - functions and methods read templates from an fs.FS. -

    - -

    - For testing code that implements fs.FS, - the new testing/fstest - package provides a TestFS - function that checks for and reports common mistakes. - It also provides a simple in-memory file system implementation, - MapFS, - which can be useful for testing code that accepts fs.FS - implementations. -

    - -

    Deprecation of io/ioutil

    - -

    - The io/ioutil package has - turned out to be a poorly defined and hard to understand collection - of things. All functionality provided by the package has been moved - to other packages. The io/ioutil package remains and - will continue to work as before, but we encourage new code to use - the new definitions in the io and - os packages. - - Here is a list of the new locations of the names exported - by io/ioutil: -

    -

    - - - -

    Minor changes to the library

    - -

    - As always, there are various minor changes and updates to the library, - made with the Go 1 promise of compatibility - in mind. -

    - -
    archive/zip
    -
    -

    - The new Reader.Open - method implements the fs.FS - interface. -

    -
    -
    - -
    crypto/dsa
    -
    -

    - The crypto/dsa package is now deprecated. - See issue #40337. -

    -
    -
    - -
    crypto/hmac
    -
    -

    - New will now panic if - separate calls to the hash generation function fail to return new values. - Previously, the behavior was undefined and invalid outputs were sometimes - generated. -

    -
    -
    - -
    crypto/tls
    -
    -

    - I/O operations on closing or closed TLS connections can now be detected - using the new net.ErrClosed - error. A typical use would be errors.Is(err, net.ErrClosed). -

    - -

    - A default write deadline is now set in - Conn.Close - before sending the "close notify" alert, in order to prevent blocking - indefinitely. -

    - -

    - Clients now return a handshake error if the server selects - - an ALPN protocol that was not in - - the list advertised by the client. -

    - -

    - Servers will now prefer other available AEAD cipher suites (such as ChaCha20Poly1305) - over AES-GCM cipher suites if either the client or server doesn't have AES hardware - support, unless both - Config.PreferServerCipherSuites - and Config.CipherSuites - are set. The client is assumed not to have AES hardware support if it does - not signal a preference for AES-GCM cipher suites. -

    - -

    - Config.Clone now - returns nil if the receiver is nil, rather than panicking. -

    -
    -
    - -
    crypto/x509
    -
    -

    - The GODEBUG=x509ignoreCN=0 flag will be removed in Go 1.17. - It enables the legacy behavior of treating the CommonName - field on X.509 certificates as a host name when no Subject Alternative - Names are present. -

    - -

    - ParseCertificate and - CreateCertificate - now enforce string encoding restrictions for the DNSNames, - EmailAddresses, and URIs fields. These fields - can only contain strings with characters within the ASCII range. -

    - -

    - CreateCertificate - now verifies the generated certificate's signature using the signer's - public key. If the signature is invalid, an error is returned, instead of - a malformed certificate. -

    - -

    - DSA signature verification is no longer supported. Note that DSA signature - generation was never supported. - See issue #40337. -

    - -

    - On Windows, Certificate.Verify - will now return all certificate chains that are built by the platform - certificate verifier, instead of just the highest ranked chain. -

    - -

    - The new SystemRootsError.Unwrap - method allows accessing the Err - field through the errors package functions. -

    - -

    - On Unix systems, the crypto/x509 package is now more - efficient in how it stores its copy of the system cert pool. - Programs that use only a small number of roots will use around a - half megabyte less memory. -

    - -
    -
    - -
    debug/elf
    -
    -

    - More DT - and PT - constants have been added. -

    -
    -
    - -
    encoding/asn1
    -
    -

    - Unmarshal and - UnmarshalWithParams - now return an error instead of panicking when the argument is not - a pointer or is nil. This change matches the behavior of other - encoding packages such as encoding/json. -

    -
    -
    - -
    encoding/json
    -
    -

    - The json struct field tags understood by - Marshal, - Unmarshal, - and related functionality now permit semicolon characters within - a JSON object name for a Go struct field. -

    -
    -
    - -
    encoding/xml
    -
    -

    - The encoder has always taken care to avoid using namespace prefixes - beginning with xml, which are reserved by the XML - specification. - Now, following the specification more closely, that check is - case-insensitive, so that prefixes beginning - with XML, XmL, and so on are also - avoided. -

    -
    -
    - -
    flag
    -
    -

    - The new Func function - allows registering a flag implemented by calling a function, - as a lighter-weight alternative to implementing the - Value interface. -

    -
    -
    - -
    go/build
    -
    -

    - The Package - struct has new fields that report information - about //go:embed directives in the package: - EmbedPatterns, - EmbedPatternPos, - TestEmbedPatterns, - TestEmbedPatternPos, - XTestEmbedPatterns, - XTestEmbedPatternPos. -

    - -

    - The Package field - IgnoredGoFiles - will no longer include files that start with "_" or ".", - as those files are always ignored. - IgnoredGoFiles is for files ignored because of - build constraints. -

    - -

    - The new Package - field IgnoredOtherFiles - has a list of non-Go files ignored because of build constraints. -

    -
    -
    - -
    go/build/constraint
    -
    -

    - The new - go/build/constraint - package parses build constraint lines, both the original - // +build syntax and the //go:build - syntax that will be introduced in Go 1.17. - This package exists so that tools built with Go 1.16 will be able - to process Go 1.17 source code. - See https://golang.org/design/draft-gobuild - for details about the build constraint syntaxes and the planned - transition to the //go:build syntax. - Note that //go:build lines are not supported - in Go 1.16 and should not be introduced into Go programs yet. -

    -
    -
    - -
    html/template
    -
    -

    - The new template.ParseFS - function and template.Template.ParseFS - method are like template.ParseGlob - and template.Template.ParseGlob, - but read the templates from an fs.FS. -

    -
    -
    - -
    io
    -
    -

    - The package now defines a - ReadSeekCloser interface. -

    - -

    - The package now defines - Discard, - NopCloser, and - ReadAll, - to be used instead of the same names in the - io/ioutil package. -

    -
    -
    - -
    log
    -
    -

    - The new Default function - provides access to the default Logger. -

    -
    -
    - -
    log/syslog
    -
    -

    - The Writer - now uses the local message format - (omitting the host name and using a shorter time stamp) - when logging to custom Unix domain sockets, - matching the format already used for the default log socket. -

    -
    -
    - -
    mime/multipart
    -
    -

    - The Reader's - ReadForm - method no longer rejects form data - when passed the maximum int64 value as a limit. -

    -
    -
    - -
    net
    -
    -

    - The case of I/O on a closed network connection, or I/O on a network - connection that is closed before any of the I/O completes, can now - be detected using the new ErrClosed - error. A typical use would be errors.Is(err, net.ErrClosed). - In earlier releases the only way to reliably detect this case was to - match the string returned by the Error method - with "use of closed network connection". -

    - -

    - In previous Go releases the default TCP listener backlog size on Linux systems, - set by /proc/sys/net/core/somaxconn, was limited to a maximum of 65535. - On Linux kernel version 4.1 and above, the maximum is now 4294967295. -

    - -

    - On Linux, host name lookups no longer use DNS before checking - /etc/hosts when /etc/nsswitch.conf - is missing; this is common on musl-based systems and makes - Go programs match the behavior of C programs on those systems. -

    -
    -
    - -
    net/http
    -
    -

    - In the net/http package, the - behavior of StripPrefix - has been changed to strip the prefix from the request URL's - RawPath field in addition to its Path field. - In past releases, only the Path field was trimmed, and so if the - request URL contained any escaped characters the URL would be modified to - have mismatched Path and RawPath fields. - In Go 1.16, StripPrefix trims both fields. - If there are escaped characters in the prefix part of the request URL the - handler serves a 404 instead of its previous behavior of invoking the - underlying handler with a mismatched Path/RawPath pair. -

    - -

    - The net/http package now rejects HTTP range requests - of the form "Range": "bytes=--N" where "-N" is a negative suffix length, for - example "Range": "bytes=--2". It now replies with a 416 "Range Not Satisfiable" response. -

    - -

    - Cookies set with SameSiteDefaultMode - now behave according to the current spec (no attribute is set) instead of - generating a SameSite key without a value. -

    - -

    - The Client now sends - an explicit Content-Length: 0 - header in PATCH requests with empty bodies, - matching the existing behavior of POST and PUT. -

    - -

    - The ProxyFromEnvironment - function no longer returns the setting of the HTTP_PROXY - environment variable for https:// URLs when - HTTPS_PROXY is unset. -

    - -

    - The Transport - type has a new field - GetProxyConnectHeader - which may be set to a function that returns headers to send to a - proxy during a CONNECT request. - In effect GetProxyConnectHeader is a dynamic - version of the existing field - ProxyConnectHeader; - if GetProxyConnectHeader is not nil, - then ProxyConnectHeader is ignored. -

    - -

    - The new http.FS - function converts an fs.FS - to an http.FileSystem. -

    -
    -
    - -
    net/http/httputil
    -
    -

    - ReverseProxy - now flushes buffered data more aggressively when proxying - streamed responses with unknown body lengths. -

    -
    -
    - -
    net/smtp
    -
    -

    - The Client's - Mail - method now sends the SMTPUTF8 directive to - servers that support it, signaling that addresses are encoded in UTF-8. -

    -
    -
    - -
    os
    -
    -

    - Process.Signal now - returns ErrProcessDone - instead of the unexported errFinished when the process has - already finished. -

    - -

    - The package defines a new type - DirEntry - as an alias for fs.DirEntry. - The new ReadDir - function and the new - File.ReadDir - method can be used to read the contents of a directory into a - slice of DirEntry. - The File.Readdir - method (note the lower case d in dir) - still exists, returning a slice of - FileInfo, but for - most programs it will be more efficient to switch to - File.ReadDir. -

    - -

    - The package now defines - CreateTemp, - MkdirTemp, - ReadFile, and - WriteFile, - to be used instead of functions defined in the - io/ioutil package. -

    - -

    - The types FileInfo, - FileMode, and - PathError - are now aliases for types of the same name in the - io/fs package. - Function signatures in the os - package have been updated to refer to the names in the - io/fs package. - This should not affect any existing code. -

    - -

    - The new DirFS function - provides an implementation of - fs.FS backed by a tree - of operating system files. -

    -
    -
    - -
    os/signal
    -
    -

    - The new - NotifyContext - function allows creating contexts that are canceled upon arrival of - specific signals. -

    -
    -
    - -
    path
    -
    -

    - The Match function now - returns an error if the unmatched part of the pattern has a - syntax error. Previously, the function returned early on a failed - match, and thus did not report any later syntax error in the - pattern. -

    -
    -
    - -
    path/filepath
    -
    -

    - The new function - WalkDir - is similar to - Walk, - but is typically more efficient. - The function passed to WalkDir receives a - fs.DirEntry - instead of a - fs.FileInfo. - (To clarify for those who recall the Walk function - as taking an os.FileInfo, - os.FileInfo is now an alias for fs.FileInfo.) -

    - -

    - The Match and - Glob functions now - return an error if the unmatched part of the pattern has a - syntax error. Previously, the functions returned early on a failed - match, and thus did not report any later syntax error in the - pattern. -

    -
    -
    - -
    runtime/debug
    -
    -

    - The runtime.Error values - used when SetPanicOnFault is enabled may now have an - Addr method. If that method exists, it returns the memory - address that triggered the fault. -

    -
    -
    - -
    strconv
    -
    -

    - ParseFloat now uses - the Eisel-Lemire - algorithm, improving performance by up to a factor of 2. This can - also speed up decoding textual formats like encoding/json. -

    -
    -
    - -
    syscall
    -
    -

    - NewCallback - and - NewCallbackCDecl - now correctly support callback functions with multiple - sub-uintptr-sized arguments in a row. This may - require changing uses of these functions to eliminate manual - padding between small arguments. -

    - -

    - SysProcAttr on Windows has a new NoInheritHandles field that disables inheriting handles when creating a new process. -

    - -

    - DLLError on Windows now has an Unwrap method for unwrapping its underlying error. -

    - -

    - On Linux, - Setgid, - Setuid, - and related calls are now implemented. - Previously, they returned an syscall.EOPNOTSUPP error. -

    - -

    - On Linux, the new functions - AllThreadsSyscall - and AllThreadsSyscall6 - may be used to make a system call on all Go threads in the process. - These functions may only be used by programs that do not use cgo; - if a program uses cgo, they will always return - syscall.ENOTSUP. -

    -
    -
    - -
    testing/iotest
    -
    -

    - The new - ErrReader - function returns an - io.Reader that always - returns an error. -

    - -

    - The new - TestReader - function tests that an io.Reader - behaves correctly. -

    -
    -
    - -
    text/template
    -
    -

    - Newlines characters are now allowed inside action delimiters, - permitting actions to span multiple lines. -

    - -

    - The new template.ParseFS - function and template.Template.ParseFS - method are like template.ParseGlob - and template.Template.ParseGlob, - but read the templates from an fs.FS. -

    -
    -
    - -
    text/template/parse
    -
    -

    - A new CommentNode - was added to the parse tree. The Mode - field in the parse.Tree enables access to it. -

    -
    -
    - -
    time/tzdata
    -
    -

    - The slim timezone data format is now used for the timezone database in - $GOROOT/lib/time/zoneinfo.zip and the embedded copy in this - package. This reduces the size of the timezone database by about 350 KB. -

    -
    -
    - -
    unicode
    -
    -

    - The unicode package and associated - support throughout the system has been upgraded from Unicode 12.0.0 to - Unicode 13.0.0, - which adds 5,930 new characters, including four new scripts, and 55 new emoji. - Unicode 13.0.0 also designates plane 3 (U+30000-U+3FFFF) as the tertiary - ideographic plane. -

    -
    -
    diff --git a/doc/go1.17.html b/doc/go1.17.html new file mode 100644 index 0000000000000000000000000000000000000000..c1b5ab3f6fba73d3eb37b93ebd20420e3de4109b --- /dev/null +++ b/doc/go1.17.html @@ -0,0 +1,1240 @@ + + + + + + +

    Introduction to Go 1.17

    + +

    + The latest Go release, version 1.17, arrives six months after Go 1.16. + Most of its changes are in the implementation of the toolchain, runtime, and libraries. + As always, the release maintains the Go 1 promise of compatibility. + We expect almost all Go programs to continue to compile and run as before. +

    + +

    Changes to the language

    + +

    + Go 1.17 includes three small enhancements to the language. +

    + +
      +
    • + Conversions + from slice to array pointer: An expression s of + type []T may now be converted to array pointer type + *[N]T. If a is the result of such a + conversion, then corresponding indices that are in range refer to + the same underlying elements: &a[i] == &s[i] + for 0 <= i < N. The conversion panics if + len(s) is less than N. +
    • + +
    • + unsafe.Add: + unsafe.Add(ptr, len) adds len + to ptr and returns the updated pointer + unsafe.Pointer(uintptr(ptr) + uintptr(len)). +
    • + +
    • + unsafe.Slice: + For expression ptr of type *T, + unsafe.Slice(ptr, len) returns a slice of + type []T whose underlying array starts + at ptr and whose length and capacity + are len. +
    • +
    + +

    + The package unsafe enhancements were added to simplify writing code that conforms + to unsafe.Pointer's safety + rules, but the rules remain unchanged. In particular, existing + programs that correctly use unsafe.Pointer remain + valid, and new programs must still follow the rules when + using unsafe.Add or unsafe.Slice. +

    + + +

    + Note that the new conversion from slice to array pointer is the + first case in which a type conversion can panic at run time. + Analysis tools that assume type conversions can never panic + should be updated to consider this possibility. +

    + +

    Ports

    + +

    Darwin

    + +

    + As announced in the Go 1.16 release + notes, Go 1.17 requires macOS 10.13 High Sierra or later; support + for previous versions has been discontinued. +

    + +

    Windows

    + +

    + Go 1.17 adds support of 64-bit ARM architecture on Windows (the + windows/arm64 port). This port supports cgo. +

    + +

    OpenBSD

    + +

    + The 64-bit MIPS architecture on OpenBSD (the openbsd/mips64 + port) now supports cgo. +

    + +

    + In Go 1.16, on the 64-bit x86 and 64-bit ARM architectures on + OpenBSD (the openbsd/amd64 and openbsd/arm64 + ports) system calls are made through libc, instead + of directly using machine instructions. In Go 1.17, this is also + done on the 32-bit x86 and 32-bit ARM architectures on OpenBSD + (the openbsd/386 and openbsd/arm ports). + This ensures compatibility with OpenBSD 6.9 onwards, which require + system calls to be made through libc for non-static + Go binaries. +

    + +

    ARM64

    + +

    + Go programs now maintain stack frame pointers on the 64-bit ARM + architecture on all operating systems. Previously it maintained + stack frame pointers only on Linux, macOS, and iOS. +

    + +

    loong64 GOARCH value reserved

    + +

    + The main Go compiler does not yet support the LoongArch + architecture, but we've reserved the GOARCH value + "loong64". + This means that Go files named *_loong64.go will now + be ignored by Go + tools except when that GOARCH value is being used. +

    + +

    Tools

    + +

    Go command

    + + +

    Pruned module graphs in go 1.17 modules

    + +

    + If a module specifies go 1.17 or higher, the module + graph includes only the immediate dependencies of + other go 1.17 modules, not their full transitive + dependencies. (See Module graph pruning + for more detail.) +

    + +

    + For the go command to correctly resolve transitive imports using + the pruned module graph, the go.mod file for each module needs to + include more detail about the transitive dependencies relevant to that module. + If a module specifies go 1.17 or higher in its + go.mod file, its go.mod file now contains an + explicit require + directive for every module that provides a transitively-imported package. + (In previous versions, the go.mod file typically only included + explicit requirements for directly-imported packages.) +

    + +

    + Since the expanded go.mod file needed for module graph pruning + includes all of the dependencies needed to load the imports of any package in + the main module, if the main module specifies + go 1.17 or higher the go tool no longer + reads (or even downloads) go.mod files for dependencies if they + are not needed in order to complete the requested command. + (See Lazy loading.) +

    + +

    + Because the number of explicit requirements may be substantially larger in an + expanded Go 1.17 go.mod file, the newly-added requirements + on indirect dependencies in a go 1.17 + module are maintained in a separate require block from the block + containing direct dependencies. +

    + +

    + To facilitate the upgrade to Go 1.17 pruned module graphs, the + go mod tidy + subcommand now supports a -go flag to set or change + the go version in the go.mod file. To convert + the go.mod file for an existing module to Go 1.17 without + changing the selected versions of its dependencies, run: +

    + +
    +  go mod tidy -go=1.17
    +
    + +

    + By default, go mod tidy verifies that + the selected versions of dependencies relevant to the main module are the same + versions that would be used by the prior Go release (Go 1.16 for a module that + specifies go 1.17), and preserves + the go.sum entries needed by that release even for dependencies + that are not normally needed by other commands. +

    + +

    + The -compat flag allows that version to be overridden to support + older (or only newer) versions, up to the version specified by + the go directive in the go.mod file. To tidy + a go 1.17 module for Go 1.17 only, without saving + checksums for (or checking for consistency with) Go 1.16: +

    + +
    +  go mod tidy -compat=1.17
    +
    + +

    + Note that even if the main module is tidied with -compat=1.17, + users who require the module from a + go 1.16 or earlier module will still be able to + use it, provided that the packages use only compatible language and library + features. +

    + +

    + The go mod graph + subcommand also supports the -go flag, which causes it to report + the graph as seen by the indicated Go version, showing dependencies that may + otherwise be pruned out. +

    + +

    Module deprecation comments

    + +

    + Module authors may deprecate a module by adding a + // Deprecated: + comment to go.mod, then tagging a new version. + go get now prints a warning if a module needed to + build packages named on the command line is deprecated. go + list -m -u prints deprecations for all + dependencies (use -f or -json to show the full + message). The go command considers different major versions to + be distinct modules, so this mechanism may be used, for example, to provide + users with migration instructions for a new major version. +

    + +

    go get

    + +

    + The go get -insecure flag is + deprecated and has been removed. To permit the use of insecure schemes + when fetching dependencies, please use the GOINSECURE + environment variable. The -insecure flag also bypassed module + sum validation, use GOPRIVATE or GONOSUMDB if + you need that functionality. See go help + environment for details. +

    + +

    + go get prints a deprecation warning when installing + commands outside the main module (without the -d flag). + go install cmd@version should be used + instead to install a command at a specific version, using a suffix like + @latest or @v1.2.3. In Go 1.18, the -d + flag will always be enabled, and go get will only + be used to change dependencies in go.mod. +

    + +

    go.mod files missing go directives

    + +

    + If the main module's go.mod file does not contain + a go directive and + the go command cannot update the go.mod file, the + go command now assumes go 1.11 instead of the + current release. (go mod init has added + go directives automatically since + Go 1.12.) +

    + +

    + If a module dependency lacks an explicit go.mod file, or + its go.mod file does not contain + a go directive, + the go command now assumes go 1.16 for that + dependency instead of the current release. (Dependencies developed in GOPATH + mode may lack a go.mod file, and + the vendor/modules.txt has to date never recorded + the go versions indicated by dependencies' go.mod + files.) +

    + +

    vendor contents

    + +

    + If the main module specifies go 1.17 or higher, + go mod vendor + now annotates + vendor/modules.txt with the go version indicated by + each vendored module in its own go.mod file. The annotated + version is used when building the module's packages from vendored source code. +

    + +

    + If the main module specifies go 1.17 or higher, + go mod vendor now omits go.mod + and go.sum files for vendored dependencies, which can otherwise + interfere with the ability of the go command to identify the correct + module root when invoked within the vendor tree. +

    + +

    Password prompts

    + +

    + The go command by default now suppresses SSH password prompts and + Git Credential Manager prompts when fetching Git repositories using SSH, as it + already did previously for other Git password prompts. Users authenticating to + private Git repos with password-protected SSH may configure + an ssh-agent to enable the go command to use + password-protected SSH keys. +

    + +

    go mod download

    + +

    + When go mod download is invoked without + arguments, it will no longer save sums for downloaded module content to + go.sum. It may still make changes to go.mod and + go.sum needed to load the build list. This is the same as the + behavior in Go 1.15. To save sums for all modules, use go + mod download all. +

    + +

    //go:build lines

    + +

    + The go command now understands //go:build lines + and prefers them over // +build lines. The new syntax uses + boolean expressions, just like Go, and should be less error-prone. + As of this release, the new syntax is fully supported, and all Go files + should be updated to have both forms with the same meaning. To aid in + migration, gofmt now automatically + synchronizes the two forms. For more details on the syntax and migration plan, + see + https://golang.org/design/draft-gobuild. +

    + +

    go run

    + +

    + go run now accepts arguments with version suffixes + (for example, go run + example.com/cmd@v1.0.0). This causes go + run to build and run packages in module-aware mode, ignoring the + go.mod file in the current directory or any parent directory, if + there is one. This is useful for running executables without installing them or + without changing dependencies of the current module. +

    + +

    Gofmt

    + +

    + gofmt (and go fmt) now synchronizes + //go:build lines with // +build lines. If a file + only has // +build lines, they will be moved to the appropriate + location in the file, and matching //go:build lines will be + added. Otherwise, // +build lines will be overwritten based on + any existing //go:build lines. For more information, see + https://golang.org/design/draft-gobuild. +

    + +

    Vet

    + +

    New warning for mismatched //go:build and // +build lines

    + +

    + The vet tool now verifies that //go:build and + // +build lines are in the correct part of the file and + synchronized with each other. If they aren't, + gofmt can be used to fix them. For more + information, see + https://golang.org/design/draft-gobuild. +

    + +

    New warning for calling signal.Notify on unbuffered channels

    + +

    + The vet tool now warns about calls to signal.Notify + with incoming signals being sent to an unbuffered channel. Using an unbuffered channel + risks missing signals sent on them as signal.Notify does not block when + sending to a channel. For example: +

    + +
    +c := make(chan os.Signal)
    +// signals are sent on c before the channel is read from.
    +// This signal may be dropped as c is unbuffered.
    +signal.Notify(c, os.Interrupt)
    +
    + +

    + Users of signal.Notify should use channels with sufficient buffer space to keep up with the + expected signal rate. +

    + +

    New warnings for Is, As and Unwrap methods

    + +

    + The vet tool now warns about methods named As, Is or Unwrap + on types implementing the error interface that have a different signature than the + one expected by the errors package. The errors.{As,Is,Unwrap} functions + expect such methods to implement either Is(error) bool, + As(interface{}) bool, or Unwrap() error + respectively. The functions errors.{As,Is,Unwrap} will ignore methods with the same + names but a different signature. For example: +

    + +
    +type MyError struct { hint string }
    +func (m MyError) Error() string { ... } // MyError implements error.
    +func (MyError) Is(target interface{}) bool { ... } // target is interface{} instead of error.
    +func Foo() bool {
    +	x, y := MyError{"A"}, MyError{"B"}
    +	return errors.Is(x, y) // returns false as x != y and MyError does not have an `Is(error) bool` function.
    +}
    +
    + +

    Cover

    + +

    + The cover tool now uses an optimized parser + from golang.org/x/tools/cover, which may be noticeably faster + when parsing large coverage profiles. +

    + +

    Compiler

    + +

    + Go 1.17 implements a new way of passing function arguments and results using + registers instead of the stack. + Benchmarks for a representative set of Go packages and programs show + performance improvements of about 5%, and a typical reduction in + binary size of about 2%. + This is currently enabled for Linux, macOS, and Windows on the + 64-bit x86 architecture (the linux/amd64, + darwin/amd64, and windows/amd64 ports). +

    + +

    + This change does not affect the functionality of any safe Go code + and is designed to have no impact on most assembly code. + It may affect code that violates + the unsafe.Pointer + rules when accessing function arguments, or that depends on + undocumented behavior involving comparing function code pointers. + To maintain compatibility with existing assembly functions, the + compiler generates adapter functions that convert between the new + register-based calling convention and the previous stack-based + calling convention. + These adapters are typically invisible to users, except that taking + the address of a Go function in assembly code or taking the address + of an assembly function in Go code + using reflect.ValueOf(fn).Pointer() + or unsafe.Pointer will now return the address of the + adapter. + Code that depends on the value of these code pointers may no longer + behave as expected. + Adapters also may cause a very small performance overhead in two + cases: calling an assembly function indirectly from Go via + a func value, and calling Go functions from assembly. +

    + +

    + The format of stack traces from the runtime (printed when an uncaught panic + occurs, or when runtime.Stack is called) is improved. Previously, + the function arguments were printed as hexadecimal words based on the memory + layout. Now each argument in the source code is printed separately, separated + by commas. Aggregate-typed (struct, array, string, slice, interface, and complex) + arguments are delimited by curly braces. A caveat is that the value of an + argument that only lives in a register and is not stored to memory may be + inaccurate. Function return values (which were usually inaccurate) are no longer + printed. +

    + +

    + Functions containing closures can now be inlined. + One effect of this change is that a function with a closure may + produce a distinct closure code pointer for each place that the + function is inlined. + Go function values are not directly comparable, but this change + could reveal bugs in code that uses reflect + or unsafe.Pointer to bypass this language restriction + and compare functions by code pointer. +

    + + + +

    + When the linker uses external linking mode, which is the default + when linking a program that uses cgo, and the linker is invoked + with a -I option, the option will now be passed to the + external linker as a -Wl,--dynamic-linker option. +

    + +

    Core library

    + +

    Cgo

    + +

    + The runtime/cgo package now provides a + new facility that allows to turn any Go values to a safe representation + that can be used to pass values between C and Go safely. See + runtime/cgo.Handle for more information. +

    + +

    URL query parsing

    + + +

    + The net/url and net/http packages used to accept + ";" (semicolon) as a setting separator in URL queries, in + addition to "&" (ampersand). Now, settings with non-percent-encoded + semicolons are rejected and net/http servers will log a warning to + Server.ErrorLog + when encountering one in a request URL. +

    + +

    + For example, before Go 1.17 the Query + method of the URL example?a=1;b=2&c=3 would have returned + map[a:[1] b:[2] c:[3]], while now it returns map[c:[3]]. +

    + +

    + When encountering such a query string, + URL.Query + and + Request.FormValue + ignore any settings that contain a semicolon, + ParseQuery + returns the remaining settings and an error, and + Request.ParseForm + and + Request.ParseMultipartForm + return an error but still set Request fields based on the + remaining settings. +

    + +

    + net/http users can restore the original behavior by using the new + AllowQuerySemicolons + handler wrapper. This will also suppress the ErrorLog warning. + Note that accepting semicolons as query separators can lead to security issues + if different systems interpret cache keys differently. + See issue 25192 for more information. +

    + +

    TLS strict ALPN

    + + +

    + When Config.NextProtos + is set, servers now enforce that there is an overlap between the configured + protocols and the ALPN protocols advertised by the client, if any. If there is + no mutually supported protocol, the connection is closed with the + no_application_protocol alert, as required by RFC 7301. This + helps mitigate the ALPACA cross-protocol attack. +

    + +

    + As an exception, when the value "h2" is included in the server's + Config.NextProtos, HTTP/1.1 clients will be allowed to connect as + if they didn't support ALPN. + See issue 46310 for more information. +

    + +

    Minor changes to the library

    + +

    + As always, there are various minor changes and updates to the library, + made with the Go 1 promise of compatibility + in mind. +

    + +
    archive/zip
    +
    +

    + The new methods File.OpenRaw, Writer.CreateRaw, Writer.Copy provide support for cases where performance is a primary concern. +

    +
    +
    + +
    bufio
    +
    +

    + The Writer.WriteRune method + now writes the replacement character U+FFFD for negative rune values, + as it does for other invalid runes. +

    +
    +
    + +
    bytes
    +
    +

    + The Buffer.WriteRune method + now writes the replacement character U+FFFD for negative rune values, + as it does for other invalid runes. +

    +
    +
    + +
    compress/lzw
    +
    +

    + The NewReader + function is guaranteed to return a value of the new + type Reader, + and similarly NewWriter + is guaranteed to return a value of the new + type Writer. + These new types both implement a Reset method + (Reader.Reset, + Writer.Reset) + that allows reuse of the Reader or Writer. +

    +
    +
    + +
    crypto/ed25519
    +
    +

    + The crypto/ed25519 package has been rewritten, and all + operations are now approximately twice as fast on amd64 and arm64. + The observable behavior has not otherwise changed. +

    +
    +
    + +
    crypto/elliptic
    +
    +

    + CurveParams + methods now automatically invoke faster and safer dedicated + implementations for known curves (P-224, P-256, and P-521) when + available. Note that this is a best-effort approach and applications + should avoid using the generic, not constant-time CurveParams + methods and instead use dedicated + Curve implementations + such as P256. +

    + +

    + The P521 curve + implementation has been rewritten using code generated by the + fiat-crypto project, + which is based on a formally-verified model of the arithmetic + operations. It is now constant-time and three times faster on amd64 and + arm64. The observable behavior has not otherwise changed. +

    +
    +
    + +
    crypto/rand
    +
    +

    + The crypto/rand package now uses the getentropy + syscall on macOS and the getrandom syscall on Solaris, + Illumos, and DragonFlyBSD. +

    +
    +
    + +
    crypto/tls
    +
    +

    + The new Conn.HandshakeContext + method allows the user to control cancellation of an in-progress TLS + handshake. The provided context is accessible from various callbacks through the new + ClientHelloInfo.Context and + CertificateRequestInfo.Context + methods. Canceling the context after the handshake has finished has no effect. +

    + +

    + Cipher suite ordering is now handled entirely by the + crypto/tls package. Currently, cipher suites are sorted based + on their security, performance, and hardware support taking into account + both the local and peer's hardware. The order of the + Config.CipherSuites + field is now ignored, as well as the + Config.PreferServerCipherSuites + field. Note that Config.CipherSuites still allows + applications to choose what TLS 1.0–1.2 cipher suites to enable. +

    + +

    + The 3DES cipher suites have been moved to + InsecureCipherSuites + due to fundamental block size-related + weakness. They are still enabled by default but only as a last resort, + thanks to the cipher suite ordering change above. +

    + +

    + Beginning in the next release, Go 1.18, the + Config.MinVersion + for crypto/tls clients will default to TLS 1.2, disabling TLS 1.0 + and TLS 1.1 by default. Applications will be able to override the change by + explicitly setting Config.MinVersion. + This will not affect crypto/tls servers. +

    +
    +
    + +
    crypto/x509
    +
    +

    + CreateCertificate + now returns an error if the provided private key doesn't match the + parent's public key, if any. The resulting certificate would have failed + to verify. +

    + +

    + The temporary GODEBUG=x509ignoreCN=0 flag has been removed. +

    + +

    + ParseCertificate + has been rewritten, and now consumes ~70% fewer resources. The observable + behavior has not otherwise changed, except for error messages. +

    + +

    + On BSD systems, /etc/ssl/certs is now searched for trusted + roots. This adds support for the new system trusted certificate store in + FreeBSD 12.2+. +

    + +

    + Beginning in the next release, Go 1.18, crypto/x509 will + reject certificates signed with the SHA-1 hash function. This doesn't + apply to self-signed root certificates. Practical attacks against SHA-1 + have been demonstrated in 2017 and publicly + trusted Certificate Authorities have not issued SHA-1 certificates since 2015. +

    +
    +
    + +
    database/sql
    +
    +

    + The DB.Close method now closes + the connector field if the type in this field implements the + io.Closer interface. +

    + +

    + The new + NullInt16 + and + NullByte + structs represent the int16 and byte values that may be null. These can be used as + destinations of the Scan method, + similar to NullString. +

    +
    +
    + +
    debug/elf
    +
    +

    + The SHT_MIPS_ABIFLAGS + constant has been added. +

    +
    +
    + +
    encoding/binary
    +
    +

    + binary.Uvarint will stop reading after 10 bytes to avoid + wasted computations. If more than 10 bytes are needed, the byte count returned is -11. +
    + Previous Go versions could return larger negative counts when reading incorrectly encoded varints. +

    +
    +
    + +
    encoding/csv
    +
    +

    + The new + Reader.FieldPos + method returns the line and column corresponding to the start of + a given field in the record most recently returned by + Read. +

    +
    +
    + +
    encoding/xml
    +
    +

    + When a comment appears within a + Directive, it is now replaced + with a single space instead of being completely elided. +

    + +

    + Invalid element or attribute names with leading, trailing, or multiple + colons are now stored unmodified into the + Name.Local field. +

    +
    +
    + +
    flag
    +
    +

    + Flag declarations now panic if an invalid name is specified. +

    +
    +
    + +
    go/build
    +
    +

    + The new + Context.ToolTags + field holds the build tags appropriate to the current Go + toolchain configuration. +

    +
    +
    + +
    go/format
    +
    +

    + The Source and + Node functions now + synchronize //go:build lines with // +build + lines. If a file only has // +build lines, they will be + moved to the appropriate location in the file, and matching + //go:build lines will be added. Otherwise, + // +build lines will be overwritten based on any existing + //go:build lines. For more information, see + https://golang.org/design/draft-gobuild. +

    +
    +
    + +
    go/parser
    +
    +

    + The new SkipObjectResolution + Mode value instructs the parser not to resolve identifiers to + their declaration. This may improve parsing speed. +

    +
    +
    + +
    image
    +
    +

    + The concrete image types (RGBA, Gray16 and so on) + now implement a new RGBA64Image + interface. The concrete types that previously implemented + draw.Image now also implement + draw.RGBA64Image, a + new interface in the image/draw package. +

    +
    +
    + +
    io/fs
    +
    +

    + The new FileInfoToDirEntry function converts a FileInfo to a DirEntry. +

    +
    +
    + +
    math
    +
    +

    + The math package now defines three more constants: MaxUint, MaxInt and MinInt. + For 32-bit systems their values are 2^32 - 1, 2^31 - 1 and -2^31, respectively. + For 64-bit systems their values are 2^64 - 1, 2^63 - 1 and -2^63, respectively. +

    +
    +
    + +
    mime
    +
    +

    + On Unix systems, the table of MIME types is now read from the local system's + Shared MIME-info Database + when available. +

    +
    +
    + +
    mime/multipart
    +
    +

    + Part.FileName + now applies + filepath.Base to the + return value. This mitigates potential path traversal vulnerabilities in + applications that accept multipart messages, such as net/http + servers that call + Request.FormFile. +

    +
    +
    + +
    net
    +
    +

    + The new method IP.IsPrivate reports whether an address is + a private IPv4 address according to RFC 1918 + or a local IPv6 address according RFC 4193. +

    + +

    + The Go DNS resolver now only sends one DNS query when resolving an address for an IPv4-only or IPv6-only network, + rather than querying for both address families. +

    + +

    + The ErrClosed sentinel error and + ParseError error type now implement + the net.Error interface. +

    + +

    + The ParseIP and ParseCIDR + functions now reject IPv4 addresses which contain decimal components with leading zeros. + + These components were always interpreted as decimal, but some operating systems treat them as octal. + This mismatch could hypothetically lead to security issues if a Go application was used to validate IP addresses + which were then used in their original form with non-Go applications which interpreted components as octal. Generally, + it is advisable to always re-encode values after validation, which avoids this class of parser misalignment issues. +

    +
    +
    + +
    net/http
    +
    +

    + The net/http package now uses the new + (*tls.Conn).HandshakeContext + with the Request context + when performing TLS handshakes in the client or server. +

    + +

    + Setting the Server + ReadTimeout or WriteTimeout fields to a negative value now indicates no timeout + rather than an immediate timeout. +

    + +

    + The ReadRequest function + now returns an error when the request has multiple Host headers. +

    + +

    + When producing a redirect to the cleaned version of a URL, + ServeMux now always + uses relative URLs in the Location header. Previously it + would echo the full URL of the request, which could lead to unintended + redirects if the client could be made to send an absolute request URL. +

    + +

    + When interpreting certain HTTP headers handled by net/http, + non-ASCII characters are now ignored or rejected. +

    + +

    + If + Request.ParseForm + returns an error when called by + Request.ParseMultipartForm, + the latter now continues populating + Request.MultipartForm + before returning it. +

    +
    +
    + +
    net/http/httptest
    +
    +

    + ResponseRecorder.WriteHeader + now panics when the provided code is not a valid three-digit HTTP status code. + This matches the behavior of ResponseWriter + implementations in the net/http package. +

    +
    +
    + +
    net/url
    +
    +

    + The new method Values.Has + reports whether a query parameter is set. +

    +
    +
    + +
    os
    +
    +

    + The File.WriteString method + has been optimized to not make a copy of the input string. +

    +
    +
    + +
    reflect
    +
    +

    + The new + Value.CanConvert + method reports whether a value can be converted to a type. + This may be used to avoid a panic when converting a slice to an + array pointer type if the slice is too short. + Previously it was sufficient to use + Type.ConvertibleTo + for this, but the newly permitted conversion from slice to array + pointer type can panic even if the types are convertible. +

    + +

    + The new + StructField.IsExported + and + Method.IsExported + methods report whether a struct field or type method is exported. + They provide a more readable alternative to checking whether PkgPath + is empty. +

    + +

    + The new VisibleFields function + returns all the visible fields in a struct type, including fields inside anonymous struct members. +

    + +

    + The ArrayOf function now panics when + called with a negative length. +

    + +

    + Checking the Type.ConvertibleTo method + is no longer sufficient to guarantee that a call to + Value.Convert will not panic. + It may panic when converting `[]T` to `*[N]T` if the slice's length is less than N. + See the language changes section above. +

    +
    +
    + +
    runtime/metrics
    +
    +

    + New metrics were added that track total bytes and objects allocated and freed. + A new metric tracking the distribution of goroutine scheduling latencies was + also added. +

    +
    +
    + +
    runtime/pprof
    +
    +

    + Block profiles are no longer biased to favor infrequent long events over + frequent short events. +

    +
    +
    + +
    strconv
    +
    +

    + The strconv package now uses Ulf Adams's Ryū algorithm for formatting floating-point numbers. + This algorithm improves performance on most inputs and is more than 99% faster on worst-case inputs. +

    + +

    + The new QuotedPrefix function + returns the quoted string (as understood by + Unquote) + at the start of input. +

    +
    +
    + +
    strings
    +
    +

    + The Builder.WriteRune method + now writes the replacement character U+FFFD for negative rune values, + as it does for other invalid runes. +

    +
    +
    + +
    sync/atomic
    +
    +

    + atomic.Value now has Swap and + CompareAndSwap methods that provide + additional atomic operations. +

    +
    +
    + +
    syscall
    +
    +

    +

    + The GetQueuedCompletionStatus and + PostQueuedCompletionStatus + functions are now deprecated. These functions have incorrect signatures and are superseded by + equivalents in the golang.org/x/sys/windows package. +

    + +

    + On Unix-like systems, the process group of a child process is now set with signals blocked. + This avoids sending a SIGTTOU to the child when the parent is in a background process group. +

    + +

    + The Windows version of + SysProcAttr + has two new fields. AdditionalInheritedHandles is + a list of additional handles to be inherited by the new child + process. ParentProcess permits specifying the + parent process of the new process. + +

    + The constant MSG_CMSG_CLOEXEC is now defined on + DragonFly and all OpenBSD systems (it was already defined on + some OpenBSD systems and all FreeBSD, NetBSD, and Linux systems). +

    + +

    + The constants SYS_WAIT6 and WEXITED + are now defined on NetBSD systems (SYS_WAIT6 was + already defined on DragonFly and FreeBSD systems; + WEXITED was already defined on Darwin, DragonFly, + FreeBSD, Linux, and Solaris systems). +

    +
    +
    + +
    testing
    +
    +

    + Added a new testing flag -shuffle which controls the execution order of tests and benchmarks. +

    +

    + The new + T.Setenv + and B.Setenv + methods support setting an environment variable for the duration + of the test or benchmark. +

    +
    +
    + +
    text/template/parse
    +
    +

    + The new SkipFuncCheck Mode + value changes the template parser to not verify that functions are defined. +

    +
    +
    + +
    time
    +
    +

    + The Time type now has a + GoString method that + will return a more useful value for times when printed with the + %#v format specifier in the fmt package. +

    + +

    + The new Time.IsDST method can be used to check whether the time + is in Daylight Savings Time in its configured location. +

    + +

    + The new Time.UnixMilli and + Time.UnixMicro + methods return the number of milliseconds and microseconds elapsed since + January 1, 1970 UTC respectively. +
    + The new UnixMilli and + UnixMicro functions + return the local Time corresponding to the given Unix time. +

    + +

    + The package now accepts comma "," as a separator for fractional seconds when parsing and formatting time. + For example, the following time layouts are now accepted: +

      +
    • 2006-01-02 15:04:05,999999999 -0700 MST
    • +
    • Mon Jan _2 15:04:05,000000 2006
    • +
    • Monday, January 2 15:04:05,000 2006
    • +
    +

    + +

    + The new constant Layout + defines the reference time. +

    +
    +
    + +
    unicode
    +
    +

    + The Is, + IsGraphic, + IsLetter, + IsLower, + IsMark, + IsNumber, + IsPrint, + IsPunct, + IsSpace, + IsSymbol, and + IsUpper functions + now return false on negative rune values, as they do for other invalid runes. +

    +
    +
    diff --git a/doc/go_spec.html b/doc/go_spec.html index 59c9ce3c434e67614bf3e906f820a854e57b1f0b..fd5fee46eb220c1111d8aa33853037f751d4bdbb 100644 --- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -1,6 +1,6 @@ @@ -490,8 +490,8 @@ After a backslash, certain single-character escapes represent special values: \n U+000A line feed or newline \r U+000D carriage return \t U+0009 horizontal tab -\v U+000b vertical tab -\\ U+005c backslash +\v U+000B vertical tab +\\ U+005C backslash \' U+0027 single quote (valid escape only within rune literals) \" U+0022 double quote (valid escape only within string literals) @@ -830,7 +830,7 @@ The underlying type of []B1, B3, and B4 i

    Method sets

    -A type may have a method set associated with it. +A type has a (possibly empty) method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. @@ -3532,9 +3532,9 @@ within Greeting, who will have the value

    -If the final argument is assignable to a slice type []T, it is -passed unchanged as the value for a ...T parameter if the argument -is followed by .... In this case no new slice is created. +If the final argument is assignable to a slice type []T and +is followed by ..., it is passed unchanged as the value +for a ...T parameter. In this case no new slice is created.

    @@ -3681,8 +3681,8 @@ The bitwise logical and shift operators apply to integers only. ^ bitwise XOR integers &^ bit clear (AND NOT) integers -<< left shift integer << unsigned integer ->> right shift integer >> unsigned integer +<< left shift integer << integer >= 0 +>> right shift integer >> integer >= 0 @@ -4164,6 +4164,10 @@ in any of these cases:

  • x is a string and T is a slice of bytes or runes.
  • +
  • + x is a slice, T is a pointer to an array, + and the slice and array types have identical element types. +
  • @@ -4314,6 +4318,28 @@ MyRunes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4} +

    Conversions from slice to array pointer

    + +

    +Converting a slice to an array pointer yields a pointer to the underlying array of the slice. +If the length of the slice is less than the length of the array, +a run-time panic occurs. +

    + +
    +s := make([]byte, 2, 4)
    +s0 := (*[0]byte)(s)      // s0 != nil
    +s1 := (*[1]byte)(s[1:])  // &s1[0] == &s[1]
    +s2 := (*[2]byte)(s)      // &s2[0] == &s[0]
    +s4 := (*[4]byte)(s)      // panics: len([4]byte) > len(s)
    +
    +var t []string
    +t0 := (*[0]string)(t)    // t0 == nil
    +t1 := (*[1]string)(t)    // panics: len([1]string) > len(t)
    +
    +u := make([]byte, 0)
    +u0 = (*[0]byte)(u)       // u0 != nil
    +

    Constant expressions

    @@ -4648,7 +4674,7 @@ The following built-in functions are not permitted in statement context:
     append cap complex imag len make new real
    -unsafe.Alignof unsafe.Offsetof unsafe.Sizeof
    +unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice
     
    @@ -4887,7 +4913,7 @@ if x := f(); x < y {
     
     

    "Switch" statements provide multi-way execution. -An expression or type specifier is compared to the "cases" +An expression or type is compared to the "cases" inside the "switch" to determine which branch to execute.

    @@ -4931,9 +4957,9 @@ ExprSwitchCase = "case" ExpressionList | "default" .

    If the switch expression evaluates to an untyped constant, it is first implicitly -converted to its default type; -if it is an untyped boolean value, it is first implicitly converted to type bool. +converted to its default type. The predeclared untyped value nil cannot be used as a switch expression. +The switch expression type must be comparable.

    @@ -4998,7 +5024,7 @@ floating point, or string constants in case expressions. A type switch compares types rather than values. It is otherwise similar to an expression switch. It is marked by a special switch expression that has the form of a type assertion -using the reserved word type rather than an actual type: +using the keyword type rather than an actual type:

    @@ -6689,6 +6715,10 @@ type Pointer *ArbitraryType
     func Alignof(variable ArbitraryType) uintptr
     func Offsetof(selector ArbitraryType) uintptr
     func Sizeof(variable ArbitraryType) uintptr
    +
    +type IntegerType int  // shorthand for an integer type; it is not a real type
    +func Add(ptr Pointer, len IntegerType) Pointer
    +func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
     

    @@ -6745,6 +6775,40 @@ Calls to Alignof, Offsetof, and Sizeof are compile-time constant expressions of type uintptr.

    +

    +The function Add adds len to ptr +and returns the updated pointer unsafe.Pointer(uintptr(ptr) + uintptr(len)). +The len argument must be of integer type or an untyped constant. +A constant len argument must be representable by a value of type int; +if it is an untyped constant it is given type int. +The rules for valid uses of Pointer still apply. +

    + +

    +The function Slice returns a slice whose underlying array starts at ptr +and whose length and capacity are len. +Slice(ptr, len) is equivalent to +

    + +
    +(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
    +
    + +

    +except that, as a special case, if ptr +is nil and len is zero, +Slice returns nil. +

    + +

    +The len argument must be of integer type or an untyped constant. +A constant len argument must be non-negative and representable by a value of type int; +if it is an untyped constant it is given type int. +At run time, if len is negative, +or if ptr is nil and len is not zero, +a run-time panic occurs. +

    +

    Size and alignment guarantees

    diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index 8d225846dbcda496ba803b828c4786b21cc84d01..0000000000000000000000000000000000000000 Binary files a/favicon.ico and /dev/null differ diff --git a/misc/android/go_android_exec.go b/misc/android/go_android_exec.go index 7aa7fe56fc5e0440dceac276f552a977f773eac2..3af2bee5839b937aff5d724317e5b68df2259b06 100644 --- a/misc/android/go_android_exec.go +++ b/misc/android/go_android_exec.go @@ -14,7 +14,6 @@ import ( "fmt" "go/build" "io" - "io/ioutil" "log" "os" "os/exec" @@ -276,7 +275,7 @@ func adbCopyGoroot() error { if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil { return err } - s, err := ioutil.ReadAll(stat) + s, err := io.ReadAll(stat) if err != nil { return err } @@ -294,7 +293,7 @@ func adbCopyGoroot() error { goroot := runtime.GOROOT() // Build go for android. goCmd := filepath.Join(goroot, "bin", "go") - tmpGo, err := ioutil.TempFile("", "go_android_exec-cmd-go-*") + tmpGo, err := os.CreateTemp("", "go_android_exec-cmd-go-*") if err != nil { return err } diff --git a/misc/cgo/errors/argposition_test.go b/misc/cgo/errors/argposition_test.go new file mode 100644 index 0000000000000000000000000000000000000000..331095f74737eccd7f85381f2d84130d69a3452f --- /dev/null +++ b/misc/cgo/errors/argposition_test.go @@ -0,0 +1,134 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Issue 42580: cmd/cgo: shifting identifier position in ast + +package errorstest + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +type ShortPosition struct { + Line int + Column int + Visited bool +} + +type IdentPositionInfo map[string][]ShortPosition + +type Visitor struct { + identPosInfo IdentPositionInfo + fset *token.FileSet + t *testing.T +} + +func (v *Visitor) Visit(node ast.Node) ast.Visitor { + if ident, ok := node.(*ast.Ident); ok { + if expectedPositions, ok := v.identPosInfo[ident.Name]; ok { + gotMatch := false + var errorMessage strings.Builder + for caseIndex, expectedPos := range expectedPositions { + actualPosition := v.fset.PositionFor(ident.Pos(), true) + errorOccured := false + if expectedPos.Line != actualPosition.Line { + fmt.Fprintf(&errorMessage, "wrong line number for ident %s: expected: %d got: %d\n", ident.Name, expectedPos.Line, actualPosition.Line) + errorOccured = true + } + if expectedPos.Column != actualPosition.Column { + fmt.Fprintf(&errorMessage, "wrong column number for ident %s: expected: %d got: %d\n", ident.Name, expectedPos.Column, actualPosition.Column) + errorOccured = true + } + if errorOccured { + continue + } + gotMatch = true + expectedPositions[caseIndex].Visited = true + } + + if !gotMatch { + v.t.Errorf(errorMessage.String()) + } + } + } + return v +} + +func TestArgumentsPositions(t *testing.T) { + testdata, err := filepath.Abs("testdata") + if err != nil { + t.Fatal(err) + } + + tmpPath := t.TempDir() + + dir := filepath.Join(tmpPath, "src", "testpositions") + if err := os.MkdirAll(dir, 0755); err != nil { + t.Fatal(err) + } + + cmd := exec.Command("go", "tool", "cgo", + "-srcdir", testdata, + "-objdir", dir, + "issue42580.go") + cmd.Stderr = new(bytes.Buffer) + + err = cmd.Run() + if err != nil { + t.Fatalf("%s: %v\n%s", cmd, err, cmd.Stderr) + } + mainProcessed, err := ioutil.ReadFile(filepath.Join(dir, "issue42580.cgo1.go")) + if err != nil { + t.Fatal(err) + } + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", mainProcessed, parser.AllErrors) + if err != nil { + fmt.Println(err) + return + } + + expectation := IdentPositionInfo{ + "checkedPointer": []ShortPosition{ + ShortPosition{ + Line: 32, + Column: 56, + }, + }, + "singleInnerPointerChecked": []ShortPosition{ + ShortPosition{ + Line: 37, + Column: 91, + }, + }, + "doublePointerChecked": []ShortPosition{ + ShortPosition{ + Line: 42, + Column: 91, + }, + }, + } + for _, decl := range f.Decls { + if fdecl, ok := decl.(*ast.FuncDecl); ok { + ast.Walk(&Visitor{expectation, fset, t}, fdecl.Body) + } + } + for ident, positions := range expectation { + for _, position := range positions { + if !position.Visited { + t.Errorf("Position %d:%d missed for %s ident", position.Line, position.Column, ident) + } + } + } +} diff --git a/misc/cgo/errors/badsym_test.go b/misc/cgo/errors/badsym_test.go index b2701bf922e52927f0a34b28fc7cdffe62008f7a..fc687567bf71b2aa962a0b6f79d6e67627d24479 100644 --- a/misc/cgo/errors/badsym_test.go +++ b/misc/cgo/errors/badsym_test.go @@ -6,7 +6,6 @@ package errorstest import ( "bytes" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -55,7 +54,7 @@ func TestBadSymbol(t *testing.T) { makeFile := func(mdir, base, source string) string { ret := filepath.Join(mdir, base) - if err := ioutil.WriteFile(ret, []byte(source), 0644); err != nil { + if err := os.WriteFile(ret, []byte(source), 0644); err != nil { t.Fatal(err) } return ret @@ -100,7 +99,7 @@ func TestBadSymbol(t *testing.T) { // _cgo_import.go. rewrite := func(from, to string) { - obj, err := ioutil.ReadFile(from) + obj, err := os.ReadFile(from) if err != nil { t.Fatal(err) } @@ -115,7 +114,7 @@ func TestBadSymbol(t *testing.T) { obj = bytes.ReplaceAll(obj, []byte(magicInput), []byte(magicReplace)) - if err := ioutil.WriteFile(to, obj, 0644); err != nil { + if err := os.WriteFile(to, obj, 0644); err != nil { t.Fatal(err) } } diff --git a/misc/cgo/errors/errors_test.go b/misc/cgo/errors/errors_test.go index 1bdf843451d366b693857abe92a11d31ede08d6f..68a30a44fe427d434df9dc634f702fc38552aa5e 100644 --- a/misc/cgo/errors/errors_test.go +++ b/misc/cgo/errors/errors_test.go @@ -7,7 +7,6 @@ package errorstest import ( "bytes" "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -25,7 +24,7 @@ func check(t *testing.T, file string) { t.Run(file, func(t *testing.T) { t.Parallel() - contents, err := ioutil.ReadFile(path(file)) + contents, err := os.ReadFile(path(file)) if err != nil { t.Fatal(err) } @@ -41,7 +40,8 @@ func check(t *testing.T, file string) { if len(frags) == 1 { continue } - re, err := regexp.Compile(string(frags[1])) + frag := fmt.Sprintf(":%d:.*%s", i+1, frags[1]) + re, err := regexp.Compile(frag) if err != nil { t.Errorf("Invalid regexp after `ERROR HERE: `: %#q", frags[1]) continue @@ -56,7 +56,7 @@ func check(t *testing.T, file string) { } func expect(t *testing.T, file string, errors []*regexp.Regexp) { - dir, err := ioutil.TempDir("", filepath.Base(t.Name())) + dir, err := os.MkdirTemp("", filepath.Base(t.Name())) if err != nil { t.Fatal(err) } diff --git a/misc/cgo/errors/ptr_test.go b/misc/cgo/errors/ptr_test.go index 4a46b6023bb510ab70e146c80358b7f56bee5f52..0f39dc8e547ed0f47f51749b7e3c22173b5f2ea3 100644 --- a/misc/cgo/errors/ptr_test.go +++ b/misc/cgo/errors/ptr_test.go @@ -10,7 +10,6 @@ import ( "bytes" "flag" "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -463,7 +462,7 @@ func buildPtrTests(t *testing.T) (dir, exe string) { gopath = *tmp dir = "" } else { - d, err := ioutil.TempDir("", filepath.Base(t.Name())) + d, err := os.MkdirTemp("", filepath.Base(t.Name())) if err != nil { t.Fatal(err) } @@ -475,7 +474,7 @@ func buildPtrTests(t *testing.T) (dir, exe string) { if err := os.MkdirAll(src, 0777); err != nil { t.Fatal(err) } - if err := ioutil.WriteFile(filepath.Join(src, "go.mod"), []byte("module ptrtest"), 0666); err != nil { + if err := os.WriteFile(filepath.Join(src, "go.mod"), []byte("module ptrtest"), 0666); err != nil { t.Fatal(err) } @@ -535,10 +534,10 @@ func buildPtrTests(t *testing.T) (dir, exe string) { fmt.Fprintf(&cgo1, "}\n\n") fmt.Fprintf(&cgo1, "%s\n", ptrTestMain) - if err := ioutil.WriteFile(filepath.Join(src, "cgo1.go"), cgo1.Bytes(), 0666); err != nil { + if err := os.WriteFile(filepath.Join(src, "cgo1.go"), cgo1.Bytes(), 0666); err != nil { t.Fatal(err) } - if err := ioutil.WriteFile(filepath.Join(src, "cgo2.go"), cgo2.Bytes(), 0666); err != nil { + if err := os.WriteFile(filepath.Join(src, "cgo2.go"), cgo2.Bytes(), 0666); err != nil { t.Fatal(err) } diff --git a/misc/cgo/errors/testdata/err2.go b/misc/cgo/errors/testdata/err2.go index 1d22401aee53a6e0783b3dc16d7a1555d67d4180..a90598fe35b6304434bba7253c29b1d62173dcc4 100644 --- a/misc/cgo/errors/testdata/err2.go +++ b/misc/cgo/errors/testdata/err2.go @@ -40,15 +40,15 @@ func main() { C.foop = x // ERROR HERE // issue 13129: used to output error about C.unsignedshort with CC=clang - var x C.ushort - x = int(0) // ERROR HERE: C\.ushort + var x1 C.ushort + x1 = int(0) // ERROR HERE: C\.ushort // issue 13423 _ = C.fopen() // ERROR HERE // issue 13467 - var x rune = '✈' - var _ rune = C.transform(x) // ERROR HERE: C\.int + var x2 rune = '✈' + var _ rune = C.transform(x2) // ERROR HERE: C\.int // issue 13635: used to output error about C.unsignedchar. // This test tests all such types. @@ -91,10 +91,10 @@ func main() { // issue 26745 _ = func(i int) int { - return C.i + 1 // ERROR HERE: :13 + return C.i + 1 // ERROR HERE: 14 } _ = func(i int) { - C.fi(i) // ERROR HERE: :6 + C.fi(i) // ERROR HERE: 7 } C.fi = C.fi // ERROR HERE diff --git a/misc/cgo/errors/testdata/issue42580.go b/misc/cgo/errors/testdata/issue42580.go new file mode 100644 index 0000000000000000000000000000000000000000..aba80dfebadf56a3d4c261697f6ac1537c9ca4ad --- /dev/null +++ b/misc/cgo/errors/testdata/issue42580.go @@ -0,0 +1,44 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Issue 42580: cmd/cgo: shifting identifier position in ast + +package cgotest + +// typedef int (*intFunc) (); +// +// char* strarg = ""; +// +// int func_with_char(char* arg, void* dummy) +// {return 5;} +// +// int* get_arr(char* arg, void* dummy) +// {return NULL;} +import "C" +import "unsafe" + +// Test variables +var ( + checkedPointer = []byte{1} + doublePointerChecked = []byte{1} + singleInnerPointerChecked = []byte{1} +) + +// This test checks the positions of variable identifiers. +// Changing the positions of the test variables idents after this point will break the test. + +func TestSingleArgumentCast() C.int { + retcode := C.func_with_char((*C.char)(unsafe.Pointer(&checkedPointer[0])), unsafe.Pointer(C.strarg)) + return retcode +} + +func TestSingleArgumentCastRecFuncAsSimpleArg() C.int { + retcode := C.func_with_char((*C.char)(unsafe.Pointer(C.get_arr((*C.char)(unsafe.Pointer(&singleInnerPointerChecked[0])), unsafe.Pointer(C.strarg)))), nil) + return retcode +} + +func TestSingleArgumentCastRecFunc() C.int { + retcode := C.func_with_char((*C.char)(unsafe.Pointer(C.get_arr((*C.char)(unsafe.Pointer(&doublePointerChecked[0])), unsafe.Pointer(C.strarg)))), unsafe.Pointer(C.strarg)) + return retcode +} diff --git a/misc/cgo/life/life_test.go b/misc/cgo/life/life_test.go index 3c95d87d8ade053b1fc3108df6c98f59a68b845c..0becb262b43e9ab8cafcccd5148d798ea451ac64 100644 --- a/misc/cgo/life/life_test.go +++ b/misc/cgo/life/life_test.go @@ -6,7 +6,6 @@ package life_test import ( "bytes" - "io/ioutil" "log" "os" "os/exec" @@ -21,7 +20,7 @@ func TestMain(m *testing.M) { } func testMain(m *testing.M) int { - GOPATH, err := ioutil.TempDir("", "cgolife") + GOPATH, err := os.MkdirTemp("", "cgolife") if err != nil { log.Panic(err) } @@ -38,7 +37,7 @@ func testMain(m *testing.M) int { log.Panic(err) } os.Setenv("PWD", modRoot) - if err := ioutil.WriteFile("go.mod", []byte("module cgolife\n"), 0666); err != nil { + if err := os.WriteFile("go.mod", []byte("module cgolife\n"), 0666); err != nil { log.Panic(err) } diff --git a/misc/cgo/stdio/stdio_test.go b/misc/cgo/stdio/stdio_test.go index ab5d328f676cd564bc71fab1dc5a5ae93de0edb6..675418f98d026e22a61d3a4088c4100638127f84 100644 --- a/misc/cgo/stdio/stdio_test.go +++ b/misc/cgo/stdio/stdio_test.go @@ -6,7 +6,6 @@ package stdio_test import ( "bytes" - "io/ioutil" "log" "os" "os/exec" @@ -21,7 +20,7 @@ func TestMain(m *testing.M) { } func testMain(m *testing.M) int { - GOPATH, err := ioutil.TempDir("", "cgostdio") + GOPATH, err := os.MkdirTemp("", "cgostdio") if err != nil { log.Panic(err) } @@ -38,7 +37,7 @@ func testMain(m *testing.M) int { log.Panic(err) } os.Setenv("PWD", modRoot) - if err := ioutil.WriteFile("go.mod", []byte("module cgostdio\n"), 0666); err != nil { + if err := os.WriteFile("go.mod", []byte("module cgostdio\n"), 0666); err != nil { log.Panic(err) } diff --git a/misc/cgo/test/callback.go b/misc/cgo/test/callback.go index 814888e3ac5d9a7fad84e00dfb90e5e4c01d1bdd..08dd9b39d8191ec1a2d4a7ef30151724ffa4b85e 100644 --- a/misc/cgo/test/callback.go +++ b/misc/cgo/test/callback.go @@ -182,7 +182,7 @@ func testCallbackCallers(t *testing.T) { "runtime.cgocallbackg1", "runtime.cgocallbackg", "runtime.cgocallback", - "runtime.asmcgocall", + "runtime.systemstack_switch", "runtime.cgocall", "test._Cfunc_callback", "test.nestedCall.func1", diff --git a/misc/cgo/test/cgo_test.go b/misc/cgo/test/cgo_test.go index f7a76d047bd9515eff224e909dda41b1a89cf08e..143f23f0e0cc36983ad44595469b5b7f55ff0d56 100644 --- a/misc/cgo/test/cgo_test.go +++ b/misc/cgo/test/cgo_test.go @@ -59,6 +59,7 @@ func Test28896(t *testing.T) { test28896(t) } func Test30065(t *testing.T) { test30065(t) } func Test32579(t *testing.T) { test32579(t) } func Test31891(t *testing.T) { test31891(t) } +func Test45451(t *testing.T) { test45451(t) } func TestAlign(t *testing.T) { testAlign(t) } func TestAtol(t *testing.T) { testAtol(t) } func TestBlocking(t *testing.T) { testBlocking(t) } @@ -80,6 +81,7 @@ func TestNamedEnum(t *testing.T) { testNamedEnum(t) } func TestCastToEnum(t *testing.T) { testCastToEnum(t) } func TestErrno(t *testing.T) { testErrno(t) } func TestFpVar(t *testing.T) { testFpVar(t) } +func TestHandle(t *testing.T) { testHandle(t) } func TestHelpers(t *testing.T) { testHelpers(t) } func TestLibgcc(t *testing.T) { testLibgcc(t) } func TestMultipleAssign(t *testing.T) { testMultipleAssign(t) } diff --git a/misc/cgo/test/issue1435.go b/misc/cgo/test/issue1435.go index a1c7cacde73844b55fc42dc8cb6e7e08469559fc..92c6b998465caf39aeb7aebe1d39f266542de0f1 100644 --- a/misc/cgo/test/issue1435.go +++ b/misc/cgo/test/issue1435.go @@ -8,7 +8,8 @@ package cgotest import ( "fmt" - "io/ioutil" + "os" + "sort" "strings" "syscall" "testing" @@ -64,7 +65,7 @@ import "C" func compareStatus(filter, expect string) error { expected := filter + expect pid := syscall.Getpid() - fs, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/task", pid)) + fs, err := os.ReadDir(fmt.Sprintf("/proc/%d/task", pid)) if err != nil { return fmt.Errorf("unable to find %d tasks: %v", pid, err) } @@ -72,7 +73,7 @@ func compareStatus(filter, expect string) error { foundAThread := false for _, f := range fs { tf := fmt.Sprintf("/proc/%s/status", f.Name()) - d, err := ioutil.ReadFile(tf) + d, err := os.ReadFile(tf) if err != nil { // There are a surprising number of ways this // can error out on linux. We've seen all of @@ -105,11 +106,23 @@ func compareStatus(filter, expect string) error { // "Pid:\t". } if strings.HasPrefix(line, filter) { - if line != expected { - return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc) + if line == expected { + foundAThread = true + break + } + if filter == "Groups:" && strings.HasPrefix(line, "Groups:\t") { + // https://github.com/golang/go/issues/46145 + // Containers don't reliably output this line in sorted order so manually sort and compare that. + a := strings.Split(line[8:], " ") + sort.Strings(a) + got := strings.Join(a, " ") + if got == expected[8:] { + foundAThread = true + break + } + } - foundAThread = true - break + return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc) } } } diff --git a/misc/cgo/test/issue6997_linux.go b/misc/cgo/test/issue6997_linux.go index 0c98ea0794eec302d134e0855769758c7286ec35..f19afb8b7ad81610520a3f8f5f69a3b5d8d0a746 100644 --- a/misc/cgo/test/issue6997_linux.go +++ b/misc/cgo/test/issue6997_linux.go @@ -5,7 +5,7 @@ // +build !android // Test that pthread_cancel works as expected -// (NPTL uses SIGRTMIN to implement thread cancelation) +// (NPTL uses SIGRTMIN to implement thread cancellation) // See https://golang.org/issue/6997 package cgotest @@ -17,8 +17,10 @@ extern int CancelThread(); */ import "C" -import "testing" -import "time" +import ( + "testing" + "time" +) func test6997(t *testing.T) { r := C.StartThread() diff --git a/misc/cgo/test/issue8148.c b/misc/cgo/test/issue8148.c new file mode 100644 index 0000000000000000000000000000000000000000..927b4346cbe03df4339c3a342629921bf1dc4b6c --- /dev/null +++ b/misc/cgo/test/issue8148.c @@ -0,0 +1,11 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "_cgo_export.h" + +int get8148(void) { + T t; + t.i = 42; + return issue8148Callback(&t); +} diff --git a/misc/cgo/test/issue8148.go b/misc/cgo/test/issue8148.go index f704788aef8a45da1d8d195f8c478ee7d617f3e5..aee9003d5075bf02751a477e9fc7ffb8da2d4009 100644 --- a/misc/cgo/test/issue8148.go +++ b/misc/cgo/test/issue8148.go @@ -10,14 +10,7 @@ package cgotest /* typedef struct { int i; } T; - -int issue8148Callback(T*); - -static int get() { - T t; - t.i = 42; - return issue8148Callback(&t); -} +int get8148(void); */ import "C" @@ -27,5 +20,5 @@ func issue8148Callback(t *C.T) C.int { } func Issue8148() int { - return int(C.get()) + return int(C.get8148()) } diff --git a/misc/cgo/test/pkg_test.go b/misc/cgo/test/pkg_test.go index 94abaa03e8d4b98cd679bcb69f5a3c38c6110ef6..14013a4cd962b621f6e5a9a4cade19c0504edcb6 100644 --- a/misc/cgo/test/pkg_test.go +++ b/misc/cgo/test/pkg_test.go @@ -5,7 +5,6 @@ package cgotest import ( - "io/ioutil" "os" "os/exec" "path/filepath" @@ -37,7 +36,7 @@ func TestCrossPackageTests(t *testing.T) { } } - GOPATH, err := ioutil.TempDir("", "cgotest") + GOPATH, err := os.MkdirTemp("", "cgotest") if err != nil { t.Fatal(err) } @@ -47,7 +46,7 @@ func TestCrossPackageTests(t *testing.T) { if err := overlayDir(modRoot, "testdata"); err != nil { t.Fatal(err) } - if err := ioutil.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module cgotest\n"), 0666); err != nil { + if err := os.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module cgotest\n"), 0666); err != nil { t.Fatal(err) } diff --git a/misc/cgo/test/setgid_linux.go b/misc/cgo/test/setgid_linux.go index 6773f94d3d6816c6cbdab8e827e22a908b70ef18..7c64946cb34b68fa1770fbfdc9cbf1f3ab7d1092 100644 --- a/misc/cgo/test/setgid_linux.go +++ b/misc/cgo/test/setgid_linux.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Test that setgid does not hang on GNU/Linux. +// Test that setgid does not hang on Linux. // See https://golang.org/issue/3871 for details. package cgotest diff --git a/misc/cgo/test/test.go b/misc/cgo/test/test.go index 65823b1ca0e95189576b265ba6e097933428c1d6..3b8f548b13dd206fedd0653d8472c5b8d9958d12 100644 --- a/misc/cgo/test/test.go +++ b/misc/cgo/test/test.go @@ -899,6 +899,10 @@ static uint16_t issue31093F(uint16_t v) { return v; } // issue 32579 typedef struct S32579 { unsigned char data[1]; } S32579; +// issue 37033, cgo.Handle +extern void GoFunc37033(uintptr_t handle); +void cFunc37033(uintptr_t handle) { GoFunc37033(handle); } + // issue 38649 // Test that #define'd type aliases work. #define netbsd_gid unsigned int @@ -908,6 +912,9 @@ typedef struct S32579 { unsigned char data[1]; } S32579; enum Enum40494 { X_40494 }; union Union40494 { int x; }; void issue40494(enum Enum40494 e, union Union40494* up) {} + +// Issue 45451, bad handling of go:notinheap types. +typedef struct issue45451Undefined issue45451; */ import "C" @@ -920,6 +927,7 @@ import ( "os/signal" "reflect" "runtime" + "runtime/cgo" "sync" "syscall" "testing" @@ -2230,6 +2238,23 @@ func test32579(t *testing.T) { } } +// issue 37033, check if cgo.Handle works properly + +func testHandle(t *testing.T) { + ch := make(chan int) + + for i := 0; i < 42; i++ { + h := cgo.NewHandle(ch) + go func() { + C.cFunc37033(C.uintptr_t(h)) + }() + if v := <-ch; issue37033 != v { + t.Fatalf("unexpected receiving value: got %d, want %d", v, issue37033) + } + h.Delete() + } +} + // issue 38649 var issue38649 C.netbsd_gid = 42 @@ -2244,3 +2269,19 @@ var issue39877 *C.void = nil func Issue40494() { C.issue40494(C.enum_Enum40494(C.X_40494), (*C.union_Union40494)(nil)) } + +// Issue 45451. +func test45451(t *testing.T) { + var u *C.issue45451 + typ := reflect.ValueOf(u).Type().Elem() + + // The type is undefined in C so allocating it should panic. + defer func() { + if r := recover(); r == nil { + t.Error("expected panic") + } + }() + + _ = reflect.New(typ) + t.Errorf("reflect.New(%v) should have panicked", typ) +} diff --git a/misc/cgo/test/testdata/issue9400/asm_386.s b/misc/cgo/test/testdata/issue9400/asm_386.s index 7f158b5c39d8d299d4fbfbcac465ad71efb30fd9..96b8b60c10f589dbb7f17de0eb1805cfa959082c 100644 --- a/misc/cgo/test/testdata/issue9400/asm_386.s +++ b/misc/cgo/test/testdata/issue9400/asm_386.s @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !gccgo +// +build gc #include "textflag.h" diff --git a/misc/cgo/test/testdata/issue9400/asm_amd64x.s b/misc/cgo/test/testdata/issue9400/asm_amd64x.s index 48b86190a59fc6378b8a7dda8a644c1da4a52414..99509bce5e1518cfdc20d329fa5c936eb1721a4b 100644 --- a/misc/cgo/test/testdata/issue9400/asm_amd64x.s +++ b/misc/cgo/test/testdata/issue9400/asm_amd64x.s @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // +build amd64 amd64p32 -// +build !gccgo +// +build gc #include "textflag.h" diff --git a/misc/cgo/test/testdata/issue9400/asm_arm.s b/misc/cgo/test/testdata/issue9400/asm_arm.s index 96c278520f398d2106ef2220c88462ef0c30cafd..cc92856c2ff482f6015f0f4e0f62fd9047daf16c 100644 --- a/misc/cgo/test/testdata/issue9400/asm_arm.s +++ b/misc/cgo/test/testdata/issue9400/asm_arm.s @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !gccgo +// +build gc #include "textflag.h" diff --git a/misc/cgo/test/testdata/issue9400/asm_arm64.s b/misc/cgo/test/testdata/issue9400/asm_arm64.s index 2ebbfcca3b607580fbd3c93384ef9a40de172171..2565793f9ab00d3e068dc8e6e76b6b014c43c1fc 100644 --- a/misc/cgo/test/testdata/issue9400/asm_arm64.s +++ b/misc/cgo/test/testdata/issue9400/asm_arm64.s @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !gccgo +// +build gc #include "textflag.h" diff --git a/misc/cgo/test/testdata/issue9400/asm_mips64x.s b/misc/cgo/test/testdata/issue9400/asm_mips64x.s index 63dc90605e6f8edf4f5d42150283efe2fad66ed1..693231ddfe1b94a8cd896cd3a29aaff2b3d52a94 100644 --- a/misc/cgo/test/testdata/issue9400/asm_mips64x.s +++ b/misc/cgo/test/testdata/issue9400/asm_mips64x.s @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // +build mips64 mips64le -// +build !gccgo +// +build gc #include "textflag.h" diff --git a/misc/cgo/test/testdata/issue9400/asm_mipsx.s b/misc/cgo/test/testdata/issue9400/asm_mipsx.s index 7a92735194245ca6d850ec35d844d0677174f118..63261bbf9d08d82e3c85d52a99e2adfb96afecee 100644 --- a/misc/cgo/test/testdata/issue9400/asm_mipsx.s +++ b/misc/cgo/test/testdata/issue9400/asm_mipsx.s @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // +build mips mipsle -// +build !gccgo +// +build gc #include "textflag.h" diff --git a/misc/cgo/test/testdata/issue9400/asm_ppc64x.s b/misc/cgo/test/testdata/issue9400/asm_ppc64x.s index c88ec3b21e76517d9ec44d0a0b5fe0f236a41d52..b5613fb6ec73f6f2b5daa70095c314a44870394f 100644 --- a/misc/cgo/test/testdata/issue9400/asm_ppc64x.s +++ b/misc/cgo/test/testdata/issue9400/asm_ppc64x.s @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // +build ppc64 ppc64le -// +build !gccgo +// +build gc #include "textflag.h" diff --git a/misc/cgo/test/testdata/issue9400/asm_riscv64.s b/misc/cgo/test/testdata/issue9400/asm_riscv64.s index 20fcc0066d6b2b272534fdaf464b623947431d32..244dadac35097526a7a7d4384aae91d321eb7900 100644 --- a/misc/cgo/test/testdata/issue9400/asm_riscv64.s +++ b/misc/cgo/test/testdata/issue9400/asm_riscv64.s @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // +build riscv64 -// +build !gccgo +// +build gc #include "textflag.h" diff --git a/misc/cgo/test/testdata/issue9400/asm_s390x.s b/misc/cgo/test/testdata/issue9400/asm_s390x.s index fc9ad724c15fc11b4544b7fb06cf9c0e68ae30bd..4856492958b116f6105c464c810a428819000d1a 100644 --- a/misc/cgo/test/testdata/issue9400/asm_s390x.s +++ b/misc/cgo/test/testdata/issue9400/asm_s390x.s @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !gccgo +// +build gc #include "textflag.h" diff --git a/misc/cgo/test/testx.c b/misc/cgo/test/testx.c new file mode 100644 index 0000000000000000000000000000000000000000..1258e326a41d1a8dda48d0462f66dd43c1b5f5a7 --- /dev/null +++ b/misc/cgo/test/testx.c @@ -0,0 +1,24 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "_cgo_export.h" + +void lockOSThreadC(void) { + lockOSThreadCallback(); +} + +void issue7978c(uint32_t *sync) { + while(__atomic_load_n(sync, __ATOMIC_SEQ_CST) != 0) + ; + __atomic_add_fetch(sync, 1, __ATOMIC_SEQ_CST); + while(__atomic_load_n(sync, __ATOMIC_SEQ_CST) != 2) + ; + issue7978cb(); + __atomic_add_fetch(sync, 1, __ATOMIC_SEQ_CST); + while(__atomic_load_n(sync, __ATOMIC_SEQ_CST) != 6) + ; +} + +void f7665(void) { +} diff --git a/misc/cgo/test/testx.go b/misc/cgo/test/testx.go index 2b2e69ec00f162e7f2c9c0f141c73704ec2c7c83..823c3e13d2927aac7bfd240ddd46b69882a8aff0 100644 --- a/misc/cgo/test/testx.go +++ b/misc/cgo/test/testx.go @@ -12,6 +12,7 @@ package cgotest import ( "runtime" + "runtime/cgo" "runtime/debug" "strings" "sync" @@ -26,7 +27,6 @@ import ( extern void doAdd(int, int); // issue 1328 -extern void BackIntoGo(void); void IntoC(void); // issue 1560 @@ -38,11 +38,7 @@ long long mysleep(int seconds); long long twoSleep(int); // issue 3775 -void lockOSThreadCallback(void); -inline static void lockOSThreadC(void) -{ - lockOSThreadCallback(); -} +void lockOSThreadC(void); int usleep(unsigned usec); // issue 4054 part 2 - part 1 in test.go @@ -81,21 +77,9 @@ extern void f7665(void); #include -void issue7978cb(void); - // use ugly atomic variable sync since that doesn't require calling back into // Go code or OS dependencies -static void issue7978c(uint32_t *sync) { - while(__atomic_load_n(sync, __ATOMIC_SEQ_CST) != 0) - ; - __atomic_add_fetch(sync, 1, __ATOMIC_SEQ_CST); - while(__atomic_load_n(sync, __ATOMIC_SEQ_CST) != 2) - ; - issue7978cb(); - __atomic_add_fetch(sync, 1, __ATOMIC_SEQ_CST); - while(__atomic_load_n(sync, __ATOMIC_SEQ_CST) != 6) - ; -} +void issue7978c(uint32_t *sync); // issue 8331 part 2 - part 1 in test.go // A typedef of an unnamed struct is the same struct when @@ -428,9 +412,6 @@ func test6907Go(t *testing.T) { // issue 7665 -//export f7665 -func f7665() {} - var bad7665 unsafe.Pointer = C.f7665 var good7665 uintptr = uintptr(C.f7665) @@ -558,6 +539,17 @@ func test31891(t *testing.T) { C.callIssue31891() } +// issue 37033, check if cgo.Handle works properly + +var issue37033 = 42 + +//export GoFunc37033 +func GoFunc37033(handle C.uintptr_t) { + h := cgo.Handle(handle) + ch := h.Value().(chan int) + ch <- issue37033 +} + // issue 38408 // A typedef pointer can be used as the element type. // No runtime test; just make sure it compiles. diff --git a/misc/cgo/testcarchive/carchive_test.go b/misc/cgo/testcarchive/carchive_test.go index 6a5adf79ca05bb72cdbb048fade45c15d13f499b..55be3c5f70710ffe3f1847ac9a545790cdf0d501 100644 --- a/misc/cgo/testcarchive/carchive_test.go +++ b/misc/cgo/testcarchive/carchive_test.go @@ -10,7 +10,6 @@ import ( "debug/elf" "flag" "fmt" - "io/ioutil" "log" "os" "os/exec" @@ -53,7 +52,7 @@ func testMain(m *testing.M) int { // We need a writable GOPATH in which to run the tests. // Construct one in a temporary directory. var err error - GOPATH, err = ioutil.TempDir("", "carchive_test") + GOPATH, err = os.MkdirTemp("", "carchive_test") if err != nil { log.Panic(err) } @@ -74,7 +73,7 @@ func testMain(m *testing.M) int { log.Panic(err) } os.Setenv("PWD", modRoot) - if err := ioutil.WriteFile("go.mod", []byte("module testcarchive\n"), 0666); err != nil { + if err := os.WriteFile("go.mod", []byte("module testcarchive\n"), 0666); err != nil { log.Panic(err) } @@ -176,7 +175,7 @@ func genHeader(t *testing.T, header, dir string) { // The 'cgo' command generates a number of additional artifacts, // but we're only interested in the header. // Shunt the rest of the outputs to a temporary directory. - objDir, err := ioutil.TempDir(GOPATH, "_obj") + objDir, err := os.MkdirTemp(GOPATH, "_obj") if err != nil { t.Fatal(err) } @@ -252,7 +251,7 @@ var badLineRegexp = regexp.MustCompile(`(?m)^#line [0-9]+ "/.*$`) // the user and make the files change based on details of the location // of GOPATH. func checkLineComments(t *testing.T, hdrname string) { - hdr, err := ioutil.ReadFile(hdrname) + hdr, err := os.ReadFile(hdrname) if err != nil { if !os.IsNotExist(err) { t.Error(err) @@ -618,7 +617,7 @@ func TestExtar(t *testing.T) { t.Fatal(err) } s := strings.Replace(testar, "PWD", dir, 1) - if err := ioutil.WriteFile("testar", []byte(s), 0777); err != nil { + if err := os.WriteFile("testar", []byte(s), 0777); err != nil { t.Fatal(err) } @@ -776,7 +775,7 @@ func TestSIGPROF(t *testing.T) { // tool with -buildmode=c-archive, it passes -shared to the compiler, // so we override that. The go tool doesn't work this way, but Bazel // will likely do it in the future. And it ought to work. This test -// was added because at one time it did not work on PPC GNU/Linux. +// was added because at one time it did not work on PPC Linux. func TestCompileWithoutShared(t *testing.T) { // For simplicity, reuse the signal forwarding test. checkSignalForwardingTest(t) diff --git a/misc/cgo/testcarchive/testdata/libgo6/sigprof.go b/misc/cgo/testcarchive/testdata/libgo6/sigprof.go index 4cb05dc61786ab0f1278ab493996c20022fb044a..31527c59af1213aeee245b5daa545ee7194fa93d 100644 --- a/misc/cgo/testcarchive/testdata/libgo6/sigprof.go +++ b/misc/cgo/testcarchive/testdata/libgo6/sigprof.go @@ -5,7 +5,7 @@ package main import ( - "io/ioutil" + "io" "runtime/pprof" ) @@ -13,7 +13,7 @@ import "C" //export go_start_profile func go_start_profile() { - pprof.StartCPUProfile(ioutil.Discard) + pprof.StartCPUProfile(io.Discard) } //export go_stop_profile diff --git a/misc/cgo/testcarchive/testdata/main_unix.c b/misc/cgo/testcarchive/testdata/main_unix.c index b23ac1c2428bac3abcf1d242c44b3c42e19abcd8..bd00f9d233995f771575c66109f11f92f1396dd6 100644 --- a/misc/cgo/testcarchive/testdata/main_unix.c +++ b/misc/cgo/testcarchive/testdata/main_unix.c @@ -36,7 +36,7 @@ int install_handler() { return 2; } // gccgo does not set SA_ONSTACK for SIGSEGV. - if (getenv("GCCGO") == "" && (osa.sa_flags&SA_ONSTACK) == 0) { + if (getenv("GCCGO") == NULL && (osa.sa_flags&SA_ONSTACK) == 0) { fprintf(stderr, "Go runtime did not install signal handler\n"); return 2; } diff --git a/misc/cgo/testcshared/cshared_test.go b/misc/cgo/testcshared/cshared_test.go index 3a4886cf30a88be4fcd62dff23256a4ec058097d..19ad8c76a838b54bad64c35635216088ff40db80 100644 --- a/misc/cgo/testcshared/cshared_test.go +++ b/misc/cgo/testcshared/cshared_test.go @@ -11,7 +11,6 @@ import ( "encoding/binary" "flag" "fmt" - "io/ioutil" "log" "os" "os/exec" @@ -125,7 +124,7 @@ func testMain(m *testing.M) int { // Copy testdata into GOPATH/src/testcshared, along with a go.mod file // declaring the same path. - GOPATH, err := ioutil.TempDir("", "cshared_test") + GOPATH, err := os.MkdirTemp("", "cshared_test") if err != nil { log.Panic(err) } @@ -140,7 +139,7 @@ func testMain(m *testing.M) int { log.Panic(err) } os.Setenv("PWD", modRoot) - if err := ioutil.WriteFile("go.mod", []byte("module testcshared\n"), 0666); err != nil { + if err := os.WriteFile("go.mod", []byte("module testcshared\n"), 0666); err != nil { log.Panic(err) } @@ -260,7 +259,7 @@ func createHeaders() error { // The 'cgo' command generates a number of additional artifacts, // but we're only interested in the header. // Shunt the rest of the outputs to a temporary directory. - objDir, err := ioutil.TempDir("", "testcshared_obj") + objDir, err := os.MkdirTemp("", "testcshared_obj") if err != nil { return err } @@ -293,11 +292,60 @@ func createHeaders() error { "-installsuffix", "testcshared", "-o", libgoname, filepath.Join(".", "libgo", "libgo.go")} + if GOOS == "windows" && strings.HasSuffix(args[6], ".a") { + args[6] = strings.TrimSuffix(args[6], ".a") + ".dll" + } cmd = exec.Command(args[0], args[1:]...) out, err = cmd.CombinedOutput() if err != nil { return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out) } + if GOOS == "windows" { + // We can't simply pass -Wl,--out-implib, because this relies on having imports from multiple packages, + // which results in the linkers output implib getting overwritten at each step. So instead build the + // import library the traditional way, using a def file. + err = os.WriteFile("libgo.def", + []byte("LIBRARY libgo.dll\nEXPORTS\n\tDidInitRun\n\tDidMainRun\n\tDivu\n\tFromPkg\n\t_cgo_dummy_export\n"), + 0644) + if err != nil { + return fmt.Errorf("unable to write def file: %v", err) + } + out, err = exec.Command(cc[0], append(cc[1:], "-print-prog-name=dlltool")...).CombinedOutput() + if err != nil { + return fmt.Errorf("unable to find dlltool path: %v\n%s\n", err, out) + } + args := []string{strings.TrimSpace(string(out)), "-D", args[6], "-l", libgoname, "-d", "libgo.def"} + + // This is an unfortunate workaround for https://github.com/mstorsjo/llvm-mingw/issues/205 in which + // we basically reimplement the contents of the dlltool.sh wrapper: https://git.io/JZFlU + dlltoolContents, err := os.ReadFile(args[0]) + if err != nil { + return fmt.Errorf("unable to read dlltool: %v\n", err) + } + if bytes.HasPrefix(dlltoolContents, []byte("#!/bin/sh")) && bytes.Contains(dlltoolContents, []byte("llvm-dlltool")) { + base, name := filepath.Split(args[0]) + args[0] = filepath.Join(base, "llvm-dlltool") + var machine string + switch strings.SplitN(name, "-", 2)[0] { + case "i686": + machine = "i386" + case "x86_64": + machine = "i386:x86-64" + case "armv7": + machine = "arm" + case "aarch64": + machine = "arm64" + } + if len(machine) > 0 { + args = append(args, "-m", machine) + } + } + + out, err = exec.Command(args[0], args[1:]...).CombinedOutput() + if err != nil { + return fmt.Errorf("unable to run dlltool to create import library: %v\n%s\n", err, out) + } + } if runtime.GOOS != GOOS && GOOS == "android" { args = append(adbCmd(), "push", libgoname, fmt.Sprintf("%s/%s", androiddir, libgoname)) @@ -381,7 +429,7 @@ func main() { srcfile := filepath.Join(tmpdir, "test.go") objfile := filepath.Join(tmpdir, "test.dll") - if err := ioutil.WriteFile(srcfile, []byte(prog), 0666); err != nil { + if err := os.WriteFile(srcfile, []byte(prog), 0666); err != nil { t.Fatal(err) } argv := []string{"build", "-buildmode=c-shared"} @@ -401,7 +449,7 @@ func main() { defer f.Close() section := f.Section(".edata") if section == nil { - t.Fatalf(".edata section is not present") + t.Skip(".edata section is not present") } // TODO: deduplicate this struct from cmd/link/internal/ld/pe.go @@ -643,7 +691,7 @@ func TestPIE(t *testing.T) { // Test that installing a second time recreates the header file. func TestCachedInstall(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "cshared") + tmpdir, err := os.MkdirTemp("", "cshared") if err != nil { t.Fatal(err) } @@ -719,14 +767,14 @@ func TestCachedInstall(t *testing.T) { // copyFile copies src to dst. func copyFile(t *testing.T, dst, src string) { t.Helper() - data, err := ioutil.ReadFile(src) + data, err := os.ReadFile(src) if err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil { t.Fatal(err) } - if err := ioutil.WriteFile(dst, data, 0666); err != nil { + if err := os.WriteFile(dst, data, 0666); err != nil { t.Fatal(err) } } @@ -743,14 +791,19 @@ func TestGo2C2Go(t *testing.T) { t.Parallel() - tmpdir, err := ioutil.TempDir("", "cshared-TestGo2C2Go") + tmpdir, err := os.MkdirTemp("", "cshared-TestGo2C2Go") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpdir) lib := filepath.Join(tmpdir, "libtestgo2c2go."+libSuffix) - run(t, nil, "go", "build", "-buildmode=c-shared", "-o", lib, "./go2c2go/go") + var env []string + if GOOS == "windows" && strings.HasSuffix(lib, ".a") { + env = append(env, "CGO_LDFLAGS=-Wl,--out-implib,"+lib, "CGO_LDFLAGS_ALLOW=.*") + lib = strings.TrimSuffix(lib, ".a") + ".dll" + } + run(t, env, "go", "build", "-buildmode=c-shared", "-o", lib, "./go2c2go/go") cgoCflags := os.Getenv("CGO_CFLAGS") if cgoCflags != "" { diff --git a/misc/cgo/testgodefs/testgodefs_test.go b/misc/cgo/testgodefs/testgodefs_test.go index 4c2312c1c89633803f76def2a6733e0fa64a6f3c..aae34043605d340a64c6ede42b79ce1ca4575a6e 100644 --- a/misc/cgo/testgodefs/testgodefs_test.go +++ b/misc/cgo/testgodefs/testgodefs_test.go @@ -6,7 +6,6 @@ package testgodefs import ( "bytes" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -34,7 +33,7 @@ func TestGoDefs(t *testing.T) { t.Fatal(err) } - gopath, err := ioutil.TempDir("", "testgodefs-gopath") + gopath, err := os.MkdirTemp("", "testgodefs-gopath") if err != nil { t.Fatal(err) } @@ -58,20 +57,20 @@ func TestGoDefs(t *testing.T) { t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr) } - if err := ioutil.WriteFile(filepath.Join(dir, fp+"_defs.go"), out, 0644); err != nil { + if err := os.WriteFile(filepath.Join(dir, fp+"_defs.go"), out, 0644); err != nil { t.Fatal(err) } } - main, err := ioutil.ReadFile(filepath.Join("testdata", "main.go")) + main, err := os.ReadFile(filepath.Join("testdata", "main.go")) if err != nil { t.Fatal(err) } - if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), main, 0644); err != nil { + if err := os.WriteFile(filepath.Join(dir, "main.go"), main, 0644); err != nil { t.Fatal(err) } - if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module testgodefs\ngo 1.14\n"), 0644); err != nil { + if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module testgodefs\ngo 1.14\n"), 0644); err != nil { t.Fatal(err) } diff --git a/misc/cgo/testplugin/plugin_test.go b/misc/cgo/testplugin/plugin_test.go index 8869528015d86d2a8ef8252c930d1ab8ab97a491..9697dbf7a78e3c35b2a60ed168f8a254e67e94b0 100644 --- a/misc/cgo/testplugin/plugin_test.go +++ b/misc/cgo/testplugin/plugin_test.go @@ -9,7 +9,6 @@ import ( "context" "flag" "fmt" - "io/ioutil" "log" "os" "os/exec" @@ -31,15 +30,28 @@ func TestMain(m *testing.M) { os.Exit(testMain(m)) } +// tmpDir is used to cleanup logged commands -- s/tmpDir/$TMPDIR/ +var tmpDir string + +// prettyPrintf prints lines with tmpDir sanitized. +func prettyPrintf(format string, args ...interface{}) { + s := fmt.Sprintf(format, args...) + if tmpDir != "" { + s = strings.ReplaceAll(s, tmpDir, "$TMPDIR") + } + fmt.Print(s) +} + func testMain(m *testing.M) int { // Copy testdata into GOPATH/src/testplugin, along with a go.mod file // declaring the same path. - GOPATH, err := ioutil.TempDir("", "plugin_test") + GOPATH, err := os.MkdirTemp("", "plugin_test") if err != nil { log.Panic(err) } defer os.RemoveAll(GOPATH) + tmpDir = GOPATH modRoot := filepath.Join(GOPATH, "src", "testplugin") altRoot := filepath.Join(GOPATH, "alt", "src", "testplugin") @@ -50,14 +62,20 @@ func testMain(m *testing.M) int { if err := overlayDir(dstRoot, srcRoot); err != nil { log.Panic(err) } - if err := ioutil.WriteFile(filepath.Join(dstRoot, "go.mod"), []byte("module testplugin\n"), 0666); err != nil { + prettyPrintf("mkdir -p %s\n", dstRoot) + prettyPrintf("rsync -a %s/ %s\n", srcRoot, dstRoot) + + if err := os.WriteFile(filepath.Join(dstRoot, "go.mod"), []byte("module testplugin\n"), 0666); err != nil { log.Panic(err) } + prettyPrintf("echo 'module testplugin' > %s/go.mod\n", dstRoot) } os.Setenv("GOPATH", filepath.Join(GOPATH, "alt")) if err := os.Chdir(altRoot); err != nil { log.Panic(err) + } else { + prettyPrintf("cd %s\n", altRoot) } os.Setenv("PWD", altRoot) goCmd(nil, "build", "-buildmode=plugin", "-o", filepath.Join(modRoot, "plugin-mismatch.so"), "./plugin-mismatch") @@ -65,6 +83,8 @@ func testMain(m *testing.M) int { os.Setenv("GOPATH", GOPATH) if err := os.Chdir(modRoot); err != nil { log.Panic(err) + } else { + prettyPrintf("cd %s\n", modRoot) } os.Setenv("PWD", modRoot) @@ -72,13 +92,14 @@ func testMain(m *testing.M) int { goCmd(nil, "build", "-buildmode=plugin", "./plugin1") goCmd(nil, "build", "-buildmode=plugin", "./plugin2") - so, err := ioutil.ReadFile("plugin2.so") + so, err := os.ReadFile("plugin2.so") if err != nil { log.Panic(err) } - if err := ioutil.WriteFile("plugin2-dup.so", so, 0444); err != nil { + if err := os.WriteFile("plugin2-dup.so", so, 0444); err != nil { log.Panic(err) } + prettyPrintf("cp plugin2.so plugin2-dup.so\n") goCmd(nil, "build", "-buildmode=plugin", "-o=sub/plugin1.so", "./sub/plugin1") goCmd(nil, "build", "-buildmode=plugin", "-o=unnamed1.so", "./unnamed1/main.go") @@ -95,8 +116,53 @@ func goCmd(t *testing.T, op string, args ...string) { run(t, "go", append([]string{op, "-gcflags", gcflags}, args...)...) } +// escape converts a string to something suitable for a shell command line. +func escape(s string) string { + s = strings.Replace(s, "\\", "\\\\", -1) + s = strings.Replace(s, "'", "\\'", -1) + // Conservative guess at characters that will force quoting + if s == "" || strings.ContainsAny(s, "\\ ;#*&$~?!|[]()<>{}`") { + s = "'" + s + "'" + } + return s +} + +// asCommandLine renders cmd as something that could be copy-and-pasted into a command line +func asCommandLine(cwd string, cmd *exec.Cmd) string { + s := "(" + if cmd.Dir != "" && cmd.Dir != cwd { + s += "cd" + escape(cmd.Dir) + ";" + } + for _, e := range cmd.Env { + if !strings.HasPrefix(e, "PATH=") && + !strings.HasPrefix(e, "HOME=") && + !strings.HasPrefix(e, "USER=") && + !strings.HasPrefix(e, "SHELL=") { + s += " " + s += escape(e) + } + } + // These EVs are relevant to this test. + for _, e := range os.Environ() { + if strings.HasPrefix(e, "PWD=") || + strings.HasPrefix(e, "GOPATH=") || + strings.HasPrefix(e, "LD_LIBRARY_PATH=") { + s += " " + s += escape(e) + } + } + for _, a := range cmd.Args { + s += " " + s += escape(a) + } + s += " )" + return s +} + func run(t *testing.T, bin string, args ...string) string { cmd := exec.Command(bin, args...) + cmdLine := asCommandLine(".", cmd) + prettyPrintf("%s\n", cmdLine) cmd.Stderr = new(strings.Builder) out, err := cmd.Output() if err != nil { @@ -197,6 +263,17 @@ func TestIssue25756(t *testing.T) { } } +// Test with main using -buildmode=pie with plugin for issue #43228 +func TestIssue25756pie(t *testing.T) { + if os.Getenv("GO_BUILDER_NAME") == "darwin-arm64-11_0-toothrot" { + t.Skip("broken on darwin/arm64 builder in sharded mode; see issue 46239") + } + + goCmd(t, "build", "-buildmode=plugin", "-o", "life.so", "./issue25756/plugin") + goCmd(t, "build", "-buildmode=pie", "-o", "issue25756pie.exe", "./issue25756/main.go") + run(t, "./issue25756pie.exe") +} + func TestMethod(t *testing.T) { // Exported symbol's method must be live. goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./method/plugin.go") diff --git a/misc/cgo/testplugin/testdata/method2/main.go b/misc/cgo/testplugin/testdata/method2/main.go index 6a87e7b6a0fe0d83cc1f87c7ae2b93d1d9cb71c3..89afbda3d479af1e0c3f520bbd000c973d9af2dd 100644 --- a/misc/cgo/testplugin/testdata/method2/main.go +++ b/misc/cgo/testplugin/testdata/method2/main.go @@ -15,7 +15,7 @@ import ( var t p.T -type I interface { M() } +type I interface{ M() } func main() { pl, err := plugin.Open("method2.so") diff --git a/misc/cgo/testsanitizers/cc_test.go b/misc/cgo/testsanitizers/cc_test.go index 0192a663dddabe58ea68c34f010e09fdfa552d04..384b6250e1ef168ae130f6d77a5e302341d1cd99 100644 --- a/misc/cgo/testsanitizers/cc_test.go +++ b/misc/cgo/testsanitizers/cc_test.go @@ -11,7 +11,6 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -36,7 +35,7 @@ func requireOvercommit(t *testing.T) { overcommit.Once.Do(func() { var out []byte - out, overcommit.err = ioutil.ReadFile("/proc/sys/vm/overcommit_memory") + out, overcommit.err = os.ReadFile("/proc/sys/vm/overcommit_memory") if overcommit.err != nil { return } @@ -313,14 +312,14 @@ int main() { `) func (c *config) checkCSanitizer() (skip bool, err error) { - dir, err := ioutil.TempDir("", c.sanitizer) + dir, err := os.MkdirTemp("", c.sanitizer) if err != nil { return false, fmt.Errorf("failed to create temp directory: %v", err) } defer os.RemoveAll(dir) src := filepath.Join(dir, "return0.c") - if err := ioutil.WriteFile(src, cMain, 0600); err != nil { + if err := os.WriteFile(src, cMain, 0600); err != nil { return false, fmt.Errorf("failed to write C source file: %v", err) } @@ -418,7 +417,7 @@ func (d *tempDir) Join(name string) string { func newTempDir(t *testing.T) *tempDir { t.Helper() - dir, err := ioutil.TempDir("", filepath.Dir(t.Name())) + dir, err := os.MkdirTemp("", filepath.Dir(t.Name())) if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } @@ -440,3 +439,14 @@ func hangProneCmd(name string, arg ...string) *exec.Cmd { } return cmd } + +// mSanSupported is a copy of the function cmd/internal/sys.MSanSupported, +// because the internal pacakage can't be used here. +func mSanSupported(goos, goarch string) bool { + switch goos { + case "linux": + return goarch == "amd64" || goarch == "arm64" + default: + return false + } +} diff --git a/misc/cgo/testsanitizers/cshared_test.go b/misc/cgo/testsanitizers/cshared_test.go index 56063ea620162c8ae5ab6a99dc9c3ad5bae59512..8fd03715a11bd8f9bc3076904049d88bad7a24eb 100644 --- a/misc/cgo/testsanitizers/cshared_test.go +++ b/misc/cgo/testsanitizers/cshared_test.go @@ -6,7 +6,7 @@ package sanitizers_test import ( "fmt" - "io/ioutil" + "os" "strings" "testing" ) @@ -19,6 +19,12 @@ func TestShared(t *testing.T) { if err != nil { t.Fatal(err) } + + GOARCH, err := goEnv("GOARCH") + if err != nil { + t.Fatal(err) + } + libExt := "so" if GOOS == "darwin" { libExt = "dylib" @@ -41,6 +47,11 @@ func TestShared(t *testing.T) { for _, tc := range cases { tc := tc name := strings.TrimSuffix(tc.src, ".go") + //The memory sanitizer tests require support for the -msan option. + if tc.sanitizer == "memory" && !mSanSupported(GOOS, GOARCH) { + t.Logf("skipping %s test on %s/%s; -msan option is not supported.", name, GOOS, GOARCH) + continue + } t.Run(name, func(t *testing.T) { t.Parallel() config := configure(tc.sanitizer) @@ -53,7 +64,7 @@ func TestShared(t *testing.T) { mustRun(t, config.goCmd("build", "-buildmode=c-shared", "-o", lib, srcPath(tc.src))) cSrc := dir.Join("main.c") - if err := ioutil.WriteFile(cSrc, cMain, 0600); err != nil { + if err := os.WriteFile(cSrc, cMain, 0600); err != nil { t.Fatalf("failed to write C source file: %v", err) } diff --git a/misc/cgo/testsanitizers/msan_test.go b/misc/cgo/testsanitizers/msan_test.go index 5e2f9759baef49c1f92b9feed27f0de24e6c3000..5ee9947a58504faa5f94e22b6419a7c46e882948 100644 --- a/misc/cgo/testsanitizers/msan_test.go +++ b/misc/cgo/testsanitizers/msan_test.go @@ -10,6 +10,19 @@ import ( ) func TestMSAN(t *testing.T) { + goos, err := goEnv("GOOS") + if err != nil { + t.Fatal(err) + } + goarch, err := goEnv("GOARCH") + if err != nil { + t.Fatal(err) + } + // The msan tests require support for the -msan option. + if !mSanSupported(goos, goarch) { + t.Skipf("skipping on %s/%s; -msan option is not supported.", goos, goarch) + } + t.Parallel() requireOvercommit(t) config := configure("memory") @@ -29,6 +42,7 @@ func TestMSAN(t *testing.T) { {src: "msan5.go"}, {src: "msan6.go"}, {src: "msan7.go"}, + {src: "msan8.go"}, {src: "msan_fail.go", wantErr: true}, } for _, tc := range cases { diff --git a/misc/cgo/testsanitizers/testdata/msan8.go b/misc/cgo/testsanitizers/testdata/msan8.go new file mode 100644 index 0000000000000000000000000000000000000000..1cb5c5677fa758711ae44a6d04e3fdcccbe6c00e --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan8.go @@ -0,0 +1,109 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +#include +#include +#include + +#include + +// cgoTracebackArg is the type of the argument passed to msanGoTraceback. +struct cgoTracebackArg { + uintptr_t context; + uintptr_t sigContext; + uintptr_t* buf; + uintptr_t max; +}; + +// msanGoTraceback is registered as the cgo traceback function. +// This will be called when a signal occurs. +void msanGoTraceback(void* parg) { + struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg); + arg->buf[0] = 0; +} + +// msanGoWait will be called with all registers undefined as far as +// msan is concerned. It just waits for a signal. +// Because the registers are msan-undefined, the signal handler will +// be invoked with all registers msan-undefined. +__attribute__((noinline)) +void msanGoWait(unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6) { + sigset_t mask; + + sigemptyset(&mask); + sigsuspend(&mask); +} + +// msanGoSignalThread is the thread ID of the msanGoLoop thread. +static pthread_t msanGoSignalThread; + +// msanGoSignalThreadSet is used to record that msanGoSignalThread +// has been initialized. This is accessed atomically. +static int32_t msanGoSignalThreadSet; + +// uninit is explicitly poisoned, so that we can make all registers +// undefined by calling msanGoWait. +static unsigned long uninit; + +// msanGoLoop loops calling msanGoWait, with the arguments passed +// such that msan thinks that they are undefined. msan permits +// undefined values to be used as long as they are not used to +// for conditionals or for memory access. +void msanGoLoop() { + int i; + + msanGoSignalThread = pthread_self(); + __atomic_store_n(&msanGoSignalThreadSet, 1, __ATOMIC_SEQ_CST); + + // Force uninit to be undefined for msan. + __msan_poison(&uninit, sizeof uninit); + for (i = 0; i < 100; i++) { + msanGoWait(uninit, uninit, uninit, uninit, uninit, uninit); + } +} + +// msanGoReady returns whether msanGoSignalThread is set. +int msanGoReady() { + return __atomic_load_n(&msanGoSignalThreadSet, __ATOMIC_SEQ_CST) != 0; +} + +// msanGoSendSignal sends a signal to the msanGoLoop thread. +void msanGoSendSignal() { + pthread_kill(msanGoSignalThread, SIGWINCH); +} +*/ +import "C" + +import ( + "runtime" + "time" +) + +func main() { + runtime.SetCgoTraceback(0, C.msanGoTraceback, nil, nil) + + c := make(chan bool) + go func() { + defer func() { c <- true }() + C.msanGoLoop() + }() + + for C.msanGoReady() == 0 { + time.Sleep(time.Microsecond) + } + +loop: + for { + select { + case <-c: + break loop + default: + C.msanGoSendSignal() + time.Sleep(time.Microsecond) + } + } +} diff --git a/misc/cgo/testsanitizers/testdata/tsan9.go b/misc/cgo/testsanitizers/testdata/tsan9.go index f166d8b495ac458a4a6dd236d0913cd90a68a0e9..06304be751b5e217113a7d280c31ca61636117a2 100644 --- a/misc/cgo/testsanitizers/testdata/tsan9.go +++ b/misc/cgo/testsanitizers/testdata/tsan9.go @@ -44,7 +44,7 @@ void spin() { import "C" import ( - "io/ioutil" + "io" "runtime/pprof" "time" ) @@ -60,7 +60,7 @@ func goSpin() { } func main() { - pprof.StartCPUProfile(ioutil.Discard) + pprof.StartCPUProfile(io.Discard) go C.spin() goSpin() pprof.StopCPUProfile() diff --git a/misc/cgo/testshared/shared_test.go b/misc/cgo/testshared/shared_test.go index f52391c6f6c6ab1750fce523ad87606f25a5c708..e77f84891543f584ccb5bd2aa538f0e6237e9d17 100644 --- a/misc/cgo/testshared/shared_test.go +++ b/misc/cgo/testshared/shared_test.go @@ -13,7 +13,6 @@ import ( "fmt" "go/build" "io" - "io/ioutil" "log" "os" "os/exec" @@ -90,7 +89,7 @@ func goCmd(t *testing.T, args ...string) string { // TestMain calls testMain so that the latter can use defer (TestMain exits with os.Exit). func testMain(m *testing.M) (int, error) { - workDir, err := ioutil.TempDir("", "shared_test") + workDir, err := os.MkdirTemp("", "shared_test") if err != nil { return 0, err } @@ -177,7 +176,7 @@ func cloneTestdataModule(gopath string) (string, error) { if err := overlayDir(modRoot, "testdata"); err != nil { return "", err } - if err := ioutil.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module testshared\n"), 0644); err != nil { + if err := os.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module testshared\n"), 0644); err != nil { return "", err } return modRoot, nil @@ -318,7 +317,7 @@ func TestShlibnameFiles(t *testing.T) { } for _, pkg := range pkgs { shlibnamefile := filepath.Join(gorootInstallDir, pkg+".shlibname") - contentsb, err := ioutil.ReadFile(shlibnamefile) + contentsb, err := os.ReadFile(shlibnamefile) if err != nil { t.Errorf("error reading shlibnamefile for %s: %v", pkg, err) continue @@ -791,7 +790,7 @@ func resetFileStamps() { // It also sets the time of the file, so that we can see if it is rewritten. func touch(t *testing.T, path string) (cleanup func()) { t.Helper() - data, err := ioutil.ReadFile(path) + data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } @@ -837,14 +836,14 @@ func touch(t *testing.T, path string) (cleanup func()) { // user-writable. perm := fi.Mode().Perm() | 0200 - if err := ioutil.WriteFile(path, data, perm); err != nil { + if err := os.WriteFile(path, data, perm); err != nil { t.Fatal(err) } if err := os.Chtimes(path, nearlyNew, nearlyNew); err != nil { t.Fatal(err) } return func() { - if err := ioutil.WriteFile(path, old, perm); err != nil { + if err := os.WriteFile(path, old, perm); err != nil { t.Fatal(err) } } diff --git a/misc/cgo/testshared/testdata/depBase/asm.s b/misc/cgo/testshared/testdata/depBase/asm.s index a8acf77f0b941d918214a4773f9a892e5fca8cb4..0f1111f3927d3625330bcd04b7f55632a21ad7e8 100644 --- a/misc/cgo/testshared/testdata/depBase/asm.s +++ b/misc/cgo/testshared/testdata/depBase/asm.s @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !gccgo +// +build gc #include "textflag.h" diff --git a/misc/cgo/testshared/testdata/depBase/stubs.go b/misc/cgo/testshared/testdata/depBase/stubs.go index 04534f38dddf97d4283807f1f8c6e34abc64327f..c77953803bd51338a3d245ecf3fe2dfaa9836b8a 100644 --- a/misc/cgo/testshared/testdata/depBase/stubs.go +++ b/misc/cgo/testshared/testdata/depBase/stubs.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !gccgo +// +build gc package depBase diff --git a/misc/cgo/testso/so_test.go b/misc/cgo/testso/so_test.go index 57f0fd34f78aca081045e20a22c9f3c68e10cbbd..2023c51f113785d14482ee7a7b3556825784e7c3 100644 --- a/misc/cgo/testso/so_test.go +++ b/misc/cgo/testso/so_test.go @@ -7,7 +7,6 @@ package so_test import ( - "io/ioutil" "log" "os" "os/exec" @@ -37,7 +36,7 @@ func requireTestSOSupported(t *testing.T) { func TestSO(t *testing.T) { requireTestSOSupported(t) - GOPATH, err := ioutil.TempDir("", "cgosotest") + GOPATH, err := os.MkdirTemp("", "cgosotest") if err != nil { log.Fatal(err) } @@ -47,7 +46,7 @@ func TestSO(t *testing.T) { if err := overlayDir(modRoot, "testdata"); err != nil { log.Panic(err) } - if err := ioutil.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module cgosotest\n"), 0666); err != nil { + if err := os.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module cgosotest\n"), 0666); err != nil { log.Panic(err) } @@ -80,6 +79,10 @@ func TestSO(t *testing.T) { case "windows": ext = "dll" args = append(args, "-DEXPORT_DLL") + // At least in mingw-clang it is not permitted to just name a .dll + // on the command line. You must name the corresponding import + // library instead, even though the dll is used when the executable is run. + args = append(args, "-Wl,-out-implib,libcgosotest.a") case "aix": ext = "so.1" } diff --git a/misc/cgo/testso/testdata/cgoso.go b/misc/cgo/testso/testdata/cgoso.go index bba5de331212f2506e630e5c484cc1cc0aa905fc..b59b2a8e8b1541f6a2a351b8e716267a5441d13d 100644 --- a/misc/cgo/testso/testdata/cgoso.go +++ b/misc/cgo/testso/testdata/cgoso.go @@ -14,7 +14,7 @@ package cgosotest #cgo solaris LDFLAGS: -L. -lcgosotest #cgo netbsd LDFLAGS: -L. libcgosotest.so #cgo darwin LDFLAGS: -L. libcgosotest.dylib -#cgo windows LDFLAGS: -L. libcgosotest.dll +#cgo windows LDFLAGS: -L. libcgosotest.a #cgo aix LDFLAGS: -L. -l cgosotest void init(void); diff --git a/misc/cgo/testsovar/so_test.go b/misc/cgo/testsovar/so_test.go index 57f0fd34f78aca081045e20a22c9f3c68e10cbbd..2023c51f113785d14482ee7a7b3556825784e7c3 100644 --- a/misc/cgo/testsovar/so_test.go +++ b/misc/cgo/testsovar/so_test.go @@ -7,7 +7,6 @@ package so_test import ( - "io/ioutil" "log" "os" "os/exec" @@ -37,7 +36,7 @@ func requireTestSOSupported(t *testing.T) { func TestSO(t *testing.T) { requireTestSOSupported(t) - GOPATH, err := ioutil.TempDir("", "cgosotest") + GOPATH, err := os.MkdirTemp("", "cgosotest") if err != nil { log.Fatal(err) } @@ -47,7 +46,7 @@ func TestSO(t *testing.T) { if err := overlayDir(modRoot, "testdata"); err != nil { log.Panic(err) } - if err := ioutil.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module cgosotest\n"), 0666); err != nil { + if err := os.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module cgosotest\n"), 0666); err != nil { log.Panic(err) } @@ -80,6 +79,10 @@ func TestSO(t *testing.T) { case "windows": ext = "dll" args = append(args, "-DEXPORT_DLL") + // At least in mingw-clang it is not permitted to just name a .dll + // on the command line. You must name the corresponding import + // library instead, even though the dll is used when the executable is run. + args = append(args, "-Wl,-out-implib,libcgosotest.a") case "aix": ext = "so.1" } diff --git a/misc/cgo/testsovar/testdata/cgoso.go b/misc/cgo/testsovar/testdata/cgoso.go index 9c7f95e92ea454e68ff238aa6bf18ee2732e3a94..d9deb556da87758e579d1996439ea9cfb70cf4ea 100644 --- a/misc/cgo/testsovar/testdata/cgoso.go +++ b/misc/cgo/testsovar/testdata/cgoso.go @@ -18,7 +18,7 @@ package cgosotest #cgo solaris LDFLAGS: -L. -lcgosotest #cgo netbsd LDFLAGS: -L. libcgosotest.so #cgo darwin LDFLAGS: -L. libcgosotest.dylib -#cgo windows LDFLAGS: -L. libcgosotest.dll +#cgo windows LDFLAGS: -L. libcgosotest.a #cgo aix LDFLAGS: -L. -l cgosotest #include "cgoso_c.h" diff --git a/misc/chrome/gophertool/popup.html b/misc/chrome/gophertool/popup.html index 97404062761126972a9e8c8de0d6c4ddde4d9b7f..ad42a3847c7d6265e1f666eb00c7122af7465c0f 100644 --- a/misc/chrome/gophertool/popup.html +++ b/misc/chrome/gophertool/popup.html @@ -15,7 +15,7 @@ pkg id/name:

    Also: buildbots -Github +GitHub diff --git a/misc/ios/detect.go b/misc/ios/detect.go index d32bcc3202a443a26a7b2a394342d18e5feeb650..cde57238923be6b959a3ac6c9341903416cbd17e 100644 --- a/misc/ios/detect.go +++ b/misc/ios/detect.go @@ -16,7 +16,6 @@ import ( "bytes" "crypto/x509" "fmt" - "io/ioutil" "os" "os/exec" "strings" @@ -38,7 +37,7 @@ func main() { fmt.Println("# will be overwritten when running Go programs.") for _, mp := range mps { fmt.Println() - f, err := ioutil.TempFile("", "go_ios_detect_") + f, err := os.CreateTemp("", "go_ios_detect_") check(err) fname := f.Name() defer os.Remove(fname) diff --git a/misc/ios/go_ios_exec.go b/misc/ios/go_ios_exec.go index 0acf1b259c0451b20a0d025fff4e91bf7b599614..9e63717d9214d6eb98726bb21d9f2c993fded2ac 100644 --- a/misc/ios/go_ios_exec.go +++ b/misc/ios/go_ios_exec.go @@ -26,7 +26,6 @@ import ( "fmt" "go/build" "io" - "io/ioutil" "log" "net" "os" @@ -79,7 +78,7 @@ func main() { func runMain() (int, error) { var err error - tmpdir, err = ioutil.TempDir("", "go_ios_exec_") + tmpdir, err = os.MkdirTemp("", "go_ios_exec_") if err != nil { return 1, err } @@ -205,13 +204,13 @@ func assembleApp(appdir, bin string) error { } entitlementsPath := filepath.Join(tmpdir, "Entitlements.plist") - if err := ioutil.WriteFile(entitlementsPath, []byte(entitlementsPlist()), 0744); err != nil { + if err := os.WriteFile(entitlementsPath, []byte(entitlementsPlist()), 0744); err != nil { return err } - if err := ioutil.WriteFile(filepath.Join(appdir, "Info.plist"), []byte(infoPlist(pkgpath)), 0744); err != nil { + if err := os.WriteFile(filepath.Join(appdir, "Info.plist"), []byte(infoPlist(pkgpath)), 0744); err != nil { return err } - if err := ioutil.WriteFile(filepath.Join(appdir, "ResourceRules.plist"), []byte(resourceRules), 0744); err != nil { + if err := os.WriteFile(filepath.Join(appdir, "ResourceRules.plist"), []byte(resourceRules), 0744); err != nil { return err } return nil diff --git a/misc/linkcheck/linkcheck.go b/misc/linkcheck/linkcheck.go index d9bfd2f767eb190cff1cb75170c5ebbd549aeb99..570b430da4f1cbc76f14250822406e5449978757 100644 --- a/misc/linkcheck/linkcheck.go +++ b/misc/linkcheck/linkcheck.go @@ -11,7 +11,7 @@ import ( "errors" "flag" "fmt" - "io/ioutil" + "io" "log" "net/http" "os" @@ -144,7 +144,7 @@ func doCrawl(url string) error { if res.StatusCode != 200 { return errors.New(res.Status) } - slurp, err := ioutil.ReadAll(res.Body) + slurp, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { log.Fatalf("Error reading %s body: %v", url, err) diff --git a/misc/reboot/experiment_toolid_test.go b/misc/reboot/experiment_toolid_test.go index eabf06b19ee3af65421177ce215e66c366de6e30..4f40284d80f107dd0014d1bfc1171d2a78ac05ef 100644 --- a/misc/reboot/experiment_toolid_test.go +++ b/misc/reboot/experiment_toolid_test.go @@ -13,7 +13,6 @@ package reboot_test import ( "bytes" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -23,7 +22,7 @@ import ( func TestExperimentToolID(t *testing.T) { // Set up GOROOT - goroot, err := ioutil.TempDir("", "experiment-goroot") + goroot, err := os.MkdirTemp("", "experiment-goroot") if err != nil { t.Fatal(err) } @@ -34,13 +33,13 @@ func TestExperimentToolID(t *testing.T) { t.Fatal(err) } - if err := ioutil.WriteFile(filepath.Join(goroot, "VERSION"), []byte("go1.999"), 0666); err != nil { + if err := os.WriteFile(filepath.Join(goroot, "VERSION"), []byte("go1.999"), 0666); err != nil { t.Fatal(err) } env := append(os.Environ(), "GOROOT=", "GOROOT_BOOTSTRAP="+runtime.GOROOT()) // Use a clean cache. - gocache, err := ioutil.TempDir("", "experiment-gocache") + gocache, err := os.MkdirTemp("", "experiment-gocache") if err != nil { t.Fatal(err) } diff --git a/misc/reboot/reboot_test.go b/misc/reboot/reboot_test.go index 717c0fb709638c2faa089a93828844fae0b24e85..6bafc608b5e234a051c7a19c7e4282cea7297f21 100644 --- a/misc/reboot/reboot_test.go +++ b/misc/reboot/reboot_test.go @@ -7,7 +7,6 @@ package reboot_test import ( - "io/ioutil" "os" "os/exec" "path/filepath" @@ -16,7 +15,7 @@ import ( ) func TestRepeatBootstrap(t *testing.T) { - goroot, err := ioutil.TempDir("", "reboot-goroot") + goroot, err := os.MkdirTemp("", "reboot-goroot") if err != nil { t.Fatal(err) } @@ -27,7 +26,7 @@ func TestRepeatBootstrap(t *testing.T) { t.Fatal(err) } - if err := ioutil.WriteFile(filepath.Join(goroot, "VERSION"), []byte(runtime.Version()), 0666); err != nil { + if err := os.WriteFile(filepath.Join(goroot, "VERSION"), []byte(runtime.Version()), 0666); err != nil { t.Fatal(err) } diff --git a/misc/trace/trace_viewer_full.html b/misc/trace/trace_viewer_full.html index ef2e0ea5733571a6fb021a6ccb6202eeeb24720a..ae6e35fca22badf007ee2a735c2937ebb7eba138 100644 --- a/misc/trace/trace_viewer_full.html +++ b/misc/trace/trace_viewer_full.html @@ -993,13 +993,13 @@
    - X no feedback
    - 0 uninitialized
    - . premonomorphic
    - 1 monomorphic
    - ^ recompute handler
    - P polymorphic
    - N megamorphic
    + X no feedback
    + 0 uninitialized
    + . premonomorphic
    + 1 monomorphic
    + ^ recompute handler
    + P polymorphic
    + N megamorphic
    G generic
    @@ -3596,7 +3596,7 @@
    Graphics Pipeline and Raster Tasks
    - When raster tasks are completed in comparison to the rest of the graphics pipeline.
    + When raster tasks are completed in comparison to the rest of the graphics pipeline.
    Only pages where raster tasks are completed after beginFrame is issued are included.
    diff --git a/misc/wasm/wasm_exec.js b/misc/wasm/wasm_exec.js index 82041e6bb901bde57c43e689984e60a499573aca..231185a123ed12f8bc6b2ad7a56a00e3615a656a 100644 --- a/misc/wasm/wasm_exec.js +++ b/misc/wasm/wasm_exec.js @@ -296,8 +296,8 @@ setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); }, - // func walltime1() (sec int64, nsec int32) - "runtime.walltime1": (sp) => { + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { sp >>>= 0; const msec = (new Date).getTime(); setInt64(sp + 8, msec / 1000); @@ -401,6 +401,7 @@ storeValue(sp + 56, result); this.mem.setUint8(sp + 64, 1); } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 56, err); this.mem.setUint8(sp + 64, 0); } @@ -417,6 +418,7 @@ storeValue(sp + 40, result); this.mem.setUint8(sp + 48, 1); } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 40, err); this.mem.setUint8(sp + 48, 0); } @@ -433,6 +435,7 @@ storeValue(sp + 40, result); this.mem.setUint8(sp + 48, 1); } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 40, err); this.mem.setUint8(sp + 48, 0); } diff --git a/robots.txt b/robots.txt deleted file mode 100644 index 1f53798bb4fe33c86020be7f10c44f29486fd190..0000000000000000000000000000000000000000 --- a/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: / diff --git a/src/archive/tar/stat_actime1.go b/src/archive/tar/stat_actime1.go index 1bdd1c9dcb24cfbf5cf33c9ffef36ff9a0b25da1..4fdf2a04b3df40d6f1d59c119faa9acb5029f317 100644 --- a/src/archive/tar/stat_actime1.go +++ b/src/archive/tar/stat_actime1.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || linux || dragonfly || openbsd || solaris // +build aix linux dragonfly openbsd solaris package tar diff --git a/src/archive/tar/stat_actime2.go b/src/archive/tar/stat_actime2.go index 6f17dbe30725c120218885cc662587b8c7dcb4d2..5a9a35cbb4e22d9df42f307843730032d7e0b392 100644 --- a/src/archive/tar/stat_actime2.go +++ b/src/archive/tar/stat_actime2.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || freebsd || netbsd // +build darwin freebsd netbsd package tar diff --git a/src/archive/tar/stat_unix.go b/src/archive/tar/stat_unix.go index 581d87dca9de7e53837856b6f5e71af42aea1ee0..3957349d6ef0aa4461062c3203c7bd852d6328ed 100644 --- a/src/archive/tar/stat_unix.go +++ b/src/archive/tar/stat_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || linux || darwin || dragonfly || freebsd || openbsd || netbsd || solaris // +build aix linux darwin dragonfly freebsd openbsd netbsd solaris package tar diff --git a/src/archive/tar/tar_test.go b/src/archive/tar/tar_test.go index 91b38401b6c1030f05b81caabb5ac19faa271d25..e9fafc7cc70df5cd5f6308f42c87c6d577f6dabf 100644 --- a/src/archive/tar/tar_test.go +++ b/src/archive/tar/tar_test.go @@ -262,16 +262,11 @@ func TestFileInfoHeaderDir(t *testing.T) { func TestFileInfoHeaderSymlink(t *testing.T) { testenv.MustHaveSymlink(t) - tmpdir, err := os.MkdirTemp("", "TestFileInfoHeaderSymlink") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() link := filepath.Join(tmpdir, "link") target := tmpdir - err = os.Symlink(target, link) - if err != nil { + if err := os.Symlink(target, link); err != nil { t.Fatal(err) } fi, err := os.Lstat(link) diff --git a/src/archive/zip/reader.go b/src/archive/zip/reader.go index c288ad965bc92cd4477e8c9da1eb8033d84c3cbe..2d53f4c7231653d651b4f3b5bb1a15325b4a33ac 100644 --- a/src/archive/zip/reader.go +++ b/src/archive/zip/reader.go @@ -52,12 +52,9 @@ type File struct { FileHeader zip *Reader zipr io.ReaderAt - zipsize int64 headerOffset int64 -} - -func (f *File) hasDataDescriptor() bool { - return f.Flags&0x8 != 0 + zip64 bool // zip64 extended information extra field presence + descErr error // error reading the data descriptor during init } // OpenReader will open the Zip file specified by name and return a ReadCloser. @@ -99,7 +96,15 @@ func (z *Reader) init(r io.ReaderAt, size int64) error { return err } z.r = r - z.File = make([]*File, 0, end.directoryRecords) + // Since the number of directory records is not validated, it is not + // safe to preallocate z.File without first checking that the specified + // number of files is reasonable, since a malformed archive may + // indicate it contains up to 1 << 128 - 1 files. Since each file has a + // header which will be _at least_ 30 bytes we can safely preallocate + // if (data size / 30) >= end.directoryRecords. + if (uint64(size)-end.directorySize)/30 >= end.directoryRecords { + z.File = make([]*File, 0, end.directoryRecords) + } z.Comment = end.comment rs := io.NewSectionReader(r, 0, size) if _, err = rs.Seek(int64(end.directoryOffset), io.SeekStart); err != nil { @@ -112,7 +117,7 @@ func (z *Reader) init(r io.ReaderAt, size int64) error { // a bad one, and then only report an ErrFormat or UnexpectedEOF if // the file count modulo 65536 is incorrect. for { - f := &File{zip: z, zipr: r, zipsize: size} + f := &File{zip: z, zipr: r} err = readDirectoryHeader(f, buf) if err == ErrFormat || err == io.ErrUnexpectedEOF { break @@ -120,6 +125,7 @@ func (z *Reader) init(r io.ReaderAt, size int64) error { if err != nil { return err } + f.readDataDescriptor() z.File = append(z.File, f) } if uint16(len(z.File)) != uint16(end.directoryRecords) { // only compare 16 bits here @@ -180,26 +186,68 @@ func (f *File) Open() (io.ReadCloser, error) { return nil, ErrAlgorithm } var rc io.ReadCloser = dcomp(r) - var desr io.Reader - if f.hasDataDescriptor() { - desr = io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset+size, dataDescriptorLen) - } rc = &checksumReader{ rc: rc, hash: crc32.NewIEEE(), f: f, - desr: desr, } return rc, nil } +// OpenRaw returns a Reader that provides access to the File's contents without +// decompression. +func (f *File) OpenRaw() (io.Reader, error) { + bodyOffset, err := f.findBodyOffset() + if err != nil { + return nil, err + } + r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, int64(f.CompressedSize64)) + return r, nil +} + +func (f *File) readDataDescriptor() { + if !f.hasDataDescriptor() { + return + } + + bodyOffset, err := f.findBodyOffset() + if err != nil { + f.descErr = err + return + } + + // In section 4.3.9.2 of the spec: "However ZIP64 format MAY be used + // regardless of the size of a file. When extracting, if the zip64 + // extended information extra field is present for the file the + // compressed and uncompressed sizes will be 8 byte values." + // + // Historically, this package has used the compressed and uncompressed + // sizes from the central directory to determine if the package is + // zip64. + // + // For this case we allow either the extra field or sizes to determine + // the data descriptor length. + zip64 := f.zip64 || f.isZip64() + n := int64(dataDescriptorLen) + if zip64 { + n = dataDescriptor64Len + } + size := int64(f.CompressedSize64) + r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset+size, n) + dd, err := readDataDescriptor(r, zip64) + if err != nil { + f.descErr = err + return + } + f.CRC32 = dd.crc32 +} + type checksumReader struct { rc io.ReadCloser hash hash.Hash32 nread uint64 // number of bytes read so far f *File - desr io.Reader // if non-nil, where to read the data descriptor - err error // sticky error + err error // sticky error } func (r *checksumReader) Stat() (fs.FileInfo, error) { @@ -220,12 +268,12 @@ func (r *checksumReader) Read(b []byte) (n int, err error) { if r.nread != r.f.UncompressedSize64 { return 0, io.ErrUnexpectedEOF } - if r.desr != nil { - if err1 := readDataDescriptor(r.desr, r.f); err1 != nil { - if err1 == io.EOF { + if r.f.hasDataDescriptor() { + if r.f.descErr != nil { + if r.f.descErr == io.EOF { err = io.ErrUnexpectedEOF } else { - err = err1 + err = r.f.descErr } } else if r.hash.Sum32() != r.f.CRC32 { err = ErrChecksum @@ -336,6 +384,8 @@ parseExtras: switch fieldTag { case zip64ExtraID: + f.zip64 = true + // update directory values from the zip64 extra block. // They should only be consulted if the sizes read earlier // are maxed out. @@ -435,8 +485,9 @@ parseExtras: return nil } -func readDataDescriptor(r io.Reader, f *File) error { - var buf [dataDescriptorLen]byte +func readDataDescriptor(r io.Reader, zip64 bool) (*dataDescriptor, error) { + // Create enough space for the largest possible size + var buf [dataDescriptor64Len]byte // The spec says: "Although not originally assigned a // signature, the value 0x08074b50 has commonly been adopted @@ -446,10 +497,9 @@ func readDataDescriptor(r io.Reader, f *File) error { // descriptors and should account for either case when reading // ZIP files to ensure compatibility." // - // dataDescriptorLen includes the size of the signature but - // first read just those 4 bytes to see if it exists. + // First read just those 4 bytes to see if the signature exists. if _, err := io.ReadFull(r, buf[:4]); err != nil { - return err + return nil, err } off := 0 maybeSig := readBuf(buf[:4]) @@ -458,21 +508,28 @@ func readDataDescriptor(r io.Reader, f *File) error { // bytes. off += 4 } - if _, err := io.ReadFull(r, buf[off:12]); err != nil { - return err + + end := dataDescriptorLen - 4 + if zip64 { + end = dataDescriptor64Len - 4 } - b := readBuf(buf[:12]) - if b.uint32() != f.CRC32 { - return ErrChecksum + if _, err := io.ReadFull(r, buf[off:end]); err != nil { + return nil, err } + b := readBuf(buf[:end]) - // The two sizes that follow here can be either 32 bits or 64 bits - // but the spec is not very clear on this and different - // interpretations has been made causing incompatibilities. We - // already have the sizes from the central directory so we can - // just ignore these. + out := &dataDescriptor{ + crc32: b.uint32(), + } - return nil + if zip64 { + out.compressedSize = b.uint64() + out.uncompressedSize = b.uint64() + } else { + out.compressedSize = uint64(b.uint32()) + out.uncompressedSize = uint64(b.uint32()) + } + return out, nil } func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error) { @@ -628,10 +685,11 @@ func (b *readBuf) sub(n int) readBuf { } // A fileListEntry is a File and its ename. -// If file == nil, the fileListEntry describes a directory, without metadata. +// If file == nil, the fileListEntry describes a directory without metadata. type fileListEntry struct { - name string - file *File // nil for directories + name string + file *File + isDir bool } type fileInfoDirEntry interface { @@ -640,20 +698,26 @@ type fileInfoDirEntry interface { } func (e *fileListEntry) stat() fileInfoDirEntry { - if e.file != nil { + if !e.isDir { return headerFileInfo{&e.file.FileHeader} } return e } // Only used for directories. -func (f *fileListEntry) Name() string { _, elem, _ := split(f.name); return elem } -func (f *fileListEntry) Size() int64 { return 0 } -func (f *fileListEntry) ModTime() time.Time { return time.Time{} } -func (f *fileListEntry) Mode() fs.FileMode { return fs.ModeDir | 0555 } -func (f *fileListEntry) Type() fs.FileMode { return fs.ModeDir } -func (f *fileListEntry) IsDir() bool { return true } -func (f *fileListEntry) Sys() interface{} { return nil } +func (f *fileListEntry) Name() string { _, elem, _ := split(f.name); return elem } +func (f *fileListEntry) Size() int64 { return 0 } +func (f *fileListEntry) Mode() fs.FileMode { return fs.ModeDir | 0555 } +func (f *fileListEntry) Type() fs.FileMode { return fs.ModeDir } +func (f *fileListEntry) IsDir() bool { return true } +func (f *fileListEntry) Sys() interface{} { return nil } + +func (f *fileListEntry) ModTime() time.Time { + if f.file == nil { + return time.Time{} + } + return f.file.FileHeader.Modified.UTC() +} func (f *fileListEntry) Info() (fs.FileInfo, error) { return f, nil } @@ -673,15 +737,32 @@ func toValidName(name string) string { func (r *Reader) initFileList() { r.fileListOnce.Do(func() { dirs := make(map[string]bool) + knownDirs := make(map[string]bool) for _, file := range r.File { + isDir := len(file.Name) > 0 && file.Name[len(file.Name)-1] == '/' name := toValidName(file.Name) for dir := path.Dir(name); dir != "."; dir = path.Dir(dir) { dirs[dir] = true } - r.fileList = append(r.fileList, fileListEntry{name, file}) + entry := fileListEntry{ + name: name, + file: file, + isDir: isDir, + } + r.fileList = append(r.fileList, entry) + if isDir { + knownDirs[name] = true + } } for dir := range dirs { - r.fileList = append(r.fileList, fileListEntry{dir + "/", nil}) + if !knownDirs[dir] { + entry := fileListEntry{ + name: dir, + file: nil, + isDir: true, + } + r.fileList = append(r.fileList, entry) + } } sort.Slice(r.fileList, func(i, j int) bool { return fileEntryLess(r.fileList[i].name, r.fileList[j].name) }) @@ -705,7 +786,7 @@ func (r *Reader) Open(name string) (fs.File, error) { if e == nil || !fs.ValidPath(name) { return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} } - if e.file == nil || strings.HasSuffix(e.file.Name, "/") { + if e.isDir { return &openDir{e, r.openReadDir(name), 0}, nil } rc, err := e.file.Open() @@ -730,7 +811,7 @@ func split(name string) (dir, elem string, isDir bool) { return name[:i], name[i+1:], isDir } -var dotFile = &fileListEntry{name: "./"} +var dotFile = &fileListEntry{name: "./", isDir: true} func (r *Reader) openLookup(name string) *fileListEntry { if name == "." { diff --git a/src/archive/zip/reader_test.go b/src/archive/zip/reader_test.go index 5faf1f49b51e7b8e4cd25fbb34dfd32d46ed03bd..37dafe6c8e7c448c8fa28359e07fc2cdbb73560f 100644 --- a/src/archive/zip/reader_test.go +++ b/src/archive/zip/reader_test.go @@ -499,9 +499,15 @@ func TestReader(t *testing.T) { func readTestZip(t *testing.T, zt ZipTest) { var z *Reader var err error + var raw []byte if zt.Source != nil { rat, size := zt.Source() z, err = NewReader(rat, size) + raw = make([]byte, size) + if _, err := rat.ReadAt(raw, 0); err != nil { + t.Errorf("ReadAt error=%v", err) + return + } } else { path := filepath.Join("testdata", zt.Name) if zt.Obscured { @@ -519,6 +525,12 @@ func readTestZip(t *testing.T, zt ZipTest) { defer rc.Close() z = &rc.Reader } + var err2 error + raw, err2 = os.ReadFile(path) + if err2 != nil { + t.Errorf("ReadFile(%s) error=%v", path, err2) + return + } } if err != zt.Error { t.Errorf("error=%v, want %v", err, zt.Error) @@ -545,7 +557,7 @@ func readTestZip(t *testing.T, zt ZipTest) { // test read of each file for i, ft := range zt.File { - readTestFile(t, zt, ft, z.File[i]) + readTestFile(t, zt, ft, z.File[i], raw) } if t.Failed() { return @@ -557,7 +569,7 @@ func readTestZip(t *testing.T, zt ZipTest) { for i := 0; i < 5; i++ { for j, ft := range zt.File { go func(j int, ft ZipTestFile) { - readTestFile(t, zt, ft, z.File[j]) + readTestFile(t, zt, ft, z.File[j], raw) done <- true }(j, ft) n++ @@ -574,7 +586,7 @@ func equalTimeAndZone(t1, t2 time.Time) bool { return t1.Equal(t2) && name1 == name2 && offset1 == offset2 } -func readTestFile(t *testing.T, zt ZipTest, ft ZipTestFile, f *File) { +func readTestFile(t *testing.T, zt ZipTest, ft ZipTestFile, f *File, raw []byte) { if f.Name != ft.Name { t.Errorf("name=%q, want %q", f.Name, ft.Name) } @@ -594,6 +606,31 @@ func readTestFile(t *testing.T, zt ZipTest, ft ZipTestFile, f *File) { t.Errorf("%v: UncompressedSize=%#x does not match UncompressedSize64=%#x", f.Name, size, f.UncompressedSize64) } + // Check that OpenRaw returns the correct byte segment + rw, err := f.OpenRaw() + if err != nil { + t.Errorf("%v: OpenRaw error=%v", f.Name, err) + return + } + start, err := f.DataOffset() + if err != nil { + t.Errorf("%v: DataOffset error=%v", f.Name, err) + return + } + got, err := io.ReadAll(rw) + if err != nil { + t.Errorf("%v: OpenRaw ReadAll error=%v", f.Name, err) + return + } + end := uint64(start) + f.CompressedSize64 + want := raw[start:end] + if !bytes.Equal(got, want) { + t.Logf("got %q", got) + t.Logf("want %q", want) + t.Errorf("%v: OpenRaw returned unexpected bytes", f.Name) + return + } + r, err := f.Open() if err != nil { t.Errorf("%v", err) @@ -776,8 +813,8 @@ func returnRecursiveZip() (r io.ReaderAt, size int64) { // "archive/zip" // "bytes" // "io" -// "io/ioutil" // "log" +// "os" // ) // // type zeros struct{} @@ -1073,12 +1110,62 @@ func TestIssue12449(t *testing.T) { } func TestFS(t *testing.T) { - z, err := OpenReader("testdata/unix.zip") + for _, test := range []struct { + file string + want []string + }{ + { + "testdata/unix.zip", + []string{"hello", "dir/bar", "readonly"}, + }, + { + "testdata/subdir.zip", + []string{"a/b/c"}, + }, + } { + t.Run(test.file, func(t *testing.T) { + t.Parallel() + z, err := OpenReader(test.file) + if err != nil { + t.Fatal(err) + } + defer z.Close() + if err := fstest.TestFS(z, test.want...); err != nil { + t.Error(err) + } + }) + } +} + +func TestFSModTime(t *testing.T) { + t.Parallel() + z, err := OpenReader("testdata/subdir.zip") if err != nil { t.Fatal(err) } - if err := fstest.TestFS(z, "hello", "dir/bar", "dir/empty", "readonly"); err != nil { - t.Fatal(err) + defer z.Close() + + for _, test := range []struct { + name string + want time.Time + }{ + { + "a", + time.Date(2021, 4, 19, 12, 29, 56, 0, timeZone(-7*time.Hour)).UTC(), + }, + { + "a/b/c", + time.Date(2021, 4, 19, 12, 29, 59, 0, timeZone(-7*time.Hour)).UTC(), + }, + } { + fi, err := fs.Stat(z, test.name) + if err != nil { + t.Errorf("%s: %v", test.name, err) + continue + } + if got := fi.ModTime(); !got.Equal(test.want) { + t.Errorf("%s: got modtime %v, want %v", test.name, got, test.want) + } } } @@ -1116,3 +1203,184 @@ func TestCVE202127919(t *testing.T) { t.Errorf("Error reading file: %v", err) } } + +func TestReadDataDescriptor(t *testing.T) { + tests := []struct { + desc string + in []byte + zip64 bool + want *dataDescriptor + wantErr error + }{{ + desc: "valid 32 bit with signature", + in: []byte{ + 0x50, 0x4b, 0x07, 0x08, // signature + 0x00, 0x01, 0x02, 0x03, // crc32 + 0x04, 0x05, 0x06, 0x07, // compressed size + 0x08, 0x09, 0x0a, 0x0b, // uncompressed size + }, + want: &dataDescriptor{ + crc32: 0x03020100, + compressedSize: 0x07060504, + uncompressedSize: 0x0b0a0908, + }, + }, { + desc: "valid 32 bit without signature", + in: []byte{ + 0x00, 0x01, 0x02, 0x03, // crc32 + 0x04, 0x05, 0x06, 0x07, // compressed size + 0x08, 0x09, 0x0a, 0x0b, // uncompressed size + }, + want: &dataDescriptor{ + crc32: 0x03020100, + compressedSize: 0x07060504, + uncompressedSize: 0x0b0a0908, + }, + }, { + desc: "valid 64 bit with signature", + in: []byte{ + 0x50, 0x4b, 0x07, 0x08, // signature + 0x00, 0x01, 0x02, 0x03, // crc32 + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, // compressed size + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, // uncompressed size + }, + zip64: true, + want: &dataDescriptor{ + crc32: 0x03020100, + compressedSize: 0x0b0a090807060504, + uncompressedSize: 0x131211100f0e0d0c, + }, + }, { + desc: "valid 64 bit without signature", + in: []byte{ + 0x00, 0x01, 0x02, 0x03, // crc32 + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, // compressed size + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, // uncompressed size + }, + zip64: true, + want: &dataDescriptor{ + crc32: 0x03020100, + compressedSize: 0x0b0a090807060504, + uncompressedSize: 0x131211100f0e0d0c, + }, + }, { + desc: "invalid 32 bit with signature", + in: []byte{ + 0x50, 0x4b, 0x07, 0x08, // signature + 0x00, 0x01, 0x02, 0x03, // crc32 + 0x04, 0x05, // unexpected end + }, + wantErr: io.ErrUnexpectedEOF, + }, { + desc: "invalid 32 bit without signature", + in: []byte{ + 0x00, 0x01, 0x02, 0x03, // crc32 + 0x04, 0x05, // unexpected end + }, + wantErr: io.ErrUnexpectedEOF, + }, { + desc: "invalid 64 bit with signature", + in: []byte{ + 0x50, 0x4b, 0x07, 0x08, // signature + 0x00, 0x01, 0x02, 0x03, // crc32 + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, // compressed size + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, // unexpected end + }, + zip64: true, + wantErr: io.ErrUnexpectedEOF, + }, { + desc: "invalid 64 bit without signature", + in: []byte{ + 0x00, 0x01, 0x02, 0x03, // crc32 + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, // compressed size + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, // unexpected end + }, + zip64: true, + wantErr: io.ErrUnexpectedEOF, + }} + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + r := bytes.NewReader(test.in) + + desc, err := readDataDescriptor(r, test.zip64) + if err != test.wantErr { + t.Fatalf("got err %v; want nil", err) + } + if test.want == nil { + return + } + if desc == nil { + t.Fatalf("got nil DataDescriptor; want non-nil") + } + if desc.crc32 != test.want.crc32 { + t.Errorf("got CRC32 %#x; want %#x", desc.crc32, test.want.crc32) + } + if desc.compressedSize != test.want.compressedSize { + t.Errorf("got CompressedSize %#x; want %#x", desc.compressedSize, test.want.compressedSize) + } + if desc.uncompressedSize != test.want.uncompressedSize { + t.Errorf("got UncompressedSize %#x; want %#x", desc.uncompressedSize, test.want.uncompressedSize) + } + }) + } +} + +func TestCVE202133196(t *testing.T) { + // Archive that indicates it has 1 << 128 -1 files, + // this would previously cause a panic due to attempting + // to allocate a slice with 1 << 128 -1 elements. + data := []byte{ + 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x08, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02, + 0x03, 0x62, 0x61, 0x65, 0x03, 0x04, 0x00, 0x00, + 0xff, 0xff, 0x50, 0x4b, 0x07, 0x08, 0xbe, 0x20, + 0x5c, 0x6c, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x00, + 0x14, 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xbe, 0x20, 0x5c, 0x6c, 0x09, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x03, 0x50, 0x4b, 0x06, 0x06, 0x2c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, + 0x00, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x31, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x50, 0x4b, 0x06, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, + 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00, 0x00, + } + _, err := NewReader(bytes.NewReader(data), int64(len(data))) + if err != ErrFormat { + t.Fatalf("unexpected error, got: %v, want: %v", err, ErrFormat) + } + + // Also check that an archive containing a handful of empty + // files doesn't cause an issue + b := bytes.NewBuffer(nil) + w := NewWriter(b) + for i := 0; i < 5; i++ { + _, err := w.Create("") + if err != nil { + t.Fatalf("Writer.Create failed: %s", err) + } + } + if err := w.Close(); err != nil { + t.Fatalf("Writer.Close failed: %s", err) + } + r, err := NewReader(bytes.NewReader(b.Bytes()), int64(b.Len())) + if err != nil { + t.Fatalf("NewReader failed: %s", err) + } + if len(r.File) != 5 { + t.Errorf("Archive has unexpected number of files, got %d, want 5", len(r.File)) + } +} diff --git a/src/archive/zip/struct.go b/src/archive/zip/struct.go index 4dd29f35fa63f6e8d898ab2b55faa46b3611d918..ff9f605eb697eec319602d455917953b70b906e6 100644 --- a/src/archive/zip/struct.go +++ b/src/archive/zip/struct.go @@ -42,7 +42,7 @@ const ( directoryHeaderLen = 46 // + filename + extra + comment directoryEndLen = 22 // + comment dataDescriptorLen = 16 // four uint32: descriptor signature, crc32, compressed size, size - dataDescriptor64Len = 24 // descriptor with 8 byte sizes + dataDescriptor64Len = 24 // two uint32: signature, crc32 | two uint64: compressed size, size directory64LocLen = 20 // directory64EndLen = 56 // + extra @@ -315,6 +315,10 @@ func (h *FileHeader) isZip64() bool { return h.CompressedSize64 >= uint32max || h.UncompressedSize64 >= uint32max } +func (f *FileHeader) hasDataDescriptor() bool { + return f.Flags&0x8 != 0 +} + func msdosModeToFileMode(m uint32) (mode fs.FileMode) { if m&msdosDir != 0 { mode = fs.ModeDir | 0777 @@ -341,11 +345,9 @@ func fileModeToUnixMode(mode fs.FileMode) uint32 { case fs.ModeSocket: m = s_IFSOCK case fs.ModeDevice: - if mode&fs.ModeCharDevice != 0 { - m = s_IFCHR - } else { - m = s_IFBLK - } + m = s_IFBLK + case fs.ModeDevice | fs.ModeCharDevice: + m = s_IFCHR } if mode&fs.ModeSetuid != 0 { m |= s_ISUID @@ -388,3 +390,11 @@ func unixModeToFileMode(m uint32) fs.FileMode { } return mode } + +// dataDescriptor holds the data descriptor that optionally follows the file +// contents in the zip file. +type dataDescriptor struct { + crc32 uint32 + compressedSize uint64 + uncompressedSize uint64 +} diff --git a/src/archive/zip/testdata/subdir.zip b/src/archive/zip/testdata/subdir.zip new file mode 100644 index 0000000000000000000000000000000000000000..324d06b48d19e59fa5e5875bf006bdc31c097090 Binary files /dev/null and b/src/archive/zip/testdata/subdir.zip differ diff --git a/src/archive/zip/writer.go b/src/archive/zip/writer.go index cdc534eaf01922df345eb4a44b372ef4c8fbc1bc..3b23cc3391d9e676a6735266fa8bb76a73ace755 100644 --- a/src/archive/zip/writer.go +++ b/src/archive/zip/writer.go @@ -37,6 +37,7 @@ type Writer struct { type header struct { *FileHeader offset uint64 + raw bool } // NewWriter returns a new Writer writing a zip file to w. @@ -245,22 +246,31 @@ func detectUTF8(s string) (valid, require bool) { return true, require } +// prepare performs the bookkeeping operations required at the start of +// CreateHeader and CreateRaw. +func (w *Writer) prepare(fh *FileHeader) error { + if w.last != nil && !w.last.closed { + if err := w.last.close(); err != nil { + return err + } + } + if len(w.dir) > 0 && w.dir[len(w.dir)-1].FileHeader == fh { + // See https://golang.org/issue/11144 confusion. + return errors.New("archive/zip: invalid duplicate FileHeader") + } + return nil +} + // CreateHeader adds a file to the zip archive using the provided FileHeader // for the file metadata. Writer takes ownership of fh and may mutate // its fields. The caller must not modify fh after calling CreateHeader. // // This returns a Writer to which the file contents should be written. // The file's contents must be written to the io.Writer before the next -// call to Create, CreateHeader, or Close. +// call to Create, CreateHeader, CreateRaw, or Close. func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { - if w.last != nil && !w.last.closed { - if err := w.last.close(); err != nil { - return nil, err - } - } - if len(w.dir) > 0 && w.dir[len(w.dir)-1].FileHeader == fh { - // See https://golang.org/issue/11144 confusion. - return nil, errors.New("archive/zip: invalid duplicate FileHeader") + if err := w.prepare(fh); err != nil { + return nil, err } // The ZIP format has a sad state of affairs regarding character encoding. @@ -365,7 +375,7 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { ow = fw } w.dir = append(w.dir, h) - if err := writeHeader(w.cw, fh); err != nil { + if err := writeHeader(w.cw, h); err != nil { return nil, err } // If we're creating a directory, fw is nil. @@ -373,7 +383,7 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { return ow, nil } -func writeHeader(w io.Writer, h *FileHeader) error { +func writeHeader(w io.Writer, h *header) error { const maxUint16 = 1<<16 - 1 if len(h.Name) > maxUint16 { return errLongName @@ -390,9 +400,20 @@ func writeHeader(w io.Writer, h *FileHeader) error { b.uint16(h.Method) b.uint16(h.ModifiedTime) b.uint16(h.ModifiedDate) - b.uint32(0) // since we are writing a data descriptor crc32, - b.uint32(0) // compressed size, - b.uint32(0) // and uncompressed size should be zero + // In raw mode (caller does the compression), the values are either + // written here or in the trailing data descriptor based on the header + // flags. + if h.raw && !h.hasDataDescriptor() { + b.uint32(h.CRC32) + b.uint32(uint32(min64(h.CompressedSize64, uint32max))) + b.uint32(uint32(min64(h.UncompressedSize64, uint32max))) + } else { + // When this package handle the compression, these values are + // always written to the trailing data descriptor. + b.uint32(0) // crc32 + b.uint32(0) // compressed size + b.uint32(0) // uncompressed size + } b.uint16(uint16(len(h.Name))) b.uint16(uint16(len(h.Extra))) if _, err := w.Write(buf[:]); err != nil { @@ -405,6 +426,65 @@ func writeHeader(w io.Writer, h *FileHeader) error { return err } +func min64(x, y uint64) uint64 { + if x < y { + return x + } + return y +} + +// CreateRaw adds a file to the zip archive using the provided FileHeader and +// returns a Writer to which the file contents should be written. The file's +// contents must be written to the io.Writer before the next call to Create, +// CreateHeader, CreateRaw, or Close. +// +// In contrast to CreateHeader, the bytes passed to Writer are not compressed. +func (w *Writer) CreateRaw(fh *FileHeader) (io.Writer, error) { + if err := w.prepare(fh); err != nil { + return nil, err + } + + fh.CompressedSize = uint32(min64(fh.CompressedSize64, uint32max)) + fh.UncompressedSize = uint32(min64(fh.UncompressedSize64, uint32max)) + + h := &header{ + FileHeader: fh, + offset: uint64(w.cw.count), + raw: true, + } + w.dir = append(w.dir, h) + if err := writeHeader(w.cw, h); err != nil { + return nil, err + } + + if strings.HasSuffix(fh.Name, "/") { + w.last = nil + return dirWriter{}, nil + } + + fw := &fileWriter{ + header: h, + zipw: w.cw, + } + w.last = fw + return fw, nil +} + +// Copy copies the file f (obtained from a Reader) into w. It copies the raw +// form directly bypassing decompression, compression, and validation. +func (w *Writer) Copy(f *File) error { + r, err := f.OpenRaw() + if err != nil { + return err + } + fw, err := w.CreateRaw(&f.FileHeader) + if err != nil { + return err + } + _, err = io.Copy(fw, r) + return err +} + // RegisterCompressor registers or overrides a custom compressor for a specific // method ID. If a compressor for a given method is not found, Writer will // default to looking up the compressor at the package level. @@ -446,6 +526,9 @@ func (w *fileWriter) Write(p []byte) (int, error) { if w.closed { return 0, errors.New("zip: write to closed file") } + if w.raw { + return w.zipw.Write(p) + } w.crc32.Write(p) return w.rawCount.Write(p) } @@ -455,6 +538,9 @@ func (w *fileWriter) close() error { return errors.New("zip: file closed twice") } w.closed = true + if w.raw { + return w.writeDataDescriptor() + } if err := w.comp.Close(); err != nil { return err } @@ -474,26 +560,33 @@ func (w *fileWriter) close() error { fh.UncompressedSize = uint32(fh.UncompressedSize64) } + return w.writeDataDescriptor() +} + +func (w *fileWriter) writeDataDescriptor() error { + if !w.hasDataDescriptor() { + return nil + } // Write data descriptor. This is more complicated than one would // think, see e.g. comments in zipfile.c:putextended() and // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588. // The approach here is to write 8 byte sizes if needed without // adding a zip64 extra in the local header (too late anyway). var buf []byte - if fh.isZip64() { + if w.isZip64() { buf = make([]byte, dataDescriptor64Len) } else { buf = make([]byte, dataDescriptorLen) } b := writeBuf(buf) b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X - b.uint32(fh.CRC32) - if fh.isZip64() { - b.uint64(fh.CompressedSize64) - b.uint64(fh.UncompressedSize64) + b.uint32(w.CRC32) + if w.isZip64() { + b.uint64(w.CompressedSize64) + b.uint64(w.UncompressedSize64) } else { - b.uint32(fh.CompressedSize) - b.uint32(fh.UncompressedSize) + b.uint32(w.CompressedSize) + b.uint32(w.UncompressedSize) } _, err := w.zipw.Write(buf) return err diff --git a/src/archive/zip/writer_test.go b/src/archive/zip/writer_test.go index 5985144e5c2d25fb3df2fe37682fa0fd4c1b3d8a..97c6c5297994684ca7fcfc5ccc492025709d795c 100644 --- a/src/archive/zip/writer_test.go +++ b/src/archive/zip/writer_test.go @@ -6,8 +6,10 @@ package zip import ( "bytes" + "compress/flate" "encoding/binary" "fmt" + "hash/crc32" "io" "io/fs" "math/rand" @@ -57,6 +59,18 @@ var writeTests = []WriteTest{ Method: Deflate, Mode: 0755 | fs.ModeSymlink, }, + { + Name: "device", + Data: []byte("device file"), + Method: Deflate, + Mode: 0755 | fs.ModeDevice, + }, + { + Name: "chardevice", + Data: []byte("char device file"), + Method: Deflate, + Mode: 0755 | fs.ModeDevice | fs.ModeCharDevice, + }, } func TestWriter(t *testing.T) { @@ -353,6 +367,171 @@ func TestWriterDirAttributes(t *testing.T) { } } +func TestWriterCopy(t *testing.T) { + // make a zip file + buf := new(bytes.Buffer) + w := NewWriter(buf) + for _, wt := range writeTests { + testCreate(t, w, &wt) + } + if err := w.Close(); err != nil { + t.Fatal(err) + } + + // read it back + src, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) + if err != nil { + t.Fatal(err) + } + for i, wt := range writeTests { + testReadFile(t, src.File[i], &wt) + } + + // make a new zip file copying the old compressed data. + buf2 := new(bytes.Buffer) + dst := NewWriter(buf2) + for _, f := range src.File { + if err := dst.Copy(f); err != nil { + t.Fatal(err) + } + } + if err := dst.Close(); err != nil { + t.Fatal(err) + } + + // read the new one back + r, err := NewReader(bytes.NewReader(buf2.Bytes()), int64(buf2.Len())) + if err != nil { + t.Fatal(err) + } + for i, wt := range writeTests { + testReadFile(t, r.File[i], &wt) + } +} + +func TestWriterCreateRaw(t *testing.T) { + files := []struct { + name string + content []byte + method uint16 + flags uint16 + crc32 uint32 + uncompressedSize uint64 + compressedSize uint64 + }{ + { + name: "small store w desc", + content: []byte("gophers"), + method: Store, + flags: 0x8, + }, + { + name: "small deflate wo desc", + content: bytes.Repeat([]byte("abcdefg"), 2048), + method: Deflate, + }, + } + + // write a zip file + archive := new(bytes.Buffer) + w := NewWriter(archive) + + for i := range files { + f := &files[i] + f.crc32 = crc32.ChecksumIEEE(f.content) + size := uint64(len(f.content)) + f.uncompressedSize = size + f.compressedSize = size + + var compressedContent []byte + if f.method == Deflate { + var buf bytes.Buffer + w, err := flate.NewWriter(&buf, flate.BestSpeed) + if err != nil { + t.Fatalf("flate.NewWriter err = %v", err) + } + _, err = w.Write(f.content) + if err != nil { + t.Fatalf("flate Write err = %v", err) + } + err = w.Close() + if err != nil { + t.Fatalf("flate Writer.Close err = %v", err) + } + compressedContent = buf.Bytes() + f.compressedSize = uint64(len(compressedContent)) + } + + h := &FileHeader{ + Name: f.name, + Method: f.method, + Flags: f.flags, + CRC32: f.crc32, + CompressedSize64: f.compressedSize, + UncompressedSize64: f.uncompressedSize, + } + w, err := w.CreateRaw(h) + if err != nil { + t.Fatal(err) + } + if compressedContent != nil { + _, err = w.Write(compressedContent) + } else { + _, err = w.Write(f.content) + } + if err != nil { + t.Fatalf("%s Write got %v; want nil", f.name, err) + } + } + + if err := w.Close(); err != nil { + t.Fatal(err) + } + + // read it back + r, err := NewReader(bytes.NewReader(archive.Bytes()), int64(archive.Len())) + if err != nil { + t.Fatal(err) + } + for i, want := range files { + got := r.File[i] + if got.Name != want.name { + t.Errorf("got Name %s; want %s", got.Name, want.name) + } + if got.Method != want.method { + t.Errorf("%s: got Method %#x; want %#x", want.name, got.Method, want.method) + } + if got.Flags != want.flags { + t.Errorf("%s: got Flags %#x; want %#x", want.name, got.Flags, want.flags) + } + if got.CRC32 != want.crc32 { + t.Errorf("%s: got CRC32 %#x; want %#x", want.name, got.CRC32, want.crc32) + } + if got.CompressedSize64 != want.compressedSize { + t.Errorf("%s: got CompressedSize64 %d; want %d", want.name, got.CompressedSize64, want.compressedSize) + } + if got.UncompressedSize64 != want.uncompressedSize { + t.Errorf("%s: got UncompressedSize64 %d; want %d", want.name, got.UncompressedSize64, want.uncompressedSize) + } + + r, err := got.Open() + if err != nil { + t.Errorf("%s: Open err = %v", got.Name, err) + continue + } + + buf, err := io.ReadAll(r) + if err != nil { + t.Errorf("%s: ReadAll err = %v", got.Name, err) + continue + } + + if !bytes.Equal(buf, want.content) { + t.Errorf("%v: ReadAll returned unexpected bytes", got.Name) + } + } +} + func testCreate(t *testing.T, w *Writer, wt *WriteTest) { header := &FileHeader{ Name: wt.Name, @@ -378,15 +557,15 @@ func testReadFile(t *testing.T, f *File, wt *WriteTest) { testFileMode(t, f, wt.Mode) rc, err := f.Open() if err != nil { - t.Fatal("opening:", err) + t.Fatalf("opening %s: %v", f.Name, err) } b, err := io.ReadAll(rc) if err != nil { - t.Fatal("reading:", err) + t.Fatalf("reading %s: %v", f.Name, err) } err = rc.Close() if err != nil { - t.Fatal("closing:", err) + t.Fatalf("closing %s: %v", f.Name, err) } if !bytes.Equal(b, wt.Data) { t.Errorf("File contents %q, want %q", b, wt.Data) diff --git a/src/bufio/bufio.go b/src/bufio/bufio.go index 6baf9b9e400f388c621bd8f5709d8918f1feebff..ec928e7ad69ed54d213d3a38d9efb011ff32eb82 100644 --- a/src/bufio/bufio.go +++ b/src/bufio/bufio.go @@ -670,7 +670,8 @@ func (b *Writer) WriteByte(c byte) error { // WriteRune writes a single Unicode code point, returning // the number of bytes written and any error. func (b *Writer) WriteRune(r rune) (size int, err error) { - if r < utf8.RuneSelf { + // Compare as uint32 to correctly handle negative runes. + if uint32(r) < utf8.RuneSelf { err = b.WriteByte(byte(r)) if err != nil { return 0, err diff --git a/src/bufio/bufio_test.go b/src/bufio/bufio_test.go index d7b34bd0d8f66b485a49ad355fce86c3a9d5f726..ebcc711db9d48cdefa44d6ec9bddd2406c13b59d 100644 --- a/src/bufio/bufio_test.go +++ b/src/bufio/bufio_test.go @@ -534,6 +534,20 @@ func TestReadWriteRune(t *testing.T) { } } +func TestWriteInvalidRune(t *testing.T) { + // Invalid runes, including negative ones, should be written as the + // replacement character. + for _, r := range []rune{-1, utf8.MaxRune + 1} { + var buf bytes.Buffer + w := NewWriter(&buf) + w.WriteRune(r) + w.Flush() + if s := buf.String(); s != "\uFFFD" { + t.Errorf("WriteRune(%d) wrote %q, not replacement character", r, s) + } + } +} + func TestReadStringAllocs(t *testing.T) { r := strings.NewReader(" foo foo 42 42 42 42 42 42 42 42 4.2 4.2 4.2 4.2\n") buf := NewReader(r) diff --git a/src/bufio/scan.go b/src/bufio/scan.go index af46a14fbbe0ff9f34680b7e0f6467afa3e207ef..4846d4f733677777a50e24d971cdf643954d4f23 100644 --- a/src/bufio/scan.go +++ b/src/bufio/scan.go @@ -48,7 +48,8 @@ type Scanner struct { // and the next token to return to the user, if any, plus an error, if any. // // Scanning stops if the function returns an error, in which case some of -// the input may be discarded. +// the input may be discarded. If that error is ErrFinalToken, scanning +// stops with no error. // // Otherwise, the Scanner advances the input. If the token is not nil, // the Scanner returns it to the user. If the token is nil, the diff --git a/src/bytes/boundary_test.go b/src/bytes/boundary_test.go index ea84f1e40fd856d3f2a173e83fc965c89ffcc00d..5a47526593b093678b85e0c45c2dfbd5a42d16ed 100644 --- a/src/bytes/boundary_test.go +++ b/src/bytes/boundary_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +//go:build linux // +build linux package bytes_test diff --git a/src/bytes/buffer.go b/src/bytes/buffer.go index f19a4cfff0926c7eba61cead788062b074b1a091..549b077708f80e3742e79b0e93e29d3432d3c6c7 100644 --- a/src/bytes/buffer.go +++ b/src/bytes/buffer.go @@ -275,7 +275,8 @@ func (b *Buffer) WriteByte(c byte) error { // included to match bufio.Writer's WriteRune. The buffer is grown as needed; // if it becomes too large, WriteRune will panic with ErrTooLarge. func (b *Buffer) WriteRune(r rune) (n int, err error) { - if r < utf8.RuneSelf { + // Compare as uint32 to correctly handle negative runes. + if uint32(r) < utf8.RuneSelf { b.WriteByte(byte(r)) return 1, nil } diff --git a/src/bytes/buffer_test.go b/src/bytes/buffer_test.go index fec5ef8a35f637dacd1c95b9ad3034b6f3559308..9c9b7440ffaa753625a365534a2c7ec30baf4ff2 100644 --- a/src/bytes/buffer_test.go +++ b/src/bytes/buffer_test.go @@ -6,6 +6,7 @@ package bytes_test import ( . "bytes" + "fmt" "io" "math/rand" "testing" @@ -387,6 +388,16 @@ func TestRuneIO(t *testing.T) { } } +func TestWriteInvalidRune(t *testing.T) { + // Invalid runes, including negative ones, should be written as + // utf8.RuneError. + for _, r := range []rune{-1, utf8.MaxRune + 1} { + var buf Buffer + buf.WriteRune(r) + check(t, fmt.Sprintf("TestWriteInvalidRune (%d)", r), &buf, "\uFFFD") + } +} + func TestNext(t *testing.T) { b := []byte{0, 1, 2, 3, 4} tmp := make([]byte, 5) diff --git a/src/cmd/api/goapi.go b/src/cmd/api/goapi.go index efc2696f8fecc72bfdf6ad27ffc99ec814cdaae0..b07a238d679a1636657fb7618b8c0d7e7ea34a72 100644 --- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -215,8 +215,7 @@ func main() { } optional := fileFeatures(*nextFile) exception := fileFeatures(*exceptFile) - fail = !compareAPI(bw, features, required, optional, exception, - *allowNew && strings.Contains(runtime.Version(), "devel")) + fail = !compareAPI(bw, features, required, optional, exception, *allowNew) } // export emits the exported package features. diff --git a/src/cmd/api/run.go b/src/cmd/api/run.go index ecb1d0f81aa46695d47634b94eb6571368e0ffef..81979de191abc2f13263a467fc7e34aab31b8026 100644 --- a/src/cmd/api/run.go +++ b/src/cmd/api/run.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore // The run program is invoked via the dist tool. @@ -9,8 +10,11 @@ package main import ( + "errors" "fmt" exec "internal/execabs" + "internal/goversion" + "io/fs" "log" "os" "path/filepath" @@ -42,6 +46,7 @@ func main() { apiDir := filepath.Join(goroot, "api") out, err := exec.Command(goCmd(), "tool", "api", "-c", findAPIDirFiles(apiDir), + allowNew(apiDir), "-next", filepath.Join(apiDir, "next.txt"), "-except", filepath.Join(apiDir, "except.txt")).CombinedOutput() if err != nil { @@ -70,3 +75,35 @@ func findAPIDirFiles(apiDir string) string { } return strings.Join(apiFiles, ",") } + +// allowNew returns the -allow_new flag to use for the 'go tool api' invocation. +func allowNew(apiDir string) string { + // Verify that the api/go1.n.txt for previous Go version exists. + // It definitely should, otherwise it's a signal that the logic below may be outdated. + if _, err := os.Stat(filepath.Join(apiDir, fmt.Sprintf("go1.%d.txt", goversion.Version-1))); err != nil { + log.Fatalln("Problem with api file for previous release:", err) + } + + // See whether the api/go1.n.txt for this Go version has been created. + // (As of April 2021, it gets created during the release of the first Beta.) + _, err := os.Stat(filepath.Join(apiDir, fmt.Sprintf("go1.%d.txt", goversion.Version))) + if errors.Is(err, fs.ErrNotExist) { + // It doesn't exist, so we're in development or before Beta 1. + // At this stage, unmentioned API additions are deemed okay. + // (They will be quietly shown in API check output, but the test won't fail). + return "-allow_new=true" + } else if err == nil { + // The api/go1.n.txt for this Go version has been created, + // so we're definitely past Beta 1 in the release cycle. + // + // From this point, enforce that api/go1.n.txt is an accurate and complete + // representation of what's going into the release by failing API check if + // there are API additions (a month into the freeze, there shouldn't be many). + // + // See golang.org/issue/43956. + return "-allow_new=false" + } else { + log.Fatal(err) + } + panic("unreachable") +} diff --git a/src/cmd/asm/internal/arch/arch.go b/src/cmd/asm/internal/arch/arch.go index a62e55191e6898d43efc4aef5030bf9df5aea1ac..026d8abf81305f51d953b500c35d3bd2d7c7a087 100644 --- a/src/cmd/asm/internal/arch/arch.go +++ b/src/cmd/asm/internal/arch/arch.go @@ -109,6 +109,10 @@ func archX86(linkArch *obj.LinkArch) *Arch { register["SB"] = RSB register["FP"] = RFP register["PC"] = RPC + if linkArch == &x86.Linkamd64 { + // Alias g to R14 + register["g"] = x86.REGG + } // Register prefix not used on this architecture. instructions := make(map[string]obj.As) diff --git a/src/cmd/asm/internal/arch/arm64.go b/src/cmd/asm/internal/arch/arm64.go index e557630ca60e860c7fdc81b3b047ef1b0d661cc6..40d828a1fea7485d3389c529f16180ecdd140bc7 100644 --- a/src/cmd/asm/internal/arch/arm64.go +++ b/src/cmd/asm/internal/arch/arm64.go @@ -147,7 +147,16 @@ func arm64RegisterNumber(name string, n int16) (int16, bool) { return 0, false } -// ARM64RegisterExtension parses an ARM64 register with extension or arrangement. +// ARM64RegisterShift constructs an ARM64 register with shift operation. +func ARM64RegisterShift(reg, op, count int16) (int64, error) { + // the base register of shift operations must be general register. + if reg > arm64.REG_R31 || reg < arm64.REG_R0 { + return 0, errors.New("invalid register for shift operation") + } + return int64(reg&31)<<16 | int64(op)<<22 | int64(uint16(count)), nil +} + +// ARM64RegisterExtension constructs an ARM64 register with extension or arrangement. func ARM64RegisterExtension(a *obj.Addr, ext string, reg, num int16, isAmount, isIndex bool) error { Rnum := (reg & 31) + int16(num<<5) if isAmount { @@ -155,154 +164,163 @@ func ARM64RegisterExtension(a *obj.Addr, ext string, reg, num int16, isAmount, i return errors.New("index shift amount is out of range") } } - switch ext { - case "UXTB": - if !isAmount { - return errors.New("invalid register extension") - } - if a.Type == obj.TYPE_MEM { - return errors.New("invalid shift for the register offset addressing mode") - } - a.Reg = arm64.REG_UXTB + Rnum - case "UXTH": - if !isAmount { - return errors.New("invalid register extension") - } - if a.Type == obj.TYPE_MEM { - return errors.New("invalid shift for the register offset addressing mode") - } - a.Reg = arm64.REG_UXTH + Rnum - case "UXTW": - if !isAmount { - return errors.New("invalid register extension") - } - // effective address of memory is a base register value and an offset register value. - if a.Type == obj.TYPE_MEM { - a.Index = arm64.REG_UXTW + Rnum - } else { - a.Reg = arm64.REG_UXTW + Rnum - } - case "UXTX": - if !isAmount { - return errors.New("invalid register extension") - } - if a.Type == obj.TYPE_MEM { - return errors.New("invalid shift for the register offset addressing mode") - } - a.Reg = arm64.REG_UXTX + Rnum - case "SXTB": - if !isAmount { - return errors.New("invalid register extension") - } - a.Reg = arm64.REG_SXTB + Rnum - case "SXTH": - if !isAmount { - return errors.New("invalid register extension") - } - if a.Type == obj.TYPE_MEM { - return errors.New("invalid shift for the register offset addressing mode") - } - a.Reg = arm64.REG_SXTH + Rnum - case "SXTW": - if !isAmount { - return errors.New("invalid register extension") - } - if a.Type == obj.TYPE_MEM { - a.Index = arm64.REG_SXTW + Rnum - } else { - a.Reg = arm64.REG_SXTW + Rnum - } - case "SXTX": - if !isAmount { - return errors.New("invalid register extension") - } - if a.Type == obj.TYPE_MEM { - a.Index = arm64.REG_SXTX + Rnum - } else { - a.Reg = arm64.REG_SXTX + Rnum - } - case "LSL": - if !isAmount { - return errors.New("invalid register extension") - } - a.Index = arm64.REG_LSL + Rnum - case "B8": - if isIndex { - return errors.New("invalid register extension") - } - a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_8B & 15) << 5) - case "B16": - if isIndex { - return errors.New("invalid register extension") - } - a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_16B & 15) << 5) - case "H4": - if isIndex { - return errors.New("invalid register extension") - } - a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_4H & 15) << 5) - case "H8": - if isIndex { - return errors.New("invalid register extension") - } - a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_8H & 15) << 5) - case "S2": - if isIndex { - return errors.New("invalid register extension") - } - a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_2S & 15) << 5) - case "S4": - if isIndex { - return errors.New("invalid register extension") - } - a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_4S & 15) << 5) - case "D1": - if isIndex { - return errors.New("invalid register extension") - } - a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_1D & 15) << 5) - case "D2": - if isIndex { - return errors.New("invalid register extension") - } - a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_2D & 15) << 5) - case "Q1": - if isIndex { - return errors.New("invalid register extension") - } - a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_1Q & 15) << 5) - case "B": - if !isIndex { - return nil - } - a.Reg = arm64.REG_ELEM + (reg & 31) + ((arm64.ARNG_B & 15) << 5) - a.Index = num - case "H": - if !isIndex { - return nil - } - a.Reg = arm64.REG_ELEM + (reg & 31) + ((arm64.ARNG_H & 15) << 5) - a.Index = num - case "S": - if !isIndex { - return nil - } - a.Reg = arm64.REG_ELEM + (reg & 31) + ((arm64.ARNG_S & 15) << 5) - a.Index = num - case "D": - if !isIndex { - return nil + if reg <= arm64.REG_R31 && reg >= arm64.REG_R0 { + switch ext { + case "UXTB": + if !isAmount { + return errors.New("invalid register extension") + } + if a.Type == obj.TYPE_MEM { + return errors.New("invalid shift for the register offset addressing mode") + } + a.Reg = arm64.REG_UXTB + Rnum + case "UXTH": + if !isAmount { + return errors.New("invalid register extension") + } + if a.Type == obj.TYPE_MEM { + return errors.New("invalid shift for the register offset addressing mode") + } + a.Reg = arm64.REG_UXTH + Rnum + case "UXTW": + if !isAmount { + return errors.New("invalid register extension") + } + // effective address of memory is a base register value and an offset register value. + if a.Type == obj.TYPE_MEM { + a.Index = arm64.REG_UXTW + Rnum + } else { + a.Reg = arm64.REG_UXTW + Rnum + } + case "UXTX": + if !isAmount { + return errors.New("invalid register extension") + } + if a.Type == obj.TYPE_MEM { + return errors.New("invalid shift for the register offset addressing mode") + } + a.Reg = arm64.REG_UXTX + Rnum + case "SXTB": + if !isAmount { + return errors.New("invalid register extension") + } + a.Reg = arm64.REG_SXTB + Rnum + case "SXTH": + if !isAmount { + return errors.New("invalid register extension") + } + if a.Type == obj.TYPE_MEM { + return errors.New("invalid shift for the register offset addressing mode") + } + a.Reg = arm64.REG_SXTH + Rnum + case "SXTW": + if !isAmount { + return errors.New("invalid register extension") + } + if a.Type == obj.TYPE_MEM { + a.Index = arm64.REG_SXTW + Rnum + } else { + a.Reg = arm64.REG_SXTW + Rnum + } + case "SXTX": + if !isAmount { + return errors.New("invalid register extension") + } + if a.Type == obj.TYPE_MEM { + a.Index = arm64.REG_SXTX + Rnum + } else { + a.Reg = arm64.REG_SXTX + Rnum + } + case "LSL": + if !isAmount { + return errors.New("invalid register extension") + } + a.Index = arm64.REG_LSL + Rnum + default: + return errors.New("unsupported general register extension type: " + ext) + } - a.Reg = arm64.REG_ELEM + (reg & 31) + ((arm64.ARNG_D & 15) << 5) - a.Index = num - default: - return errors.New("unsupported register extension type: " + ext) + } else if reg <= arm64.REG_V31 && reg >= arm64.REG_V0 { + switch ext { + case "B8": + if isIndex { + return errors.New("invalid register extension") + } + a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_8B & 15) << 5) + case "B16": + if isIndex { + return errors.New("invalid register extension") + } + a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_16B & 15) << 5) + case "H4": + if isIndex { + return errors.New("invalid register extension") + } + a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_4H & 15) << 5) + case "H8": + if isIndex { + return errors.New("invalid register extension") + } + a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_8H & 15) << 5) + case "S2": + if isIndex { + return errors.New("invalid register extension") + } + a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_2S & 15) << 5) + case "S4": + if isIndex { + return errors.New("invalid register extension") + } + a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_4S & 15) << 5) + case "D1": + if isIndex { + return errors.New("invalid register extension") + } + a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_1D & 15) << 5) + case "D2": + if isIndex { + return errors.New("invalid register extension") + } + a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_2D & 15) << 5) + case "Q1": + if isIndex { + return errors.New("invalid register extension") + } + a.Reg = arm64.REG_ARNG + (reg & 31) + ((arm64.ARNG_1Q & 15) << 5) + case "B": + if !isIndex { + return nil + } + a.Reg = arm64.REG_ELEM + (reg & 31) + ((arm64.ARNG_B & 15) << 5) + a.Index = num + case "H": + if !isIndex { + return nil + } + a.Reg = arm64.REG_ELEM + (reg & 31) + ((arm64.ARNG_H & 15) << 5) + a.Index = num + case "S": + if !isIndex { + return nil + } + a.Reg = arm64.REG_ELEM + (reg & 31) + ((arm64.ARNG_S & 15) << 5) + a.Index = num + case "D": + if !isIndex { + return nil + } + a.Reg = arm64.REG_ELEM + (reg & 31) + ((arm64.ARNG_D & 15) << 5) + a.Index = num + default: + return errors.New("unsupported simd register extension type: " + ext) + } + } else { + return errors.New("invalid register and extension combination") } - return nil } -// ARM64RegisterArrangement parses an ARM64 vector register arrangement. +// ARM64RegisterArrangement constructs an ARM64 vector register arrangement. func ARM64RegisterArrangement(reg int16, name, arng string) (int64, error) { var curQ, curSize uint16 if name[0] != 'V' { diff --git a/src/cmd/asm/internal/asm/asm.go b/src/cmd/asm/internal/asm/asm.go index c4032759bb5aaf2cec3a83e04adacec84b1cdb6a..cf0d1550f99f0e3d6f014b1b4782553692348bb2 100644 --- a/src/cmd/asm/internal/asm/asm.go +++ b/src/cmd/asm/internal/asm/asm.go @@ -134,6 +134,14 @@ func (p *Parser) asmText(operands [][]lex.Token) { next++ } + // Issue an error if we see a function defined as ABIInternal + // without NOSPLIT. In ABIInternal, obj needs to know the function + // signature in order to construct the morestack path, so this + // currently isn't supported for asm functions. + if nameAddr.Sym.ABI() == obj.ABIInternal && flag&obj.NOSPLIT == 0 { + p.errorf("TEXT %q: ABIInternal requires NOSPLIT", name) + } + // Next operand is the frame and arg size. // Bizarre syntax: $frameSize-argSize is two words, not subtraction. // Both frameSize and argSize must be simple integers; only frameSize @@ -799,22 +807,11 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { p.errorf("can't handle %s instruction with 4 operands", op) return case 5: - if p.arch.Family == sys.PPC64 && arch.IsPPC64RLD(op) { - // Always reg, reg, con, con, reg. (con, con is a 'mask'). + if p.arch.Family == sys.PPC64 { prog.From = a[0] + // Second arg is always a register type on ppc64. prog.Reg = p.getRegister(prog, op, &a[1]) - mask1 := p.getConstant(prog, op, &a[2]) - mask2 := p.getConstant(prog, op, &a[3]) - var mask uint32 - if mask1 < mask2 { - mask = (^uint32(0) >> uint(mask1)) & (^uint32(0) << uint(31-mask2)) - } else { - mask = (^uint32(0) >> uint(mask2+1)) & (^uint32(0) << uint(31-(mask1-1))) - } - prog.SetFrom3(obj.Addr{ - Type: obj.TYPE_CONST, - Offset: int64(mask), - }) + prog.SetRestArgs([]obj.Addr{a[2], a[3]}) prog.To = a[4] break } diff --git a/src/cmd/asm/internal/asm/endtoend_test.go b/src/cmd/asm/internal/asm/endtoend_test.go index 7472507caf18ef64725e1a1822d8fb63632bed86..ead8b27b015df922982c11db2cb881dcb10a1765 100644 --- a/src/cmd/asm/internal/asm/endtoend_test.go +++ b/src/cmd/asm/internal/asm/endtoend_test.go @@ -8,6 +8,7 @@ import ( "bufio" "bytes" "fmt" + "internal/buildcfg" "io/ioutil" "os" "path/filepath" @@ -19,7 +20,6 @@ import ( "cmd/asm/internal/lex" "cmd/internal/obj" - "cmd/internal/objabi" ) // An end-to-end test for the assembler: Do we print what we parse? @@ -36,6 +36,7 @@ func testEndToEnd(t *testing.T, goarch, file string) { var ok bool testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. ctxt.Bso = bufio.NewWriter(os.Stdout) + ctxt.IsAsm = true defer ctxt.Bso.Flush() failed := false ctxt.DiagFunc = func(format string, args ...interface{}) { @@ -269,7 +270,7 @@ var ( errQuotesRE = regexp.MustCompile(`"([^"]*)"`) ) -func testErrors(t *testing.T, goarch, file string) { +func testErrors(t *testing.T, goarch, file string, flags ...string) { input := filepath.Join("testdata", file+".s") architecture, ctxt := setArch(goarch) lexer := lex.NewLexer(input) @@ -278,6 +279,7 @@ func testErrors(t *testing.T, goarch, file string) { var ok bool testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. ctxt.Bso = bufio.NewWriter(os.Stdout) + ctxt.IsAsm = true defer ctxt.Bso.Flush() failed := false var errBuf bytes.Buffer @@ -290,6 +292,14 @@ func testErrors(t *testing.T, goarch, file string) { } errBuf.WriteString(s) } + for _, flag := range flags { + switch flag { + case "dynlink": + ctxt.Flag_dynlink = true + default: + t.Errorf("unknown flag %s", flag) + } + } pList.Firstpc, ok = parser.Parse() obj.Flushplist(ctxt, pList, nil, "") if ok && !failed { @@ -358,10 +368,10 @@ func Test386EndToEnd(t *testing.T) { } func TestARMEndToEnd(t *testing.T) { - defer func(old int) { objabi.GOARM = old }(objabi.GOARM) + defer func(old int) { buildcfg.GOARM = old }(buildcfg.GOARM) for _, goarm := range []int{5, 6, 7} { t.Logf("GOARM=%d", goarm) - objabi.GOARM = goarm + buildcfg.GOARM = goarm testEndToEnd(t, "arm", "arm") if goarm == 6 { testEndToEnd(t, "arm", "armv6") @@ -428,6 +438,10 @@ func TestAMD64Errors(t *testing.T) { testErrors(t, "amd64", "amd64error") } +func TestAMD64DynLinkErrors(t *testing.T) { + testErrors(t, "amd64", "amd64dynlinkerror", "dynlink") +} + func TestMIPSEndToEnd(t *testing.T) { testEndToEnd(t, "mips", "mips") testEndToEnd(t, "mips64", "mips64") @@ -437,8 +451,12 @@ func TestPPC64EndToEnd(t *testing.T) { testEndToEnd(t, "ppc64", "ppc64") } -func TestRISCVEncoder(t *testing.T) { - testEndToEnd(t, "riscv64", "riscvenc") +func TestRISCVEndToEnd(t *testing.T) { + testEndToEnd(t, "riscv64", "riscv64") +} + +func TestRISCVErrors(t *testing.T) { + testErrors(t, "riscv64", "riscv64error") } func TestS390XEndToEnd(t *testing.T) { diff --git a/src/cmd/asm/internal/asm/operand_test.go b/src/cmd/asm/internal/asm/operand_test.go index 2e83e176b297cb5840944f7c3a6787370644ac97..8ef02b1a0e8acacafb379d12030e1c522de984f0 100644 --- a/src/cmd/asm/internal/asm/operand_test.go +++ b/src/cmd/asm/internal/asm/operand_test.go @@ -5,20 +5,20 @@ package asm import ( + "internal/buildcfg" "strings" "testing" "cmd/asm/internal/arch" "cmd/asm/internal/lex" "cmd/internal/obj" - "cmd/internal/objabi" ) // A simple in-out test: Do we print what we parse? func setArch(goarch string) (*arch.Arch, *obj.Link) { - objabi.GOOS = "linux" // obj can handle this OS for all architectures. - objabi.GOARCH = goarch + buildcfg.GOOS = "linux" // obj can handle this OS for all architectures. + buildcfg.GOARCH = goarch architecture := arch.Set(goarch) if architecture == nil { panic("asm: unrecognized architecture " + goarch) @@ -259,6 +259,7 @@ var amd64OperandTests = []operandTest{ {"R15", "R15"}, {"R8", "R8"}, {"R9", "R9"}, + {"g", "R14"}, {"SI", "SI"}, {"SP", "SP"}, {"X0", "X0"}, diff --git a/src/cmd/asm/internal/asm/parse.go b/src/cmd/asm/internal/asm/parse.go index 154cf9c7a7854bf2cfe56ed1ef041286a1d04fc3..4cddcf48a466eebf1a09b8e93eb9183dd7d4bb8e 100644 --- a/src/cmd/asm/internal/asm/parse.go +++ b/src/cmd/asm/internal/asm/parse.go @@ -305,7 +305,7 @@ func (p *Parser) pseudo(word string, operands [][]lex.Token) bool { // references and writes symabis information to w. // // The symabis format is documented at -// cmd/compile/internal/gc.readSymABIs. +// cmd/compile/internal/ssagen.ReadSymABIs. func (p *Parser) symDefRef(w io.Writer, word string, operands [][]lex.Token) { switch word { case "TEXT": @@ -689,7 +689,11 @@ func (p *Parser) registerShift(name string, prefix rune) int64 { p.errorf("unexpected %s in register shift", tok.String()) } if p.arch.Family == sys.ARM64 { - return int64(r1&31)<<16 | int64(op)<<22 | int64(uint16(count)) + off, err := arch.ARM64RegisterShift(r1, op, count) + if err != nil { + p.errorf(err.Error()) + } + return off } else { return int64((r1 & 15) | op<<5 | count) } @@ -999,15 +1003,18 @@ func (p *Parser) registerIndirect(a *obj.Addr, prefix rune) { p.errorf("unimplemented two-register form") } a.Index = r1 - if scale == 0 && p.arch.Family == sys.ARM64 { - // scale is 1 by default for ARM64 - a.Scale = 1 + if scale != 0 && scale != 1 && p.arch.Family == sys.ARM64 { + // Support (R1)(R2) (no scaling) and (R1)(R2*1). + p.errorf("arm64 doesn't support scaled register format") } else { a.Scale = int16(scale) } } p.get(')') } else if scale != 0 { + if p.arch.Family == sys.ARM64 { + p.errorf("arm64 doesn't support scaled register format") + } // First (R) was missing, all we have is (R*scale). a.Reg = 0 a.Index = r1 diff --git a/src/cmd/asm/internal/asm/pseudo_test.go b/src/cmd/asm/internal/asm/pseudo_test.go index 622ee25ce7159bca3951e7f8da7f617e1169ec2c..fe6ffa60740f8a939da0beaadc40e73796b51474 100644 --- a/src/cmd/asm/internal/asm/pseudo_test.go +++ b/src/cmd/asm/internal/asm/pseudo_test.go @@ -25,11 +25,13 @@ func tokenize(s string) [][]lex.Token { func TestErroneous(t *testing.T) { - tests := []struct { + type errtest struct { pseudo string operands string expected string - }{ + } + + nonRuntimeTests := []errtest{ {"TEXT", "", "expect two or three operands for TEXT"}, {"TEXT", "%", "expect two or three operands for TEXT"}, {"TEXT", "1, 1", "TEXT symbol \"\" must be a symbol(SB)"}, @@ -58,23 +60,44 @@ func TestErroneous(t *testing.T) { {"PCDATA", "1", "expect two operands for PCDATA"}, } + runtimeTests := []errtest{ + {"TEXT", "foo(SB),0", "TEXT \"foo\": ABIInternal requires NOSPLIT"}, + } + + testcats := []struct { + compilingRuntime bool + tests []errtest + }{ + { + compilingRuntime: false, + tests: nonRuntimeTests, + }, + { + compilingRuntime: true, + tests: runtimeTests, + }, + } + // Note these errors should be independent of the architecture. // Just run the test with amd64. parser := newParser("amd64") var buf bytes.Buffer parser.errorWriter = &buf - for _, test := range tests { - parser.errorCount = 0 - parser.lineNum++ - if !parser.pseudo(test.pseudo, tokenize(test.operands)) { - t.Fatalf("Wrong pseudo-instruction: %s", test.pseudo) - } - errorLine := buf.String() - if test.expected != errorLine { - t.Errorf("Unexpected error %q; expected %q", errorLine, test.expected) + for _, cat := range testcats { + for _, test := range cat.tests { + parser.compilingRuntime = cat.compilingRuntime + parser.errorCount = 0 + parser.lineNum++ + if !parser.pseudo(test.pseudo, tokenize(test.operands)) { + t.Fatalf("Wrong pseudo-instruction: %s", test.pseudo) + } + errorLine := buf.String() + if test.expected != errorLine { + t.Errorf("Unexpected error %q; expected %q", errorLine, test.expected) + } + buf.Reset() } - buf.Reset() } } diff --git a/src/cmd/asm/internal/asm/testdata/amd64dynlinkerror.s b/src/cmd/asm/internal/asm/testdata/amd64dynlinkerror.s new file mode 100644 index 0000000000000000000000000000000000000000..1eee1a17db2518fb4fc8c488deb91c3e5c0f61d8 --- /dev/null +++ b/src/cmd/asm/internal/asm/testdata/amd64dynlinkerror.s @@ -0,0 +1,68 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test to make sure that if we use R15 after it is clobbered by +// a global variable access while dynamic linking, we get an error. +// See issue 43661. + +TEXT ·a1(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MOVL $0, R15 + RET +TEXT ·a2(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MOVQ $0, R15 + RET +TEXT ·a3(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + XORL R15, R15 + RET +TEXT ·a4(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + XORQ R15, R15 + RET +TEXT ·a5(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + XORL R15, R15 + RET +TEXT ·a6(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + POPQ R15 + PUSHQ R15 + RET +TEXT ·a7(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MOVQ R15, AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a8(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + ADDQ AX, R15 // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a9(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + ORQ R15, R15 // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a10(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + JEQ one + ORQ R15, R15 // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" +one: + RET +TEXT ·a11(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + JEQ one + JMP two +one: + ORQ R15, R15 // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" +two: + RET +TEXT ·a12(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + JMP one +two: + ORQ R15, R15 + RET +one: + MOVL $0, R15 + JMP two diff --git a/src/cmd/asm/internal/asm/testdata/arm64.s b/src/cmd/asm/internal/asm/testdata/arm64.s index 91e3a0ca0a425feffe687c7866c6daacce4b778e..d8a20edfc13039d205c10a0579ab9414a54daa54 100644 --- a/src/cmd/asm/internal/asm/testdata/arm64.s +++ b/src/cmd/asm/internal/asm/testdata/arm64.s @@ -64,6 +64,16 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 CMN R1.SXTX<<2, R10 // 5fe921ab CMPW R2.UXTH<<3, R11 // 7f2d226b CMNW R1.SXTB, R9 // 3f81212b + ADD R1<<1, RSP, R3 // e367218b + ADDW R1<<2, R3, RSP // 7f48210b + SUB R1<<3, RSP // ff6f21cb + SUBS R1<<4, RSP, R3 // e37321eb + ADDS R1<<1, RSP, R4 // e46721ab + CMP R1<<2, RSP // ff6b21eb + CMN R1<<3, RSP // ff6f21ab + ADDS R1<<1, ZR, R4 // e40701ab + ADD R3<<50, ZR, ZR // ffcb038b + CMP R4<<24, ZR // ff6304eb CMPW $0x60060, R2 // CMPW $393312, R2 // 1b0c8052db00a0725f001b6b CMPW $40960, R0 // 1f284071 CMPW $27745, R2 // 3b8c8d525f001b6b @@ -79,7 +89,7 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 CMP R1<<33, R2 CMP R22.SXTX, RSP // ffe336eb CMP $0x22220000, RSP // CMP $572653568, RSP // 5b44a4d2ff633beb - CMPW $0x22220000, RSP // CMPW $572653568, RSP // 5b44a452ff633b6b + CMPW $0x22220000, RSP // CMPW $572653568, RSP // 5b44a452ff433b6b CCMN MI, ZR, R1, $4 // e44341ba // MADD Rn,Rm,Ra,Rd MADD R1, R2, R3, R4 // 6408019b @@ -207,6 +217,18 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 VUADDW2 V9.B16, V12.H8, V14.H8 // 8e11296e VUADDW2 V13.H8, V20.S4, V30.S4 // 9e126d6e VUADDW2 V21.S4, V24.D2, V29.D2 // 1d13b56e + VUMAX V3.B8, V2.B8, V1.B8 // 4164232e + VUMAX V3.B16, V2.B16, V1.B16 // 4164236e + VUMAX V3.H4, V2.H4, V1.H4 // 4164632e + VUMAX V3.H8, V2.H8, V1.H8 // 4164636e + VUMAX V3.S2, V2.S2, V1.S2 // 4164a32e + VUMAX V3.S4, V2.S4, V1.S4 // 4164a36e + VUMIN V3.B8, V2.B8, V1.B8 // 416c232e + VUMIN V3.B16, V2.B16, V1.B16 // 416c236e + VUMIN V3.H4, V2.H4, V1.H4 // 416c632e + VUMIN V3.H8, V2.H8, V1.H8 // 416c636e + VUMIN V3.S2, V2.S2, V1.S2 // 416ca32e + VUMIN V3.S4, V2.S4, V1.S4 // 416ca36e FCCMPS LT, F1, F2, $1 // 41b4211e FMADDS F1, F3, F2, F4 // 440c011f FMADDD F4, F5, F4, F4 // 8414441f @@ -352,6 +374,10 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 MOVD $1, ZR MOVD $1, R1 MOVK $1, R1 + MOVD $0x1000100010001000, RSP // MOVD $1152939097061330944, RSP // ff8304b2 + MOVW $0x10001000, RSP // MOVW $268439552, RSP // ff830432 + ADDW $0x10001000, R1 // ADDW $268439552, R1 // fb83043221001b0b + ADDW $0x22220000, RSP, R3 // ADDW $572653568, RSP, R3 // 5b44a452e3433b0b // move a large constant to a Vd. VMOVS $0x80402010, V11 // VMOVS $2151686160, V11 @@ -380,13 +406,13 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 // LD1/ST1 VLD1 (R8), [V1.B16, V2.B16] // 01a1404c VLD1.P (R3), [V31.H8, V0.H8] // 7fa4df4c - VLD1.P (R8)(R20), [V21.B16, V22.B16] // VLD1.P (R8)(R20*1), [V21.B16,V22.B16] // 15a1d44c + VLD1.P (R8)(R20), [V21.B16, V22.B16] // 15a1d44c VLD1.P 64(R1), [V5.B16, V6.B16, V7.B16, V8.B16] // 2520df4c VLD1.P 1(R0), V4.B[15] // 041cdf4d VLD1.P 2(R0), V4.H[7] // 0458df4d VLD1.P 4(R0), V4.S[3] // 0490df4d VLD1.P 8(R0), V4.D[1] // 0484df4d - VLD1.P (R0)(R1), V4.D[1] // VLD1.P (R0)(R1*1), V4.D[1] // 0484c14d + VLD1.P (R0)(R1), V4.D[1] // 0484c14d VLD1 (R0), V4.D[1] // 0484404d VST1.P [V4.S4, V5.S4], 32(R1) // 24a89f4c VST1 [V0.S4, V1.S4], (R0) // 00a8004c @@ -394,29 +420,29 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 VLD1.P 24(R30), [V3.S2,V4.S2,V5.S2] // c36bdf0c VLD2 (R29), [V23.H8, V24.H8] // b787404c VLD2.P 16(R0), [V18.B8, V19.B8] // 1280df0c - VLD2.P (R1)(R2), [V15.S2, V16.S2] // VLD2.P (R1)(R2*1), [V15.S2,V16.S2] // 2f88c20c + VLD2.P (R1)(R2), [V15.S2, V16.S2] // 2f88c20c VLD3 (R27), [V11.S4, V12.S4, V13.S4] // 6b4b404c VLD3.P 48(RSP), [V11.S4, V12.S4, V13.S4] // eb4bdf4c - VLD3.P (R30)(R2), [V14.D2, V15.D2, V16.D2] // VLD3.P (R30)(R2*1), [V14.D2,V15.D2,V16.D2] // ce4fc24c + VLD3.P (R30)(R2), [V14.D2, V15.D2, V16.D2] // ce4fc24c VLD4 (R15), [V10.H4, V11.H4, V12.H4, V13.H4] // ea05400c VLD4.P 32(R24), [V31.B8, V0.B8, V1.B8, V2.B8] // 1f03df0c - VLD4.P (R13)(R9), [V14.S2, V15.S2, V16.S2, V17.S2] // VLD4.P (R13)(R9*1), [V14.S2,V15.S2,V16.S2,V17.S2] // ae09c90c + VLD4.P (R13)(R9), [V14.S2, V15.S2, V16.S2, V17.S2] // ae09c90c VLD1R (R1), [V9.B8] // 29c0400d VLD1R.P (R1), [V9.B8] // 29c0df0d VLD1R.P 1(R1), [V2.B8] // 22c0df0d VLD1R.P 2(R1), [V2.H4] // 22c4df0d VLD1R (R0), [V0.B16] // 00c0404d VLD1R.P (R0), [V0.B16] // 00c0df4d - VLD1R.P (R15)(R1), [V15.H4] // VLD1R.P (R15)(R1*1), [V15.H4] // efc5c10d + VLD1R.P (R15)(R1), [V15.H4] // efc5c10d VLD2R (R15), [V15.H4, V16.H4] // efc5600d VLD2R.P 16(R0), [V0.D2, V1.D2] // 00ccff4d - VLD2R.P (R0)(R5), [V31.D1, V0.D1] // VLD2R.P (R0)(R5*1), [V31.D1, V0.D1] // 1fcce50d + VLD2R.P (R0)(R5), [V31.D1, V0.D1] // 1fcce50d VLD3R (RSP), [V31.S2, V0.S2, V1.S2] // ffeb400d VLD3R.P 6(R15), [V15.H4, V16.H4, V17.H4] // efe5df0d - VLD3R.P (R15)(R6), [V15.H8, V16.H8, V17.H8] // VLD3R.P (R15)(R6*1), [V15.H8, V16.H8, V17.H8] // efe5c64d + VLD3R.P (R15)(R6), [V15.H8, V16.H8, V17.H8] // efe5c64d VLD4R (R0), [V0.B8, V1.B8, V2.B8, V3.B8] // 00e0600d VLD4R.P 16(RSP), [V31.S4, V0.S4, V1.S4, V2.S4] // ffebff4d - VLD4R.P (R15)(R9), [V15.H4, V16.H4, V17.H4, V18.H4] // VLD4R.P (R15)(R9*1), [V15.H4, V16.H4, V17.H4, V18.H4] // efe5e90d + VLD4R.P (R15)(R9), [V15.H4, V16.H4, V17.H4, V18.H4] // efe5e90d VST1.P [V24.S2], 8(R2) // 58789f0c VST1 [V29.S2, V30.S2], (R29) // bdab000c VST1 [V14.H4, V15.H4, V16.H4], (R27) // 6e67000c @@ -424,17 +450,17 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 VST1.P V4.H[7], 2(R0) // 04589f4d VST1.P V4.S[3], 4(R0) // 04909f4d VST1.P V4.D[1], 8(R0) // 04849f4d - VST1.P V4.D[1], (R0)(R1) // VST1.P V4.D[1], (R0)(R1*1) // 0484814d + VST1.P V4.D[1], (R0)(R1) // 0484814d VST1 V4.D[1], (R0) // 0484004d VST2 [V22.H8, V23.H8], (R23) // f686004c VST2.P [V14.H4, V15.H4], 16(R17) // 2e869f0c - VST2.P [V14.H4, V15.H4], (R3)(R17) // VST2.P [V14.H4,V15.H4], (R3)(R17*1) // 6e84910c + VST2.P [V14.H4, V15.H4], (R3)(R17) // 6e84910c VST3 [V1.D2, V2.D2, V3.D2], (R11) // 614d004c VST3.P [V18.S4, V19.S4, V20.S4], 48(R25) // 324b9f4c - VST3.P [V19.B8, V20.B8, V21.B8], (R3)(R7) // VST3.P [V19.B8, V20.B8, V21.B8], (R3)(R7*1) // 7340870c + VST3.P [V19.B8, V20.B8, V21.B8], (R3)(R7) // 7340870c VST4 [V22.D2, V23.D2, V24.D2, V25.D2], (R3) // 760c004c VST4.P [V14.D2, V15.D2, V16.D2, V17.D2], 64(R15) // ee0d9f4c - VST4.P [V24.B8, V25.B8, V26.B8, V27.B8], (R3)(R23) // VST4.P [V24.B8, V25.B8, V26.B8, V27.B8], (R3)(R23*1) // 7800970c + VST4.P [V24.B8, V25.B8, V26.B8, V27.B8], (R3)(R23) // 7800970c // pre/post-indexed FMOVS.P F20, 4(R0) // 144400bc @@ -521,29 +547,30 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 // shifted or extended register offset. MOVD (R2)(R6.SXTW), R4 // 44c866f8 - MOVD (R3)(R6), R5 // MOVD (R3)(R6*1), R5 // 656866f8 - MOVD (R2)(R6), R4 // MOVD (R2)(R6*1), R4 // 446866f8 + MOVD (R3)(R6), R5 // 656866f8 + MOVD (R3)(R6*1), R5 // 656866f8 + MOVD (R2)(R6), R4 // 446866f8 MOVWU (R19)(R20<<2), R20 // 747a74b8 MOVD (R2)(R6<<3), R4 // 447866f8 MOVD (R3)(R7.SXTX<<3), R8 // 68f867f8 MOVWU (R5)(R4.UXTW), R10 // aa4864b8 MOVBU (R3)(R9.UXTW), R8 // 68486938 - MOVBU (R5)(R8), R10 // MOVBU (R5)(R8*1), R10 // aa686838 + MOVBU (R5)(R8), R10 // aa686838 MOVHU (R2)(R7.SXTW<<1), R11 // 4bd86778 MOVHU (R1)(R2<<1), R5 // 25786278 MOVB (R9)(R3.UXTW), R6 // 2649a338 - MOVB (R10)(R6), R15 // MOVB (R10)(R6*1), R15 // 4f69a638 + MOVB (R10)(R6), R15 // 4f69a638 MOVB (R29)(R30<<0), R14 // ae7bbe38 - MOVB (R29)(R30), R14 // MOVB (R29)(R30*1), R14 // ae6bbe38 + MOVB (R29)(R30), R14 // ae6bbe38 MOVH (R5)(R7.SXTX<<1), R19 // b3f8a778 MOVH (R8)(R4<<1), R10 // 0a79a478 MOVW (R9)(R8.SXTW<<2), R19 // 33d9a8b8 MOVW (R1)(R4.SXTX), R11 // 2be8a4b8 MOVW (R1)(R4.SXTX), ZR // 3fe8a4b8 - MOVW (R2)(R5), R12 // MOVW (R2)(R5*1), R12 // 4c68a5b8 - FMOVS (R2)(R6), F4 // FMOVS (R2)(R6*1), F4 // 446866bc + MOVW (R2)(R5), R12 // 4c68a5b8 + FMOVS (R2)(R6), F4 // 446866bc FMOVS (R2)(R6<<2), F4 // 447866bc - FMOVD (R2)(R6), F4 // FMOVD (R2)(R6*1), F4 // 446866fc + FMOVD (R2)(R6), F4 // 446866fc FMOVD (R2)(R6<<3), F4 // 447866fc MOVD R5, (R2)(R6<<3) // 457826f8 @@ -553,15 +580,16 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 MOVW R7, (R3)(R4.SXTW) // 67c824b8 MOVB R4, (R2)(R6.SXTX) // 44e82638 MOVB R8, (R3)(R9.UXTW) // 68482938 - MOVB R10, (R5)(R8) // MOVB R10, (R5)(R8*1) // aa682838 + MOVB R10, (R5)(R8) // aa682838 + MOVB R10, (R5)(R8*1) // aa682838 MOVH R11, (R2)(R7.SXTW<<1) // 4bd82778 MOVH R5, (R1)(R2<<1) // 25782278 MOVH R7, (R2)(R5.SXTX<<1) // 47f82578 MOVH R8, (R3)(R6.UXTW) // 68482678 MOVB R4, (R2)(R6.SXTX) // 44e82638 - FMOVS F4, (R2)(R6) // FMOVS F4, (R2)(R6*1) // 446826bc + FMOVS F4, (R2)(R6) // 446826bc FMOVS F4, (R2)(R6<<2) // 447826bc - FMOVD F4, (R2)(R6) // FMOVD F4, (R2)(R6*1) // 446826fc + FMOVD F4, (R2)(R6) // 446826fc FMOVD F4, (R2)(R6<<3) // 447826fc // vmov @@ -571,9 +599,12 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 VMOV R20, V1.S[0] // 811e044e VMOV R20, V1.S[1] // 811e0c4e VMOV R1, V9.H4 // 290c020e + VDUP R1, V9.H4 // 290c020e VMOV R22, V11.D2 // cb0e084e + VDUP R22, V11.D2 // cb0e084e VMOV V2.B16, V4.B16 // 441ca24e VMOV V20.S[0], V20 // 9406045e + VDUP V20.S[0], V20 // 9406045e VMOV V12.D[0], V12.D[1] // 8c05186e VMOV V10.S[0], V12.S[1] // 4c050c6e VMOV V9.H[0], V12.H[1] // 2c05066e @@ -982,6 +1013,54 @@ again: FSTPS (F3, F4), x(SB) FSTPS (F3, F4), x+8(SB) +// FLDPQ/FSTPQ + FLDPQ -4000(R0), (F1, F2) // 1b803ed1610b40ad + FLDPQ -1024(R0), (F1, F2) // 010860ad + FLDPQ (R0), (F1, F2) // 010840ad + FLDPQ 16(R0), (F1, F2) // 018840ad + FLDPQ -16(R0), (F1, F2) // 01887fad + FLDPQ.W 32(R0), (F1, F2) // 0108c1ad + FLDPQ.P 32(R0), (F1, F2) // 0108c1ac + FLDPQ 11(R0), (F1, F2) // 1b2c0091610b40ad + FLDPQ 1024(R0), (F1, F2) // 1b001091610b40ad + FLDPQ 4104(R0), (F1, F2) + FLDPQ -4000(RSP), (F1, F2) // fb833ed1610b40ad + FLDPQ -1024(RSP), (F1, F2) // e10b60ad + FLDPQ (RSP), (F1, F2) // e10b40ad + FLDPQ 16(RSP), (F1, F2) // e18b40ad + FLDPQ -16(RSP), (F1, F2) // e18b7fad + FLDPQ.W 32(RSP), (F1, F2) // e10bc1ad + FLDPQ.P 32(RSP), (F1, F2) // e10bc1ac + FLDPQ 11(RSP), (F1, F2) // fb2f0091610b40ad + FLDPQ 1024(RSP), (F1, F2) // fb031091610b40ad + FLDPQ 4104(RSP), (F1, F2) + FLDPQ -31(R0), (F1, F2) // 1b7c00d1610b40ad + FLDPQ -4(R0), (F1, F2) // 1b1000d1610b40ad + FLDPQ x(SB), (F1, F2) + FLDPQ x+8(SB), (F1, F2) + FSTPQ (F3, F4), -4000(R5) // bb803ed1631300ad + FSTPQ (F3, F4), -1024(R5) // a31020ad + FSTPQ (F3, F4), (R5) // a31000ad + FSTPQ (F3, F4), 16(R5) // a39000ad + FSTPQ (F3, F4), -16(R5) // a3903fad + FSTPQ.W (F3, F4), 32(R5) // a31081ad + FSTPQ.P (F3, F4), 32(R5) // a31081ac + FSTPQ (F3, F4), 11(R5) // bb2c0091631300ad + FSTPQ (F3, F4), 1024(R5) // bb001091631300ad + FSTPQ (F3, F4), 4104(R5) + FSTPQ (F3, F4), -4000(RSP) // fb833ed1631300ad + FSTPQ (F3, F4), -1024(RSP) // e31320ad + FSTPQ (F3, F4), (RSP) // e31300ad + FSTPQ (F3, F4), 16(RSP) // e39300ad + FSTPQ (F3, F4), -16(RSP) // e3933fad + FSTPQ.W (F3, F4), 32(RSP) // e31381ad + FSTPQ.P (F3, F4), 32(RSP) // e31381ac + FSTPQ (F3, F4), 11(RSP) // fb2f0091631300ad + FSTPQ (F3, F4), 1024(RSP) // fb031091631300ad + FSTPQ (F3, F4), 4104(RSP) + FSTPQ (F3, F4), x(SB) + FSTPQ (F3, F4), x+8(SB) + // System Register MSR $1, SPSel // bf4100d5 MSR $9, DAIFSet // df4903d5 diff --git a/src/cmd/asm/internal/asm/testdata/arm64enc.s b/src/cmd/asm/internal/asm/testdata/arm64enc.s index e802ee76f5b9ee6efb23b33d0a97e10680159ae8..a29862822d30a15b54d313e63676711f6bb7c0f3 100644 --- a/src/cmd/asm/internal/asm/testdata/arm64enc.s +++ b/src/cmd/asm/internal/asm/testdata/arm64enc.s @@ -188,7 +188,7 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$-8 MOVBU 2916(R24), R3 // 03936d39 MOVBU (R19)(R14<<0), R23 // 777a6e38 MOVBU (R2)(R8.SXTX), R19 // 53e86838 - MOVBU (R27)(R23), R14 // MOVBU (R27)(R23*1), R14 // 6e6b7738 + MOVBU (R27)(R23), R14 // 6e6b7738 MOVHU.P 107(R14), R13 // cdb54678 MOVHU.W 192(R3), R2 // 620c4c78 MOVHU 6844(R4), R19 // 93787579 @@ -201,9 +201,9 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$-8 MOVB 997(R9), R23 // 37958f39 //TODO MOVBW (R2<<1)(R21), R15 // af7ae238 //TODO MOVBW (R26)(R0), R21 // 1568fa38 - MOVB (R5)(R15), R16 // MOVB (R5)(R15*1), R16 // b068af38 + MOVB (R5)(R15), R16 // b068af38 MOVB (R19)(R26.SXTW), R19 // 73caba38 - MOVB (R29)(R30), R14 // MOVB (R29)(R30*1), R14 // ae6bbe38 + MOVB (R29)(R30), R14 // ae6bbe38 //TODO MOVHW.P 218(R22), R25 // d9a6cd78 MOVH.P 179(R23), R5 // e5368b78 //TODO MOVHW.W 136(R2), R27 // 5b8cc878 @@ -357,12 +357,12 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$-8 MOVD R25, -137(R17) // 397217f8 MOVW R4, (R12)(R22.UXTW<<2) // 845936b8 MOVD R27, (R5)(R15.UXTW<<3) // bb582ff8 - MOVB R2, (R10)(R16) // MOVB R2, (R10)(R16*1) // 42693038 - MOVB R2, (R29)(R26) // MOVB R2, (R29)(R26*1) // a26b3a38 + MOVB R2, (R10)(R16) // 42693038 + MOVB R2, (R29)(R26) // a26b3a38 MOVH R11, -80(R23) // eb021b78 MOVH R11, (R27)(R14.SXTW<<1) // 6bdb2e78 - MOVB R19, (R0)(R4) // MOVB R19, (R0)(R4*1) // 13682438 - MOVB R1, (R6)(R4) // MOVB R1, (R6)(R4*1) // c1682438 + MOVB R19, (R0)(R4) // 13682438 + MOVB R1, (R6)(R4) // c1682438 MOVH R3, (R11)(R13<<1) // 63792d78 //TODO STTR 55(R4), R29 // 9d7803b8 //TODO STTR 124(R5), R25 // b9c807f8 @@ -669,6 +669,7 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$-8 VCMEQ V24.S4, V13.S4, V12.S4 // ac8db86e VCNT V13.B8, V11.B8 // ab59200e VMOV V31.B[15], V18 // f2071f5e + VDUP V31.B[15], V18 // f2071f5e VDUP V31.B[13], V20.B16 // f4071b4e VEOR V4.B8, V18.B8, V7.B8 // 471e242e VEXT $4, V2.B8, V1.B8, V3.B8 // 2320022e @@ -679,27 +680,28 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$-8 VLD1 (R24), [V18.D1, V19.D1, V20.D1] // 126f400c VLD1 (R29), [V14.D1, V15.D1, V16.D1, V17.D1] // ae2f400c VLD1.P 16(R23), [V1.B16] // e172df4c - VLD1.P (R6)(R11), [V31.D1] // VLD1.P (R6)(R11*1), [V31.D1] // df7ccb0c + VLD1.P (R6)(R11), [V31.D1] // df7ccb0c VLD1.P 16(R7), [V31.D1, V0.D1] // ffacdf0c - VLD1.P (R19)(R4), [V24.B8, V25.B8] // VLD1.P (R19)(R4*1), [V24.B8, V25.B8] // 78a2c40c - VLD1.P (R20)(R8), [V7.H8, V8.H8, V9.H8] // VLD1.P (R20)(R8*1), [V7.H8, V8.H8, V9.H8] // 8766c84c + VLD1.P (R19)(R4), [V24.B8, V25.B8] // 78a2c40c + VLD1.P (R20)(R8), [V7.H8, V8.H8, V9.H8] // 8766c84c VLD1.P 32(R30), [V5.B8, V6.B8, V7.B8, V8.B8] // c523df0c VLD1 (R19), V14.B[15] // 6e1e404d VLD1 (R29), V0.H[1] // a04b400d VLD1 (R27), V2.S[0] // 6283400d VLD1 (R21), V5.D[1] // a586404d VLD1.P 1(R19), V10.B[14] // 6a1adf4d - VLD1.P (R3)(R14), V16.B[11] // VLD1.P (R3)(R14*1), V16.B[11] // 700cce4d + VLD1.P (R3)(R14), V16.B[11] // 700cce4d VLD1.P 2(R1), V28.H[2] // 3c50df0d - VLD1.P (R13)(R20), V9.H[2] // VLD1.P (R13)(R20*1), V9.H[2] // a951d40d + VLD1.P (R13)(R20), V9.H[2] // a951d40d VLD1.P 4(R17), V1.S[3] // 2192df4d - VLD1.P (R14)(R2), V17.S[2] // VLD1.P (R14)(R2*1), V17.S[2] // d181c24d + VLD1.P (R14)(R2), V17.S[2] // d181c24d VLD1.P 8(R5), V30.D[1] // be84df4d - VLD1.P (R27)(R13), V27.D[0] // VLD1.P (R27)(R13*1), V27.D[0] // 7b87cd0d + VLD1.P (R27)(R13), V27.D[0] // 7b87cd0d //TODO FMOVS.P -29(RSP), F8 // e8375ebc //TODO FMOVS.W 71(R29), F28 // bc7f44bc FMOVS 6160(R4), F23 // 971058bd VMOV V18.B[10], V27 // 5b06155e + VDUP V18.B[10], V27 // 5b06155e VMOV V12.B[2], V28.B[12] // 9c15196e VMOV R30, V4.B[13] // c41f1b4e VMOV V2.B16, V4.B16 // 441ca24e @@ -732,25 +734,25 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$-8 VSHL $7, V22.D2, V25.D2 // d956474f VST1 [V14.H4, V15.H4, V16.H4], (R27) // 6e67000c VST1 [V2.S4, V3.S4, V4.S4, V5.S4], (R14) // c229004c - VST1.P [V25.S4], (R7)(R29) // VST1.P [V25.S4], (R7)(R29*1) // f9789d4c + VST1.P [V25.S4], (R7)(R29) // f9789d4c VST1.P [V25.D2, V26.D2], 32(R7) // f9ac9f4c - VST1.P [V14.D1, V15.D1], (R7)(R23) // VST1.P [V14.D1, V15.D1], (R7)(R23*1) // eeac970c + VST1.P [V14.D1, V15.D1], (R7)(R23) // eeac970c VST1.P [V25.D2, V26.D2, V27.D2], 48(R27) // 796f9f4c - VST1.P [V13.H8, V14.H8, V15.H8], (R3)(R14) // VST1.P [V13.H8, V14.H8, V15.H8], (R3)(R14*1) // 6d648e4c + VST1.P [V13.H8, V14.H8, V15.H8], (R3)(R14) // 6d648e4c VST1.P [V16.S4, V17.S4, V18.S4, V19.S4], 64(R6) // d0289f4c - VST1.P [V19.H4, V20.H4, V21.H4, V22.H4], (R4)(R16) // VST1.P [V19.H4, V20.H4, V21.H4, V22.H4], (R4)(R16*1) // 9324900c + VST1.P [V19.H4, V20.H4, V21.H4, V22.H4], (R4)(R16) // 9324900c VST1 V12.B[3], (R1) // 2c0c000d VST1 V12.B[3], (R1) // 2c0c000d VST1 V25.S[2], (R20) // 9982004d VST1 V9.D[1], (RSP) // e987004d VST1.P V30.B[6], 1(R3) // 7e189f0d - VST1.P V8.B[0], (R3)(R21) // VST1.P V8.B[0], (R3)(R21*1) // 6800950d + VST1.P V8.B[0], (R3)(R21) // 6800950d VST1.P V15.H[5], 2(R10) // 4f499f4d - VST1.P V1.H[7], (R23)(R11) // VST1.P V1.H[7], (R23)(R11*1) // e15a8b4d + VST1.P V1.H[7], (R23)(R11) // e15a8b4d VST1.P V26.S[0], 4(R11) // 7a819f0d - VST1.P V9.S[1], (R16)(R21) // VST1.P V9.S[1], (R16)(R21*1) // 0992950d + VST1.P V9.S[1], (R16)(R21) // 0992950d VST1.P V16.D[0], 8(R9) // 30859f0d - VST1.P V23.D[1], (R21)(R16) // VST1.P V23.D[1], (R21)(R16*1) // b786904d + VST1.P V23.D[1], (R21)(R16) // b786904d VSUB V1, V12, V23 // 9785e17e VUADDLV V31.S4, V11 // eb3bb06e UCVTFWS R11, F19 // 7301231e diff --git a/src/cmd/asm/internal/asm/testdata/arm64error.s b/src/cmd/asm/internal/asm/testdata/arm64error.s index e579f20836a4495bdb76c7ae26b82da92febe66c..cf57179e43016156cf148dcbd001c54d5620b00e 100644 --- a/src/cmd/asm/internal/asm/testdata/arm64error.s +++ b/src/cmd/asm/internal/asm/testdata/arm64error.s @@ -8,12 +8,60 @@ TEXT errors(SB),$0 ADDSW R7->32, R14, R13 // ERROR "shift amount out of range 0 to 31" ADD R1.UXTB<<5, R2, R3 // ERROR "shift amount out of range 0 to 4" ADDS R1.UXTX<<7, R2, R3 // ERROR "shift amount out of range 0 to 4" + ADDS R5, R6, RSP // ERROR "illegal destination register" + SUBS R5, R6, RSP // ERROR "illegal destination register" + ADDSW R5, R6, RSP // ERROR "illegal destination register" + SUBSW R5, R6, RSP // ERROR "illegal destination register" + ADDS $0xff, R6, RSP // ERROR "illegal destination register" + ADDS $0xffff0, R6, RSP // ERROR "illegal destination register" + ADDS $0x1000100010001000, R6, RSP // ERROR "illegal destination register" + ADDS $0x10001000100011, R6, RSP // ERROR "illegal destination register" + ADDSW $0xff, R6, RSP // ERROR "illegal destination register" + ADDSW $0xffff0, R6, RSP // ERROR "illegal destination register" + ADDSW $0x1000100010001000, R6, RSP // ERROR "illegal destination register" + ADDSW $0x10001000100011, R6, RSP // ERROR "illegal destination register" + SUBS $0xff, R6, RSP // ERROR "illegal destination register" + SUBS $0xffff0, R6, RSP // ERROR "illegal destination register" + SUBS $0x1000100010001000, R6, RSP // ERROR "illegal destination register" + SUBS $0x10001000100011, R6, RSP // ERROR "illegal destination register" + SUBSW $0xff, R6, RSP // ERROR "illegal destination register" + SUBSW $0xffff0, R6, RSP // ERROR "illegal destination register" + SUBSW $0x1000100010001000, R6, RSP // ERROR "illegal destination register" + SUBSW $0x10001000100011, R6, RSP // ERROR "illegal destination register" AND $0x22220000, R2, RSP // ERROR "illegal combination" ANDS $0x22220000, R2, RSP // ERROR "illegal combination" ADD R1, R2, R3, R4 // ERROR "illegal combination" BICW R7@>33, R5, R16 // ERROR "shift amount out of range 0 to 31" + NEGW R7<<33, R5 // ERROR "shift amount out of range 0 to 31" + NEGSW R7<<33, R5 // ERROR "shift amount out of range 0 to 31" + ADD R7@>2, R5, R16 // ERROR "unsupported shift operator" + ADDW R7@>2, R5, R16 // ERROR "unsupported shift operator" + ADDS R7@>2, R5, R16 // ERROR "unsupported shift operator" + ADDSW R7@>2, R5, R16 // ERROR "unsupported shift operator" + SUB R7@>2, R5, R16 // ERROR "unsupported shift operator" + SUBW R7@>2, R5, R16 // ERROR "unsupported shift operator" + SUBS R7@>2, R5, R16 // ERROR "unsupported shift operator" + SUBSW R7@>2, R5, R16 // ERROR "unsupported shift operator" + CMP R7@>2, R5 // ERROR "unsupported shift operator" + CMPW R7@>2, R5 // ERROR "unsupported shift operator" + CMN R7@>2, R5 // ERROR "unsupported shift operator" + CMNW R7@>2, R5 // ERROR "unsupported shift operator" + NEG R7@>2, R5 // ERROR "unsupported shift operator" + NEGW R7@>2, R5 // ERROR "unsupported shift operator" + NEGS R7@>2, R5 // ERROR "unsupported shift operator" + NEGSW R7@>2, R5 // ERROR "unsupported shift operator" CINC CS, R2, R3, R4 // ERROR "illegal combination" CSEL LT, R1, R2 // ERROR "illegal combination" + CINC AL, R2, R3 // ERROR "invalid condition" + CINC NV, R2, R3 // ERROR "invalid condition" + CINVW AL, R2, R3 // ERROR "invalid condition" + CINV NV, R2, R3 // ERROR "invalid condition" + CNEG AL, R2, R3 // ERROR "invalid condition" + CNEGW NV, R2, R3 // ERROR "invalid condition" + CSET AL, R2 // ERROR "invalid condition" + CSET NV, R2 // ERROR "invalid condition" + CSETMW AL, R2 // ERROR "invalid condition" + CSETM NV, R2 // ERROR "invalid condition" LDP.P 8(R2), (R2, R3) // ERROR "constrained unpredictable behavior" LDP.W 8(R3), (R2, R3) // ERROR "constrained unpredictable behavior" LDP (R1), (R2, R2) // ERROR "constrained unpredictable behavior" @@ -21,8 +69,8 @@ TEXT errors(SB),$0 LDP (R0), (R3, ZR) // ERROR "invalid register pair" LDXPW (RSP), (R2, R2) // ERROR "constrained unpredictable behavior" LDAXPW (R5), (R2, R2) // ERROR "constrained unpredictable behavior" - MOVD.P 300(R2), R3 // ERROR "offset out of range [-255,254]" - MOVD.P R3, 344(R2) // ERROR "offset out of range [-255,254]" + MOVD.P 300(R2), R3 // ERROR "offset out of range [-256,255]" + MOVD.P R3, 344(R2) // ERROR "offset out of range [-256,255]" MOVD (R3)(R7.SXTX<<2), R8 // ERROR "invalid index shift amount" MOVWU (R5)(R4.UXTW<<3), R10 // ERROR "invalid index shift amount" MOVWU (R5)(R4<<1), R10 // ERROR "invalid index shift amount" @@ -58,13 +106,13 @@ TEXT errors(SB),$0 VMOV V8.H[9], R3 // ERROR "register element index out of range 0 to 7" VMOV V8.S[4], R3 // ERROR "register element index out of range 0 to 3" VMOV V8.D[2], R3 // ERROR "register element index out of range 0 to 1" - VDUP V8.B[16], R3.B16 // ERROR "register element index out of range 0 to 15" - VDUP V8.B[17], R3.B8 // ERROR "register element index out of range 0 to 15" - VDUP V8.H[9], R3.H4 // ERROR "register element index out of range 0 to 7" - VDUP V8.H[9], R3.H8 // ERROR "register element index out of range 0 to 7" - VDUP V8.S[4], R3.S2 // ERROR "register element index out of range 0 to 3" - VDUP V8.S[4], R3.S4 // ERROR "register element index out of range 0 to 3" - VDUP V8.D[2], R3.D2 // ERROR "register element index out of range 0 to 1" + VDUP V8.B[16], V3.B16 // ERROR "register element index out of range 0 to 15" + VDUP V8.B[17], V3.B8 // ERROR "register element index out of range 0 to 15" + VDUP V8.H[9], V3.H4 // ERROR "register element index out of range 0 to 7" + VDUP V8.H[9], V3.H8 // ERROR "register element index out of range 0 to 7" + VDUP V8.S[4], V3.S2 // ERROR "register element index out of range 0 to 3" + VDUP V8.S[4], V3.S4 // ERROR "register element index out of range 0 to 3" + VDUP V8.D[2], V3.D2 // ERROR "register element index out of range 0 to 1" VFMLA V1.D2, V12.D2, V3.S2 // ERROR "operand mismatch" VFMLA V1.S2, V12.S2, V3.D2 // ERROR "operand mismatch" VFMLA V1.S4, V12.S2, V3.D2 // ERROR "operand mismatch" @@ -109,6 +157,9 @@ TEXT errors(SB),$0 VREV16 V1.D1, V2.D1 // ERROR "invalid arrangement" VREV16 V1.B8, V2.B16 // ERROR "invalid arrangement" VREV16 V1.H4, V2.H4 // ERROR "invalid arrangement" + FLDPQ (R0), (R1, R2) // ERROR "invalid register pair" + FLDPQ (R1), (F2, F2) // ERROR "constrained unpredictable behavior" + FSTPQ (R1, R2), (R0) // ERROR "invalid register pair" FLDPD (R0), (R1, R2) // ERROR "invalid register pair" FLDPD (R1), (F2, F2) // ERROR "constrained unpredictable behavior" FLDPS (R2), (F3, F3) // ERROR "constrained unpredictable behavior" @@ -355,10 +406,17 @@ TEXT errors(SB),$0 VBIF V0.D2, V1.D2, V2.D2 // ERROR "invalid arrangement" VUADDW V9.B8, V12.H8, V14.B8 // ERROR "invalid arrangement" VUADDW2 V9.B8, V12.S4, V14.S4 // ERROR "operand mismatch" + VUMAX V1.D2, V2.D2, V3.D2 // ERROR "invalid arrangement" + VUMIN V1.D2, V2.D2, V3.D2 // ERROR "invalid arrangement" + VUMAX V1.B8, V2.B8, V3.B16 // ERROR "operand mismatch" + VUMIN V1.H4, V2.S4, V3.H4 // ERROR "operand mismatch" VSLI $64, V7.D2, V8.D2 // ERROR "shift out of range" VUSRA $0, V7.D2, V8.D2 // ERROR "shift out of range" CASPD (R3, R4), (R2), (R8, R9) // ERROR "source register pair must start from even register" CASPD (R2, R3), (R2), (R9, R10) // ERROR "destination register pair must start from even register" CASPD (R2, R4), (R2), (R8, R9) // ERROR "source register pair must be contiguous" CASPD (R2, R3), (R2), (R8, R10) // ERROR "destination register pair must be contiguous" + ADD R1>>2, RSP, R3 // ERROR "illegal combination" + ADDS R2<<3, R3, RSP // ERROR "unexpected SP reference" + CMP R1<<5, RSP // ERROR "the left shift amount out of range 0 to 4" RET diff --git a/src/cmd/asm/internal/asm/testdata/mips64.s b/src/cmd/asm/internal/asm/testdata/mips64.s index 21ab82f319c1471ab8926a54baa19f777b473abd..99044d89f7be331b44df28456c5ebc90442a217e 100644 --- a/src/cmd/asm/internal/asm/testdata/mips64.s +++ b/src/cmd/asm/internal/asm/testdata/mips64.s @@ -407,6 +407,8 @@ label4: SRLV R27, R6, R17 // 03668816 SRA R11, R19, R20 // 0173a007 SRAV R20, R19, R19 // 02939817 + ROTR R19, R18, R20 // 0272a046 + ROTRV R9, R13, R16 // 012d8056 // LSHW rreg ',' rreg // { @@ -418,6 +420,8 @@ label4: SRLV R27, R6 // 03663016 SRA R11, R19 // 01739807 SRAV R20, R19 // 02939817 + ROTR R20, R19 // 02939846 + ROTRV R16, R9 // 02094856 // LSHW imm ',' sreg ',' rreg // { @@ -429,6 +433,8 @@ label4: SRLV $31, R6, R17 // 00068ffa SRA $8, R8, R19 // 00089a03 SRAV $19, R8, R7 // 00083cfb + ROTR $12, R8, R3 // 00281b02 + ROTRV $8, R22, R22 // 0036b23a // LSHW imm ',' rreg // { @@ -440,6 +446,8 @@ label4: SRLV $31, R17 // 00118ffa SRA $3, R12 // 000c60c3 SRAV $12, R3 // 00031b3b + ROTR $12, R8 // 00284302 + ROTRV $63, R22 // 0036b7fe // LAND/LXOR/LNOR/LOR rreg ',' rreg diff --git a/src/cmd/asm/internal/asm/testdata/ppc64.s b/src/cmd/asm/internal/asm/testdata/ppc64.s index 8f6eb14f73cba702053fd4394948ce9fa6485d71..b6c0aa5035c013652dcd7df91505d78cfd9ac6a7 100644 --- a/src/cmd/asm/internal/asm/testdata/ppc64.s +++ b/src/cmd/asm/internal/asm/testdata/ppc64.s @@ -41,6 +41,8 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 MOVDBR (R3)(R4), R5 // 7ca41c28 MOVWBR (R3)(R4), R5 // 7ca41c2c MOVHBR (R3)(R4), R5 // 7ca41e2c + MOVD $foo+4009806848(FP), R5 // 3ca1ef0138a5cc20 + MOVD $foo(SB), R5 // 3ca0000038a50000 MOVDU 8(R3), R4 // e8830009 MOVDU (R3)(R4), R5 // 7ca4186a @@ -77,6 +79,15 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 MOVBU R4, 1(R3) // 9c830001 MOVBU R5, (R3)(R4) // 7ca419ee + MOVB $0, R4 // 38800000 + MOVBZ $0, R4 // 38800000 + MOVH $0, R4 // 38800000 + MOVHZ $0, R4 // 38800000 + MOVW $0, R4 // 38800000 + MOVWZ $0, R4 // 38800000 + MOVD $0, R4 // 38800000 + MOVD $0, R0 // 38000000 + ADD $1, R3 // 38630001 ADD $1, R3, R4 // 38830001 ADD $-1, R4 // 3884ffff @@ -280,11 +291,17 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 ROTLW R3, R4, R5 // 5c85183e EXTSWSLI $3, R4, R5 // 7c851ef4 RLWMI $7, R3, $65535, R6 // 50663c3e + RLWMI $7, R3, $16, $31, R6 // 50663c3e RLWMICC $7, R3, $65535, R6 // 50663c3f + RLWMICC $7, R3, $16, $31, R6 // 50663c3f RLWNM $3, R4, $7, R6 // 54861f7e + RLWNM $3, R4, $29, $31, R6 // 54861f7e RLWNM R3, R4, $7, R6 // 5c861f7e + RLWNM R3, R4, $29, $31, R6 // 5c861f7e RLWNMCC $3, R4, $7, R6 // 54861f7f + RLWNMCC $3, R4, $29, $31, R6 // 54861f7f RLWNMCC R3, R4, $7, R6 // 5c861f7f + RLWNMCC R3, R4, $29, $31, R6 // 5c861f7f RLDMI $0, R4, $7, R6 // 7886076c RLDMICC $0, R4, $7, R6 // 7886076d RLDIMI $0, R4, $7, R6 // 788601cc @@ -303,6 +320,8 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 RLDICCC $0, R4, $15, R6 // 788603c9 CLRLSLWI $16, R5, $8, R4 // 54a4422e CLRLSLDI $24, R4, $2, R3 // 78831588 + RLDCR $1, R1, $-16, R1 // 78210ee4 + RLDCRCC $1, R1, $-16, R1 // 78210ee5 BEQ 0(PC) // 41820000 BEQ CR1,0(PC) // 41860000 diff --git a/src/cmd/asm/internal/asm/testdata/riscvenc.s b/src/cmd/asm/internal/asm/testdata/riscv64.s similarity index 94% rename from src/cmd/asm/internal/asm/testdata/riscvenc.s rename to src/cmd/asm/internal/asm/testdata/riscv64.s index 9a49d96ca0acdf9acd162ce69ed45a2383b51c82..77c0764c48117a78132e0407837c45dc7194b208 100644 --- a/src/cmd/asm/internal/asm/testdata/riscvenc.s +++ b/src/cmd/asm/internal/asm/testdata/riscv64.s @@ -280,6 +280,9 @@ start: MOV $2047, X5 // 9b02f07f MOV $-2048, X5 // 9b020080 + // Converted to load of symbol. + MOV $4294967296, X5 // 97020000 + MOV (X5), X6 // 03b30200 MOV 4(X5), X6 // 03b34200 MOVB (X5), X6 // 03830200 @@ -325,7 +328,7 @@ start: // These jumps can get printed as jumps to 2 because they go to the // second instruction in the function (the first instruction is an // invisible stack pointer adjustment). - JMP start // JMP 2 // 6ff09fc2 + JMP start // JMP 2 // 6ff01fc2 JMP (X5) // 67800200 JMP 4(X5) // 67804200 @@ -338,16 +341,16 @@ start: JMP asmtest(SB) // 970f0000 // Branch pseudo-instructions - BEQZ X5, start // BEQZ X5, 2 // e38602c0 - BGEZ X5, start // BGEZ X5, 2 // e3d402c0 - BGT X5, X6, start // BGT X5, X6, 2 // e34253c0 - BGTU X5, X6, start // BGTU X5, X6, 2 // e36053c0 - BGTZ X5, start // BGTZ X5, 2 // e34e50be - BLE X5, X6, start // BLE X5, X6, 2 // e35c53be - BLEU X5, X6, start // BLEU X5, X6, 2 // e37a53be - BLEZ X5, start // BLEZ X5, 2 // e35850be - BLTZ X5, start // BLTZ X5, 2 // e3c602be - BNEZ X5, start // BNEZ X5, 2 // e39402be + BEQZ X5, start // BEQZ X5, 2 // e38202c0 + BGEZ X5, start // BGEZ X5, 2 // e3d002c0 + BGT X5, X6, start // BGT X5, X6, 2 // e34e53be + BGTU X5, X6, start // BGTU X5, X6, 2 // e36c53be + BGTZ X5, start // BGTZ X5, 2 // e34a50be + BLE X5, X6, start // BLE X5, X6, 2 // e35853be + BLEU X5, X6, start // BLEU X5, X6, 2 // e37653be + BLEZ X5, start // BLEZ X5, 2 // e35450be + BLTZ X5, start // BLTZ X5, 2 // e3c202be + BNEZ X5, start // BNEZ X5, 2 // e39002be // Set pseudo-instructions SEQZ X15, X15 // 93b71700 diff --git a/src/cmd/asm/internal/asm/testdata/riscv64error.s b/src/cmd/asm/internal/asm/testdata/riscv64error.s new file mode 100644 index 0000000000000000000000000000000000000000..fb43e68fc1740b131328778f0612ccf99455674f --- /dev/null +++ b/src/cmd/asm/internal/asm/testdata/riscv64error.s @@ -0,0 +1,14 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +TEXT errors(SB),$0 + MOV $0, 0(SP) // ERROR "constant load must target register" + MOV $0, 8(SP) // ERROR "constant load must target register" + MOV $1234, 0(SP) // ERROR "constant load must target register" + MOV $1234, 8(SP) // ERROR "constant load must target register" + MOVB $1, X5 // ERROR "unsupported constant load" + MOVH $1, X5 // ERROR "unsupported constant load" + MOVW $1, X5 // ERROR "unsupported constant load" + MOVF $1, X5 // ERROR "unsupported constant load" + RET diff --git a/src/cmd/asm/internal/flags/flags.go b/src/cmd/asm/internal/flags/flags.go index 1335860315a839ffc2d48580eda32d87790876ed..dd947c7b5ba5b805d4319d78fe432cafdbf1faad 100644 --- a/src/cmd/asm/internal/flags/flags.go +++ b/src/cmd/asm/internal/flags/flags.go @@ -32,11 +32,13 @@ var ( D MultiFlag I MultiFlag PrintOut int + DebugV bool ) func init() { flag.Var(&D, "D", "predefined symbol with optional simple value -D=identifier=value; can be set multiple times") flag.Var(&I, "I", "include directory; can be set multiple times") + flag.BoolVar(&DebugV, "v", false, "print debug output") objabi.AddVersionFlag() // -V objabi.Flagcount("S", "print assembly and machine code", &PrintOut) } diff --git a/src/cmd/asm/internal/lex/input.go b/src/cmd/asm/internal/lex/input.go index da4ebe6d6e3447fa5a672863b68e459b720646ec..e373ae817e0d83f70bdc3ae5fe7b58f89c179f26 100644 --- a/src/cmd/asm/internal/lex/input.go +++ b/src/cmd/asm/internal/lex/input.go @@ -6,6 +6,7 @@ package lex import ( "fmt" + "internal/buildcfg" "os" "path/filepath" "strconv" @@ -45,6 +46,21 @@ func NewInput(name string) *Input { // predefine installs the macros set by the -D flag on the command line. func predefine(defines flags.MultiFlag) map[string]*Macro { macros := make(map[string]*Macro) + + // Set macros for GOEXPERIMENTs so we can easily switch + // runtime assembly code based on them. + if *flags.CompilingRuntime { + for _, exp := range buildcfg.EnabledExperiments() { + // Define macro. + name := "GOEXPERIMENT_" + exp + macros[name] = &Macro{ + name: name, + args: nil, + tokens: Tokenize("1"), + } + } + } + for _, name := range defines { value := "1" i := strings.IndexRune(name, '=') diff --git a/src/cmd/asm/main.go b/src/cmd/asm/main.go index 31636e30458974920aa6d5a4ced11513a48cecdc..043bc696e58ae2fb0fdd74bcc0e5cdcfe9bb7a39 100644 --- a/src/cmd/asm/main.go +++ b/src/cmd/asm/main.go @@ -8,6 +8,7 @@ import ( "bufio" "flag" "fmt" + "internal/buildcfg" "log" "os" @@ -25,7 +26,8 @@ func main() { log.SetFlags(0) log.SetPrefix("asm: ") - GOARCH := objabi.GOARCH + buildcfg.Check() + GOARCH := buildcfg.GOARCH architecture := arch.Set(GOARCH) if architecture == nil { @@ -36,6 +38,7 @@ func main() { ctxt := obj.Linknew(architecture.LinkArch) ctxt.Debugasm = flags.PrintOut + ctxt.Debugvlog = flags.DebugV ctxt.Flag_dynlink = *flags.Dynlink ctxt.Flag_linkshared = *flags.Linkshared ctxt.Flag_shared = *flags.Shared || *flags.Dynlink @@ -67,7 +70,7 @@ func main() { defer buf.Close() if !*flags.SymABIs { - fmt.Fprintf(buf, "go object %s %s %s\n", objabi.GOOS, objabi.GOARCH, objabi.Version) + buf.WriteString(objabi.HeaderString()) fmt.Fprintf(buf, "!\n") } diff --git a/src/cmd/cgo/doc.go b/src/cmd/cgo/doc.go index e782c866ac771640c365ad97e5055b7cabdceaa3..a6787f640501133173e82804d1a2c1fc289504a5 100644 --- a/src/cmd/cgo/doc.go +++ b/src/cmd/cgo/doc.go @@ -387,6 +387,9 @@ and of course there is nothing stopping the C code from doing anything it likes. However, programs that break these rules are likely to fail in unexpected and unpredictable ways. +The runtime/cgo.Handle type can be used to safely pass Go values +between Go and C. See the runtime/cgo package documentation for details. + Note: the current implementation has a bug. While Go code is permitted to write nil or a C pointer (but not a Go pointer) to C memory, the current implementation may sometimes cause a runtime error if the diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index b5e28e325459c0ac54122e97ceeeb74de54133c2..a73e998877af812762afcc4716c17b78adf7f065 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -909,7 +909,7 @@ func (p *Package) rewriteCall(f *File, call *Call) (string, bool) { var sbCheck bytes.Buffer for i, param := range params { origArg := args[i] - arg, nu := p.mangle(f, &args[i]) + arg, nu := p.mangle(f, &args[i], true) if nu { needsUnsafe = true } @@ -952,7 +952,7 @@ func (p *Package) rewriteCall(f *File, call *Call) (string, bool) { sb.WriteString("return ") } - m, nu := p.mangle(f, &call.Call.Fun) + m, nu := p.mangle(f, &call.Call.Fun, false) if nu { needsUnsafe = true } @@ -1086,7 +1086,8 @@ func (p *Package) hasPointer(f *File, t ast.Expr, top bool) bool { // rewriting calls when it finds them. // It removes the corresponding references in f.Ref and f.Calls, so that we // don't try to do the replacement again in rewriteRef or rewriteCall. -func (p *Package) mangle(f *File, arg *ast.Expr) (ast.Expr, bool) { +// If addPosition is true, add position info to the idents of C names in arg. +func (p *Package) mangle(f *File, arg *ast.Expr, addPosition bool) (ast.Expr, bool) { needsUnsafe := false f.walk(arg, ctxExpr, func(f *File, arg interface{}, context astContext) { px, ok := arg.(*ast.Expr) @@ -1101,7 +1102,7 @@ func (p *Package) mangle(f *File, arg *ast.Expr) (ast.Expr, bool) { for _, r := range f.Ref { if r.Expr == px { - *px = p.rewriteName(f, r) + *px = p.rewriteName(f, r, addPosition) r.Done = true break } @@ -1361,7 +1362,7 @@ func (p *Package) rewriteRef(f *File) { } } - expr := p.rewriteName(f, r) + expr := p.rewriteName(f, r, false) if *godefs { // Substitute definition for mangled type name. @@ -1424,8 +1425,23 @@ func (p *Package) rewriteRef(f *File) { } // rewriteName returns the expression used to rewrite a reference. -func (p *Package) rewriteName(f *File, r *Ref) ast.Expr { - var expr ast.Expr = ast.NewIdent(r.Name.Mangle) // default +// If addPosition is true, add position info in the ident name. +func (p *Package) rewriteName(f *File, r *Ref, addPosition bool) ast.Expr { + getNewIdent := ast.NewIdent + if addPosition { + getNewIdent = func(newName string) *ast.Ident { + mangledIdent := ast.NewIdent(newName) + if len(newName) == len(r.Name.Go) { + return mangledIdent + } + p := fset.Position((*r.Expr).End()) + if p.Column == 0 { + return mangledIdent + } + return ast.NewIdent(fmt.Sprintf("%s /*line :%d:%d*/", newName, p.Line, p.Column)) + } + } + var expr ast.Expr = getNewIdent(r.Name.Mangle) // default switch r.Context { case ctxCall, ctxCall2: if r.Name.Kind != "func" { @@ -1453,7 +1469,7 @@ func (p *Package) rewriteName(f *File, r *Ref) ast.Expr { n.Mangle = "_C2func_" + n.Go f.Name["2"+r.Name.Go] = n } - expr = ast.NewIdent(n.Mangle) + expr = getNewIdent(n.Mangle) r.Name = n break } @@ -1484,7 +1500,7 @@ func (p *Package) rewriteName(f *File, r *Ref) ast.Expr { // issue 7757. expr = &ast.CallExpr{ Fun: &ast.Ident{NamePos: (*r.Expr).Pos(), Name: "_Cgo_ptr"}, - Args: []ast.Expr{ast.NewIdent(name.Mangle)}, + Args: []ast.Expr{getNewIdent(name.Mangle)}, } case "type": // Okay - might be new(T) @@ -1566,9 +1582,17 @@ func (p *Package) gccMachine() []string { case "s390x": return []string{"-m64"} case "mips64", "mips64le": - return []string{"-mabi=64"} + if gomips64 == "hardfloat" { + return []string{"-mabi=64", "-mhard-float"} + } else if gomips64 == "softfloat" { + return []string{"-mabi=64", "-msoft-float"} + } case "mips", "mipsle": - return []string{"-mabi=32"} + if gomips == "hardfloat" { + return []string{"-mabi=32", "-mfp32", "-mhard-float", "-mno-odd-spreg"} + } else if gomips == "softfloat" { + return []string{"-mabi=32", "-msoft-float"} + } } return nil } @@ -1614,6 +1638,8 @@ func (p *Package) gccCmd() []string { c = append(c, "-maix64") c = append(c, "-mcmodel=large") } + // disable LTO so we get an object whose symbols we can read + c = append(c, "-fno-lto") c = append(c, "-") //read input from standard input return c } diff --git a/src/cmd/cgo/main.go b/src/cmd/cgo/main.go index c1116e28ecdb25324fd9e0d18251db85f4be9ba8..c6a0c525e64f212bd51bb7a5eb317e46a085ffbc 100644 --- a/src/cmd/cgo/main.go +++ b/src/cmd/cgo/main.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Cgo; see gmp.go for an overview. +// Cgo; see doc.go for an overview. // TODO(rsc): // Emit correct line number annotations. @@ -17,9 +17,11 @@ import ( "go/ast" "go/printer" "go/token" + "internal/buildcfg" "io" "io/ioutil" "os" + "os/exec" "path/filepath" "reflect" "runtime" @@ -245,7 +247,7 @@ var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo var importSyscall = flag.Bool("import_syscall", true, "import syscall in generated code") var trimpath = flag.String("trimpath", "", "applies supplied rewrites or trims prefixes to recorded source file paths") -var goarch, goos string +var goarch, goos, gomips, gomips64 string func main() { objabi.AddVersionFlag() // -V @@ -302,6 +304,14 @@ func main() { p := newPackage(args[:i]) + // We need a C compiler to be available. Check this. + gccName := p.gccBaseCmd()[0] + _, err := exec.LookPath(gccName) + if err != nil { + fatalf("C compiler %q not found: %v", gccName, err) + os.Exit(2) + } + // Record CGO_LDFLAGS from the environment for external linking. if ldflags := os.Getenv("CGO_LDFLAGS"); ldflags != "" { args, err := splitQuoted(ldflags) @@ -405,6 +415,9 @@ func newPackage(args []string) *Package { if s := os.Getenv("GOOS"); s != "" { goos = s } + buildcfg.Check() + gomips = buildcfg.GOMIPS + gomips64 = buildcfg.GOMIPS64 ptrSize := ptrSizeMap[goarch] if ptrSize == 0 { fatalf("unknown ptrSize for $GOARCH %q", goarch) diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index 8e83f02202f73c15a92b8a1b5fce6f4b18236103..94152f4278cb04b44325a936c50d8d11887f4b3d 100644 --- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -168,8 +168,18 @@ func (p *Package) writeDefs() { if *gccgo { fmt.Fprintf(fc, "extern byte *%s;\n", n.C) } else { - fmt.Fprintf(fm, "extern char %s[];\n", n.C) - fmt.Fprintf(fm, "void *_cgohack_%s = %s;\n\n", n.C, n.C) + // Force a reference to all symbols so that + // the external linker will add DT_NEEDED + // entries as needed on ELF systems. + // Treat function variables differently + // to avoid type confict errors from LTO + // (Link Time Optimization). + if n.Kind == "fpvar" { + fmt.Fprintf(fm, "extern void %s();\n", n.C) + } else { + fmt.Fprintf(fm, "extern char %s[];\n", n.C) + fmt.Fprintf(fm, "void *_cgohack_%s = %s;\n\n", n.C, n.C) + } fmt.Fprintf(fgo2, "//go:linkname __cgo_%s %s\n", n.C, n.C) fmt.Fprintf(fgo2, "//go:cgo_import_static %s\n", n.C) fmt.Fprintf(fgo2, "var __cgo_%s byte\n", n.C) @@ -1021,14 +1031,28 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) { } fmt.Fprintf(fgcc, "}\n") - // Build the wrapper function compiled by cmd/compile. - // This unpacks the argument struct above and calls the Go function. + // In internal linking mode, the Go linker sees both + // the C wrapper written above and the Go wrapper it + // references. Hence, export the C wrapper (e.g., for + // if we're building a shared object). The Go linker + // will resolve the C wrapper's reference to the Go + // wrapper without a separate export. fmt.Fprintf(fgo2, "//go:cgo_export_dynamic %s\n", exp.ExpName) + // cgo_export_static refers to a symbol by its linker + // name, so set the linker name of the Go wrapper. fmt.Fprintf(fgo2, "//go:linkname _cgoexp%s_%s _cgoexp%s_%s\n", cPrefix, exp.ExpName, cPrefix, exp.ExpName) + // In external linking mode, the Go linker sees the Go + // wrapper, but not the C wrapper. For this case, + // export the Go wrapper so the host linker can + // resolve the reference from the C wrapper to the Go + // wrapper. fmt.Fprintf(fgo2, "//go:cgo_export_static _cgoexp%s_%s\n", cPrefix, exp.ExpName) + + // Build the wrapper function compiled by cmd/compile. + // This unpacks the argument struct above and calls the Go function. fmt.Fprintf(fgo2, "func _cgoexp%s_%s(a *%s) {\n", cPrefix, exp.ExpName, gotype) - fmt.Fprintf(fm, "int _cgoexp%s_%s;\n", cPrefix, exp.ExpName) + fmt.Fprintf(fm, "void _cgoexp%s_%s(void* p){}\n", cPrefix, exp.ExpName) if gccResult != "void" { // Write results back to frame. @@ -1717,8 +1741,12 @@ typedef struct __go_open_array { struct __go_string __go_byte_array_to_string(const void* p, intgo len); struct __go_open_array __go_string_to_byte_array (struct __go_string str); +extern void runtime_throw(const char *); + const char *_cgoPREFIX_Cfunc_CString(struct __go_string s) { char *p = malloc(s.__length+1); + if(p == NULL) + runtime_throw("runtime: C malloc failed"); memmove(p, s.__data, s.__length); p[s.__length] = 0; return p; @@ -1726,6 +1754,8 @@ const char *_cgoPREFIX_Cfunc_CString(struct __go_string s) { void *_cgoPREFIX_Cfunc_CBytes(struct __go_open_array b) { char *p = malloc(b.__count); + if(p == NULL) + runtime_throw("runtime: C malloc failed"); memmove(p, b.__values, b.__count); return p; } @@ -1744,14 +1774,13 @@ Slice _cgoPREFIX_Cfunc_GoBytes(char *p, int32_t n) { return __go_string_to_byte_array(s); } -extern void runtime_throw(const char *); void *_cgoPREFIX_Cfunc__CMalloc(size_t n) { - void *p = malloc(n); - if(p == NULL && n == 0) - p = malloc(1); - if(p == NULL) - runtime_throw("runtime: C malloc failed"); - return p; + void *p = malloc(n); + if(p == NULL && n == 0) + p = malloc(1); + if(p == NULL) + runtime_throw("runtime: C malloc failed"); + return p; } struct __go_type_descriptor; diff --git a/src/cmd/compile/abi-internal.md b/src/cmd/compile/abi-internal.md new file mode 100644 index 0000000000000000000000000000000000000000..2bb4055083263044df3c400a52a3cf6dd27dde40 --- /dev/null +++ b/src/cmd/compile/abi-internal.md @@ -0,0 +1,645 @@ +# Go internal ABI specification + +This document describes Go’s internal application binary interface +(ABI), known as ABIInternal. +Go's ABI defines the layout of data in memory and the conventions for +calling between Go functions. +This ABI is *unstable* and will change between Go versions. +If you’re writing assembly code, please instead refer to Go’s +[assembly documentation](/doc/asm.html), which describes Go’s stable +ABI, known as ABI0. + +All functions defined in Go source follow ABIInternal. +However, ABIInternal and ABI0 functions are able to call each other +through transparent *ABI wrappers*, described in the [internal calling +convention proposal](https://golang.org/design/27539-internal-abi). + +Go uses a common ABI design across all architectures. +We first describe the common ABI, and then cover per-architecture +specifics. + +*Rationale*: For the reasoning behind using a common ABI across +architectures instead of the platform ABI, see the [register-based Go +calling convention proposal](https://golang.org/design/40724-register-calling). + +## Memory layout + +Go's built-in types have the following sizes and alignments. +Many, though not all, of these sizes are guaranteed by the [language +specification](/doc/go_spec.html#Size_and_alignment_guarantees). +Those that aren't guaranteed may change in future versions of Go (for +example, we've considered changing the alignment of int64 on 32-bit). + +| Type | 64-bit | | 32-bit | | +| --- | --- | --- | --- | --- | +| | Size | Align | Size | Align | +| bool, uint8, int8 | 1 | 1 | 1 | 1 | +| uint16, int16 | 2 | 2 | 2 | 2 | +| uint32, int32 | 4 | 4 | 4 | 4 | +| uint64, int64 | 8 | 8 | 8 | 4 | +| int, uint | 8 | 8 | 4 | 4 | +| float32 | 4 | 4 | 4 | 4 | +| float64 | 8 | 8 | 8 | 4 | +| complex64 | 8 | 4 | 8 | 4 | +| complex128 | 16 | 8 | 16 | 4 | +| uintptr, *T, unsafe.Pointer | 8 | 8 | 4 | 4 | + +The types `byte` and `rune` are aliases for `uint8` and `int32`, +respectively, and hence have the same size and alignment as these +types. + +The layout of `map`, `chan`, and `func` types is equivalent to *T. + +To describe the layout of the remaining composite types, we first +define the layout of a *sequence* S of N fields with types +t1, t2, ..., tN. +We define the byte offset at which each field begins relative to a +base address of 0, as well as the size and alignment of the sequence +as follows: + +``` +offset(S, i) = 0 if i = 1 + = align(offset(S, i-1) + sizeof(t_(i-1)), alignof(t_i)) +alignof(S) = 1 if N = 0 + = max(alignof(t_i) | 1 <= i <= N) +sizeof(S) = 0 if N = 0 + = align(offset(S, N) + sizeof(t_N), alignof(S)) +``` + +Where sizeof(T) and alignof(T) are the size and alignment of type T, +respectively, and align(x, y) rounds x up to a multiple of y. + +The `interface{}` type is a sequence of 1. a pointer to the runtime type +description for the interface's dynamic type and 2. an `unsafe.Pointer` +data field. +Any other interface type (besides the empty interface) is a sequence +of 1. a pointer to the runtime "itab" that gives the method pointers and +the type of the data field and 2. an `unsafe.Pointer` data field. +An interface can be "direct" or "indirect" depending on the dynamic +type: a direct interface stores the value directly in the data field, +and an indirect interface stores a pointer to the value in the data +field. +An interface can only be direct if the value consists of a single +pointer word. + +An array type `[N]T` is a sequence of N fields of type T. + +The slice type `[]T` is a sequence of a `*[cap]T` pointer to the slice +backing store, an `int` giving the `len` of the slice, and an `int` +giving the `cap` of the slice. + +The `string` type is a sequence of a `*[len]byte` pointer to the +string backing store, and an `int` giving the `len` of the string. + +A struct type `struct { f1 t1; ...; fM tM }` is laid out as the +sequence t1, ..., tM, tP, where tP is either: + +- Type `byte` if sizeof(tM) = 0 and any of sizeof(t*i*) ≠ 0. +- Empty (size 0 and align 1) otherwise. + +The padding byte prevents creating a past-the-end pointer by taking +the address of the final, empty fN field. + +Note that user-written assembly code should generally not depend on Go +type layout and should instead use the constants defined in +[`go_asm.h`](/doc/asm.html#data-offsets). + +## Function call argument and result passing + +Function calls pass arguments and results using a combination of the +stack and machine registers. +Each argument or result is passed either entirely in registers or +entirely on the stack. +Because access to registers is generally faster than access to the +stack, arguments and results are preferentially passed in registers. +However, any argument or result that contains a non-trivial array or +does not fit entirely in the remaining available registers is passed +on the stack. + +Each architecture defines a sequence of integer registers and a +sequence of floating-point registers. +At a high level, arguments and results are recursively broken down +into values of base types and these base values are assigned to +registers from these sequences. + +Arguments and results can share the same registers, but do not share +the same stack space. +Beyond the arguments and results passed on the stack, the caller also +reserves spill space on the stack for all register-based arguments +(but does not populate this space). + +The receiver, arguments, and results of function or method F are +assigned to registers or the stack using the following algorithm: + +1. Let NI and NFP be the length of integer and floating-point register + sequences defined by the architecture. + Let I and FP be 0; these are the indexes of the next integer and + floating-pointer register. + Let S, the type sequence defining the stack frame, be empty. +1. If F is a method, assign F’s receiver. +1. For each argument A of F, assign A. +1. Add a pointer-alignment field to S. This has size 0 and the same + alignment as `uintptr`. +1. Reset I and FP to 0. +1. For each result R of F, assign R. +1. Add a pointer-alignment field to S. +1. For each register-assigned receiver and argument of F, let T be its + type and add T to the stack sequence S. + This is the argument's (or receiver's) spill space and will be + uninitialized at the call. +1. Add a pointer-alignment field to S. + +Assigning a receiver, argument, or result V of underlying type T works +as follows: + +1. Remember I and FP. +1. If T has zero size, add T to the stack sequence S and return. +1. Try to register-assign V. +1. If step 2 failed, reset I and FP to the values from step 1, add T + to the stack sequence S, and assign V to this field in S. + +Register-assignment of a value V of underlying type T works as follows: + +1. If T is a boolean or integral type that fits in an integer + register, assign V to register I and increment I. +1. If T is an integral type that fits in two integer registers, assign + the least significant and most significant halves of V to registers + I and I+1, respectively, and increment I by 2 +1. If T is a floating-point type and can be represented without loss + of precision in a floating-point register, assign V to register FP + and increment FP. +1. If T is a complex type, recursively register-assign its real and + imaginary parts. +1. If T is a pointer type, map type, channel type, or function type, + assign V to register I and increment I. +1. If T is a string type, interface type, or slice type, recursively + register-assign V’s components (2 for strings and interfaces, 3 for + slices). +1. If T is a struct type, recursively register-assign each field of V. +1. If T is an array type of length 0, do nothing. +1. If T is an array type of length 1, recursively register-assign its + one element. +1. If T is an array type of length > 1, fail. +1. If I > NI or FP > NFP, fail. +1. If any recursive assignment above fails, fail. + +The above algorithm produces an assignment of each receiver, argument, +and result to registers or to a field in the stack sequence. +The final stack sequence looks like: stack-assigned receiver, +stack-assigned arguments, pointer-alignment, stack-assigned results, +pointer-alignment, spill space for each register-assigned argument, +pointer-alignment. +The following diagram shows what this stack frame looks like on the +stack, using the typical convention where address 0 is at the bottom: + + +------------------------------+ + | . . . | + | 2nd reg argument spill space | + | 1st reg argument spill space | + | | + | . . . | + | 2nd stack-assigned result | + | 1st stack-assigned result | + | | + | . . . | + | 2nd stack-assigned argument | + | 1st stack-assigned argument | + | stack-assigned receiver | + +------------------------------+ ↓ lower addresses + +To perform a call, the caller reserves space starting at the lowest +address in its stack frame for the call stack frame, stores arguments +in the registers and argument stack fields determined by the above +algorithm, and performs the call. +At the time of a call, spill space, result stack fields, and result +registers are left uninitialized. +Upon return, the callee must have stored results to all result +registers and result stack fields determined by the above algorithm. + +There are no callee-save registers, so a call may overwrite any +register that doesn’t have a fixed meaning, including argument +registers. + +### Example + +Consider the function `func f(a1 uint8, a2 [2]uintptr, a3 uint8) (r1 +struct { x uintptr; y [2]uintptr }, r2 string)` on a 64-bit +architecture with hypothetical integer registers R0–R9. + +On entry, `a1` is assigned to `R0`, `a3` is assigned to `R1` and the +stack frame is laid out in the following sequence: + + a2 [2]uintptr + r1.x uintptr + r1.y [2]uintptr + a1Spill uint8 + a3Spill uint8 + _ [6]uint8 // alignment padding + +In the stack frame, only the `a2` field is initialized on entry; the +rest of the frame is left uninitialized. + +On exit, `r2.base` is assigned to `R0`, `r2.len` is assigned to `R1`, +and `r1.x` and `r1.y` are initialized in the stack frame. + +There are several things to note in this example. +First, `a2` and `r1` are stack-assigned because they contain arrays. +The other arguments and results are register-assigned. +Result `r2` is decomposed into its components, which are individually +register-assigned. +On the stack, the stack-assigned arguments appear at lower addresses +than the stack-assigned results, which appear at lower addresses than +the argument spill area. +Only arguments, not results, are assigned a spill area on the stack. + +### Rationale + +Each base value is assigned to its own register to optimize +construction and access. +An alternative would be to pack multiple sub-word values into +registers, or to simply map an argument's in-memory layout to +registers (this is common in C ABIs), but this typically adds cost to +pack and unpack these values. +Modern architectures have more than enough registers to pass all +arguments and results this way for nearly all functions (see the +appendix), so there’s little downside to spreading base values across +registers. + +Arguments that can’t be fully assigned to registers are passed +entirely on the stack in case the callee takes the address of that +argument. +If an argument could be split across the stack and registers and the +callee took its address, it would need to be reconstructed in memory, +a process that would be proportional to the size of the argument. + +Non-trivial arrays are always passed on the stack because indexing +into an array typically requires a computed offset, which generally +isn’t possible with registers. +Arrays in general are rare in function signatures (only 0.7% of +functions in the Go 1.15 standard library and 0.2% in kubelet). +We considered allowing array fields to be passed on the stack while +the rest of an argument’s fields are passed in registers, but this +creates the same problems as other large structs if the callee takes +the address of an argument, and would benefit <0.1% of functions in +kubelet (and even these very little). + +We make exceptions for 0 and 1-element arrays because these don’t +require computed offsets, and 1-element arrays are already decomposed +in the compiler’s SSA representation. + +The ABI assignment algorithm above is equivalent to Go’s stack-based +ABI0 calling convention if there are zero architecture registers. +This is intended to ease the transition to the register-based internal +ABI and make it easy for the compiler to generate either calling +convention. +An architecture may still define register meanings that aren’t +compatible with ABI0, but these differences should be easy to account +for in the compiler. + +The assignment algorithm assigns zero-sized values to the stack +(assignment step 2) in order to support ABI0-equivalence. +While these values take no space themselves, they do result in +alignment padding on the stack in ABI0. +Without this step, the internal ABI would register-assign zero-sized +values even on architectures that provide no argument registers +because they don't consume any registers, and hence not add alignment +padding to the stack. + +The algorithm reserves spill space for arguments in the caller’s frame +so that the compiler can generate a stack growth path that spills into +this reserved space. +If the callee has to grow the stack, it may not be able to reserve +enough additional stack space in its own frame to spill these, which +is why it’s important that the caller do so. +These slots also act as the home location if these arguments need to +be spilled for any other reason, which simplifies traceback printing. + +There are several options for how to lay out the argument spill space. +We chose to lay out each argument according to its type's usual memory +layout but to separate the spill space from the regular argument +space. +Using the usual memory layout simplifies the compiler because it +already understands this layout. +Also, if a function takes the address of a register-assigned argument, +the compiler must spill that argument to memory in its usual memory +layout and it's more convenient to use the argument spill space for +this purpose. + +Alternatively, the spill space could be structured around argument +registers. +In this approach, the stack growth spill path would spill each +argument register to a register-sized stack word. +However, if the function takes the address of a register-assigned +argument, the compiler would have to reconstruct it in memory layout +elsewhere on the stack. + +The spill space could also be interleaved with the stack-assigned +arguments so the arguments appear in order whether they are register- +or stack-assigned. +This would be close to ABI0, except that register-assigned arguments +would be uninitialized on the stack and there's no need to reserve +stack space for register-assigned results. +We expect separating the spill space to perform better because of +memory locality. +Separating the space is also potentially simpler for `reflect` calls +because this allows `reflect` to summarize the spill space as a single +number. +Finally, the long-term intent is to remove reserved spill slots +entirely – allowing most functions to be called without any stack +setup and easing the introduction of callee-save registers – and +separating the spill space makes that transition easier. + +## Closures + +A func value (e.g., `var x func()`) is a pointer to a closure object. +A closure object begins with a pointer-sized program counter +representing the entry point of the function, followed by zero or more +bytes containing the closed-over environment. + +Closure calls follow the same conventions as static function and +method calls, with one addition. Each architecture specifies a +*closure context pointer* register and calls to closures store the +address of the closure object in the closure context pointer register +prior to the call. + +## Software floating-point mode + +In "softfloat" mode, the ABI simply treats the hardware as having zero +floating-point registers. +As a result, any arguments containing floating-point values will be +passed on the stack. + +*Rationale*: Softfloat mode is about compatibility over performance +and is not commonly used. +Hence, we keep the ABI as simple as possible in this case, rather than +adding additional rules for passing floating-point values in integer +registers. + +## Architecture specifics + +This section describes per-architecture register mappings, as well as +other per-architecture special cases. + +### amd64 architecture + +The amd64 architecture uses the following sequence of 9 registers for +integer arguments and results: + + RAX, RBX, RCX, RDI, RSI, R8, R9, R10, R11 + +It uses X0 – X14 for floating-point arguments and results. + +*Rationale*: These sequences are chosen from the available registers +to be relatively easy to remember. + +Registers R12 and R13 are permanent scratch registers. +R15 is a scratch register except in dynamically linked binaries. + +*Rationale*: Some operations such as stack growth and reflection calls +need dedicated scratch registers in order to manipulate call frames +without corrupting arguments or results. + +Special-purpose registers are as follows: + +| Register | Call meaning | Return meaning | Body meaning | +| --- | --- | --- | --- | +| RSP | Stack pointer | Same | Same | +| RBP | Frame pointer | Same | Same | +| RDX | Closure context pointer | Scratch | Scratch | +| R12 | Scratch | Scratch | Scratch | +| R13 | Scratch | Scratch | Scratch | +| R14 | Current goroutine | Same | Same | +| R15 | GOT reference temporary if dynlink | Same | Same | +| X15 | Zero value | Same | Scratch | + +*Rationale*: These register meanings are compatible with Go’s +stack-based calling convention except for R14 and X15, which will have +to be restored on transitions from ABI0 code to ABIInternal code. +In ABI0, these are undefined, so transitions from ABIInternal to ABI0 +can ignore these registers. + +*Rationale*: For the current goroutine pointer, we chose a register +that requires an additional REX byte. +While this adds one byte to every function prologue, it is hardly ever +accessed outside the function prologue and we expect making more +single-byte registers available to be a net win. + +*Rationale*: We could allow R14 (the current goroutine pointer) to be +a scratch register in function bodies because it can always be +restored from TLS on amd64. +However, we designate it as a fixed register for simplicity and for +consistency with other architectures that may not have a copy of the +current goroutine pointer in TLS. + +*Rationale*: We designate X15 as a fixed zero register because +functions often have to bulk zero their stack frames, and this is more +efficient with a designated zero register. + +*Implementation note*: Registers with fixed meaning at calls but not +in function bodies must be initialized by "injected" calls such as +signal-based panics. + +#### Stack layout + +The stack pointer, RSP, grows down and is always aligned to 8 bytes. + +The amd64 architecture does not use a link register. + +A function's stack frame is laid out as follows: + + +------------------------------+ + | return PC | + | RBP on entry | + | ... locals ... | + | ... outgoing arguments ... | + +------------------------------+ ↓ lower addresses + +The "return PC" is pushed as part of the standard amd64 `CALL` +operation. +On entry, a function subtracts from RSP to open its stack frame and +saves the value of RBP directly below the return PC. +A leaf function that does not require any stack space may omit the +saved RBP. + +The Go ABI's use of RBP as a frame pointer register is compatible with +amd64 platform conventions so that Go can inter-operate with platform +debuggers and profilers. + +#### Flags + +The direction flag (D) is always cleared (set to the “forward” +direction) at a call. +The arithmetic status flags are treated like scratch registers and not +preserved across calls. +All other bits in RFLAGS are system flags. + +At function calls and returns, the CPU is in x87 mode (not MMX +technology mode). + +*Rationale*: Go on amd64 does not use either the x87 registers or MMX +registers. Hence, we follow the SysV platform conventions in order to +simplify transitions to and from the C ABI. + +At calls, the MXCSR control bits are always set as follows: + +| Flag | Bit | Value | Meaning | +| --- | --- | --- | --- | +| FZ | 15 | 0 | Do not flush to zero | +| RC | 14/13 | 0 (RN) | Round to nearest | +| PM | 12 | 1 | Precision masked | +| UM | 11 | 1 | Underflow masked | +| OM | 10 | 1 | Overflow masked | +| ZM | 9 | 1 | Divide-by-zero masked | +| DM | 8 | 1 | Denormal operations masked | +| IM | 7 | 1 | Invalid operations masked | +| DAZ | 6 | 0 | Do not zero de-normals | + +The MXCSR status bits are callee-save. + +*Rationale*: Having a fixed MXCSR control configuration allows Go +functions to use SSE operations without modifying or saving the MXCSR. +Functions are allowed to modify it between calls (as long as they +restore it), but as of this writing Go code never does. +The above fixed configuration matches the process initialization +control bits specified by the ELF AMD64 ABI. + +The x87 floating-point control word is not used by Go on amd64. + +## Future directions + +### Spill path improvements + +The ABI currently reserves spill space for argument registers so the +compiler can statically generate an argument spill path before calling +into `runtime.morestack` to grow the stack. +This ensures there will be sufficient spill space even when the stack +is nearly exhausted and keeps stack growth and stack scanning +essentially unchanged from ABI0. + +However, this wastes stack space (the median wastage is 16 bytes per +call), resulting in larger stacks and increased cache footprint. +A better approach would be to reserve stack space only when spilling. +One way to ensure enough space is available to spill would be for +every function to ensure there is enough space for the function's own +frame *as well as* the spill space of all functions it calls. +For most functions, this would change the threshold for the prologue +stack growth check. +For `nosplit` functions, this would change the threshold used in the +linker's static stack size check. + +Allocating spill space in the callee rather than the caller may also +allow for faster reflection calls in the common case where a function +takes only register arguments, since it would allow reflection to make +these calls directly without allocating any frame. + +The statically-generated spill path also increases code size. +It is possible to instead have a generic spill path in the runtime, as +part of `morestack`. +However, this complicates reserving the spill space, since spilling +all possible register arguments would, in most cases, take +significantly more space than spilling only those used by a particular +function. +Some options are to spill to a temporary space and copy back only the +registers used by the function, or to grow the stack if necessary +before spilling to it (using a temporary space if necessary), or to +use a heap-allocated space if insufficient stack space is available. +These options all add enough complexity that we will have to make this +decision based on the actual code size growth caused by the static +spill paths. + +### Clobber sets + +As defined, the ABI does not use callee-save registers. +This significantly simplifies the garbage collector and the compiler's +register allocator, but at some performance cost. +A potentially better balance for Go code would be to use *clobber +sets*: for each function, the compiler records the set of registers it +clobbers (including those clobbered by functions it calls) and any +register not clobbered by function F can remain live across calls to +F. + +This is generally a good fit for Go because Go's package DAG allows +function metadata like the clobber set to flow up the call graph, even +across package boundaries. +Clobber sets would require relatively little change to the garbage +collector, unlike general callee-save registers. +One disadvantage of clobber sets over callee-save registers is that +they don't help with indirect function calls or interface method +calls, since static information isn't available in these cases. + +### Large aggregates + +Go encourages passing composite values by value, and this simplifies +reasoning about mutation and races. +However, this comes at a performance cost for large composite values. +It may be possible to instead transparently pass large composite +values by reference and delay copying until it is actually necessary. + +## Appendix: Register usage analysis + +In order to understand the impacts of the above design on register +usage, we +[analyzed](https://github.com/aclements/go-misc/tree/master/abi) the +impact of the above ABI on a large code base: cmd/kubelet from +[Kubernetes](https://github.com/kubernetes/kubernetes) at tag v1.18.8. + +The following table shows the impact of different numbers of available +integer and floating-point registers on argument assignment: + +``` +| | | | stack args | spills | stack total | +| ints | floats | % fit | p50 | p95 | p99 | p50 | p95 | p99 | p50 | p95 | p99 | +| 0 | 0 | 6.3% | 32 | 152 | 256 | 0 | 0 | 0 | 32 | 152 | 256 | +| 0 | 8 | 6.4% | 32 | 152 | 256 | 0 | 0 | 0 | 32 | 152 | 256 | +| 1 | 8 | 21.3% | 24 | 144 | 248 | 8 | 8 | 8 | 32 | 152 | 256 | +| 2 | 8 | 38.9% | 16 | 128 | 224 | 8 | 16 | 16 | 24 | 136 | 240 | +| 3 | 8 | 57.0% | 0 | 120 | 224 | 16 | 24 | 24 | 24 | 136 | 240 | +| 4 | 8 | 73.0% | 0 | 120 | 216 | 16 | 32 | 32 | 24 | 136 | 232 | +| 5 | 8 | 83.3% | 0 | 112 | 216 | 16 | 40 | 40 | 24 | 136 | 232 | +| 6 | 8 | 87.5% | 0 | 112 | 208 | 16 | 48 | 48 | 24 | 136 | 232 | +| 7 | 8 | 89.8% | 0 | 112 | 208 | 16 | 48 | 56 | 24 | 136 | 232 | +| 8 | 8 | 91.3% | 0 | 112 | 200 | 16 | 56 | 64 | 24 | 136 | 232 | +| 9 | 8 | 92.1% | 0 | 112 | 192 | 16 | 56 | 72 | 24 | 136 | 232 | +| 10 | 8 | 92.6% | 0 | 104 | 192 | 16 | 56 | 72 | 24 | 136 | 232 | +| 11 | 8 | 93.1% | 0 | 104 | 184 | 16 | 56 | 80 | 24 | 128 | 232 | +| 12 | 8 | 93.4% | 0 | 104 | 176 | 16 | 56 | 88 | 24 | 128 | 232 | +| 13 | 8 | 94.0% | 0 | 88 | 176 | 16 | 56 | 96 | 24 | 128 | 232 | +| 14 | 8 | 94.4% | 0 | 80 | 152 | 16 | 64 | 104 | 24 | 128 | 232 | +| 15 | 8 | 94.6% | 0 | 80 | 152 | 16 | 64 | 112 | 24 | 128 | 232 | +| 16 | 8 | 94.9% | 0 | 16 | 152 | 16 | 64 | 112 | 24 | 128 | 232 | +| ∞ | 8 | 99.8% | 0 | 0 | 0 | 24 | 112 | 216 | 24 | 120 | 216 | +``` + +The first two columns show the number of available integer and +floating-point registers. +The first row shows the results for 0 integer and 0 floating-point +registers, which is equivalent to ABI0. +We found that any reasonable number of floating-point registers has +the same effect, so we fixed it at 8 for all other rows. + +The “% fit” column gives the fraction of functions where all arguments +and results are register-assigned and no arguments are passed on the +stack. +The three “stack args” columns give the median, 95th and 99th +percentile number of bytes of stack arguments. +The “spills” columns likewise summarize the number of bytes in +on-stack spill space. +And “stack total” summarizes the sum of stack arguments and on-stack +spill slots. +Note that these are three different distributions; for example, +there’s no single function that takes 0 stack argument bytes, 16 spill +bytes, and 24 total stack bytes. + +From this, we can see that the fraction of functions that fit entirely +in registers grows very slowly once it reaches about 90%, though +curiously there is a small minority of functions that could benefit +from a huge number of registers. +Making 9 integer registers available on amd64 puts it in this realm. +We also see that the stack space required for most functions is fairly +small. +While the increasing space required for spills largely balances out +the decreasing space required for stack arguments as the number of +available registers increases, there is a general reduction in the +total stack space required with more available registers. +This does, however, suggest that eliminating spill slots in the future +would noticeably reduce stack requirements. diff --git a/src/cmd/compile/doc.go b/src/cmd/compile/doc.go index 46d47220866a4536e5c9b5c099e4e277b4e1cf84..b68ef274f379e0fa932b54307afafb0b8db1cfdf 100644 --- a/src/cmd/compile/doc.go +++ b/src/cmd/compile/doc.go @@ -83,7 +83,8 @@ Flags: Without this flag, the -o output is a combination of both linker and compiler input. -m - Print optimization decisions. + Print optimization decisions. Higher values or repetition + produce more detail. -memprofile file Write memory profile for the compilation to file. -memprofilerate rate diff --git a/src/cmd/compile/fmt_test.go b/src/cmd/compile/fmt_test.go deleted file mode 100644 index 6625ccf5e24a80ba7c2a7f7ad84ef65643d5f879..0000000000000000000000000000000000000000 --- a/src/cmd/compile/fmt_test.go +++ /dev/null @@ -1,599 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file implements TestFormats; a test that verifies -// format strings in the compiler (this directory and all -// subdirectories, recursively). -// -// TestFormats finds potential (Printf, etc.) format strings. -// If they are used in a call, the format verbs are verified -// based on the matching argument type against a precomputed -// map of valid formats (knownFormats). This map can be used to -// automatically rewrite format strings across all compiler -// files with the -r flag. -// -// The format map needs to be updated whenever a new (type, -// format) combination is found and the format verb is not -// 'v' or 'T' (as in "%v" or "%T"). To update the map auto- -// matically from the compiler source's use of format strings, -// use the -u flag. (Whether formats are valid for the values -// to be formatted must be verified manually, of course.) -// -// The -v flag prints out the names of all functions called -// with a format string, the names of files that were not -// processed, and any format rewrites made (with -r). -// -// Run as: go test -run Formats [-r][-u][-v] -// -// Known shortcomings: -// - indexed format strings ("%[2]s", etc.) are not supported -// (the test will fail) -// - format strings that are not simple string literals cannot -// be updated automatically -// (the test will fail with respective warnings) -// - format strings in _test packages outside the current -// package are not processed -// (the test will report those files) -// -package main_test - -import ( - "bytes" - "flag" - "fmt" - "go/ast" - "go/build" - "go/constant" - "go/format" - "go/importer" - "go/parser" - "go/token" - "go/types" - "internal/testenv" - "io" - "io/fs" - "io/ioutil" - "log" - "os" - "path/filepath" - "sort" - "strconv" - "strings" - "testing" - "unicode/utf8" -) - -var ( - rewrite = flag.Bool("r", false, "rewrite format strings") - update = flag.Bool("u", false, "update known formats") -) - -// The following variables collect information across all processed files. -var ( - fset = token.NewFileSet() - formatStrings = make(map[*ast.BasicLit]bool) // set of all potential format strings found - foundFormats = make(map[string]bool) // set of all formats found - callSites = make(map[*ast.CallExpr]*callSite) // map of all calls -) - -// A File is a corresponding (filename, ast) pair. -type File struct { - name string - ast *ast.File -} - -func TestFormats(t *testing.T) { - if testing.Short() && testenv.Builder() == "" { - t.Skip("Skipping in short mode") - } - testenv.MustHaveGoBuild(t) // more restrictive than necessary, but that's ok - - // process all directories - filepath.WalkDir(".", func(path string, info fs.DirEntry, err error) error { - if info.IsDir() { - if info.Name() == "testdata" { - return filepath.SkipDir - } - - importPath := filepath.Join("cmd/compile", path) - if ignoredPackages[filepath.ToSlash(importPath)] { - return filepath.SkipDir - } - - pkg, err := build.Import(importPath, path, 0) - if err != nil { - if _, ok := err.(*build.NoGoError); ok { - return nil // nothing to do here - } - t.Fatal(err) - } - collectPkgFormats(t, pkg) - } - return nil - }) - - // test and rewrite formats - updatedFiles := make(map[string]File) // files that were rewritten - for _, p := range callSites { - // test current format literal and determine updated one - out := formatReplace(p.str, func(index int, in string) string { - if in == "*" { - return in // cannot rewrite '*' (as in "%*d") - } - // in != '*' - typ := p.types[index] - format := typ + " " + in // e.g., "*Node %n" - - // check if format is known - out, known := knownFormats[format] - - // record format if not yet found - _, found := foundFormats[format] - if !found { - foundFormats[format] = true - } - - // report an error if the format is unknown and this is the first - // time we see it; ignore "%v" and "%T" which are always valid - if !known && !found && in != "%v" && in != "%T" { - t.Errorf("%s: unknown format %q for %s argument", posString(p.arg), in, typ) - } - - if out == "" { - out = in - } - return out - }) - - // replace existing format literal if it changed - if out != p.str { - // we cannot replace the argument if it's not a string literal for now - // (e.g., it may be "foo" + "bar") - lit, ok := p.arg.(*ast.BasicLit) - if !ok { - delete(callSites, p.call) // treat as if we hadn't found this site - continue - } - - if testing.Verbose() { - fmt.Printf("%s:\n\t- %q\n\t+ %q\n", posString(p.arg), p.str, out) - } - - // find argument index of format argument - index := -1 - for i, arg := range p.call.Args { - if p.arg == arg { - index = i - break - } - } - if index < 0 { - // we may have processed the same call site twice, - // but that shouldn't happen - panic("internal error: matching argument not found") - } - - // replace literal - new := *lit // make a copy - new.Value = strconv.Quote(out) // this may introduce "-quotes where there were `-quotes - p.call.Args[index] = &new - updatedFiles[p.file.name] = p.file - } - } - - // write dirty files back - var filesUpdated bool - if len(updatedFiles) > 0 && *rewrite { - for _, file := range updatedFiles { - var buf bytes.Buffer - if err := format.Node(&buf, fset, file.ast); err != nil { - t.Errorf("WARNING: gofmt %s failed: %v", file.name, err) - continue - } - if err := ioutil.WriteFile(file.name, buf.Bytes(), 0x666); err != nil { - t.Errorf("WARNING: writing %s failed: %v", file.name, err) - continue - } - fmt.Printf("updated %s\n", file.name) - filesUpdated = true - } - } - - // report the names of all functions called with a format string - if len(callSites) > 0 && testing.Verbose() { - set := make(map[string]bool) - for _, p := range callSites { - set[nodeString(p.call.Fun)] = true - } - var list []string - for s := range set { - list = append(list, s) - } - fmt.Println("\nFunctions called with a format string") - writeList(os.Stdout, list) - } - - // update formats - if len(foundFormats) > 0 && *update { - var list []string - for s := range foundFormats { - list = append(list, fmt.Sprintf("%q: \"\",", s)) - } - var buf bytes.Buffer - buf.WriteString(knownFormatsHeader) - writeList(&buf, list) - buf.WriteString("}\n") - out, err := format.Source(buf.Bytes()) - const outfile = "fmtmap_test.go" - if err != nil { - t.Errorf("WARNING: gofmt %s failed: %v", outfile, err) - out = buf.Bytes() // continue with unformatted source - } - if err = ioutil.WriteFile(outfile, out, 0644); err != nil { - t.Errorf("WARNING: updating format map failed: %v", err) - } - } - - // check that knownFormats is up to date - if !*rewrite && !*update { - var mismatch bool - for s := range foundFormats { - if _, ok := knownFormats[s]; !ok { - mismatch = true - break - } - } - if !mismatch { - for s := range knownFormats { - if _, ok := foundFormats[s]; !ok { - mismatch = true - break - } - } - } - if mismatch { - t.Errorf("format map is out of date; run 'go test -u' to update and manually verify correctness of change'") - } - } - - // all format strings of calls must be in the formatStrings set (self-verification) - for _, p := range callSites { - if lit, ok := p.arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { - if formatStrings[lit] { - // ok - delete(formatStrings, lit) - } else { - // this should never happen - panic(fmt.Sprintf("internal error: format string not found (%s)", posString(lit))) - } - } - } - - // if we have any strings left, we may need to update them manually - if len(formatStrings) > 0 && filesUpdated { - var list []string - for lit := range formatStrings { - list = append(list, fmt.Sprintf("%s: %s", posString(lit), nodeString(lit))) - } - fmt.Println("\nWARNING: Potentially missed format strings") - writeList(os.Stdout, list) - t.Fail() - } - - fmt.Println() -} - -// A callSite describes a function call that appears to contain -// a format string. -type callSite struct { - file File - call *ast.CallExpr // call containing the format string - arg ast.Expr // format argument (string literal or constant) - str string // unquoted format string - types []string // argument types -} - -func collectPkgFormats(t *testing.T, pkg *build.Package) { - // collect all files - var filenames []string - filenames = append(filenames, pkg.GoFiles...) - filenames = append(filenames, pkg.CgoFiles...) - filenames = append(filenames, pkg.TestGoFiles...) - - // TODO(gri) verify _test files outside package - for _, name := range pkg.XTestGoFiles { - // don't process this test itself - if name != "fmt_test.go" && testing.Verbose() { - fmt.Printf("WARNING: %s not processed\n", filepath.Join(pkg.Dir, name)) - } - } - - // make filenames relative to . - for i, name := range filenames { - filenames[i] = filepath.Join(pkg.Dir, name) - } - - // parse all files - files := make([]*ast.File, len(filenames)) - for i, filename := range filenames { - f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - files[i] = f - } - - // typecheck package - conf := types.Config{Importer: importer.Default()} - etypes := make(map[ast.Expr]types.TypeAndValue) - if _, err := conf.Check(pkg.ImportPath, fset, files, &types.Info{Types: etypes}); err != nil { - t.Fatal(err) - } - - // collect all potential format strings (for extra verification later) - for _, file := range files { - ast.Inspect(file, func(n ast.Node) bool { - if s, ok := stringLit(n); ok && isFormat(s) { - formatStrings[n.(*ast.BasicLit)] = true - } - return true - }) - } - - // collect all formats/arguments of calls with format strings - for index, file := range files { - ast.Inspect(file, func(n ast.Node) bool { - if call, ok := n.(*ast.CallExpr); ok { - if ignoredFunctions[nodeString(call.Fun)] { - return true - } - // look for an arguments that might be a format string - for i, arg := range call.Args { - if s, ok := stringVal(etypes[arg]); ok && isFormat(s) { - // make sure we have enough arguments - n := numFormatArgs(s) - if i+1+n > len(call.Args) { - t.Errorf("%s: not enough format args (ignore %s?)", posString(call), nodeString(call.Fun)) - break // ignore this call - } - // assume last n arguments are to be formatted; - // determine their types - argTypes := make([]string, n) - for i, arg := range call.Args[len(call.Args)-n:] { - if tv, ok := etypes[arg]; ok { - argTypes[i] = typeString(tv.Type) - } - } - // collect call site - if callSites[call] != nil { - panic("internal error: file processed twice?") - } - callSites[call] = &callSite{ - file: File{filenames[index], file}, - call: call, - arg: arg, - str: s, - types: argTypes, - } - break // at most one format per argument list - } - } - } - return true - }) - } -} - -// writeList writes list in sorted order to w. -func writeList(w io.Writer, list []string) { - sort.Strings(list) - for _, s := range list { - fmt.Fprintln(w, "\t", s) - } -} - -// posString returns a string representation of n's position -// in the form filename:line:col: . -func posString(n ast.Node) string { - if n == nil { - return "" - } - return fset.Position(n.Pos()).String() -} - -// nodeString returns a string representation of n. -func nodeString(n ast.Node) string { - var buf bytes.Buffer - if err := format.Node(&buf, fset, n); err != nil { - log.Fatal(err) // should always succeed - } - return buf.String() -} - -// typeString returns a string representation of n. -func typeString(typ types.Type) string { - return filepath.ToSlash(typ.String()) -} - -// stringLit returns the unquoted string value and true if -// n represents a string literal; otherwise it returns "" -// and false. -func stringLit(n ast.Node) (string, bool) { - if lit, ok := n.(*ast.BasicLit); ok && lit.Kind == token.STRING { - s, err := strconv.Unquote(lit.Value) - if err != nil { - log.Fatal(err) // should not happen with correct ASTs - } - return s, true - } - return "", false -} - -// stringVal returns the (unquoted) string value and true if -// tv is a string constant; otherwise it returns "" and false. -func stringVal(tv types.TypeAndValue) (string, bool) { - if tv.IsValue() && tv.Value != nil && tv.Value.Kind() == constant.String { - return constant.StringVal(tv.Value), true - } - return "", false -} - -// formatIter iterates through the string s in increasing -// index order and calls f for each format specifier '%..v'. -// The arguments for f describe the specifier's index range. -// If a format specifier contains a "*", f is called with -// the index range for "*" alone, before being called for -// the entire specifier. The result of f is the index of -// the rune at which iteration continues. -func formatIter(s string, f func(i, j int) int) { - i := 0 // index after current rune - var r rune // current rune - - next := func() { - r1, w := utf8.DecodeRuneInString(s[i:]) - if w == 0 { - r1 = -1 // signal end-of-string - } - r = r1 - i += w - } - - flags := func() { - for r == ' ' || r == '#' || r == '+' || r == '-' || r == '0' { - next() - } - } - - index := func() { - if r == '[' { - log.Fatalf("cannot handle indexed arguments: %s", s) - } - } - - digits := func() { - index() - if r == '*' { - i = f(i-1, i) - next() - return - } - for '0' <= r && r <= '9' { - next() - } - } - - for next(); r >= 0; next() { - if r == '%' { - i0 := i - next() - flags() - digits() - if r == '.' { - next() - digits() - } - index() - // accept any letter (a-z, A-Z) as format verb; - // ignore anything else - if 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' { - i = f(i0-1, i) - } - } - } -} - -// isFormat reports whether s contains format specifiers. -func isFormat(s string) (yes bool) { - formatIter(s, func(i, j int) int { - yes = true - return len(s) // stop iteration - }) - return -} - -// oneFormat reports whether s is exactly one format specifier. -func oneFormat(s string) (yes bool) { - formatIter(s, func(i, j int) int { - yes = i == 0 && j == len(s) - return j - }) - return -} - -// numFormatArgs returns the number of format specifiers in s. -func numFormatArgs(s string) int { - count := 0 - formatIter(s, func(i, j int) int { - count++ - return j - }) - return count -} - -// formatReplace replaces the i'th format specifier s in the incoming -// string in with the result of f(i, s) and returns the new string. -func formatReplace(in string, f func(i int, s string) string) string { - var buf []byte - i0 := 0 - index := 0 - formatIter(in, func(i, j int) int { - if sub := in[i:j]; sub != "*" { // ignore calls for "*" width/length specifiers - buf = append(buf, in[i0:i]...) - buf = append(buf, f(index, sub)...) - i0 = j - } - index++ - return j - }) - return string(append(buf, in[i0:]...)) -} - -// ignoredPackages is the set of packages which can -// be ignored. -var ignoredPackages = map[string]bool{} - -// ignoredFunctions is the set of functions which may have -// format-like arguments but which don't do any formatting and -// thus may be ignored. -var ignoredFunctions = map[string]bool{} - -func init() { - // verify that knownFormats entries are correctly formatted - for key, val := range knownFormats { - // key must be "typename format", and format starts with a '%' - // (formats containing '*' alone are not collected in this map) - i := strings.Index(key, "%") - if i < 0 || !oneFormat(key[i:]) { - log.Fatalf("incorrect knownFormats key: %q", key) - } - // val must be "format" or "" - if val != "" && !oneFormat(val) { - log.Fatalf("incorrect knownFormats value: %q (key = %q)", val, key) - } - } -} - -const knownFormatsHeader = `// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file implements the knownFormats map which records the valid -// formats for a given type. The valid formats must correspond to -// supported compiler formats implemented in fmt.go, or whatever -// other format verbs are implemented for the given type. The map may -// also be used to change the use of a format verb across all compiler -// sources automatically (for instance, if the implementation of fmt.go -// changes), by using the -r option together with the new formats in the -// map. To generate this file automatically from the existing source, -// run: go test -run Formats -u. -// -// See the package comment in fmt_test.go for additional information. - -package main_test - -// knownFormats entries are of the form "typename format" -> "newformat". -// An absent entry means that the format is not recognized as valid. -// An empty new format means that the format should remain unchanged. -var knownFormats = map[string]string{ -` diff --git a/src/cmd/compile/fmtmap_test.go b/src/cmd/compile/fmtmap_test.go deleted file mode 100644 index 0811df7f7b80074ede902181944f703c5dc89968..0000000000000000000000000000000000000000 --- a/src/cmd/compile/fmtmap_test.go +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file implements the knownFormats map which records the valid -// formats for a given type. The valid formats must correspond to -// supported compiler formats implemented in fmt.go, or whatever -// other format verbs are implemented for the given type. The map may -// also be used to change the use of a format verb across all compiler -// sources automatically (for instance, if the implementation of fmt.go -// changes), by using the -r option together with the new formats in the -// map. To generate this file automatically from the existing source, -// run: go test -run Formats -u. -// -// See the package comment in fmt_test.go for additional information. - -package main_test - -// knownFormats entries are of the form "typename format" -> "newformat". -// An absent entry means that the format is not recognized as valid. -// An empty new format means that the format should remain unchanged. -var knownFormats = map[string]string{ - "*bytes.Buffer %s": "", - "*cmd/compile/internal/gc.EscLocation %v": "", - "*cmd/compile/internal/gc.Mpflt %v": "", - "*cmd/compile/internal/gc.Mpint %v": "", - "*cmd/compile/internal/gc.Node %#v": "", - "*cmd/compile/internal/gc.Node %+S": "", - "*cmd/compile/internal/gc.Node %+v": "", - "*cmd/compile/internal/gc.Node %L": "", - "*cmd/compile/internal/gc.Node %S": "", - "*cmd/compile/internal/gc.Node %j": "", - "*cmd/compile/internal/gc.Node %p": "", - "*cmd/compile/internal/gc.Node %v": "", - "*cmd/compile/internal/ssa.Block %s": "", - "*cmd/compile/internal/ssa.Block %v": "", - "*cmd/compile/internal/ssa.Func %s": "", - "*cmd/compile/internal/ssa.Func %v": "", - "*cmd/compile/internal/ssa.Register %s": "", - "*cmd/compile/internal/ssa.Register %v": "", - "*cmd/compile/internal/ssa.SparseTreeNode %v": "", - "*cmd/compile/internal/ssa.Value %s": "", - "*cmd/compile/internal/ssa.Value %v": "", - "*cmd/compile/internal/ssa.sparseTreeMapEntry %v": "", - "*cmd/compile/internal/types.Field %p": "", - "*cmd/compile/internal/types.Field %v": "", - "*cmd/compile/internal/types.Sym %0S": "", - "*cmd/compile/internal/types.Sym %S": "", - "*cmd/compile/internal/types.Sym %p": "", - "*cmd/compile/internal/types.Sym %v": "", - "*cmd/compile/internal/types.Type %#L": "", - "*cmd/compile/internal/types.Type %#v": "", - "*cmd/compile/internal/types.Type %+v": "", - "*cmd/compile/internal/types.Type %-S": "", - "*cmd/compile/internal/types.Type %0S": "", - "*cmd/compile/internal/types.Type %L": "", - "*cmd/compile/internal/types.Type %S": "", - "*cmd/compile/internal/types.Type %p": "", - "*cmd/compile/internal/types.Type %s": "", - "*cmd/compile/internal/types.Type %v": "", - "*cmd/internal/obj.Addr %v": "", - "*cmd/internal/obj.LSym %v": "", - "*math/big.Float %f": "", - "*math/big.Int %#x": "", - "*math/big.Int %s": "", - "*math/big.Int %v": "", - "[16]byte %x": "", - "[]*cmd/compile/internal/ssa.Block %v": "", - "[]*cmd/compile/internal/ssa.Value %v": "", - "[][]string %q": "", - "[]byte %s": "", - "[]byte %x": "", - "[]cmd/compile/internal/ssa.Edge %v": "", - "[]cmd/compile/internal/ssa.ID %v": "", - "[]cmd/compile/internal/ssa.posetNode %v": "", - "[]cmd/compile/internal/ssa.posetUndo %v": "", - "[]cmd/compile/internal/syntax.token %s": "", - "[]string %v": "", - "[]uint32 %v": "", - "bool %v": "", - "byte %08b": "", - "byte %c": "", - "byte %q": "", - "byte %v": "", - "cmd/compile/internal/arm.shift %d": "", - "cmd/compile/internal/gc.Class %d": "", - "cmd/compile/internal/gc.Class %s": "", - "cmd/compile/internal/gc.Class %v": "", - "cmd/compile/internal/gc.Ctype %d": "", - "cmd/compile/internal/gc.Ctype %v": "", - "cmd/compile/internal/gc.Nodes %#v": "", - "cmd/compile/internal/gc.Nodes %+v": "", - "cmd/compile/internal/gc.Nodes %.v": "", - "cmd/compile/internal/gc.Nodes %v": "", - "cmd/compile/internal/gc.Op %#v": "", - "cmd/compile/internal/gc.Op %v": "", - "cmd/compile/internal/gc.Val %#v": "", - "cmd/compile/internal/gc.Val %T": "", - "cmd/compile/internal/gc.Val %v": "", - "cmd/compile/internal/gc.fmtMode %d": "", - "cmd/compile/internal/gc.initKind %d": "", - "cmd/compile/internal/gc.itag %v": "", - "cmd/compile/internal/ssa.BranchPrediction %d": "", - "cmd/compile/internal/ssa.Edge %v": "", - "cmd/compile/internal/ssa.GCNode %v": "", - "cmd/compile/internal/ssa.ID %d": "", - "cmd/compile/internal/ssa.ID %v": "", - "cmd/compile/internal/ssa.LocalSlot %s": "", - "cmd/compile/internal/ssa.LocalSlot %v": "", - "cmd/compile/internal/ssa.Location %s": "", - "cmd/compile/internal/ssa.Op %s": "", - "cmd/compile/internal/ssa.Op %v": "", - "cmd/compile/internal/ssa.Sym %v": "", - "cmd/compile/internal/ssa.ValAndOff %s": "", - "cmd/compile/internal/ssa.domain %v": "", - "cmd/compile/internal/ssa.flagConstant %s": "", - "cmd/compile/internal/ssa.posetNode %v": "", - "cmd/compile/internal/ssa.posetTestOp %v": "", - "cmd/compile/internal/ssa.rbrank %d": "", - "cmd/compile/internal/ssa.regMask %d": "", - "cmd/compile/internal/ssa.register %d": "", - "cmd/compile/internal/ssa.relation %s": "", - "cmd/compile/internal/syntax.Error %q": "", - "cmd/compile/internal/syntax.Expr %#v": "", - "cmd/compile/internal/syntax.LitKind %d": "", - "cmd/compile/internal/syntax.Node %T": "", - "cmd/compile/internal/syntax.Operator %s": "", - "cmd/compile/internal/syntax.Pos %s": "", - "cmd/compile/internal/syntax.Pos %v": "", - "cmd/compile/internal/syntax.position %s": "", - "cmd/compile/internal/syntax.token %q": "", - "cmd/compile/internal/syntax.token %s": "", - "cmd/compile/internal/types.EType %d": "", - "cmd/compile/internal/types.EType %s": "", - "cmd/compile/internal/types.EType %v": "", - "cmd/internal/obj.ABI %v": "", - "error %v": "", - "float64 %.2f": "", - "float64 %.3f": "", - "float64 %.6g": "", - "float64 %g": "", - "int %#x": "", - "int %-12d": "", - "int %-6d": "", - "int %-8o": "", - "int %02d": "", - "int %6d": "", - "int %c": "", - "int %d": "", - "int %v": "", - "int %x": "", - "int16 %d": "", - "int16 %x": "", - "int32 %#x": "", - "int32 %d": "", - "int32 %v": "", - "int32 %x": "", - "int64 %#x": "", - "int64 %+d": "", - "int64 %-10d": "", - "int64 %.5d": "", - "int64 %d": "", - "int64 %v": "", - "int64 %x": "", - "int8 %d": "", - "int8 %v": "", - "int8 %x": "", - "interface{} %#v": "", - "interface{} %T": "", - "interface{} %p": "", - "interface{} %q": "", - "interface{} %s": "", - "interface{} %v": "", - "map[*cmd/compile/internal/gc.Node]*cmd/compile/internal/ssa.Value %v": "", - "map[*cmd/compile/internal/gc.Node][]*cmd/compile/internal/gc.Node %v": "", - "map[cmd/compile/internal/ssa.ID]uint32 %v": "", - "map[int64]uint32 %v": "", - "math/big.Accuracy %s": "", - "reflect.Type %s": "", - "rune %#U": "", - "rune %c": "", - "rune %q": "", - "string %-*s": "", - "string %-16s": "", - "string %-6s": "", - "string %q": "", - "string %s": "", - "string %v": "", - "time.Duration %d": "", - "time.Duration %v": "", - "uint %04x": "", - "uint %5d": "", - "uint %d": "", - "uint %x": "", - "uint16 %d": "", - "uint16 %x": "", - "uint32 %#U": "", - "uint32 %#x": "", - "uint32 %d": "", - "uint32 %v": "", - "uint32 %x": "", - "uint64 %08x": "", - "uint64 %b": "", - "uint64 %d": "", - "uint64 %x": "", - "uint8 %#x": "", - "uint8 %d": "", - "uint8 %v": "", - "uint8 %x": "", - "uintptr %d": "", -} diff --git a/src/cmd/compile/internal/abi/abiutils.go b/src/cmd/compile/internal/abi/abiutils.go new file mode 100644 index 0000000000000000000000000000000000000000..d657ddc867bad00f44a00a3e9af54b7507b99683 --- /dev/null +++ b/src/cmd/compile/internal/abi/abiutils.go @@ -0,0 +1,825 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package abi + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/src" + "fmt" + "sync" +) + +//...................................................................... +// +// Public/exported bits of the ABI utilities. +// + +// ABIParamResultInfo stores the results of processing a given +// function type to compute stack layout and register assignments. For +// each input and output parameter we capture whether the param was +// register-assigned (and to which register(s)) or the stack offset +// for the param if is not going to be passed in registers according +// to the rules in the Go internal ABI specification (1.17). +type ABIParamResultInfo struct { + inparams []ABIParamAssignment // Includes receiver for method calls. Does NOT include hidden closure pointer. + outparams []ABIParamAssignment + offsetToSpillArea int64 + spillAreaSize int64 + inRegistersUsed int + outRegistersUsed int + config *ABIConfig // to enable String() method +} + +func (a *ABIParamResultInfo) Config() *ABIConfig { + return a.config +} + +func (a *ABIParamResultInfo) InParams() []ABIParamAssignment { + return a.inparams +} + +func (a *ABIParamResultInfo) OutParams() []ABIParamAssignment { + return a.outparams +} + +func (a *ABIParamResultInfo) InRegistersUsed() int { + return a.inRegistersUsed +} + +func (a *ABIParamResultInfo) OutRegistersUsed() int { + return a.outRegistersUsed +} + +func (a *ABIParamResultInfo) InParam(i int) *ABIParamAssignment { + return &a.inparams[i] +} + +func (a *ABIParamResultInfo) OutParam(i int) *ABIParamAssignment { + return &a.outparams[i] +} + +func (a *ABIParamResultInfo) SpillAreaOffset() int64 { + return a.offsetToSpillArea +} + +func (a *ABIParamResultInfo) SpillAreaSize() int64 { + return a.spillAreaSize +} + +// ArgWidth returns the amount of stack needed for all the inputs +// and outputs of a function or method, including ABI-defined parameter +// slots and ABI-defined spill slots for register-resident parameters. +// The name is inherited from (*Type).ArgWidth(), which it replaces. +func (a *ABIParamResultInfo) ArgWidth() int64 { + return a.spillAreaSize + a.offsetToSpillArea - a.config.LocalsOffset() +} + +// RegIndex stores the index into the set of machine registers used by +// the ABI on a specific architecture for parameter passing. RegIndex +// values 0 through N-1 (where N is the number of integer registers +// used for param passing according to the ABI rules) describe integer +// registers; values N through M (where M is the number of floating +// point registers used). Thus if the ABI says there are 5 integer +// registers and 7 floating point registers, then RegIndex value of 4 +// indicates the 5th integer register, and a RegIndex value of 11 +// indicates the 7th floating point register. +type RegIndex uint8 + +// ABIParamAssignment holds information about how a specific param or +// result will be passed: in registers (in which case 'Registers' is +// populated) or on the stack (in which case 'Offset' is set to a +// non-negative stack offset. The values in 'Registers' are indices +// (as described above), not architected registers. +type ABIParamAssignment struct { + Type *types.Type + Name types.Object // should always be *ir.Name, used to match with a particular ssa.OpArg. + Registers []RegIndex + offset int32 +} + +// Offset returns the stack offset for addressing the parameter that "a" describes. +// This will panic if "a" describes a register-allocated parameter. +func (a *ABIParamAssignment) Offset() int32 { + if len(a.Registers) > 0 { + base.Fatalf("register allocated parameters have no offset") + } + return a.offset +} + +// RegisterTypes returns a slice of the types of the registers +// corresponding to a slice of parameters. The returned slice +// has capacity for one more, likely a memory type. +func RegisterTypes(apa []ABIParamAssignment) []*types.Type { + rcount := 0 + for _, pa := range apa { + rcount += len(pa.Registers) + } + if rcount == 0 { + // Note that this catches top-level struct{} and [0]Foo, which are stack allocated. + return make([]*types.Type, 0, 1) + } + rts := make([]*types.Type, 0, rcount+1) + for _, pa := range apa { + if len(pa.Registers) == 0 { + continue + } + rts = appendParamTypes(rts, pa.Type) + } + return rts +} + +func (pa *ABIParamAssignment) RegisterTypesAndOffsets() ([]*types.Type, []int64) { + l := len(pa.Registers) + if l == 0 { + return nil, nil + } + typs := make([]*types.Type, 0, l) + offs := make([]int64, 0, l) + offs, _ = appendParamOffsets(offs, 0, pa.Type) + return appendParamTypes(typs, pa.Type), offs +} + +func appendParamTypes(rts []*types.Type, t *types.Type) []*types.Type { + w := t.Width + if w == 0 { + return rts + } + if t.IsScalar() || t.IsPtrShaped() { + if t.IsComplex() { + c := types.FloatForComplex(t) + return append(rts, c, c) + } else { + if int(t.Size()) <= types.RegSize { + return append(rts, t) + } + // assume 64bit int on 32-bit machine + // TODO endianness? Should high-order (sign bits) word come first? + if t.IsSigned() { + rts = append(rts, types.Types[types.TINT32]) + } else { + rts = append(rts, types.Types[types.TUINT32]) + } + return append(rts, types.Types[types.TUINT32]) + } + } else { + typ := t.Kind() + switch typ { + case types.TARRAY: + for i := int64(0); i < t.NumElem(); i++ { // 0 gets no registers, plus future-proofing. + rts = appendParamTypes(rts, t.Elem()) + } + case types.TSTRUCT: + for _, f := range t.FieldSlice() { + if f.Type.Size() > 0 { // embedded zero-width types receive no registers + rts = appendParamTypes(rts, f.Type) + } + } + case types.TSLICE: + return appendParamTypes(rts, synthSlice) + case types.TSTRING: + return appendParamTypes(rts, synthString) + case types.TINTER: + return appendParamTypes(rts, synthIface) + } + } + return rts +} + +// appendParamOffsets appends the offset(s) of type t, starting from "at", +// to input offsets, and returns the longer slice and the next unused offset. +func appendParamOffsets(offsets []int64, at int64, t *types.Type) ([]int64, int64) { + at = align(at, t) + w := t.Width + if w == 0 { + return offsets, at + } + if t.IsScalar() || t.IsPtrShaped() { + if t.IsComplex() || int(t.Width) > types.RegSize { // complex and *int64 on 32-bit + s := w / 2 + return append(offsets, at, at+s), at + w + } else { + return append(offsets, at), at + w + } + } else { + typ := t.Kind() + switch typ { + case types.TARRAY: + for i := int64(0); i < t.NumElem(); i++ { + offsets, at = appendParamOffsets(offsets, at, t.Elem()) + } + case types.TSTRUCT: + for i, f := range t.FieldSlice() { + offsets, at = appendParamOffsets(offsets, at, f.Type) + if f.Type.Width == 0 && i == t.NumFields()-1 { + at++ // last field has zero width + } + } + at = align(at, t) // type size is rounded up to its alignment + case types.TSLICE: + return appendParamOffsets(offsets, at, synthSlice) + case types.TSTRING: + return appendParamOffsets(offsets, at, synthString) + case types.TINTER: + return appendParamOffsets(offsets, at, synthIface) + } + } + return offsets, at +} + +// FrameOffset returns the frame-pointer-relative location that a function +// would spill its input or output parameter to, if such a spill slot exists. +// If there is none defined (e.g., register-allocated outputs) it panics. +// For register-allocated inputs that is their spill offset reserved for morestack; +// for stack-allocated inputs and outputs, that is their location on the stack. +// (In a future version of the ABI, register-resident inputs may lose their defined +// spill area to help reduce stack sizes.) +func (a *ABIParamAssignment) FrameOffset(i *ABIParamResultInfo) int64 { + if a.offset == -1 { + base.Fatalf("function parameter has no ABI-defined frame-pointer offset") + } + if len(a.Registers) == 0 { // passed on stack + return int64(a.offset) - i.config.LocalsOffset() + } + // spill area for registers + return int64(a.offset) + i.SpillAreaOffset() - i.config.LocalsOffset() +} + +// RegAmounts holds a specified number of integer/float registers. +type RegAmounts struct { + intRegs int + floatRegs int +} + +// ABIConfig captures the number of registers made available +// by the ABI rules for parameter passing and result returning. +type ABIConfig struct { + // Do we need anything more than this? + offsetForLocals int64 // e.g., obj.(*Link).FixedFrameSize() -- extra linkage information on some architectures. + regAmounts RegAmounts + regsForTypeCache map[*types.Type]int +} + +// NewABIConfig returns a new ABI configuration for an architecture with +// iRegsCount integer/pointer registers and fRegsCount floating point registers. +func NewABIConfig(iRegsCount, fRegsCount int, offsetForLocals int64) *ABIConfig { + return &ABIConfig{offsetForLocals: offsetForLocals, regAmounts: RegAmounts{iRegsCount, fRegsCount}, regsForTypeCache: make(map[*types.Type]int)} +} + +// Copy returns a copy of an ABIConfig for use in a function's compilation so that access to the cache does not need to be protected with a mutex. +func (a *ABIConfig) Copy() *ABIConfig { + b := *a + b.regsForTypeCache = make(map[*types.Type]int) + return &b +} + +// LocalsOffset returns the architecture-dependent offset from SP for args and results. +// In theory this is only used for debugging; it ought to already be incorporated into +// results from the ABI-related methods +func (a *ABIConfig) LocalsOffset() int64 { + return a.offsetForLocals +} + +// FloatIndexFor translates r into an index in the floating point parameter +// registers. If the result is negative, the input index was actually for the +// integer parameter registers. +func (a *ABIConfig) FloatIndexFor(r RegIndex) int64 { + return int64(r) - int64(a.regAmounts.intRegs) +} + +// NumParamRegs returns the number of parameter registers used for a given type, +// without regard for the number available. +func (a *ABIConfig) NumParamRegs(t *types.Type) int { + var n int + if n, ok := a.regsForTypeCache[t]; ok { + return n + } + + if t.IsScalar() || t.IsPtrShaped() { + if t.IsComplex() { + n = 2 + } else { + n = (int(t.Size()) + types.RegSize - 1) / types.RegSize + } + } else { + typ := t.Kind() + switch typ { + case types.TARRAY: + n = a.NumParamRegs(t.Elem()) * int(t.NumElem()) + case types.TSTRUCT: + for _, f := range t.FieldSlice() { + n += a.NumParamRegs(f.Type) + } + case types.TSLICE: + n = a.NumParamRegs(synthSlice) + case types.TSTRING: + n = a.NumParamRegs(synthString) + case types.TINTER: + n = a.NumParamRegs(synthIface) + } + } + a.regsForTypeCache[t] = n + + return n +} + +// preAllocateParams gets the slice sizes right for inputs and outputs. +func (a *ABIParamResultInfo) preAllocateParams(hasRcvr bool, nIns, nOuts int) { + if hasRcvr { + nIns++ + } + a.inparams = make([]ABIParamAssignment, 0, nIns) + a.outparams = make([]ABIParamAssignment, 0, nOuts) +} + +// ABIAnalyzeTypes takes an optional receiver type, arrays of ins and outs, and returns an ABIParamResultInfo, +// based on the given configuration. This is the same result computed by config.ABIAnalyze applied to the +// corresponding method/function type, except that all the embedded parameter names are nil. +// This is intended for use by ssagen/ssa.go:(*state).rtcall, for runtime functions that lack a parsed function type. +func (config *ABIConfig) ABIAnalyzeTypes(rcvr *types.Type, ins, outs []*types.Type) *ABIParamResultInfo { + setup() + s := assignState{ + stackOffset: config.offsetForLocals, + rTotal: config.regAmounts, + } + result := &ABIParamResultInfo{config: config} + result.preAllocateParams(rcvr != nil, len(ins), len(outs)) + + // Receiver + if rcvr != nil { + result.inparams = append(result.inparams, + s.assignParamOrReturn(rcvr, nil, false)) + } + + // Inputs + for _, t := range ins { + result.inparams = append(result.inparams, + s.assignParamOrReturn(t, nil, false)) + } + s.stackOffset = types.Rnd(s.stackOffset, int64(types.RegSize)) + result.inRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs + + // Outputs + s.rUsed = RegAmounts{} + for _, t := range outs { + result.outparams = append(result.outparams, s.assignParamOrReturn(t, nil, true)) + } + // The spill area is at a register-aligned offset and its size is rounded up to a register alignment. + // TODO in theory could align offset only to minimum required by spilled data types. + result.offsetToSpillArea = alignTo(s.stackOffset, types.RegSize) + result.spillAreaSize = alignTo(s.spillOffset, types.RegSize) + result.outRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs + + return result +} + +// ABIAnalyzeFuncType takes a function type 'ft' and an ABI rules description +// 'config' and analyzes the function to determine how its parameters +// and results will be passed (in registers or on the stack), returning +// an ABIParamResultInfo object that holds the results of the analysis. +func (config *ABIConfig) ABIAnalyzeFuncType(ft *types.Func) *ABIParamResultInfo { + setup() + s := assignState{ + stackOffset: config.offsetForLocals, + rTotal: config.regAmounts, + } + result := &ABIParamResultInfo{config: config} + result.preAllocateParams(ft.Receiver != nil, ft.Params.NumFields(), ft.Results.NumFields()) + + // Receiver + // TODO(register args) ? seems like "struct" and "fields" is not right anymore for describing function parameters + if ft.Receiver != nil && ft.Receiver.NumFields() != 0 { + r := ft.Receiver.FieldSlice()[0] + result.inparams = append(result.inparams, + s.assignParamOrReturn(r.Type, r.Nname, false)) + } + + // Inputs + ifsl := ft.Params.FieldSlice() + for _, f := range ifsl { + result.inparams = append(result.inparams, + s.assignParamOrReturn(f.Type, f.Nname, false)) + } + s.stackOffset = types.Rnd(s.stackOffset, int64(types.RegSize)) + result.inRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs + + // Outputs + s.rUsed = RegAmounts{} + ofsl := ft.Results.FieldSlice() + for _, f := range ofsl { + result.outparams = append(result.outparams, s.assignParamOrReturn(f.Type, f.Nname, true)) + } + // The spill area is at a register-aligned offset and its size is rounded up to a register alignment. + // TODO in theory could align offset only to minimum required by spilled data types. + result.offsetToSpillArea = alignTo(s.stackOffset, types.RegSize) + result.spillAreaSize = alignTo(s.spillOffset, types.RegSize) + result.outRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs + return result +} + +// ABIAnalyze returns the same result as ABIAnalyzeFuncType, but also +// updates the offsets of all the receiver, input, and output fields. +// If setNname is true, it also sets the FrameOffset of the Nname for +// the field(s); this is for use when compiling a function and figuring out +// spill locations. Doing this for callers can cause races for register +// outputs because their frame location transitions from BOGUS_FUNARG_OFFSET +// to zero to an as-if-AUTO offset that has no use for callers. +func (config *ABIConfig) ABIAnalyze(t *types.Type, setNname bool) *ABIParamResultInfo { + ft := t.FuncType() + result := config.ABIAnalyzeFuncType(ft) + + // Fill in the frame offsets for receiver, inputs, results + k := 0 + if t.NumRecvs() != 0 { + config.updateOffset(result, ft.Receiver.FieldSlice()[0], result.inparams[0], false, setNname) + k++ + } + for i, f := range ft.Params.FieldSlice() { + config.updateOffset(result, f, result.inparams[k+i], false, setNname) + } + for i, f := range ft.Results.FieldSlice() { + config.updateOffset(result, f, result.outparams[i], true, setNname) + } + return result +} + +func (config *ABIConfig) updateOffset(result *ABIParamResultInfo, f *types.Field, a ABIParamAssignment, isReturn, setNname bool) { + // Everything except return values in registers has either a frame home (if not in a register) or a frame spill location. + if !isReturn || len(a.Registers) == 0 { + // The type frame offset DOES NOT show effects of minimum frame size. + // Getting this wrong breaks stackmaps, see liveness/plive.go:WriteFuncMap and typebits/typebits.go:Set + off := a.FrameOffset(result) + fOffset := f.Offset + if fOffset == types.BOGUS_FUNARG_OFFSET { + if setNname && f.Nname != nil { + f.Nname.(*ir.Name).SetFrameOffset(off) + f.Nname.(*ir.Name).SetIsOutputParamInRegisters(false) + } + } else { + base.Fatalf("field offset for %s at %s has been set to %d", f.Sym.Name, base.FmtPos(f.Pos), fOffset) + } + } else { + if setNname && f.Nname != nil { + fname := f.Nname.(*ir.Name) + fname.SetIsOutputParamInRegisters(true) + fname.SetFrameOffset(0) + } + } +} + +//...................................................................... +// +// Non-public portions. + +// regString produces a human-readable version of a RegIndex. +func (c *RegAmounts) regString(r RegIndex) string { + if int(r) < c.intRegs { + return fmt.Sprintf("I%d", int(r)) + } else if int(r) < c.intRegs+c.floatRegs { + return fmt.Sprintf("F%d", int(r)-c.intRegs) + } + return fmt.Sprintf("%d", r) +} + +// ToString method renders an ABIParamAssignment in human-readable +// form, suitable for debugging or unit testing. +func (ri *ABIParamAssignment) ToString(config *ABIConfig, extra bool) string { + regs := "R{" + offname := "spilloffset" // offset is for spill for register(s) + if len(ri.Registers) == 0 { + offname = "offset" // offset is for memory arg + } + for _, r := range ri.Registers { + regs += " " + config.regAmounts.regString(r) + if extra { + regs += fmt.Sprintf("(%d)", r) + } + } + if extra { + regs += fmt.Sprintf(" | #I=%d, #F=%d", config.regAmounts.intRegs, config.regAmounts.floatRegs) + } + return fmt.Sprintf("%s } %s: %d typ: %v", regs, offname, ri.offset, ri.Type) +} + +// String method renders an ABIParamResultInfo in human-readable +// form, suitable for debugging or unit testing. +func (ri *ABIParamResultInfo) String() string { + res := "" + for k, p := range ri.inparams { + res += fmt.Sprintf("IN %d: %s\n", k, p.ToString(ri.config, false)) + } + for k, r := range ri.outparams { + res += fmt.Sprintf("OUT %d: %s\n", k, r.ToString(ri.config, false)) + } + res += fmt.Sprintf("offsetToSpillArea: %d spillAreaSize: %d", + ri.offsetToSpillArea, ri.spillAreaSize) + return res +} + +// assignState holds intermediate state during the register assigning process +// for a given function signature. +type assignState struct { + rTotal RegAmounts // total reg amounts from ABI rules + rUsed RegAmounts // regs used by params completely assigned so far + pUsed RegAmounts // regs used by the current param (or pieces therein) + stackOffset int64 // current stack offset + spillOffset int64 // current spill offset +} + +// align returns a rounded up to t's alignment +func align(a int64, t *types.Type) int64 { + return alignTo(a, int(t.Align)) +} + +// alignTo returns a rounded up to t, where t must be 0 or a power of 2. +func alignTo(a int64, t int) int64 { + if t == 0 { + return a + } + return types.Rnd(a, int64(t)) +} + +// stackSlot returns a stack offset for a param or result of the +// specified type. +func (state *assignState) stackSlot(t *types.Type) int64 { + rv := align(state.stackOffset, t) + state.stackOffset = rv + t.Width + return rv +} + +// allocateRegs returns an ordered list of register indices for a parameter or result +// that we've just determined to be register-assignable. The number of registers +// needed is assumed to be stored in state.pUsed. +func (state *assignState) allocateRegs(regs []RegIndex, t *types.Type) []RegIndex { + if t.Width == 0 { + return regs + } + ri := state.rUsed.intRegs + rf := state.rUsed.floatRegs + if t.IsScalar() || t.IsPtrShaped() { + if t.IsComplex() { + regs = append(regs, RegIndex(rf+state.rTotal.intRegs), RegIndex(rf+1+state.rTotal.intRegs)) + rf += 2 + } else if t.IsFloat() { + regs = append(regs, RegIndex(rf+state.rTotal.intRegs)) + rf += 1 + } else { + n := (int(t.Size()) + types.RegSize - 1) / types.RegSize + for i := 0; i < n; i++ { // looking ahead to really big integers + regs = append(regs, RegIndex(ri)) + ri += 1 + } + } + state.rUsed.intRegs = ri + state.rUsed.floatRegs = rf + return regs + } else { + typ := t.Kind() + switch typ { + case types.TARRAY: + for i := int64(0); i < t.NumElem(); i++ { + regs = state.allocateRegs(regs, t.Elem()) + } + return regs + case types.TSTRUCT: + for _, f := range t.FieldSlice() { + regs = state.allocateRegs(regs, f.Type) + } + return regs + case types.TSLICE: + return state.allocateRegs(regs, synthSlice) + case types.TSTRING: + return state.allocateRegs(regs, synthString) + case types.TINTER: + return state.allocateRegs(regs, synthIface) + } + } + base.Fatalf("was not expecting type %s", t) + panic("unreachable") +} + +// regAllocate creates a register ABIParamAssignment object for a param +// or result with the specified type, as a final step (this assumes +// that all of the safety/suitability analysis is complete). +func (state *assignState) regAllocate(t *types.Type, name types.Object, isReturn bool) ABIParamAssignment { + spillLoc := int64(-1) + if !isReturn { + // Spill for register-resident t must be aligned for storage of a t. + spillLoc = align(state.spillOffset, t) + state.spillOffset = spillLoc + t.Size() + } + return ABIParamAssignment{ + Type: t, + Name: name, + Registers: state.allocateRegs([]RegIndex{}, t), + offset: int32(spillLoc), + } +} + +// stackAllocate creates a stack memory ABIParamAssignment object for +// a param or result with the specified type, as a final step (this +// assumes that all of the safety/suitability analysis is complete). +func (state *assignState) stackAllocate(t *types.Type, name types.Object) ABIParamAssignment { + return ABIParamAssignment{ + Type: t, + Name: name, + offset: int32(state.stackSlot(t)), + } +} + +// intUsed returns the number of integer registers consumed +// at a given point within an assignment stage. +func (state *assignState) intUsed() int { + return state.rUsed.intRegs + state.pUsed.intRegs +} + +// floatUsed returns the number of floating point registers consumed at +// a given point within an assignment stage. +func (state *assignState) floatUsed() int { + return state.rUsed.floatRegs + state.pUsed.floatRegs +} + +// regassignIntegral examines a param/result of integral type 't' to +// determines whether it can be register-assigned. Returns TRUE if we +// can register allocate, FALSE otherwise (and updates state +// accordingly). +func (state *assignState) regassignIntegral(t *types.Type) bool { + regsNeeded := int(types.Rnd(t.Width, int64(types.PtrSize)) / int64(types.PtrSize)) + if t.IsComplex() { + regsNeeded = 2 + } + + // Floating point and complex. + if t.IsFloat() || t.IsComplex() { + if regsNeeded+state.floatUsed() > state.rTotal.floatRegs { + // not enough regs + return false + } + state.pUsed.floatRegs += regsNeeded + return true + } + + // Non-floating point + if regsNeeded+state.intUsed() > state.rTotal.intRegs { + // not enough regs + return false + } + state.pUsed.intRegs += regsNeeded + return true +} + +// regassignArray processes an array type (or array component within some +// other enclosing type) to determine if it can be register assigned. +// Returns TRUE if we can register allocate, FALSE otherwise. +func (state *assignState) regassignArray(t *types.Type) bool { + + nel := t.NumElem() + if nel == 0 { + return true + } + if nel > 1 { + // Not an array of length 1: stack assign + return false + } + // Visit element + return state.regassign(t.Elem()) +} + +// regassignStruct processes a struct type (or struct component within +// some other enclosing type) to determine if it can be register +// assigned. Returns TRUE if we can register allocate, FALSE otherwise. +func (state *assignState) regassignStruct(t *types.Type) bool { + for _, field := range t.FieldSlice() { + if !state.regassign(field.Type) { + return false + } + } + return true +} + +// synthOnce ensures that we only create the synth* fake types once. +var synthOnce sync.Once + +// synthSlice, synthString, and syncIface are synthesized struct types +// meant to capture the underlying implementations of string/slice/interface. +var synthSlice *types.Type +var synthString *types.Type +var synthIface *types.Type + +// setup performs setup for the register assignment utilities, manufacturing +// a small set of synthesized types that we'll need along the way. +func setup() { + synthOnce.Do(func() { + fname := types.BuiltinPkg.Lookup + nxp := src.NoXPos + unsp := types.Types[types.TUNSAFEPTR] + ui := types.Types[types.TUINTPTR] + synthSlice = types.NewStruct(types.NoPkg, []*types.Field{ + types.NewField(nxp, fname("ptr"), unsp), + types.NewField(nxp, fname("len"), ui), + types.NewField(nxp, fname("cap"), ui), + }) + synthString = types.NewStruct(types.NoPkg, []*types.Field{ + types.NewField(nxp, fname("data"), unsp), + types.NewField(nxp, fname("len"), ui), + }) + synthIface = types.NewStruct(types.NoPkg, []*types.Field{ + types.NewField(nxp, fname("f1"), unsp), + types.NewField(nxp, fname("f2"), unsp), + }) + }) +} + +// regassign examines a given param type (or component within some +// composite) to determine if it can be register assigned. Returns +// TRUE if we can register allocate, FALSE otherwise. +func (state *assignState) regassign(pt *types.Type) bool { + typ := pt.Kind() + if pt.IsScalar() || pt.IsPtrShaped() { + return state.regassignIntegral(pt) + } + switch typ { + case types.TARRAY: + return state.regassignArray(pt) + case types.TSTRUCT: + return state.regassignStruct(pt) + case types.TSLICE: + return state.regassignStruct(synthSlice) + case types.TSTRING: + return state.regassignStruct(synthString) + case types.TINTER: + return state.regassignStruct(synthIface) + default: + base.Fatalf("not expected") + panic("unreachable") + } +} + +// assignParamOrReturn processes a given receiver, param, or result +// of field f to determine whether it can be register assigned. +// The result of the analysis is recorded in the result +// ABIParamResultInfo held in 'state'. +func (state *assignState) assignParamOrReturn(pt *types.Type, n types.Object, isReturn bool) ABIParamAssignment { + state.pUsed = RegAmounts{} + if pt.Width == types.BADWIDTH { + base.Fatalf("should never happen") + panic("unreachable") + } else if pt.Width == 0 { + return state.stackAllocate(pt, n) + } else if state.regassign(pt) { + return state.regAllocate(pt, n, isReturn) + } else { + return state.stackAllocate(pt, n) + } +} + +// ComputePadding returns a list of "post element" padding values in +// the case where we have a structure being passed in registers. Give +// a param assignment corresponding to a struct, it returns a list of +// contaning padding values for each field, e.g. the Kth element in +// the list is the amount of padding between field K and the following +// field. For things that are not struct (or structs without padding) +// it returns a list of zeros. Example: +// +// type small struct { +// x uint16 +// y uint8 +// z int32 +// w int32 +// } +// +// For this struct we would return a list [0, 1, 0, 0], meaning that +// we have one byte of padding after the second field, and no bytes of +// padding after any of the other fields. Input parameter "storage" +// is with enough capacity to accommodate padding elements for +// the architected register set in question. +func (pa *ABIParamAssignment) ComputePadding(storage []uint64) []uint64 { + nr := len(pa.Registers) + padding := storage[:nr] + for i := 0; i < nr; i++ { + padding[i] = 0 + } + if pa.Type.Kind() != types.TSTRUCT || nr == 0 { + return padding + } + types := make([]*types.Type, 0, nr) + types = appendParamTypes(types, pa.Type) + if len(types) != nr { + panic("internal error") + } + off := int64(0) + for idx, t := range types { + ts := t.Size() + off += int64(ts) + if idx < len(types)-1 { + noff := align(off, types[idx+1]) + if noff != off { + padding[idx] = uint64(noff - off) + } + } + } + return padding +} diff --git a/src/cmd/compile/internal/amd64/galign.go b/src/cmd/compile/internal/amd64/galign.go index af58440502e00a2b5c3162f154e123cf18063c45..2785aa03368b782c3d5d9b5c8f983d0dac19b1d6 100644 --- a/src/cmd/compile/internal/amd64/galign.go +++ b/src/cmd/compile/internal/amd64/galign.go @@ -5,13 +5,13 @@ package amd64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/ssagen" "cmd/internal/obj/x86" ) var leaptr = x86.ALEAQ -func Init(arch *gc.Arch) { +func Init(arch *ssagen.ArchInfo) { arch.LinkArch = &x86.Linkamd64 arch.REGSP = x86.REGSP arch.MAXWIDTH = 1 << 50 @@ -23,4 +23,6 @@ func Init(arch *gc.Arch) { arch.SSAMarkMoves = ssaMarkMoves arch.SSAGenValue = ssaGenValue arch.SSAGenBlock = ssaGenBlock + arch.LoadRegResults = loadRegResults + arch.SpillArgReg = spillArgReg } diff --git a/src/cmd/compile/internal/amd64/ggen.go b/src/cmd/compile/internal/amd64/ggen.go index 0c1456f4d0dedeb5c469daa0511cbad26e23fc53..1484ad5404b4797552195b597fd0e22cab00e85b 100644 --- a/src/cmd/compile/internal/amd64/ggen.go +++ b/src/cmd/compile/internal/amd64/ggen.go @@ -5,22 +5,25 @@ package amd64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/x86" - "cmd/internal/objabi" + "internal/buildcfg" ) // no floating point in note handlers on Plan 9 -var isPlan9 = objabi.GOOS == "plan9" +var isPlan9 = buildcfg.GOOS == "plan9" // DUFFZERO consists of repeated blocks of 4 MOVUPSs + LEAQ, // See runtime/mkduff.go. const ( dzBlocks = 16 // number of MOV/ADD blocks dzBlockLen = 4 // number of clears per block - dzBlockSize = 19 // size of instructions in a single block - dzMovSize = 4 // size of single MOV instruction w/ offset + dzBlockSize = 23 // size of instructions in a single block + dzMovSize = 5 // size of single MOV instruction w/ offset dzLeaqSize = 4 // size of single LEAQ instruction dzClearStep = 16 // number of bytes cleared by each MOV instruction @@ -51,77 +54,101 @@ func dzDI(b int64) int64 { return -dzClearStep * (dzBlockLen - tailSteps) } -func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, state *uint32) *obj.Prog { +func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, state *uint32) *obj.Prog { const ( - ax = 1 << iota - x0 + r13 = 1 << iota // if R13 is already zeroed. + x15 // if X15 is already zeroed. Note: in new ABI, X15 is always zero. ) if cnt == 0 { return p } - if cnt%int64(gc.Widthreg) != 0 { + if cnt%int64(types.RegSize) != 0 { // should only happen with nacl - if cnt%int64(gc.Widthptr) != 0 { - gc.Fatalf("zerorange count not a multiple of widthptr %d", cnt) + if cnt%int64(types.PtrSize) != 0 { + base.Fatalf("zerorange count not a multiple of widthptr %d", cnt) } - if *state&ax == 0 { - p = pp.Appendpp(p, x86.AMOVQ, obj.TYPE_CONST, 0, 0, obj.TYPE_REG, x86.REG_AX, 0) - *state |= ax + if *state&r13 == 0 { + p = pp.Append(p, x86.AMOVQ, obj.TYPE_CONST, 0, 0, obj.TYPE_REG, x86.REG_R13, 0) + *state |= r13 } - p = pp.Appendpp(p, x86.AMOVL, obj.TYPE_REG, x86.REG_AX, 0, obj.TYPE_MEM, x86.REG_SP, off) - off += int64(gc.Widthptr) - cnt -= int64(gc.Widthptr) + p = pp.Append(p, x86.AMOVL, obj.TYPE_REG, x86.REG_R13, 0, obj.TYPE_MEM, x86.REG_SP, off) + off += int64(types.PtrSize) + cnt -= int64(types.PtrSize) } if cnt == 8 { - if *state&ax == 0 { - p = pp.Appendpp(p, x86.AMOVQ, obj.TYPE_CONST, 0, 0, obj.TYPE_REG, x86.REG_AX, 0) - *state |= ax + if *state&r13 == 0 { + p = pp.Append(p, x86.AMOVQ, obj.TYPE_CONST, 0, 0, obj.TYPE_REG, x86.REG_R13, 0) + *state |= r13 } - p = pp.Appendpp(p, x86.AMOVQ, obj.TYPE_REG, x86.REG_AX, 0, obj.TYPE_MEM, x86.REG_SP, off) - } else if !isPlan9 && cnt <= int64(8*gc.Widthreg) { - if *state&x0 == 0 { - p = pp.Appendpp(p, x86.AXORPS, obj.TYPE_REG, x86.REG_X0, 0, obj.TYPE_REG, x86.REG_X0, 0) - *state |= x0 + p = pp.Append(p, x86.AMOVQ, obj.TYPE_REG, x86.REG_R13, 0, obj.TYPE_MEM, x86.REG_SP, off) + } else if !isPlan9 && cnt <= int64(8*types.RegSize) { + if !buildcfg.Experiment.RegabiG && *state&x15 == 0 { + p = pp.Append(p, x86.AXORPS, obj.TYPE_REG, x86.REG_X15, 0, obj.TYPE_REG, x86.REG_X15, 0) + *state |= x15 } for i := int64(0); i < cnt/16; i++ { - p = pp.Appendpp(p, x86.AMOVUPS, obj.TYPE_REG, x86.REG_X0, 0, obj.TYPE_MEM, x86.REG_SP, off+i*16) + p = pp.Append(p, x86.AMOVUPS, obj.TYPE_REG, x86.REG_X15, 0, obj.TYPE_MEM, x86.REG_SP, off+i*16) } if cnt%16 != 0 { - p = pp.Appendpp(p, x86.AMOVUPS, obj.TYPE_REG, x86.REG_X0, 0, obj.TYPE_MEM, x86.REG_SP, off+cnt-int64(16)) + p = pp.Append(p, x86.AMOVUPS, obj.TYPE_REG, x86.REG_X15, 0, obj.TYPE_MEM, x86.REG_SP, off+cnt-int64(16)) } - } else if !isPlan9 && (cnt <= int64(128*gc.Widthreg)) { - if *state&x0 == 0 { - p = pp.Appendpp(p, x86.AXORPS, obj.TYPE_REG, x86.REG_X0, 0, obj.TYPE_REG, x86.REG_X0, 0) - *state |= x0 + } else if !isPlan9 && (cnt <= int64(128*types.RegSize)) { + if !buildcfg.Experiment.RegabiG && *state&x15 == 0 { + p = pp.Append(p, x86.AXORPS, obj.TYPE_REG, x86.REG_X15, 0, obj.TYPE_REG, x86.REG_X15, 0) + *state |= x15 } - p = pp.Appendpp(p, leaptr, obj.TYPE_MEM, x86.REG_SP, off+dzDI(cnt), obj.TYPE_REG, x86.REG_DI, 0) - p = pp.Appendpp(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_ADDR, 0, dzOff(cnt)) - p.To.Sym = gc.Duffzero - + // Save DI to r12. With the amd64 Go register abi, DI can contain + // an incoming parameter, whereas R12 is always scratch. + p = pp.Append(p, x86.AMOVQ, obj.TYPE_REG, x86.REG_DI, 0, obj.TYPE_REG, x86.REG_R12, 0) + // Emit duffzero call + p = pp.Append(p, leaptr, obj.TYPE_MEM, x86.REG_SP, off+dzDI(cnt), obj.TYPE_REG, x86.REG_DI, 0) + p = pp.Append(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_ADDR, 0, dzOff(cnt)) + p.To.Sym = ir.Syms.Duffzero if cnt%16 != 0 { - p = pp.Appendpp(p, x86.AMOVUPS, obj.TYPE_REG, x86.REG_X0, 0, obj.TYPE_MEM, x86.REG_DI, -int64(8)) - } - } else { - if *state&ax == 0 { - p = pp.Appendpp(p, x86.AMOVQ, obj.TYPE_CONST, 0, 0, obj.TYPE_REG, x86.REG_AX, 0) - *state |= ax + p = pp.Append(p, x86.AMOVUPS, obj.TYPE_REG, x86.REG_X15, 0, obj.TYPE_MEM, x86.REG_DI, -int64(8)) } + // Restore DI from r12 + p = pp.Append(p, x86.AMOVQ, obj.TYPE_REG, x86.REG_R12, 0, obj.TYPE_REG, x86.REG_DI, 0) - p = pp.Appendpp(p, x86.AMOVQ, obj.TYPE_CONST, 0, cnt/int64(gc.Widthreg), obj.TYPE_REG, x86.REG_CX, 0) - p = pp.Appendpp(p, leaptr, obj.TYPE_MEM, x86.REG_SP, off, obj.TYPE_REG, x86.REG_DI, 0) - p = pp.Appendpp(p, x86.AREP, obj.TYPE_NONE, 0, 0, obj.TYPE_NONE, 0, 0) - p = pp.Appendpp(p, x86.ASTOSQ, obj.TYPE_NONE, 0, 0, obj.TYPE_NONE, 0, 0) + } else { + // When the register ABI is in effect, at this point in the + // prolog we may have live values in all of RAX,RDI,RCX. Save + // them off to registers before the REPSTOSQ below, then + // restore. Note that R12 and R13 are always available as + // scratch regs; here we also use R15 (this is safe to do + // since there won't be any globals accessed in the prolog). + // See rewriteToUseGot() in obj6.go for more on r15 use. + + // Save rax/rdi/rcx + p = pp.Append(p, x86.AMOVQ, obj.TYPE_REG, x86.REG_DI, 0, obj.TYPE_REG, x86.REG_R12, 0) + p = pp.Append(p, x86.AMOVQ, obj.TYPE_REG, x86.REG_AX, 0, obj.TYPE_REG, x86.REG_R13, 0) + p = pp.Append(p, x86.AMOVQ, obj.TYPE_REG, x86.REG_CX, 0, obj.TYPE_REG, x86.REG_R15, 0) + + // Set up the REPSTOSQ and kick it off. + p = pp.Append(p, x86.AMOVQ, obj.TYPE_CONST, 0, 0, obj.TYPE_REG, x86.REG_AX, 0) + p = pp.Append(p, x86.AMOVQ, obj.TYPE_CONST, 0, cnt/int64(types.RegSize), obj.TYPE_REG, x86.REG_CX, 0) + p = pp.Append(p, leaptr, obj.TYPE_MEM, x86.REG_SP, off, obj.TYPE_REG, x86.REG_DI, 0) + p = pp.Append(p, x86.AREP, obj.TYPE_NONE, 0, 0, obj.TYPE_NONE, 0, 0) + p = pp.Append(p, x86.ASTOSQ, obj.TYPE_NONE, 0, 0, obj.TYPE_NONE, 0, 0) + + // Restore rax/rdi/rcx + p = pp.Append(p, x86.AMOVQ, obj.TYPE_REG, x86.REG_R12, 0, obj.TYPE_REG, x86.REG_DI, 0) + p = pp.Append(p, x86.AMOVQ, obj.TYPE_REG, x86.REG_R13, 0, obj.TYPE_REG, x86.REG_AX, 0) + p = pp.Append(p, x86.AMOVQ, obj.TYPE_REG, x86.REG_R15, 0, obj.TYPE_REG, x86.REG_CX, 0) + + // Record the fact that r13 is no longer zero. + *state &= ^uint32(r13) } return p } -func ginsnop(pp *gc.Progs) *obj.Prog { +func ginsnop(pp *objw.Progs) *obj.Prog { // This is a hardware nop (1-byte 0x90) instruction, // even though we describe it as an explicit XCHGL here. // Particularly, this does not zero the high 32 bits diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index 5ff05a0edd16c6b0dab15099d13e9d89d1be7fc0..ca5f36e77598c6ff54b6c3288b086f40a9f999d4 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -6,18 +6,22 @@ package amd64 import ( "fmt" + "internal/buildcfg" "math" - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/logopt" + "cmd/compile/internal/objw" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/x86" ) // markMoves marks any MOVXconst ops that need to avoid clobbering flags. -func ssaMarkMoves(s *gc.SSAGenState, b *ssa.Block) { +func ssaMarkMoves(s *ssagen.State, b *ssa.Block) { flive := b.FlagsLiveAtEnd for _, c := range b.ControlValues() { flive = c.Type.IsFlags() || flive @@ -110,7 +114,7 @@ func moveByType(t *types.Type) obj.As { // dest := dest(To) op src(From) // and also returns the created obj.Prog so it // may be further adjusted (offset, scale, etc). -func opregreg(s *gc.SSAGenState, op obj.As, dest, src int16) *obj.Prog { +func opregreg(s *ssagen.State, op obj.As, dest, src int16) *obj.Prog { p := s.Prog(op) p.From.Type = obj.TYPE_REG p.To.Type = obj.TYPE_REG @@ -164,16 +168,41 @@ func duff(size int64) (int64, int64) { return off, adj } -func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { +func getgFromTLS(s *ssagen.State, r int16) { + // See the comments in cmd/internal/obj/x86/obj6.go + // near CanUse1InsnTLS for a detailed explanation of these instructions. + if x86.CanUse1InsnTLS(base.Ctxt) { + // MOVQ (TLS), r + p := s.Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_MEM + p.From.Reg = x86.REG_TLS + p.To.Type = obj.TYPE_REG + p.To.Reg = r + } else { + // MOVQ TLS, r + // MOVQ (r)(TLS*1), r + p := s.Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_REG + p.From.Reg = x86.REG_TLS + p.To.Type = obj.TYPE_REG + p.To.Reg = r + q := s.Prog(x86.AMOVQ) + q.From.Type = obj.TYPE_MEM + q.From.Reg = r + q.From.Index = x86.REG_TLS + q.From.Scale = 1 + q.To.Type = obj.TYPE_REG + q.To.Reg = r + } +} + +func ssaGenValue(s *ssagen.State, v *ssa.Value) { switch v.Op { case ssa.OpAMD64VFMADD231SD: p := s.Prog(v.Op.Asm()) p.From = obj.Addr{Type: obj.TYPE_REG, Reg: v.Args[2].Reg()} p.To = obj.Addr{Type: obj.TYPE_REG, Reg: v.Reg()} - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: v.Args[1].Reg()}) - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } + p.SetFrom3Reg(v.Args[1].Reg()) case ssa.OpAMD64ADDQ, ssa.OpAMD64ADDL: r := v.Reg() r1 := v.Args[0].Reg() @@ -223,11 +252,16 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpAMD64BTSL, ssa.OpAMD64BTSQ, ssa.OpAMD64BTCL, ssa.OpAMD64BTCQ, ssa.OpAMD64BTRL, ssa.OpAMD64BTRQ: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } - opregreg(s, v.Op.Asm(), r, v.Args[1].Reg()) + opregreg(s, v.Op.Asm(), v.Reg(), v.Args[1].Reg()) + + case ssa.OpAMD64SHRDQ, ssa.OpAMD64SHLDQ: + p := s.Prog(v.Op.Asm()) + lo, hi, bits := v.Args[0].Reg(), v.Args[1].Reg(), v.Args[2].Reg() + p.From.Type = obj.TYPE_REG + p.From.Reg = bits + p.To.Type = obj.TYPE_REG + p.To.Reg = lo + p.SetFrom3Reg(hi) case ssa.OpAMD64DIVQU, ssa.OpAMD64DIVLU, ssa.OpAMD64DIVWU: // Arg[0] (the dividend) is in AX. @@ -370,20 +404,16 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // compute (x+y)/2 unsigned. // Do a 64-bit add, the overflow goes into the carry. // Shift right once and pull the carry back into the 63rd bit. - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(x86.AADDQ) p.From.Type = obj.TYPE_REG p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() p.From.Reg = v.Args[1].Reg() p = s.Prog(x86.ARCRQ) p.From.Type = obj.TYPE_CONST p.From.Offset = 1 p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.OpAMD64ADDQcarry, ssa.OpAMD64ADCQ: r := v.Reg0() @@ -499,21 +529,13 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpAMD64CMOVQCS, ssa.OpAMD64CMOVLCS, ssa.OpAMD64CMOVWCS, ssa.OpAMD64CMOVQGTF, ssa.OpAMD64CMOVLGTF, ssa.OpAMD64CMOVWGTF, ssa.OpAMD64CMOVQGEF, ssa.OpAMD64CMOVLGEF, ssa.OpAMD64CMOVWGEF: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.OpAMD64CMOVQNEF, ssa.OpAMD64CMOVLNEF, ssa.OpAMD64CMOVWNEF: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } // Flag condition: ^ZERO || PARITY // Generate: // CMOV*NE SRC,DST @@ -522,7 +544,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() var q *obj.Prog if v.Op == ssa.OpAMD64CMOVQNEF { q = s.Prog(x86.ACMOVQPS) @@ -534,14 +556,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { q.From.Type = obj.TYPE_REG q.From.Reg = v.Args[1].Reg() q.To.Type = obj.TYPE_REG - q.To.Reg = r + q.To.Reg = v.Reg() case ssa.OpAMD64CMOVQEQF, ssa.OpAMD64CMOVLEQF, ssa.OpAMD64CMOVWEQF: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } - // Flag condition: ZERO && !PARITY // Generate: // MOV SRC,AX @@ -558,7 +575,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG - p.From.Reg = r + p.From.Reg = v.Reg() p.To.Type = obj.TYPE_REG p.To.Reg = x86.REG_AX var q *obj.Prog @@ -572,7 +589,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { q.From.Type = obj.TYPE_REG q.From.Reg = x86.REG_AX q.To.Type = obj.TYPE_REG - q.To.Reg = r + q.To.Reg = v.Reg() case ssa.OpAMD64MULQconst, ssa.OpAMD64MULLconst: r := v.Reg() @@ -581,7 +598,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Offset = v.AuxInt p.To.Type = obj.TYPE_REG p.To.Reg = r - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: v.Args[0].Reg()}) + p.SetFrom3Reg(v.Args[0].Reg()) case ssa.OpAMD64SUBQconst, ssa.OpAMD64SUBLconst, ssa.OpAMD64ANDQconst, ssa.OpAMD64ANDLconst, @@ -591,15 +608,11 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpAMD64SHRQconst, ssa.OpAMD64SHRLconst, ssa.OpAMD64SHRWconst, ssa.OpAMD64SHRBconst, ssa.OpAMD64SARQconst, ssa.OpAMD64SARLconst, ssa.OpAMD64SARWconst, ssa.OpAMD64SARBconst, ssa.OpAMD64ROLQconst, ssa.OpAMD64ROLLconst, ssa.OpAMD64ROLWconst, ssa.OpAMD64ROLBconst: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = v.AuxInt p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.OpAMD64SBBQcarrymask, ssa.OpAMD64SBBLcarrymask: r := v.Reg() p := s.Prog(v.Op.Asm()) @@ -630,12 +643,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = o } - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) case ssa.OpAMD64LEAQ, ssa.OpAMD64LEAL, ssa.OpAMD64LEAW: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpAMD64CMPQ, ssa.OpAMD64CMPL, ssa.OpAMD64CMPW, ssa.OpAMD64CMPB, @@ -671,7 +684,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Args[1].Reg() case ssa.OpAMD64CMPQconstload, ssa.OpAMD64CMPLconstload, ssa.OpAMD64CMPWconstload, ssa.OpAMD64CMPBconstload: @@ -679,22 +692,22 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux2(&p.From, v, sc.Off()) + ssagen.AddAux2(&p.From, v, sc.Off64()) p.To.Type = obj.TYPE_CONST - p.To.Offset = sc.Val() + p.To.Offset = sc.Val64() case ssa.OpAMD64CMPQloadidx8, ssa.OpAMD64CMPQloadidx1, ssa.OpAMD64CMPLloadidx4, ssa.OpAMD64CMPLloadidx1, ssa.OpAMD64CMPWloadidx2, ssa.OpAMD64CMPWloadidx1, ssa.OpAMD64CMPBloadidx1: p := s.Prog(v.Op.Asm()) memIdx(&p.From, v) - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Args[2].Reg() case ssa.OpAMD64CMPQconstloadidx8, ssa.OpAMD64CMPQconstloadidx1, ssa.OpAMD64CMPLconstloadidx4, ssa.OpAMD64CMPLconstloadidx1, ssa.OpAMD64CMPWconstloadidx2, ssa.OpAMD64CMPWconstloadidx1, ssa.OpAMD64CMPBconstloadidx1: sc := v.AuxValAndOff() p := s.Prog(v.Op.Asm()) memIdx(&p.From, v) - gc.AddAux2(&p.From, v, sc.Off()) + ssagen.AddAux2(&p.From, v, sc.Off64()) p.To.Type = obj.TYPE_CONST - p.To.Offset = sc.Val() + p.To.Offset = sc.Val64() case ssa.OpAMD64MOVLconst, ssa.OpAMD64MOVQconst: x := v.Reg() @@ -732,18 +745,17 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpAMD64MOVBloadidx1, ssa.OpAMD64MOVWloadidx1, ssa.OpAMD64MOVLloadidx1, ssa.OpAMD64MOVQloadidx1, ssa.OpAMD64MOVSSloadidx1, ssa.OpAMD64MOVSDloadidx1, ssa.OpAMD64MOVQloadidx8, ssa.OpAMD64MOVSDloadidx8, ssa.OpAMD64MOVLloadidx8, ssa.OpAMD64MOVLloadidx4, ssa.OpAMD64MOVSSloadidx4, ssa.OpAMD64MOVWloadidx2: p := s.Prog(v.Op.Asm()) memIdx(&p.From, v) - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpAMD64MOVQstore, ssa.OpAMD64MOVSSstore, ssa.OpAMD64MOVSDstore, ssa.OpAMD64MOVLstore, ssa.OpAMD64MOVWstore, ssa.OpAMD64MOVBstore, ssa.OpAMD64MOVOstore, - ssa.OpAMD64BTCQmodify, ssa.OpAMD64BTCLmodify, ssa.OpAMD64BTRQmodify, ssa.OpAMD64BTRLmodify, ssa.OpAMD64BTSQmodify, ssa.OpAMD64BTSLmodify, ssa.OpAMD64ADDQmodify, ssa.OpAMD64SUBQmodify, ssa.OpAMD64ANDQmodify, ssa.OpAMD64ORQmodify, ssa.OpAMD64XORQmodify, ssa.OpAMD64ADDLmodify, ssa.OpAMD64SUBLmodify, ssa.OpAMD64ANDLmodify, ssa.OpAMD64ORLmodify, ssa.OpAMD64XORLmodify: p := s.Prog(v.Op.Asm()) @@ -751,7 +763,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpAMD64MOVBstoreidx1, ssa.OpAMD64MOVWstoreidx1, ssa.OpAMD64MOVLstoreidx1, ssa.OpAMD64MOVQstoreidx1, ssa.OpAMD64MOVSSstoreidx1, ssa.OpAMD64MOVSDstoreidx1, ssa.OpAMD64MOVQstoreidx8, ssa.OpAMD64MOVSDstoreidx8, ssa.OpAMD64MOVLstoreidx8, ssa.OpAMD64MOVSSstoreidx4, ssa.OpAMD64MOVLstoreidx4, ssa.OpAMD64MOVWstoreidx2, ssa.OpAMD64ADDLmodifyidx1, ssa.OpAMD64ADDLmodifyidx4, ssa.OpAMD64ADDLmodifyidx8, ssa.OpAMD64ADDQmodifyidx1, ssa.OpAMD64ADDQmodifyidx8, @@ -763,10 +775,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[2].Reg() memIdx(&p.To, v) - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpAMD64ADDQconstmodify, ssa.OpAMD64ADDLconstmodify: sc := v.AuxValAndOff() - off := sc.Off() + off := sc.Off64() val := sc.Val() if val == 1 || val == -1 { var asm obj.As @@ -786,31 +798,41 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(asm) p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux2(&p.To, v, off) + ssagen.AddAux2(&p.To, v, off) break } fallthrough case ssa.OpAMD64ANDQconstmodify, ssa.OpAMD64ANDLconstmodify, ssa.OpAMD64ORQconstmodify, ssa.OpAMD64ORLconstmodify, - ssa.OpAMD64BTCQconstmodify, ssa.OpAMD64BTCLconstmodify, ssa.OpAMD64BTSQconstmodify, ssa.OpAMD64BTSLconstmodify, - ssa.OpAMD64BTRQconstmodify, ssa.OpAMD64BTRLconstmodify, ssa.OpAMD64XORQconstmodify, ssa.OpAMD64XORLconstmodify: + ssa.OpAMD64XORQconstmodify, ssa.OpAMD64XORLconstmodify: sc := v.AuxValAndOff() - off := sc.Off() - val := sc.Val() + off := sc.Off64() + val := sc.Val64() p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = val p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux2(&p.To, v, off) + ssagen.AddAux2(&p.To, v, off) case ssa.OpAMD64MOVQstoreconst, ssa.OpAMD64MOVLstoreconst, ssa.OpAMD64MOVWstoreconst, ssa.OpAMD64MOVBstoreconst: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST sc := v.AuxValAndOff() - p.From.Offset = sc.Val() + p.From.Offset = sc.Val64() + p.To.Type = obj.TYPE_MEM + p.To.Reg = v.Args[0].Reg() + ssagen.AddAux2(&p.To, v, sc.Off64()) + case ssa.OpAMD64MOVOstorezero: + if !buildcfg.Experiment.RegabiG || s.ABI != obj.ABIInternal { + // zero X15 manually + opregreg(s, x86.AXORPS, x86.REG_X15, x86.REG_X15) + } + p := s.Prog(v.Op.Asm()) + p.From.Type = obj.TYPE_REG + p.From.Reg = x86.REG_X15 p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux2(&p.To, v, sc.Off()) + ssagen.AddAux(&p.To, v) case ssa.OpAMD64MOVQstoreconstidx1, ssa.OpAMD64MOVQstoreconstidx8, ssa.OpAMD64MOVLstoreconstidx1, ssa.OpAMD64MOVLstoreconstidx4, ssa.OpAMD64MOVWstoreconstidx1, ssa.OpAMD64MOVWstoreconstidx2, ssa.OpAMD64MOVBstoreconstidx1, ssa.OpAMD64ADDLconstmodifyidx1, ssa.OpAMD64ADDLconstmodifyidx4, ssa.OpAMD64ADDLconstmodifyidx8, ssa.OpAMD64ADDQconstmodifyidx1, ssa.OpAMD64ADDQconstmodifyidx8, ssa.OpAMD64ANDLconstmodifyidx1, ssa.OpAMD64ANDLconstmodifyidx4, ssa.OpAMD64ANDLconstmodifyidx8, ssa.OpAMD64ANDQconstmodifyidx1, ssa.OpAMD64ANDQconstmodifyidx8, @@ -819,7 +841,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST sc := v.AuxValAndOff() - p.From.Offset = sc.Val() + p.From.Offset = sc.Val64() switch { case p.As == x86.AADDQ && p.From.Offset == 1: p.As = x86.AINCQ @@ -835,7 +857,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Type = obj.TYPE_NONE } memIdx(&p.To, v) - gc.AddAux2(&p.To, v, sc.Off()) + ssagen.AddAux2(&p.To, v, sc.Off64()) case ssa.OpAMD64MOVLQSX, ssa.OpAMD64MOVWQSX, ssa.OpAMD64MOVBQSX, ssa.OpAMD64MOVLQZX, ssa.OpAMD64MOVWQZX, ssa.OpAMD64MOVBQZX, ssa.OpAMD64CVTTSS2SL, ssa.OpAMD64CVTTSD2SL, ssa.OpAMD64CVTTSS2SQ, ssa.OpAMD64CVTTSD2SQ, ssa.OpAMD64CVTSS2SD, ssa.OpAMD64CVTSD2SS: @@ -865,12 +887,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[1].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } case ssa.OpAMD64ADDLloadidx1, ssa.OpAMD64ADDLloadidx4, ssa.OpAMD64ADDLloadidx8, ssa.OpAMD64ADDQloadidx1, ssa.OpAMD64ADDQloadidx8, ssa.OpAMD64SUBLloadidx1, ssa.OpAMD64SUBLloadidx4, ssa.OpAMD64SUBLloadidx8, ssa.OpAMD64SUBQloadidx1, ssa.OpAMD64SUBQloadidx8, ssa.OpAMD64ANDLloadidx1, ssa.OpAMD64ANDLloadidx4, ssa.OpAMD64ANDLloadidx8, ssa.OpAMD64ANDQloadidx1, ssa.OpAMD64ANDQloadidx8, @@ -891,13 +910,14 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = r p.From.Index = i - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } case ssa.OpAMD64DUFFZERO: + if !buildcfg.Experiment.RegabiG || s.ABI != obj.ABIInternal { + // zero X15 manually + opregreg(s, x86.AXORPS, x86.REG_X15, x86.REG_X15) + } off := duffStart(v.AuxInt) adj := duffAdj(v.AuxInt) var p *obj.Prog @@ -911,18 +931,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { } p = s.Prog(obj.ADUFFZERO) p.To.Type = obj.TYPE_ADDR - p.To.Sym = gc.Duffzero + p.To.Sym = ir.Syms.Duffzero p.To.Offset = off - case ssa.OpAMD64MOVOconst: - if v.AuxInt != 0 { - v.Fatalf("MOVOconst can only do constant=0") - } - r := v.Reg() - opregreg(s, x86.AXORPS, r, r) case ssa.OpAMD64DUFFCOPY: p := s.Prog(obj.ADUFFCOPY) p.To.Type = obj.TYPE_ADDR - p.To.Sym = gc.Duffcopy + p.To.Sym = ir.Syms.Duffcopy if v.AuxInt%16 != 0 { v.Fatalf("bad DUFFCOPY AuxInt %v", v.AuxInt) } @@ -949,7 +963,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { return } p := s.Prog(loadByType(v.Type)) - gc.AddrAuto(&p.From, v.Args[0]) + ssagen.AddrAuto(&p.From, v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -961,44 +975,48 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(storeByType(v.Type)) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() - gc.AddrAuto(&p.To, v) + ssagen.AddrAuto(&p.To, v) case ssa.OpAMD64LoweredHasCPUFeature: p := s.Prog(x86.AMOVBQZX) p.From.Type = obj.TYPE_MEM - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() + case ssa.OpArgIntReg, ssa.OpArgFloatReg: + // The assembler needs to wrap the entry safepoint/stack growth code with spill/unspill + // The loop only runs once. + for _, ap := range v.Block.Func.RegArgs { + // Pass the spill/unspill information along to the assembler, offset by size of return PC pushed on stack. + addr := ssagen.SpillSlotAddr(ap, x86.REG_SP, v.Block.Func.Config.PtrSize) + s.FuncInfo().AddSpill( + obj.RegSpill{Reg: ap.Reg, Addr: addr, Unspill: loadByType(ap.Type), Spill: storeByType(ap.Type)}) + } + v.Block.Func.RegArgs = nil + ssagen.CheckArgReg(v) case ssa.OpAMD64LoweredGetClosurePtr: // Closure pointer is DX. - gc.CheckLoweredGetClosurePtr(v) + ssagen.CheckLoweredGetClosurePtr(v) case ssa.OpAMD64LoweredGetG: + if buildcfg.Experiment.RegabiG && s.ABI == obj.ABIInternal { + v.Fatalf("LoweredGetG should not appear in ABIInternal") + } r := v.Reg() - // See the comments in cmd/internal/obj/x86/obj6.go - // near CanUse1InsnTLS for a detailed explanation of these instructions. - if x86.CanUse1InsnTLS(gc.Ctxt) { - // MOVQ (TLS), r - p := s.Prog(x86.AMOVQ) - p.From.Type = obj.TYPE_MEM - p.From.Reg = x86.REG_TLS - p.To.Type = obj.TYPE_REG - p.To.Reg = r - } else { - // MOVQ TLS, r - // MOVQ (r)(TLS*1), r - p := s.Prog(x86.AMOVQ) - p.From.Type = obj.TYPE_REG - p.From.Reg = x86.REG_TLS - p.To.Type = obj.TYPE_REG - p.To.Reg = r - q := s.Prog(x86.AMOVQ) - q.From.Type = obj.TYPE_MEM - q.From.Reg = r - q.From.Index = x86.REG_TLS - q.From.Scale = 1 - q.To.Type = obj.TYPE_REG - q.To.Reg = r + getgFromTLS(s, r) + case ssa.OpAMD64CALLstatic: + if buildcfg.Experiment.RegabiG && s.ABI == obj.ABI0 && v.Aux.(*ssa.AuxCall).Fn.ABI() == obj.ABIInternal { + // zeroing X15 when entering ABIInternal from ABI0 + opregreg(s, x86.AXORPS, x86.REG_X15, x86.REG_X15) + // set G register from TLS + getgFromTLS(s, x86.REG_R14) } - case ssa.OpAMD64CALLstatic, ssa.OpAMD64CALLclosure, ssa.OpAMD64CALLinter: + s.Call(v) + if buildcfg.Experiment.RegabiG && s.ABI == obj.ABIInternal && v.Aux.(*ssa.AuxCall).Fn.ABI() == obj.ABI0 { + // zeroing X15 when entering ABIInternal from ABI0 + opregreg(s, x86.AXORPS, x86.REG_X15, x86.REG_X15) + // set G register from TLS + getgFromTLS(s, x86.REG_R14) + } + case ssa.OpAMD64CALLclosure, ssa.OpAMD64CALLinter: s.Call(v) case ssa.OpAMD64LoweredGetCallerPC: @@ -1012,12 +1030,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.OpAMD64LoweredGetCallerSP: // caller's SP is the address of the first arg mov := x86.AMOVQ - if gc.Widthptr == 4 { + if types.PtrSize == 4 { mov = x86.AMOVL } p := s.Prog(mov) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -gc.Ctxt.FixedFrameSize() // 0 on amd64, just to be consistent with other architectures + p.From.Offset = -base.Ctxt.FixedFrameSize() // 0 on amd64, just to be consistent with other architectures p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -1027,36 +1045,28 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN // arg0 is in DI. Set sym to match where regalloc put arg1. - p.To.Sym = gc.GCWriteBarrierReg[v.Args[1].Reg()] + p.To.Sym = ssagen.GCWriteBarrierReg[v.Args[1].Reg()] case ssa.OpAMD64LoweredPanicBoundsA, ssa.OpAMD64LoweredPanicBoundsB, ssa.OpAMD64LoweredPanicBoundsC: p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.BoundsCheckFunc[v.AuxInt] - s.UseArgs(int64(2 * gc.Widthptr)) // space used in callee args area by assembly stubs + p.To.Sym = ssagen.BoundsCheckFunc[v.AuxInt] + s.UseArgs(int64(2 * types.PtrSize)) // space used in callee args area by assembly stubs case ssa.OpAMD64NEGQ, ssa.OpAMD64NEGL, ssa.OpAMD64BSWAPQ, ssa.OpAMD64BSWAPL, ssa.OpAMD64NOTQ, ssa.OpAMD64NOTL: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.OpAMD64NEGLflags: - r := v.Reg0() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg0() - case ssa.OpAMD64BSFQ, ssa.OpAMD64BSRQ, ssa.OpAMD64BSFL, ssa.OpAMD64BSRL, ssa.OpAMD64SQRTSD: + case ssa.OpAMD64BSFQ, ssa.OpAMD64BSRQ, ssa.OpAMD64BSFL, ssa.OpAMD64BSRL, ssa.OpAMD64SQRTSD, ssa.OpAMD64SQRTSS: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() @@ -1064,7 +1074,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { switch v.Op { case ssa.OpAMD64BSFQ, ssa.OpAMD64BSRQ: p.To.Reg = v.Reg0() - case ssa.OpAMD64BSFL, ssa.OpAMD64BSRL, ssa.OpAMD64SQRTSD: + case ssa.OpAMD64BSFL, ssa.OpAMD64BSRL, ssa.OpAMD64SQRTSD, ssa.OpAMD64SQRTSS: p.To.Reg = v.Reg() } case ssa.OpAMD64ROUNDSD: @@ -1076,7 +1086,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { } p.From.Offset = val p.From.Type = obj.TYPE_CONST - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: v.Args[0].Reg()}) + p.SetFrom3Reg(v.Args[0].Reg()) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpAMD64POPCNTQ, ssa.OpAMD64POPCNTL: @@ -1115,7 +1125,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpAMD64SETNEF: p := s.Prog(v.Op.Asm()) @@ -1164,39 +1174,31 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { if logopt.Enabled() { logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name) } - if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers - gc.Warnl(v.Pos, "generated nil check") + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") } case ssa.OpAMD64MOVBatomicload, ssa.OpAMD64MOVLatomicload, ssa.OpAMD64MOVQatomicload: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg0() case ssa.OpAMD64XCHGB, ssa.OpAMD64XCHGL, ssa.OpAMD64XCHGQ: - r := v.Reg0() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output[0] not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG - p.From.Reg = r + p.From.Reg = v.Reg0() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[1].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpAMD64XADDLlock, ssa.OpAMD64XADDQlock: - r := v.Reg0() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output[0] not in same register %s", v.LongString()) - } s.Prog(x86.ALOCK) p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG - p.From.Reg = r + p.From.Reg = v.Reg0() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[1].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpAMD64CMPXCHGLlock, ssa.OpAMD64CMPXCHGQlock: if v.Args[1].Reg() != x86.REG_AX { v.Fatalf("input[1] not in AX %s", v.LongString()) @@ -1207,7 +1209,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[2].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) p = s.Prog(x86.ASETEQ) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg0() @@ -1218,21 +1220,28 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpClobber: p := s.Prog(x86.AMOVL) p.From.Type = obj.TYPE_CONST p.From.Offset = 0xdeaddead p.To.Type = obj.TYPE_MEM p.To.Reg = x86.REG_SP - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) p = s.Prog(x86.AMOVL) p.From.Type = obj.TYPE_CONST p.From.Offset = 0xdeaddead p.To.Type = obj.TYPE_MEM p.To.Reg = x86.REG_SP - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) p.To.Offset += 4 + case ssa.OpClobberReg: + x := uint64(0xdeaddeaddeaddead) + p := s.Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_CONST + p.From.Offset = int64(x) + p.To.Type = obj.TYPE_REG + p.To.Reg = v.Reg() default: v.Fatalf("genValue not implemented: %s", v.LongString()) } @@ -1257,22 +1266,22 @@ var blockJump = [...]struct { ssa.BlockAMD64NAN: {x86.AJPS, x86.AJPC}, } -var eqfJumps = [2][2]gc.IndexJump{ +var eqfJumps = [2][2]ssagen.IndexJump{ {{Jump: x86.AJNE, Index: 1}, {Jump: x86.AJPS, Index: 1}}, // next == b.Succs[0] {{Jump: x86.AJNE, Index: 1}, {Jump: x86.AJPC, Index: 0}}, // next == b.Succs[1] } -var nefJumps = [2][2]gc.IndexJump{ +var nefJumps = [2][2]ssagen.IndexJump{ {{Jump: x86.AJNE, Index: 0}, {Jump: x86.AJPC, Index: 1}}, // next == b.Succs[0] {{Jump: x86.AJNE, Index: 0}, {Jump: x86.AJPS, Index: 0}}, // next == b.Succs[1] } -func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { switch b.Kind { case ssa.BlockPlain: if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockDefer: // defer returns in rax: @@ -1285,16 +1294,22 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.To.Reg = x86.REG_AX p = s.Prog(x86.AJNE) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[1].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()}) if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockExit: case ssa.BlockRet: s.Prog(obj.ARET) case ssa.BlockRetJmp: + if buildcfg.Experiment.RegabiG && s.ABI == obj.ABI0 && b.Aux.(*obj.LSym).ABI() == obj.ABIInternal { + // zeroing X15 when entering ABIInternal from ABI0 + opregreg(s, x86.AXORPS, x86.REG_X15, x86.REG_X15) + // set G register from TLS + getgFromTLS(s, x86.REG_R14) + } p := s.Prog(obj.ARET) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN @@ -1332,3 +1347,27 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { b.Fatalf("branch not implemented: %s", b.LongString()) } } + +func loadRegResults(s *ssagen.State, f *ssa.Func) { + for _, o := range f.OwnAux.ABIInfo().OutParams() { + n := o.Name.(*ir.Name) + rts, offs := o.RegisterTypesAndOffsets() + for i := range o.Registers { + p := s.Prog(loadByType(rts[i])) + p.From.Type = obj.TYPE_MEM + p.From.Name = obj.NAME_AUTO + p.From.Sym = n.Linksym() + p.From.Offset = n.FrameOffset() + offs[i] + p.To.Type = obj.TYPE_REG + p.To.Reg = ssa.ObjRegForAbiReg(o.Registers[i], f.Config) + } + } +} + +func spillArgReg(pp *objw.Progs, p *obj.Prog, f *ssa.Func, t *types.Type, reg int16, n *ir.Name, off int64) *obj.Prog { + p = pp.Append(p, storeByType(t), obj.TYPE_REG, reg, 0, obj.TYPE_MEM, 0, n.FrameOffset()+off) + p.To.Name = obj.NAME_PARAM + p.To.Sym = n.Linksym() + p.Pos = p.Pos.WithNotStmt() + return p +} diff --git a/src/cmd/compile/internal/arm/galign.go b/src/cmd/compile/internal/arm/galign.go index 20e2f43a91c0d7f162f3e5b6ea70f6d12f24e71a..d68500280d00b79376d976a39fab34286faa65a8 100644 --- a/src/cmd/compile/internal/arm/galign.go +++ b/src/cmd/compile/internal/arm/galign.go @@ -5,22 +5,22 @@ package arm import ( - "cmd/compile/internal/gc" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/internal/obj/arm" - "cmd/internal/objabi" + "internal/buildcfg" ) -func Init(arch *gc.Arch) { +func Init(arch *ssagen.ArchInfo) { arch.LinkArch = &arm.Linkarm arch.REGSP = arm.REGSP arch.MAXWIDTH = (1 << 32) - 1 - arch.SoftFloat = objabi.GOARM == 5 + arch.SoftFloat = buildcfg.GOARM == 5 arch.ZeroRange = zerorange arch.Ginsnop = ginsnop arch.Ginsnopdefer = ginsnop - arch.SSAMarkMoves = func(s *gc.SSAGenState, b *ssa.Block) {} + arch.SSAMarkMoves = func(s *ssagen.State, b *ssa.Block) {} arch.SSAGenValue = ssaGenValue arch.SSAGenBlock = ssaGenBlock } diff --git a/src/cmd/compile/internal/arm/ggen.go b/src/cmd/compile/internal/arm/ggen.go index bd8d7ff40b8f83f986143e874e6f2b6a0eec2a1b..f2c676300a93a5531c2f160fb0dba7f121068683 100644 --- a/src/cmd/compile/internal/arm/ggen.go +++ b/src/cmd/compile/internal/arm/ggen.go @@ -5,49 +5,51 @@ package arm import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/arm" ) -func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, r0 *uint32) *obj.Prog { +func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, r0 *uint32) *obj.Prog { if cnt == 0 { return p } if *r0 == 0 { - p = pp.Appendpp(p, arm.AMOVW, obj.TYPE_CONST, 0, 0, obj.TYPE_REG, arm.REG_R0, 0) + p = pp.Append(p, arm.AMOVW, obj.TYPE_CONST, 0, 0, obj.TYPE_REG, arm.REG_R0, 0) *r0 = 1 } - if cnt < int64(4*gc.Widthptr) { - for i := int64(0); i < cnt; i += int64(gc.Widthptr) { - p = pp.Appendpp(p, arm.AMOVW, obj.TYPE_REG, arm.REG_R0, 0, obj.TYPE_MEM, arm.REGSP, 4+off+i) + if cnt < int64(4*types.PtrSize) { + for i := int64(0); i < cnt; i += int64(types.PtrSize) { + p = pp.Append(p, arm.AMOVW, obj.TYPE_REG, arm.REG_R0, 0, obj.TYPE_MEM, arm.REGSP, 4+off+i) } - } else if cnt <= int64(128*gc.Widthptr) { - p = pp.Appendpp(p, arm.AADD, obj.TYPE_CONST, 0, 4+off, obj.TYPE_REG, arm.REG_R1, 0) + } else if cnt <= int64(128*types.PtrSize) { + p = pp.Append(p, arm.AADD, obj.TYPE_CONST, 0, 4+off, obj.TYPE_REG, arm.REG_R1, 0) p.Reg = arm.REGSP - p = pp.Appendpp(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) + p = pp.Append(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffzero - p.To.Offset = 4 * (128 - cnt/int64(gc.Widthptr)) + p.To.Sym = ir.Syms.Duffzero + p.To.Offset = 4 * (128 - cnt/int64(types.PtrSize)) } else { - p = pp.Appendpp(p, arm.AADD, obj.TYPE_CONST, 0, 4+off, obj.TYPE_REG, arm.REG_R1, 0) + p = pp.Append(p, arm.AADD, obj.TYPE_CONST, 0, 4+off, obj.TYPE_REG, arm.REG_R1, 0) p.Reg = arm.REGSP - p = pp.Appendpp(p, arm.AADD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, arm.REG_R2, 0) + p = pp.Append(p, arm.AADD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, arm.REG_R2, 0) p.Reg = arm.REG_R1 - p = pp.Appendpp(p, arm.AMOVW, obj.TYPE_REG, arm.REG_R0, 0, obj.TYPE_MEM, arm.REG_R1, 4) + p = pp.Append(p, arm.AMOVW, obj.TYPE_REG, arm.REG_R0, 0, obj.TYPE_MEM, arm.REG_R1, 4) p1 := p p.Scond |= arm.C_PBIT - p = pp.Appendpp(p, arm.ACMP, obj.TYPE_REG, arm.REG_R1, 0, obj.TYPE_NONE, 0, 0) + p = pp.Append(p, arm.ACMP, obj.TYPE_REG, arm.REG_R1, 0, obj.TYPE_NONE, 0, 0) p.Reg = arm.REG_R2 - p = pp.Appendpp(p, arm.ABNE, obj.TYPE_NONE, 0, 0, obj.TYPE_BRANCH, 0, 0) - gc.Patch(p, p1) + p = pp.Append(p, arm.ABNE, obj.TYPE_NONE, 0, 0, obj.TYPE_BRANCH, 0, 0) + p.To.SetTarget(p1) } return p } -func ginsnop(pp *gc.Progs) *obj.Prog { +func ginsnop(pp *objw.Progs) *obj.Prog { p := pp.Prog(arm.AAND) p.From.Type = obj.TYPE_REG p.From.Reg = arm.REG_R0 diff --git a/src/cmd/compile/internal/arm/ssa.go b/src/cmd/compile/internal/arm/ssa.go index 765a7715465ce8af6f6297ed37d6a924d060af9c..4b083cec46b4a5cff59981de626a75f62fc0e1b7 100644 --- a/src/cmd/compile/internal/arm/ssa.go +++ b/src/cmd/compile/internal/arm/ssa.go @@ -6,16 +6,18 @@ package arm import ( "fmt" + "internal/buildcfg" "math" "math/bits" - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/logopt" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/arm" - "cmd/internal/objabi" ) // loadByType returns the load instruction of the given type. @@ -91,7 +93,7 @@ func makeshift(reg int16, typ int64, s int64) shift { } // genshift generates a Prog for r = r0 op (r1 shifted by n) -func genshift(s *gc.SSAGenState, as obj.As, r0, r1, r int16, typ int64, n int64) *obj.Prog { +func genshift(s *ssagen.State, as obj.As, r0, r1, r int16, typ int64, n int64) *obj.Prog { p := s.Prog(as) p.From.Type = obj.TYPE_SHIFT p.From.Offset = int64(makeshift(r1, typ, n)) @@ -109,7 +111,7 @@ func makeregshift(r1 int16, typ int64, r2 int16) shift { } // genregshift generates a Prog for r = r0 op (r1 shifted by r2) -func genregshift(s *gc.SSAGenState, as obj.As, r0, r1, r2, r int16, typ int64) *obj.Prog { +func genregshift(s *ssagen.State, as obj.As, r0, r1, r2, r int16, typ int64) *obj.Prog { p := s.Prog(as) p.From.Type = obj.TYPE_SHIFT p.From.Offset = int64(makeregshift(r1, typ, r2)) @@ -143,7 +145,7 @@ func getBFC(v uint32) (uint32, uint32) { return 0xffffffff, 0 } -func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { +func ssaGenValue(s *ssagen.State, v *ssa.Value) { switch v.Op { case ssa.OpCopy, ssa.OpARMMOVWreg: if v.Type.IsMemory() { @@ -171,9 +173,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = y case ssa.OpARMMOVWnop: - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } // nothing to do case ssa.OpLoadReg: if v.Type.IsFlags() { @@ -181,7 +180,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { return } p := s.Prog(loadByType(v.Type)) - gc.AddrAuto(&p.From, v.Args[0]) + ssagen.AddrAuto(&p.From, v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpStoreReg: @@ -192,7 +191,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(storeByType(v.Type)) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() - gc.AddrAuto(&p.To, v) + ssagen.AddrAuto(&p.To, v) case ssa.OpARMADD, ssa.OpARMADC, ssa.OpARMSUB, @@ -280,14 +279,14 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = v.AuxInt >> 8 - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt & 0xff}) + p.SetFrom3Const(v.AuxInt & 0xff) p.Reg = v.Args[0].Reg() p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpARMANDconst, ssa.OpARMBICconst: // try to optimize ANDconst and BICconst to BFC, which saves bytes and ticks // BFC is only available on ARMv7, and its result and source are in the same register - if objabi.GOARM == 7 && v.Reg() == v.Args[0].Reg() { + if buildcfg.GOARM == 7 && v.Reg() == v.Args[0].Reg() { var val uint32 if v.Op == ssa.OpARMANDconst { val = ^uint32(v.AuxInt) @@ -300,7 +299,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(arm.ABFC) p.From.Type = obj.TYPE_CONST p.From.Offset = int64(width) - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: int64(lsb)}) + p.SetFrom3Const(int64(lsb)) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() break @@ -543,10 +542,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { v.Fatalf("aux is of unknown type %T", v.Aux) case *obj.LSym: wantreg = "SB" - gc.AddAux(&p.From, v) - case *gc.Node: + ssagen.AddAux(&p.From, v) + case *ir.Name: wantreg = "SP" - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) case nil: // No sym, just MOVW $off(SP), R wantreg = "SP" @@ -566,7 +565,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpARMMOVBstore, @@ -579,7 +578,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpARMMOVWloadidx, ssa.OpARMMOVBUloadidx, ssa.OpARMMOVBloadidx, ssa.OpARMMOVHUloadidx, ssa.OpARMMOVHloadidx: // this is just shift 0 bits fallthrough @@ -644,7 +643,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { default: } } - if objabi.GOARM >= 6 { + if buildcfg.GOARM >= 6 { // generate more efficient "MOVB/MOVBU/MOVH/MOVHU Reg@>0, Reg" on ARMv6 & ARMv7 genshift(s, v.Op.Asm(), 0, v.Args[0].Reg(), v.Reg(), arm.SHIFT_RR, 0) return @@ -655,6 +654,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpARMREV, ssa.OpARMREV16, ssa.OpARMRBIT, + ssa.OpARMSQRTF, ssa.OpARMSQRTD, ssa.OpARMNEGF, ssa.OpARMNEGD, @@ -700,7 +700,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Udiv + p.To.Sym = ir.Syms.Udiv case ssa.OpARMLoweredWB: p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM @@ -710,39 +710,39 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.BoundsCheckFunc[v.AuxInt] + p.To.Sym = ssagen.BoundsCheckFunc[v.AuxInt] s.UseArgs(8) // space used in callee args area by assembly stubs case ssa.OpARMLoweredPanicExtendA, ssa.OpARMLoweredPanicExtendB, ssa.OpARMLoweredPanicExtendC: p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.ExtendCheckFunc[v.AuxInt] + p.To.Sym = ssagen.ExtendCheckFunc[v.AuxInt] s.UseArgs(12) // space used in callee args area by assembly stubs case ssa.OpARMDUFFZERO: p := s.Prog(obj.ADUFFZERO) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffzero + p.To.Sym = ir.Syms.Duffzero p.To.Offset = v.AuxInt case ssa.OpARMDUFFCOPY: p := s.Prog(obj.ADUFFCOPY) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffcopy + p.To.Sym = ir.Syms.Duffcopy p.To.Offset = v.AuxInt case ssa.OpARMLoweredNilCheck: // Issue a load which will fault if arg is nil. p := s.Prog(arm.AMOVB) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = arm.REGTMP if logopt.Enabled() { logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name) } - if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers - gc.Warnl(v.Pos, "generated nil check") + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") } case ssa.OpARMLoweredZero: // MOVW.P Rarg2, 4(R1) @@ -777,7 +777,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p2.Reg = arm.REG_R1 p3 := s.Prog(arm.ABLE) p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) case ssa.OpARMLoweredMove: // MOVW.P 4(R1), Rtmp // MOVW.P Rtmp, 4(R2) @@ -818,7 +818,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.Reg = arm.REG_R1 p4 := s.Prog(arm.ABLE) p4.To.Type = obj.TYPE_BRANCH - gc.Patch(p4, p) + p4.To.SetTarget(p) case ssa.OpARMEqual, ssa.OpARMNotEqual, ssa.OpARMLessThan, @@ -844,12 +844,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Reg = v.Reg() case ssa.OpARMLoweredGetClosurePtr: // Closure pointer is R7 (arm.REGCTXT). - gc.CheckLoweredGetClosurePtr(v) + ssagen.CheckLoweredGetClosurePtr(v) case ssa.OpARMLoweredGetCallerSP: // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(arm.AMOVW) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -gc.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.FixedFrameSize() p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -861,7 +861,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { v.Fatalf("FlagConstant op should never make it to codegen %v", v.LongString()) case ssa.OpARMInvertFlags: v.Fatalf("InvertFlags should never make it to codegen %v", v.LongString()) - case ssa.OpClobber: + case ssa.OpClobber, ssa.OpClobberReg: // TODO: implement for clobberdead experiment. Nop is ok for now. default: v.Fatalf("genValue not implemented: %s", v.LongString()) @@ -899,24 +899,24 @@ var blockJump = map[ssa.BlockKind]struct { } // To model a 'LEnoov' ('<=' without overflow checking) branching -var leJumps = [2][2]gc.IndexJump{ +var leJumps = [2][2]ssagen.IndexJump{ {{Jump: arm.ABEQ, Index: 0}, {Jump: arm.ABPL, Index: 1}}, // next == b.Succs[0] {{Jump: arm.ABMI, Index: 0}, {Jump: arm.ABEQ, Index: 0}}, // next == b.Succs[1] } // To model a 'GTnoov' ('>' without overflow checking) branching -var gtJumps = [2][2]gc.IndexJump{ +var gtJumps = [2][2]ssagen.IndexJump{ {{Jump: arm.ABMI, Index: 1}, {Jump: arm.ABEQ, Index: 1}}, // next == b.Succs[0] {{Jump: arm.ABEQ, Index: 1}, {Jump: arm.ABPL, Index: 0}}, // next == b.Succs[1] } -func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { switch b.Kind { case ssa.BlockPlain: if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockDefer: @@ -929,11 +929,11 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.Reg = arm.REG_R0 p = s.Prog(arm.ABNE) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[1].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()}) if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockExit: diff --git a/src/cmd/compile/internal/arm64/galign.go b/src/cmd/compile/internal/arm64/galign.go index 40d6e17ae2296cb4961cc58b589dcb4b43961c48..d3db37e16f43bcbbb261d75078df6c8d126b4a72 100644 --- a/src/cmd/compile/internal/arm64/galign.go +++ b/src/cmd/compile/internal/arm64/galign.go @@ -5,12 +5,12 @@ package arm64 import ( - "cmd/compile/internal/gc" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/internal/obj/arm64" ) -func Init(arch *gc.Arch) { +func Init(arch *ssagen.ArchInfo) { arch.LinkArch = &arm64.Linkarm64 arch.REGSP = arm64.REGSP arch.MAXWIDTH = 1 << 50 @@ -20,7 +20,7 @@ func Init(arch *gc.Arch) { arch.Ginsnop = ginsnop arch.Ginsnopdefer = ginsnop - arch.SSAMarkMoves = func(s *gc.SSAGenState, b *ssa.Block) {} + arch.SSAMarkMoves = func(s *ssagen.State, b *ssa.Block) {} arch.SSAGenValue = ssaGenValue arch.SSAGenBlock = ssaGenBlock } diff --git a/src/cmd/compile/internal/arm64/ggen.go b/src/cmd/compile/internal/arm64/ggen.go index f3fec03854f16c8308f0efce89a33b3c665c8bd4..89be4964619c16a30f469de696451a0aec5bab51 100644 --- a/src/cmd/compile/internal/arm64/ggen.go +++ b/src/cmd/compile/internal/arm64/ggen.go @@ -5,13 +5,15 @@ package arm64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/arm64" - "cmd/internal/objabi" + "internal/buildcfg" ) -var darwin = objabi.GOOS == "darwin" || objabi.GOOS == "ios" +var darwin = buildcfg.GOOS == "darwin" || buildcfg.GOOS == "ios" func padframe(frame int64) int64 { // arm64 requires that the frame size (not counting saved FP&LR) @@ -22,52 +24,52 @@ func padframe(frame int64) int64 { return frame } -func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { +func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { if cnt == 0 { return p } - if cnt < int64(4*gc.Widthptr) { - for i := int64(0); i < cnt; i += int64(gc.Widthptr) { - p = pp.Appendpp(p, arm64.AMOVD, obj.TYPE_REG, arm64.REGZERO, 0, obj.TYPE_MEM, arm64.REGSP, 8+off+i) + if cnt < int64(4*types.PtrSize) { + for i := int64(0); i < cnt; i += int64(types.PtrSize) { + p = pp.Append(p, arm64.AMOVD, obj.TYPE_REG, arm64.REGZERO, 0, obj.TYPE_MEM, arm64.REGSP, 8+off+i) } - } else if cnt <= int64(128*gc.Widthptr) && !darwin { // darwin ld64 cannot handle BR26 reloc with non-zero addend - if cnt%(2*int64(gc.Widthptr)) != 0 { - p = pp.Appendpp(p, arm64.AMOVD, obj.TYPE_REG, arm64.REGZERO, 0, obj.TYPE_MEM, arm64.REGSP, 8+off) - off += int64(gc.Widthptr) - cnt -= int64(gc.Widthptr) + } else if cnt <= int64(128*types.PtrSize) && !darwin { // darwin ld64 cannot handle BR26 reloc with non-zero addend + if cnt%(2*int64(types.PtrSize)) != 0 { + p = pp.Append(p, arm64.AMOVD, obj.TYPE_REG, arm64.REGZERO, 0, obj.TYPE_MEM, arm64.REGSP, 8+off) + off += int64(types.PtrSize) + cnt -= int64(types.PtrSize) } - p = pp.Appendpp(p, arm64.AMOVD, obj.TYPE_REG, arm64.REGSP, 0, obj.TYPE_REG, arm64.REG_R20, 0) - p = pp.Appendpp(p, arm64.AADD, obj.TYPE_CONST, 0, 8+off, obj.TYPE_REG, arm64.REG_R20, 0) + p = pp.Append(p, arm64.AMOVD, obj.TYPE_REG, arm64.REGSP, 0, obj.TYPE_REG, arm64.REG_R20, 0) + p = pp.Append(p, arm64.AADD, obj.TYPE_CONST, 0, 8+off, obj.TYPE_REG, arm64.REG_R20, 0) p.Reg = arm64.REG_R20 - p = pp.Appendpp(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) + p = pp.Append(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffzero - p.To.Offset = 4 * (64 - cnt/(2*int64(gc.Widthptr))) + p.To.Sym = ir.Syms.Duffzero + p.To.Offset = 4 * (64 - cnt/(2*int64(types.PtrSize))) } else { // Not using REGTMP, so this is async preemptible (async preemption clobbers REGTMP). // We are at the function entry, where no register is live, so it is okay to clobber // other registers const rtmp = arm64.REG_R20 - p = pp.Appendpp(p, arm64.AMOVD, obj.TYPE_CONST, 0, 8+off-8, obj.TYPE_REG, rtmp, 0) - p = pp.Appendpp(p, arm64.AMOVD, obj.TYPE_REG, arm64.REGSP, 0, obj.TYPE_REG, arm64.REGRT1, 0) - p = pp.Appendpp(p, arm64.AADD, obj.TYPE_REG, rtmp, 0, obj.TYPE_REG, arm64.REGRT1, 0) + p = pp.Append(p, arm64.AMOVD, obj.TYPE_CONST, 0, 8+off-8, obj.TYPE_REG, rtmp, 0) + p = pp.Append(p, arm64.AMOVD, obj.TYPE_REG, arm64.REGSP, 0, obj.TYPE_REG, arm64.REGRT1, 0) + p = pp.Append(p, arm64.AADD, obj.TYPE_REG, rtmp, 0, obj.TYPE_REG, arm64.REGRT1, 0) p.Reg = arm64.REGRT1 - p = pp.Appendpp(p, arm64.AMOVD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, rtmp, 0) - p = pp.Appendpp(p, arm64.AADD, obj.TYPE_REG, rtmp, 0, obj.TYPE_REG, arm64.REGRT2, 0) + p = pp.Append(p, arm64.AMOVD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, rtmp, 0) + p = pp.Append(p, arm64.AADD, obj.TYPE_REG, rtmp, 0, obj.TYPE_REG, arm64.REGRT2, 0) p.Reg = arm64.REGRT1 - p = pp.Appendpp(p, arm64.AMOVD, obj.TYPE_REG, arm64.REGZERO, 0, obj.TYPE_MEM, arm64.REGRT1, int64(gc.Widthptr)) + p = pp.Append(p, arm64.AMOVD, obj.TYPE_REG, arm64.REGZERO, 0, obj.TYPE_MEM, arm64.REGRT1, int64(types.PtrSize)) p.Scond = arm64.C_XPRE p1 := p - p = pp.Appendpp(p, arm64.ACMP, obj.TYPE_REG, arm64.REGRT1, 0, obj.TYPE_NONE, 0, 0) + p = pp.Append(p, arm64.ACMP, obj.TYPE_REG, arm64.REGRT1, 0, obj.TYPE_NONE, 0, 0) p.Reg = arm64.REGRT2 - p = pp.Appendpp(p, arm64.ABNE, obj.TYPE_NONE, 0, 0, obj.TYPE_BRANCH, 0, 0) - gc.Patch(p, p1) + p = pp.Append(p, arm64.ABNE, obj.TYPE_NONE, 0, 0, obj.TYPE_BRANCH, 0, 0) + p.To.SetTarget(p1) } return p } -func ginsnop(pp *gc.Progs) *obj.Prog { +func ginsnop(pp *objw.Progs) *obj.Prog { p := pp.Prog(arm64.AHINT) p.From.Type = obj.TYPE_CONST return p diff --git a/src/cmd/compile/internal/arm64/ssa.go b/src/cmd/compile/internal/arm64/ssa.go index 43588511ab3b1321c348140db388ad52345f2ed3..0c997bc4b3e82adaee8e20ea5bafaf29f6a01c7b 100644 --- a/src/cmd/compile/internal/arm64/ssa.go +++ b/src/cmd/compile/internal/arm64/ssa.go @@ -7,9 +7,11 @@ package arm64 import ( "math" - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/logopt" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/arm64" @@ -81,7 +83,7 @@ func makeshift(reg int16, typ int64, s int64) int64 { } // genshift generates a Prog for r = r0 op (r1 shifted by n) -func genshift(s *gc.SSAGenState, as obj.As, r0, r1, r int16, typ int64, n int64) *obj.Prog { +func genshift(s *ssagen.State, as obj.As, r0, r1, r int16, typ int64, n int64) *obj.Prog { p := s.Prog(as) p.From.Type = obj.TYPE_SHIFT p.From.Offset = makeshift(r1, typ, n) @@ -98,9 +100,11 @@ func genIndexedOperand(v *ssa.Value) obj.Addr { // Reg: base register, Index: (shifted) index register mop := obj.Addr{Type: obj.TYPE_MEM, Reg: v.Args[0].Reg()} switch v.Op { - case ssa.OpARM64MOVDloadidx8, ssa.OpARM64MOVDstoreidx8, ssa.OpARM64MOVDstorezeroidx8: + case ssa.OpARM64MOVDloadidx8, ssa.OpARM64MOVDstoreidx8, ssa.OpARM64MOVDstorezeroidx8, + ssa.OpARM64FMOVDloadidx8, ssa.OpARM64FMOVDstoreidx8: mop.Index = arm64.REG_LSL | 3<<5 | v.Args[1].Reg()&31 - case ssa.OpARM64MOVWloadidx4, ssa.OpARM64MOVWUloadidx4, ssa.OpARM64MOVWstoreidx4, ssa.OpARM64MOVWstorezeroidx4: + case ssa.OpARM64MOVWloadidx4, ssa.OpARM64MOVWUloadidx4, ssa.OpARM64MOVWstoreidx4, ssa.OpARM64MOVWstorezeroidx4, + ssa.OpARM64FMOVSloadidx4, ssa.OpARM64FMOVSstoreidx4: mop.Index = arm64.REG_LSL | 2<<5 | v.Args[1].Reg()&31 case ssa.OpARM64MOVHloadidx2, ssa.OpARM64MOVHUloadidx2, ssa.OpARM64MOVHstoreidx2, ssa.OpARM64MOVHstorezeroidx2: mop.Index = arm64.REG_LSL | 1<<5 | v.Args[1].Reg()&31 @@ -110,7 +114,7 @@ func genIndexedOperand(v *ssa.Value) obj.Addr { return mop } -func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { +func ssaGenValue(s *ssagen.State, v *ssa.Value) { switch v.Op { case ssa.OpCopy, ssa.OpARM64MOVDreg: if v.Type.IsMemory() { @@ -138,9 +142,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = y case ssa.OpARM64MOVDnop: - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } // nothing to do case ssa.OpLoadReg: if v.Type.IsFlags() { @@ -148,7 +149,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { return } p := s.Prog(loadByType(v.Type)) - gc.AddrAuto(&p.From, v.Args[0]) + ssagen.AddrAuto(&p.From, v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpStoreReg: @@ -159,7 +160,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(storeByType(v.Type)) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() - gc.AddrAuto(&p.To, v) + ssagen.AddrAuto(&p.To, v) case ssa.OpARM64ADD, ssa.OpARM64SUB, ssa.OpARM64AND, @@ -228,7 +229,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.Reg = ra p.From.Type = obj.TYPE_REG p.From.Reg = rm - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: rn}) + p.SetFrom3Reg(rn) p.To.Type = obj.TYPE_REG p.To.Reg = rt case ssa.OpARM64ADDconst, @@ -291,7 +292,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = v.AuxInt - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: v.Args[0].Reg()}) + p.SetFrom3Reg(v.Args[0].Reg()) p.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -393,10 +394,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { v.Fatalf("aux is of unknown type %T", v.Aux) case *obj.LSym: wantreg = "SB" - gc.AddAux(&p.From, v) - case *gc.Node: + ssagen.AddAux(&p.From, v) + case *ir.Name: wantreg = "SP" - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) case nil: // No sym, just MOVD $off(SP), R wantreg = "SP" @@ -417,7 +418,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpARM64MOVBloadidx, @@ -433,7 +434,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpARM64MOVHUloadidx2, ssa.OpARM64MOVWloadidx4, ssa.OpARM64MOVWUloadidx4, - ssa.OpARM64MOVDloadidx8: + ssa.OpARM64MOVDloadidx8, + ssa.OpARM64FMOVDloadidx8, + ssa.OpARM64FMOVSloadidx4: p := s.Prog(v.Op.Asm()) p.From = genIndexedOperand(v) p.To.Type = obj.TYPE_REG @@ -444,7 +447,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg0() case ssa.OpARM64MOVBstore, @@ -461,7 +464,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpARM64MOVBstoreidx, ssa.OpARM64MOVHstoreidx, ssa.OpARM64MOVWstoreidx, @@ -470,7 +473,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpARM64FMOVDstoreidx, ssa.OpARM64MOVHstoreidx2, ssa.OpARM64MOVWstoreidx4, - ssa.OpARM64MOVDstoreidx8: + ssa.OpARM64FMOVSstoreidx4, + ssa.OpARM64MOVDstoreidx8, + ssa.OpARM64FMOVDstoreidx8: p := s.Prog(v.Op.Asm()) p.To = genIndexedOperand(v) p.From.Type = obj.TYPE_REG @@ -482,7 +487,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Offset = int64(v.Args[2].Reg()) p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpARM64MOVBstorezero, ssa.OpARM64MOVHstorezero, ssa.OpARM64MOVWstorezero, @@ -492,7 +497,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = arm64.REGZERO p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpARM64MOVBstorezeroidx, ssa.OpARM64MOVHstorezeroidx, ssa.OpARM64MOVWstorezeroidx, @@ -511,20 +516,16 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Offset = int64(arm64.REGZERO) p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpARM64BFI, ssa.OpARM64BFXIL: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = v.AuxInt >> 8 - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt & 0xff}) + p.SetFrom3Const(v.AuxInt & 0xff) p.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.OpARM64SBFIZ, ssa.OpARM64SBFX, ssa.OpARM64UBFIZ, @@ -532,7 +533,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = v.AuxInt >> 8 - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt & 0xff}) + p.SetFrom3Const(v.AuxInt & 0xff) p.Reg = v.Args[0].Reg() p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -580,7 +581,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p2.From.Type = obj.TYPE_REG p2.From.Reg = arm64.REGTMP p2.To.Type = obj.TYPE_BRANCH - gc.Patch(p2, p) + p2.To.SetTarget(p) case ssa.OpARM64LoweredAtomicExchange64Variant, ssa.OpARM64LoweredAtomicExchange32Variant: swap := arm64.ASWPALD @@ -634,7 +635,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.From.Type = obj.TYPE_REG p3.From.Reg = arm64.REGTMP p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) case ssa.OpARM64LoweredAtomicAdd64Variant, ssa.OpARM64LoweredAtomicAdd32Variant: // LDADDAL Rarg1, (Rarg0), Rout @@ -698,13 +699,13 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p4.From.Type = obj.TYPE_REG p4.From.Reg = arm64.REGTMP p4.To.Type = obj.TYPE_BRANCH - gc.Patch(p4, p) + p4.To.SetTarget(p) p5 := s.Prog(arm64.ACSET) p5.From.Type = obj.TYPE_REG // assembler encodes conditional bits in Reg p5.From.Reg = arm64.COND_EQ p5.To.Type = obj.TYPE_REG p5.To.Reg = out - gc.Patch(p2, p5) + p2.To.SetTarget(p5) case ssa.OpARM64LoweredAtomicCas64Variant, ssa.OpARM64LoweredAtomicCas32Variant: // Rarg0: ptr @@ -792,7 +793,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.From.Type = obj.TYPE_REG p3.From.Reg = arm64.REGTMP p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) case ssa.OpARM64LoweredAtomicAnd8Variant, ssa.OpARM64LoweredAtomicAnd32Variant: atomic_clear := arm64.ALDCLRALW @@ -892,6 +893,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpARM64FMOVSgpfp, ssa.OpARM64FNEGS, ssa.OpARM64FNEGD, + ssa.OpARM64FSQRTS, ssa.OpARM64FSQRTD, ssa.OpARM64FCVTZSSW, ssa.OpARM64FCVTZSDW, @@ -913,6 +915,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpARM64FCVTDS, ssa.OpARM64REV, ssa.OpARM64REVW, + ssa.OpARM64REV16, ssa.OpARM64REV16W, ssa.OpARM64RBIT, ssa.OpARM64RBITW, @@ -951,7 +954,21 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Type = obj.TYPE_REG // assembler encodes conditional bits in Reg p.From.Reg = condBits[ssa.Op(v.AuxInt)] p.Reg = v.Args[0].Reg() - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: r1}) + p.SetFrom3Reg(r1) + p.To.Type = obj.TYPE_REG + p.To.Reg = v.Reg() + case ssa.OpARM64CSINC, ssa.OpARM64CSINV, ssa.OpARM64CSNEG: + p := s.Prog(v.Op.Asm()) + p.From.Type = obj.TYPE_REG // assembler encodes conditional bits in Reg + p.From.Reg = condBits[ssa.Op(v.AuxInt)] + p.Reg = v.Args[0].Reg() + p.SetFrom3Reg(v.Args[1].Reg()) + p.To.Type = obj.TYPE_REG + p.To.Reg = v.Reg() + case ssa.OpARM64CSETM: + p := s.Prog(arm64.ACSETM) + p.From.Type = obj.TYPE_REG // assembler encodes conditional bits in Reg + p.From.Reg = condBits[ssa.Op(v.AuxInt)] p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpARM64DUFFZERO: @@ -959,7 +976,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ADUFFZERO) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffzero + p.To.Sym = ir.Syms.Duffzero p.To.Offset = v.AuxInt case ssa.OpARM64LoweredZero: // STP.P (ZR,ZR), 16(R16) @@ -980,12 +997,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p2.Reg = arm64.REG_R16 p3 := s.Prog(arm64.ABLE) p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) case ssa.OpARM64DUFFCOPY: p := s.Prog(obj.ADUFFCOPY) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffcopy + p.To.Sym = ir.Syms.Duffcopy p.To.Offset = v.AuxInt case ssa.OpARM64LoweredMove: // MOVD.P 8(R16), Rtmp @@ -1013,7 +1030,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.Reg = arm64.REG_R16 p4 := s.Prog(arm64.ABLE) p4.To.Type = obj.TYPE_BRANCH - gc.Patch(p4, p) + p4.To.SetTarget(p) case ssa.OpARM64CALLstatic, ssa.OpARM64CALLclosure, ssa.OpARM64CALLinter: s.Call(v) case ssa.OpARM64LoweredWB: @@ -1025,21 +1042,21 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.BoundsCheckFunc[v.AuxInt] + p.To.Sym = ssagen.BoundsCheckFunc[v.AuxInt] s.UseArgs(16) // space used in callee args area by assembly stubs case ssa.OpARM64LoweredNilCheck: // Issue a load which will fault if arg is nil. p := s.Prog(arm64.AMOVB) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = arm64.REGTMP if logopt.Enabled() { logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name) } - if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Line==1 in generated wrappers - gc.Warnl(v.Pos, "generated nil check") + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Line==1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") } case ssa.OpARM64Equal, ssa.OpARM64NotEqual, @@ -1067,12 +1084,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Reg = v.Reg() case ssa.OpARM64LoweredGetClosurePtr: // Closure pointer is R26 (arm64.REGCTXT). - gc.CheckLoweredGetClosurePtr(v) + ssagen.CheckLoweredGetClosurePtr(v) case ssa.OpARM64LoweredGetCallerSP: // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(arm64.AMOVD) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -gc.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.FixedFrameSize() p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -1084,7 +1101,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { v.Fatalf("FlagConstant op should never make it to codegen %v", v.LongString()) case ssa.OpARM64InvertFlags: v.Fatalf("InvertFlags should never make it to codegen %v", v.LongString()) - case ssa.OpClobber: + case ssa.OpClobber, ssa.OpClobberReg: // TODO: implement for clobberdead experiment. Nop is ok for now. default: v.Fatalf("genValue not implemented: %s", v.LongString()) @@ -1142,24 +1159,24 @@ var blockJump = map[ssa.BlockKind]struct { } // To model a 'LEnoov' ('<=' without overflow checking) branching -var leJumps = [2][2]gc.IndexJump{ +var leJumps = [2][2]ssagen.IndexJump{ {{Jump: arm64.ABEQ, Index: 0}, {Jump: arm64.ABPL, Index: 1}}, // next == b.Succs[0] {{Jump: arm64.ABMI, Index: 0}, {Jump: arm64.ABEQ, Index: 0}}, // next == b.Succs[1] } // To model a 'GTnoov' ('>' without overflow checking) branching -var gtJumps = [2][2]gc.IndexJump{ +var gtJumps = [2][2]ssagen.IndexJump{ {{Jump: arm64.ABMI, Index: 1}, {Jump: arm64.ABEQ, Index: 1}}, // next == b.Succs[0] {{Jump: arm64.ABEQ, Index: 1}, {Jump: arm64.ABPL, Index: 0}}, // next == b.Succs[1] } -func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { switch b.Kind { case ssa.BlockPlain: if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockDefer: @@ -1172,11 +1189,11 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.Reg = arm64.REG_R0 p = s.Prog(arm64.ABNE) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[1].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()}) if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockExit: diff --git a/src/cmd/compile/internal/gc/racewalk.go b/src/cmd/compile/internal/base/base.go similarity index 52% rename from src/cmd/compile/internal/gc/racewalk.go rename to src/cmd/compile/internal/base/base.go index 35526174010fa361be76e2938900cc2c5067aa80..4c2516f60e364b5d177707daaa8584c0e1920777 100644 --- a/src/cmd/compile/internal/gc/racewalk.go +++ b/src/cmd/compile/internal/base/base.go @@ -1,15 +1,43 @@ -// Copyright 2012 The Go Authors. All rights reserved. +// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package base import ( - "cmd/compile/internal/types" - "cmd/internal/src" - "cmd/internal/sys" + "os" ) +var atExitFuncs []func() + +func AtExit(f func()) { + atExitFuncs = append(atExitFuncs, f) +} + +func Exit(code int) { + for i := len(atExitFuncs) - 1; i >= 0; i-- { + f := atExitFuncs[i] + atExitFuncs = atExitFuncs[:i] + f() + } + os.Exit(code) +} + +// To enable tracing support (-t flag), set EnableTrace to true. +const EnableTrace = false + +func Compiling(pkgs []string) bool { + if Ctxt.Pkgpath != "" { + for _, p := range pkgs { + if Ctxt.Pkgpath == p { + return true + } + } + } + + return false +} + // The racewalk pass is currently handled in three parts. // // First, for flag_race, it inserts calls to racefuncenter and @@ -32,7 +60,7 @@ import ( // Do not instrument the following packages at all, // at best instrumentation would cause infinite recursion. -var omit_pkgs = []string{ +var NoInstrumentPkgs = []string{ "runtime/internal/atomic", "runtime/internal/sys", "runtime/internal/math", @@ -42,52 +70,6 @@ var omit_pkgs = []string{ "internal/cpu", } -// Don't insert racefuncenterfp/racefuncexit into the following packages. +// Don't insert racefuncenter/racefuncexit into the following packages. // Memory accesses in the packages are either uninteresting or will cause false positives. -var norace_inst_pkgs = []string{"sync", "sync/atomic"} - -func ispkgin(pkgs []string) bool { - if myimportpath != "" { - for _, p := range pkgs { - if myimportpath == p { - return true - } - } - } - - return false -} - -func instrument(fn *Node) { - if fn.Func.Pragma&Norace != 0 { - return - } - - if !flag_race || !ispkgin(norace_inst_pkgs) { - fn.Func.SetInstrumentBody(true) - } - - if flag_race { - lno := lineno - lineno = src.NoXPos - - if thearch.LinkArch.Arch.Family != sys.AMD64 { - fn.Func.Enter.Prepend(mkcall("racefuncenterfp", nil, nil)) - fn.Func.Exit.Append(mkcall("racefuncexit", nil, nil)) - } else { - - // nodpc is the PC of the caller as extracted by - // getcallerpc. We use -widthptr(FP) for x86. - // This only works for amd64. This will not - // work on arm or others that might support - // race in the future. - nodpc := nodfp.copy() - nodpc.Type = types.Types[TUINTPTR] - nodpc.Xoffset = int64(-Widthptr) - fn.Func.Dcl = append(fn.Func.Dcl, nodpc) - fn.Func.Enter.Prepend(mkcall("racefuncenter", nil, nil, nodpc)) - fn.Func.Exit.Append(mkcall("racefuncexit", nil, nil)) - } - lineno = lno - } -} +var NoRacePkgs = []string{"sync", "sync/atomic"} diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go new file mode 100644 index 0000000000000000000000000000000000000000..71712ab1a56fd281f52d3befede9b833caa70fbd --- /dev/null +++ b/src/cmd/compile/internal/base/debug.go @@ -0,0 +1,189 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Debug arguments, set by -d flag. + +package base + +import ( + "fmt" + "log" + "os" + "reflect" + "strconv" + "strings" +) + +// Debug holds the parsed debugging configuration values. +var Debug DebugFlags + +// DebugFlags defines the debugging configuration values (see var Debug). +// Each struct field is a different value, named for the lower-case of the field name. +// Each field must be an int or string and must have a `help` struct tag. +// +// The -d option takes a comma-separated list of settings. +// Each setting is name=value; for ints, name is short for name=1. +type DebugFlags struct { + Append int `help:"print information about append compilation"` + Checkptr int `help:"instrument unsafe pointer conversions"` + Closure int `help:"print information about closure compilation"` + DclStack int `help:"run internal dclstack check"` + Defer int `help:"print information about defer compilation"` + DisableNil int `help:"disable nil checks"` + DumpPtrs int `help:"show Node pointers values in dump output"` + DwarfInl int `help:"print information about DWARF inlined function creation"` + Export int `help:"print export data"` + GCProg int `help:"print dump of GC programs"` + InlFuncsWithClosures int `help:"allow functions with closures to be inlined"` + Libfuzzer int `help:"enable coverage instrumentation for libfuzzer"` + LocationLists int `help:"print information about DWARF location list creation"` + Nil int `help:"print information about nil checks"` + NoOpenDefer int `help:"disable open-coded defers"` + PCTab string `help:"print named pc-value table"` + Panic int `help:"show all compiler panics"` + Slice int `help:"print information about slice compilation"` + SoftFloat int `help:"force compiler to emit soft-float code"` + TypeAssert int `help:"print information about type assertion inlining"` + TypecheckInl int `help:"eager typechecking of inline function bodies"` + WB int `help:"print information about write barriers"` + ABIWrap int `help:"print information about ABI wrapper generation"` + + any bool // set when any of the values have been set +} + +// Any reports whether any of the debug flags have been set. +func (d *DebugFlags) Any() bool { return d.any } + +type debugField struct { + name string + help string + val interface{} // *int or *string +} + +var debugTab []debugField + +func init() { + v := reflect.ValueOf(&Debug).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.Name == "any" { + continue + } + name := strings.ToLower(f.Name) + help := f.Tag.Get("help") + if help == "" { + panic(fmt.Sprintf("base.Debug.%s is missing help text", f.Name)) + } + ptr := v.Field(i).Addr().Interface() + switch ptr.(type) { + default: + panic(fmt.Sprintf("base.Debug.%s has invalid type %v (must be int or string)", f.Name, f.Type)) + case *int, *string: + // ok + } + debugTab = append(debugTab, debugField{name, help, ptr}) + } +} + +// DebugSSA is called to set a -d ssa/... option. +// If nil, those options are reported as invalid options. +// If DebugSSA returns a non-empty string, that text is reported as a compiler error. +var DebugSSA func(phase, flag string, val int, valString string) string + +// parseDebug parses the -d debug string argument. +func parseDebug(debugstr string) { + // parse -d argument + if debugstr == "" { + return + } + Debug.any = true +Split: + for _, name := range strings.Split(debugstr, ",") { + if name == "" { + continue + } + // display help about the -d option itself and quit + if name == "help" { + fmt.Print(debugHelpHeader) + maxLen := len("ssa/help") + for _, t := range debugTab { + if len(t.name) > maxLen { + maxLen = len(t.name) + } + } + for _, t := range debugTab { + fmt.Printf("\t%-*s\t%s\n", maxLen, t.name, t.help) + } + // ssa options have their own help + fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging") + fmt.Print(debugHelpFooter) + os.Exit(0) + } + val, valstring, haveInt := 1, "", true + if i := strings.IndexAny(name, "=:"); i >= 0 { + var err error + name, valstring = name[:i], name[i+1:] + val, err = strconv.Atoi(valstring) + if err != nil { + val, haveInt = 1, false + } + } + for _, t := range debugTab { + if t.name != name { + continue + } + switch vp := t.val.(type) { + case nil: + // Ignore + case *string: + *vp = valstring + case *int: + if !haveInt { + log.Fatalf("invalid debug value %v", name) + } + *vp = val + default: + panic("bad debugtab type") + } + continue Split + } + // special case for ssa for now + if DebugSSA != nil && strings.HasPrefix(name, "ssa/") { + // expect form ssa/phase/flag + // e.g. -d=ssa/generic_cse/time + // _ in phase name also matches space + phase := name[4:] + flag := "debug" // default flag is debug + if i := strings.Index(phase, "/"); i >= 0 { + flag = phase[i+1:] + phase = phase[:i] + } + err := DebugSSA(phase, flag, val, valstring) + if err != "" { + log.Fatalf(err) + } + continue Split + } + log.Fatalf("unknown debug key -d %s\n", name) + } +} + +const debugHelpHeader = `usage: -d arg[,arg]* and arg is [=] + + is one of: + +` + +const debugHelpFooter = ` + is key-specific. + +Key "checkptr" supports values: + "0": instrumentation disabled + "1": conversions involving unsafe.Pointer are instrumented + "2": conversions to unsafe.Pointer force heap allocation + +Key "pctab" supports values: + "pctospadj", "pctofile", "pctoline", "pctoinline", "pctopcdata" +` diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go new file mode 100644 index 0000000000000000000000000000000000000000..42c0c1b94b559c17ae593b9592b7ec5152e3a5f8 --- /dev/null +++ b/src/cmd/compile/internal/base/flag.go @@ -0,0 +1,469 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package base + +import ( + "encoding/json" + "flag" + "fmt" + "internal/buildcfg" + "io/ioutil" + "log" + "os" + "reflect" + "runtime" + "strings" + + "cmd/internal/objabi" + "cmd/internal/sys" +) + +func usage() { + fmt.Fprintf(os.Stderr, "usage: compile [options] file.go...\n") + objabi.Flagprint(os.Stderr) + Exit(2) +} + +// Flag holds the parsed command-line flags. +// See ParseFlag for non-zero defaults. +var Flag CmdFlags + +// A CountFlag is a counting integer flag. +// It accepts -name=value to set the value directly, +// but it also accepts -name with no =value to increment the count. +type CountFlag int + +// CmdFlags defines the command-line flags (see var Flag). +// Each struct field is a different flag, by default named for the lower-case of the field name. +// If the flag name is a single letter, the default flag name is left upper-case. +// If the flag name is "Lower" followed by a single letter, the default flag name is the lower-case of the last letter. +// +// If this default flag name can't be made right, the `flag` struct tag can be used to replace it, +// but this should be done only in exceptional circumstances: it helps everyone if the flag name +// is obvious from the field name when the flag is used elsewhere in the compiler sources. +// The `flag:"-"` struct tag makes a field invisible to the flag logic and should also be used sparingly. +// +// Each field must have a `help` struct tag giving the flag help message. +// +// The allowed field types are bool, int, string, pointers to those (for values stored elsewhere), +// CountFlag (for a counting flag), and func(string) (for a flag that uses special code for parsing). +type CmdFlags struct { + // Single letters + B CountFlag "help:\"disable bounds checking\"" + C CountFlag "help:\"disable printing of columns in error messages\"" + D string "help:\"set relative `path` for local imports\"" + E CountFlag "help:\"debug symbol export\"" + G CountFlag "help:\"accept generic code\"" + I func(string) "help:\"add `directory` to import search path\"" + K CountFlag "help:\"debug missing line numbers\"" + L CountFlag "help:\"show full file names in error messages\"" + N CountFlag "help:\"disable optimizations\"" + S CountFlag "help:\"print assembly listing\"" + // V is added by objabi.AddVersionFlag + W CountFlag "help:\"debug parse tree after type checking\"" + + LowerC int "help:\"concurrency during compilation (1 means no concurrency)\"" + LowerD func(string) "help:\"enable debugging settings; try -d help\"" + LowerE CountFlag "help:\"no limit on number of errors reported\"" + LowerH CountFlag "help:\"halt on error\"" + LowerJ CountFlag "help:\"debug runtime-initialized variables\"" + LowerL CountFlag "help:\"disable inlining\"" + LowerM CountFlag "help:\"print optimization decisions\"" + LowerO string "help:\"write output to `file`\"" + LowerP *string "help:\"set expected package import `path`\"" // &Ctxt.Pkgpath, set below + LowerR CountFlag "help:\"debug generated wrappers\"" + LowerT bool "help:\"enable tracing for debugging the compiler\"" + LowerW CountFlag "help:\"debug type checking\"" + LowerV *bool "help:\"increase debug verbosity\"" + + // Special characters + Percent int "flag:\"%\" help:\"debug non-static initializers\"" + CompilingRuntime bool "flag:\"+\" help:\"compiling runtime\"" + + // Longer names + AsmHdr string "help:\"write assembly header to `file`\"" + Bench string "help:\"append benchmark times to `file`\"" + BlockProfile string "help:\"write block profile to `file`\"" + BuildID string "help:\"record `id` as the build id in the export metadata\"" + CPUProfile string "help:\"write cpu profile to `file`\"" + Complete bool "help:\"compiling complete package (no C or assembly)\"" + ClobberDead bool "help:\"clobber dead stack slots (for debugging)\"" + ClobberDeadReg bool "help:\"clobber dead registers (for debugging)\"" + Dwarf bool "help:\"generate DWARF symbols\"" + DwarfBASEntries *bool "help:\"use base address selection entries in DWARF\"" // &Ctxt.UseBASEntries, set below + DwarfLocationLists *bool "help:\"add location lists to DWARF in optimized mode\"" // &Ctxt.Flag_locationlists, set below + Dynlink *bool "help:\"support references to Go symbols defined in other shared libraries\"" // &Ctxt.Flag_dynlink, set below + EmbedCfg func(string) "help:\"read go:embed configuration from `file`\"" + GenDwarfInl int "help:\"generate DWARF inline info records\"" // 0=disabled, 1=funcs, 2=funcs+formals/locals + GoVersion string "help:\"required version of the runtime\"" + ImportCfg func(string) "help:\"read import configuration from `file`\"" + ImportMap func(string) "help:\"add `definition` of the form source=actual to import map\"" + InstallSuffix string "help:\"set pkg directory `suffix`\"" + JSON string "help:\"version,file for JSON compiler/optimizer detail output\"" + Lang string "help:\"Go language version source code expects\"" + LinkObj string "help:\"write linker-specific object to `file`\"" + LinkShared *bool "help:\"generate code that will be linked against Go shared libraries\"" // &Ctxt.Flag_linkshared, set below + Live CountFlag "help:\"debug liveness analysis\"" + MSan bool "help:\"build code compatible with C/C++ memory sanitizer\"" + MemProfile string "help:\"write memory profile to `file`\"" + MemProfileRate int64 "help:\"set runtime.MemProfileRate to `rate`\"" + MutexProfile string "help:\"write mutex profile to `file`\"" + NoLocalImports bool "help:\"reject local (relative) imports\"" + Pack bool "help:\"write to file.a instead of file.o\"" + Race bool "help:\"enable race detector\"" + Shared *bool "help:\"generate code that can be linked into a shared library\"" // &Ctxt.Flag_shared, set below + SmallFrames bool "help:\"reduce the size limit for stack allocated objects\"" // small stacks, to diagnose GC latency; see golang.org/issue/27732 + Spectre string "help:\"enable spectre mitigations in `list` (all, index, ret)\"" + Std bool "help:\"compiling standard library\"" + SymABIs string "help:\"read symbol ABIs from `file`\"" + TraceProfile string "help:\"write an execution trace to `file`\"" + TrimPath string "help:\"remove `prefix` from recorded source file paths\"" + WB bool "help:\"enable write barrier\"" // TODO: remove + + // Configuration derived from flags; not a flag itself. + Cfg struct { + Embed struct { // set by -embedcfg + Patterns map[string][]string + Files map[string]string + } + ImportDirs []string // appended to by -I + ImportMap map[string]string // set by -importmap OR -importcfg + PackageFile map[string]string // set by -importcfg; nil means not in use + SpectreIndex bool // set by -spectre=index or -spectre=all + // Whether we are adding any sort of code instrumentation, such as + // when the race detector is enabled. + Instrumenting bool + } +} + +// ParseFlags parses the command-line flags into Flag. +func ParseFlags() { + Flag.I = addImportDir + + Flag.LowerC = 1 + Flag.LowerD = parseDebug + Flag.LowerP = &Ctxt.Pkgpath + Flag.LowerV = &Ctxt.Debugvlog + + Flag.Dwarf = buildcfg.GOARCH != "wasm" + Flag.DwarfBASEntries = &Ctxt.UseBASEntries + Flag.DwarfLocationLists = &Ctxt.Flag_locationlists + *Flag.DwarfLocationLists = true + Flag.Dynlink = &Ctxt.Flag_dynlink + Flag.EmbedCfg = readEmbedCfg + Flag.GenDwarfInl = 2 + Flag.ImportCfg = readImportCfg + Flag.ImportMap = addImportMap + Flag.LinkShared = &Ctxt.Flag_linkshared + Flag.Shared = &Ctxt.Flag_shared + Flag.WB = true + Debug.InlFuncsWithClosures = 1 + + Debug.Checkptr = -1 // so we can tell whether it is set explicitly + + Flag.Cfg.ImportMap = make(map[string]string) + + objabi.AddVersionFlag() // -V + registerFlags() + objabi.Flagparse(usage) + + if Flag.MSan && !sys.MSanSupported(buildcfg.GOOS, buildcfg.GOARCH) { + log.Fatalf("%s/%s does not support -msan", buildcfg.GOOS, buildcfg.GOARCH) + } + if Flag.Race && !sys.RaceDetectorSupported(buildcfg.GOOS, buildcfg.GOARCH) { + log.Fatalf("%s/%s does not support -race", buildcfg.GOOS, buildcfg.GOARCH) + } + if (*Flag.Shared || *Flag.Dynlink || *Flag.LinkShared) && !Ctxt.Arch.InFamily(sys.AMD64, sys.ARM, sys.ARM64, sys.I386, sys.PPC64, sys.RISCV64, sys.S390X) { + log.Fatalf("%s/%s does not support -shared", buildcfg.GOOS, buildcfg.GOARCH) + } + parseSpectre(Flag.Spectre) // left as string for RecordFlags + + Ctxt.Flag_shared = Ctxt.Flag_dynlink || Ctxt.Flag_shared + Ctxt.Flag_optimize = Flag.N == 0 + Ctxt.Debugasm = int(Flag.S) + + if flag.NArg() < 1 { + usage() + } + + if Flag.GoVersion != "" && Flag.GoVersion != runtime.Version() { + fmt.Printf("compile: version %q does not match go tool version %q\n", runtime.Version(), Flag.GoVersion) + Exit(2) + } + + if Flag.LowerO == "" { + p := flag.Arg(0) + if i := strings.LastIndex(p, "/"); i >= 0 { + p = p[i+1:] + } + if runtime.GOOS == "windows" { + if i := strings.LastIndex(p, `\`); i >= 0 { + p = p[i+1:] + } + } + if i := strings.LastIndex(p, "."); i >= 0 { + p = p[:i] + } + suffix := ".o" + if Flag.Pack { + suffix = ".a" + } + Flag.LowerO = p + suffix + } + + if Flag.Race && Flag.MSan { + log.Fatal("cannot use both -race and -msan") + } + if Flag.Race || Flag.MSan { + // -race and -msan imply -d=checkptr for now. + if Debug.Checkptr == -1 { // if not set explicitly + Debug.Checkptr = 1 + } + } + + if Flag.CompilingRuntime && Flag.N != 0 { + log.Fatal("cannot disable optimizations while compiling runtime") + } + if Flag.LowerC < 1 { + log.Fatalf("-c must be at least 1, got %d", Flag.LowerC) + } + if Flag.LowerC > 1 && !concurrentBackendAllowed() { + log.Fatalf("cannot use concurrent backend compilation with provided flags; invoked as %v", os.Args) + } + + if Flag.CompilingRuntime { + // Runtime can't use -d=checkptr, at least not yet. + Debug.Checkptr = 0 + + // Fuzzing the runtime isn't interesting either. + Debug.Libfuzzer = 0 + } + + if Debug.Checkptr == -1 { // if not set explicitly + Debug.Checkptr = 0 + } + + // set via a -d flag + Ctxt.Debugpcln = Debug.PCTab +} + +// registerFlags adds flag registrations for all the fields in Flag. +// See the comment on type CmdFlags for the rules. +func registerFlags() { + var ( + boolType = reflect.TypeOf(bool(false)) + intType = reflect.TypeOf(int(0)) + stringType = reflect.TypeOf(string("")) + ptrBoolType = reflect.TypeOf(new(bool)) + ptrIntType = reflect.TypeOf(new(int)) + ptrStringType = reflect.TypeOf(new(string)) + countType = reflect.TypeOf(CountFlag(0)) + funcType = reflect.TypeOf((func(string))(nil)) + ) + + v := reflect.ValueOf(&Flag).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.Name == "Cfg" { + continue + } + + var name string + if len(f.Name) == 1 { + name = f.Name + } else if len(f.Name) == 6 && f.Name[:5] == "Lower" && 'A' <= f.Name[5] && f.Name[5] <= 'Z' { + name = string(rune(f.Name[5] + 'a' - 'A')) + } else { + name = strings.ToLower(f.Name) + } + if tag := f.Tag.Get("flag"); tag != "" { + name = tag + } + + help := f.Tag.Get("help") + if help == "" { + panic(fmt.Sprintf("base.Flag.%s is missing help text", f.Name)) + } + + if k := f.Type.Kind(); (k == reflect.Ptr || k == reflect.Func) && v.Field(i).IsNil() { + panic(fmt.Sprintf("base.Flag.%s is uninitialized %v", f.Name, f.Type)) + } + + switch f.Type { + case boolType: + p := v.Field(i).Addr().Interface().(*bool) + flag.BoolVar(p, name, *p, help) + case intType: + p := v.Field(i).Addr().Interface().(*int) + flag.IntVar(p, name, *p, help) + case stringType: + p := v.Field(i).Addr().Interface().(*string) + flag.StringVar(p, name, *p, help) + case ptrBoolType: + p := v.Field(i).Interface().(*bool) + flag.BoolVar(p, name, *p, help) + case ptrIntType: + p := v.Field(i).Interface().(*int) + flag.IntVar(p, name, *p, help) + case ptrStringType: + p := v.Field(i).Interface().(*string) + flag.StringVar(p, name, *p, help) + case countType: + p := (*int)(v.Field(i).Addr().Interface().(*CountFlag)) + objabi.Flagcount(name, help, p) + case funcType: + f := v.Field(i).Interface().(func(string)) + objabi.Flagfn1(name, help, f) + } + } +} + +// concurrentFlagOk reports whether the current compiler flags +// are compatible with concurrent compilation. +func concurrentFlagOk() bool { + // TODO(rsc): Many of these are fine. Remove them. + return Flag.Percent == 0 && + Flag.E == 0 && + Flag.K == 0 && + Flag.L == 0 && + Flag.LowerH == 0 && + Flag.LowerJ == 0 && + Flag.LowerM == 0 && + Flag.LowerR == 0 +} + +func concurrentBackendAllowed() bool { + if !concurrentFlagOk() { + return false + } + + // Debug.S by itself is ok, because all printing occurs + // while writing the object file, and that is non-concurrent. + // Adding Debug_vlog, however, causes Debug.S to also print + // while flushing the plist, which happens concurrently. + if Ctxt.Debugvlog || Debug.Any() || Flag.Live > 0 { + return false + } + // TODO: Test and delete this condition. + if buildcfg.Experiment.FieldTrack { + return false + } + // TODO: fix races and enable the following flags + if Ctxt.Flag_shared || Ctxt.Flag_dynlink || Flag.Race { + return false + } + return true +} + +func addImportDir(dir string) { + if dir != "" { + Flag.Cfg.ImportDirs = append(Flag.Cfg.ImportDirs, dir) + } +} + +func addImportMap(s string) { + if Flag.Cfg.ImportMap == nil { + Flag.Cfg.ImportMap = make(map[string]string) + } + if strings.Count(s, "=") != 1 { + log.Fatal("-importmap argument must be of the form source=actual") + } + i := strings.Index(s, "=") + source, actual := s[:i], s[i+1:] + if source == "" || actual == "" { + log.Fatal("-importmap argument must be of the form source=actual; source and actual must be non-empty") + } + Flag.Cfg.ImportMap[source] = actual +} + +func readImportCfg(file string) { + if Flag.Cfg.ImportMap == nil { + Flag.Cfg.ImportMap = make(map[string]string) + } + Flag.Cfg.PackageFile = map[string]string{} + data, err := ioutil.ReadFile(file) + if err != nil { + log.Fatalf("-importcfg: %v", err) + } + + for lineNum, line := range strings.Split(string(data), "\n") { + lineNum++ // 1-based + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + var verb, args string + if i := strings.Index(line, " "); i < 0 { + verb = line + } else { + verb, args = line[:i], strings.TrimSpace(line[i+1:]) + } + var before, after string + if i := strings.Index(args, "="); i >= 0 { + before, after = args[:i], args[i+1:] + } + switch verb { + default: + log.Fatalf("%s:%d: unknown directive %q", file, lineNum, verb) + case "importmap": + if before == "" || after == "" { + log.Fatalf(`%s:%d: invalid importmap: syntax is "importmap old=new"`, file, lineNum) + } + Flag.Cfg.ImportMap[before] = after + case "packagefile": + if before == "" || after == "" { + log.Fatalf(`%s:%d: invalid packagefile: syntax is "packagefile path=filename"`, file, lineNum) + } + Flag.Cfg.PackageFile[before] = after + } + } +} + +func readEmbedCfg(file string) { + data, err := ioutil.ReadFile(file) + if err != nil { + log.Fatalf("-embedcfg: %v", err) + } + if err := json.Unmarshal(data, &Flag.Cfg.Embed); err != nil { + log.Fatalf("%s: %v", file, err) + } + if Flag.Cfg.Embed.Patterns == nil { + log.Fatalf("%s: invalid embedcfg: missing Patterns", file) + } + if Flag.Cfg.Embed.Files == nil { + log.Fatalf("%s: invalid embedcfg: missing Files", file) + } +} + +// parseSpectre parses the spectre configuration from the string s. +func parseSpectre(s string) { + for _, f := range strings.Split(s, ",") { + f = strings.TrimSpace(f) + switch f { + default: + log.Fatalf("unknown setting -spectre=%s", f) + case "": + // nothing + case "all": + Flag.Cfg.SpectreIndex = true + Ctxt.Retpoline = true + case "index": + Flag.Cfg.SpectreIndex = true + case "ret": + Ctxt.Retpoline = true + } + } + + if Flag.Cfg.SpectreIndex { + switch buildcfg.GOARCH { + case "amd64": + // ok + default: + log.Fatalf("GOARCH=%s does not support -spectre=index", buildcfg.GOARCH) + } + } +} diff --git a/src/cmd/compile/internal/base/link.go b/src/cmd/compile/internal/base/link.go new file mode 100644 index 0000000000000000000000000000000000000000..49fe4352b2f65793e83a48372392c7d6649b792a --- /dev/null +++ b/src/cmd/compile/internal/base/link.go @@ -0,0 +1,36 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package base + +import ( + "cmd/internal/obj" +) + +var Ctxt *obj.Link + +// TODO(mdempsky): These should probably be obj.Link methods. + +// PkgLinksym returns the linker symbol for name within the given +// package prefix. For user packages, prefix should be the package +// path encoded with objabi.PathToPrefix. +func PkgLinksym(prefix, name string, abi obj.ABI) *obj.LSym { + if name == "_" { + // TODO(mdempsky): Cleanup callers and Fatalf instead. + return linksym(prefix, "_", abi) + } + return linksym(prefix, prefix+"."+name, abi) +} + +// Linkname returns the linker symbol for the given name as it might +// appear within a //go:linkname directive. +func Linkname(name string, abi obj.ABI) *obj.LSym { + return linksym("_", name, abi) +} + +// linksym is an internal helper function for implementing the above +// exported APIs. +func linksym(pkg, name string, abi obj.ABI) *obj.LSym { + return Ctxt.LookupABIInit(name, abi, func(r *obj.LSym) { r.Pkg = pkg }) +} diff --git a/src/cmd/compile/internal/base/print.go b/src/cmd/compile/internal/base/print.go new file mode 100644 index 0000000000000000000000000000000000000000..b095fd704daad807b3847bbd2916024356161af2 --- /dev/null +++ b/src/cmd/compile/internal/base/print.go @@ -0,0 +1,264 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package base + +import ( + "fmt" + "internal/buildcfg" + "os" + "runtime/debug" + "sort" + "strings" + + "cmd/internal/src" +) + +// An errorMsg is a queued error message, waiting to be printed. +type errorMsg struct { + pos src.XPos + msg string +} + +// Pos is the current source position being processed, +// printed by Errorf, ErrorfLang, Fatalf, and Warnf. +var Pos src.XPos + +var ( + errorMsgs []errorMsg + numErrors int // number of entries in errorMsgs that are errors (as opposed to warnings) + numSyntaxErrors int +) + +// Errors returns the number of errors reported. +func Errors() int { + return numErrors +} + +// SyntaxErrors returns the number of syntax errors reported +func SyntaxErrors() int { + return numSyntaxErrors +} + +// addErrorMsg adds a new errorMsg (which may be a warning) to errorMsgs. +func addErrorMsg(pos src.XPos, format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + // Only add the position if know the position. + // See issue golang.org/issue/11361. + if pos.IsKnown() { + msg = fmt.Sprintf("%v: %s", FmtPos(pos), msg) + } + errorMsgs = append(errorMsgs, errorMsg{ + pos: pos, + msg: msg + "\n", + }) +} + +// FmtPos formats pos as a file:line string. +func FmtPos(pos src.XPos) string { + if Ctxt == nil { + return "???" + } + return Ctxt.OutermostPos(pos).Format(Flag.C == 0, Flag.L == 1) +} + +// byPos sorts errors by source position. +type byPos []errorMsg + +func (x byPos) Len() int { return len(x) } +func (x byPos) Less(i, j int) bool { return x[i].pos.Before(x[j].pos) } +func (x byPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +// FlushErrors sorts errors seen so far by line number, prints them to stdout, +// and empties the errors array. +func FlushErrors() { + if Ctxt != nil && Ctxt.Bso != nil { + Ctxt.Bso.Flush() + } + if len(errorMsgs) == 0 { + return + } + sort.Stable(byPos(errorMsgs)) + for i, err := range errorMsgs { + if i == 0 || err.msg != errorMsgs[i-1].msg { + fmt.Printf("%s", err.msg) + } + } + errorMsgs = errorMsgs[:0] +} + +// lasterror keeps track of the most recently issued error, +// to avoid printing multiple error messages on the same line. +var lasterror struct { + syntax src.XPos // source position of last syntax error + other src.XPos // source position of last non-syntax error + msg string // error message of last non-syntax error +} + +// sameline reports whether two positions a, b are on the same line. +func sameline(a, b src.XPos) bool { + p := Ctxt.PosTable.Pos(a) + q := Ctxt.PosTable.Pos(b) + return p.Base() == q.Base() && p.Line() == q.Line() +} + +// Errorf reports a formatted error at the current line. +func Errorf(format string, args ...interface{}) { + ErrorfAt(Pos, format, args...) +} + +// ErrorfAt reports a formatted error message at pos. +func ErrorfAt(pos src.XPos, format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + + if strings.HasPrefix(msg, "syntax error") { + numSyntaxErrors++ + // only one syntax error per line, no matter what error + if sameline(lasterror.syntax, pos) { + return + } + lasterror.syntax = pos + } else { + // only one of multiple equal non-syntax errors per line + // (FlushErrors shows only one of them, so we filter them + // here as best as we can (they may not appear in order) + // so that we don't count them here and exit early, and + // then have nothing to show for.) + if sameline(lasterror.other, pos) && lasterror.msg == msg { + return + } + lasterror.other = pos + lasterror.msg = msg + } + + addErrorMsg(pos, "%s", msg) + numErrors++ + + hcrash() + if numErrors >= 10 && Flag.LowerE == 0 { + FlushErrors() + fmt.Printf("%v: too many errors\n", FmtPos(pos)) + ErrorExit() + } +} + +// ErrorfVers reports that a language feature (format, args) requires a later version of Go. +func ErrorfVers(lang string, format string, args ...interface{}) { + Errorf("%s requires %s or later (-lang was set to %s; check go.mod)", fmt.Sprintf(format, args...), lang, Flag.Lang) +} + +// UpdateErrorDot is a clumsy hack that rewrites the last error, +// if it was "LINE: undefined: NAME", to be "LINE: undefined: NAME in EXPR". +// It is used to give better error messages for dot (selector) expressions. +func UpdateErrorDot(line string, name, expr string) { + if len(errorMsgs) == 0 { + return + } + e := &errorMsgs[len(errorMsgs)-1] + if strings.HasPrefix(e.msg, line) && e.msg == fmt.Sprintf("%v: undefined: %v\n", line, name) { + e.msg = fmt.Sprintf("%v: undefined: %v in %v\n", line, name, expr) + } +} + +// Warnf reports a formatted warning at the current line. +// In general the Go compiler does NOT generate warnings, +// so this should be used only when the user has opted in +// to additional output by setting a particular flag. +func Warn(format string, args ...interface{}) { + WarnfAt(Pos, format, args...) +} + +// WarnfAt reports a formatted warning at pos. +// In general the Go compiler does NOT generate warnings, +// so this should be used only when the user has opted in +// to additional output by setting a particular flag. +func WarnfAt(pos src.XPos, format string, args ...interface{}) { + addErrorMsg(pos, format, args...) + if Flag.LowerM != 0 { + FlushErrors() + } +} + +// Fatalf reports a fatal error - an internal problem - at the current line and exits. +// If other errors have already been printed, then Fatalf just quietly exits. +// (The internal problem may have been caused by incomplete information +// after the already-reported errors, so best to let users fix those and +// try again without being bothered about a spurious internal error.) +// +// But if no errors have been printed, or if -d panic has been specified, +// Fatalf prints the error as an "internal compiler error". In a released build, +// it prints an error asking to file a bug report. In development builds, it +// prints a stack trace. +// +// If -h has been specified, Fatalf panics to force the usual runtime info dump. +func Fatalf(format string, args ...interface{}) { + FatalfAt(Pos, format, args...) +} + +// FatalfAt reports a fatal error - an internal problem - at pos and exits. +// If other errors have already been printed, then FatalfAt just quietly exits. +// (The internal problem may have been caused by incomplete information +// after the already-reported errors, so best to let users fix those and +// try again without being bothered about a spurious internal error.) +// +// But if no errors have been printed, or if -d panic has been specified, +// FatalfAt prints the error as an "internal compiler error". In a released build, +// it prints an error asking to file a bug report. In development builds, it +// prints a stack trace. +// +// If -h has been specified, FatalfAt panics to force the usual runtime info dump. +func FatalfAt(pos src.XPos, format string, args ...interface{}) { + FlushErrors() + + if Debug.Panic != 0 || numErrors == 0 { + fmt.Printf("%v: internal compiler error: ", FmtPos(pos)) + fmt.Printf(format, args...) + fmt.Printf("\n") + + // If this is a released compiler version, ask for a bug report. + if strings.HasPrefix(buildcfg.Version, "go") { + fmt.Printf("\n") + fmt.Printf("Please file a bug report including a short program that triggers the error.\n") + fmt.Printf("https://golang.org/issue/new\n") + } else { + // Not a release; dump a stack trace, too. + fmt.Println() + os.Stdout.Write(debug.Stack()) + fmt.Println() + } + } + + hcrash() + ErrorExit() +} + +// hcrash crashes the compiler when -h is set, to find out where a message is generated. +func hcrash() { + if Flag.LowerH != 0 { + FlushErrors() + if Flag.LowerO != "" { + os.Remove(Flag.LowerO) + } + panic("-h") + } +} + +// ErrorExit handles an error-status exit. +// It flushes any pending errors, removes the output file, and exits. +func ErrorExit() { + FlushErrors() + if Flag.LowerO != "" { + os.Remove(Flag.LowerO) + } + os.Exit(2) +} + +// ExitIfErrors calls ErrorExit if any errors have been reported. +func ExitIfErrors() { + if Errors() > 0 { + ErrorExit() + } +} + +var AutogeneratedPos src.XPos diff --git a/src/cmd/compile/internal/gc/timings.go b/src/cmd/compile/internal/base/timings.go similarity index 99% rename from src/cmd/compile/internal/gc/timings.go rename to src/cmd/compile/internal/base/timings.go index 56b3899e2f6ae1e6fe9e62552baa4e2178753756..f599f4e05f63309a7a5e98d90680be6e88c32ae3 100644 --- a/src/cmd/compile/internal/gc/timings.go +++ b/src/cmd/compile/internal/base/timings.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package base import ( "fmt" @@ -11,6 +11,8 @@ import ( "time" ) +var Timer Timings + // Timings collects the execution times of labeled phases // which are added trough a sequence of Start/Stop calls. // Events may be associated with each phase via AddEvent. diff --git a/src/cmd/compile/internal/bitvec/bv.go b/src/cmd/compile/internal/bitvec/bv.go new file mode 100644 index 0000000000000000000000000000000000000000..bcac1fe351fac13c7d06850495cc13364f3d0ddd --- /dev/null +++ b/src/cmd/compile/internal/bitvec/bv.go @@ -0,0 +1,190 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bitvec + +import ( + "math/bits" + + "cmd/compile/internal/base" +) + +const ( + wordBits = 32 + wordMask = wordBits - 1 + wordShift = 5 +) + +// A BitVec is a bit vector. +type BitVec struct { + N int32 // number of bits in vector + B []uint32 // words holding bits +} + +func New(n int32) BitVec { + nword := (n + wordBits - 1) / wordBits + return BitVec{n, make([]uint32, nword)} +} + +type Bulk struct { + words []uint32 + nbit int32 + nword int32 +} + +func NewBulk(nbit int32, count int32) Bulk { + nword := (nbit + wordBits - 1) / wordBits + size := int64(nword) * int64(count) + if int64(int32(size*4)) != size*4 { + base.Fatalf("NewBulk too big: nbit=%d count=%d nword=%d size=%d", nbit, count, nword, size) + } + return Bulk{ + words: make([]uint32, size), + nbit: nbit, + nword: nword, + } +} + +func (b *Bulk) Next() BitVec { + out := BitVec{b.nbit, b.words[:b.nword]} + b.words = b.words[b.nword:] + return out +} + +func (bv1 BitVec) Eq(bv2 BitVec) bool { + if bv1.N != bv2.N { + base.Fatalf("bvequal: lengths %d and %d are not equal", bv1.N, bv2.N) + } + for i, x := range bv1.B { + if x != bv2.B[i] { + return false + } + } + return true +} + +func (dst BitVec) Copy(src BitVec) { + copy(dst.B, src.B) +} + +func (bv BitVec) Get(i int32) bool { + if i < 0 || i >= bv.N { + base.Fatalf("bvget: index %d is out of bounds with length %d\n", i, bv.N) + } + mask := uint32(1 << uint(i%wordBits)) + return bv.B[i>>wordShift]&mask != 0 +} + +func (bv BitVec) Set(i int32) { + if i < 0 || i >= bv.N { + base.Fatalf("bvset: index %d is out of bounds with length %d\n", i, bv.N) + } + mask := uint32(1 << uint(i%wordBits)) + bv.B[i/wordBits] |= mask +} + +func (bv BitVec) Unset(i int32) { + if i < 0 || i >= bv.N { + base.Fatalf("bvunset: index %d is out of bounds with length %d\n", i, bv.N) + } + mask := uint32(1 << uint(i%wordBits)) + bv.B[i/wordBits] &^= mask +} + +// bvnext returns the smallest index >= i for which bvget(bv, i) == 1. +// If there is no such index, bvnext returns -1. +func (bv BitVec) Next(i int32) int32 { + if i >= bv.N { + return -1 + } + + // Jump i ahead to next word with bits. + if bv.B[i>>wordShift]>>uint(i&wordMask) == 0 { + i &^= wordMask + i += wordBits + for i < bv.N && bv.B[i>>wordShift] == 0 { + i += wordBits + } + } + + if i >= bv.N { + return -1 + } + + // Find 1 bit. + w := bv.B[i>>wordShift] >> uint(i&wordMask) + i += int32(bits.TrailingZeros32(w)) + + return i +} + +func (bv BitVec) IsEmpty() bool { + for _, x := range bv.B { + if x != 0 { + return false + } + } + return true +} + +func (bv BitVec) Not() { + for i, x := range bv.B { + bv.B[i] = ^x + } +} + +// union +func (dst BitVec) Or(src1, src2 BitVec) { + if len(src1.B) == 0 { + return + } + _, _ = dst.B[len(src1.B)-1], src2.B[len(src1.B)-1] // hoist bounds checks out of the loop + + for i, x := range src1.B { + dst.B[i] = x | src2.B[i] + } +} + +// intersection +func (dst BitVec) And(src1, src2 BitVec) { + if len(src1.B) == 0 { + return + } + _, _ = dst.B[len(src1.B)-1], src2.B[len(src1.B)-1] // hoist bounds checks out of the loop + + for i, x := range src1.B { + dst.B[i] = x & src2.B[i] + } +} + +// difference +func (dst BitVec) AndNot(src1, src2 BitVec) { + if len(src1.B) == 0 { + return + } + _, _ = dst.B[len(src1.B)-1], src2.B[len(src1.B)-1] // hoist bounds checks out of the loop + + for i, x := range src1.B { + dst.B[i] = x &^ src2.B[i] + } +} + +func (bv BitVec) String() string { + s := make([]byte, 2+bv.N) + copy(s, "#*") + for i := int32(0); i < bv.N; i++ { + ch := byte('0') + if bv.Get(i) { + ch = '1' + } + s[2+i] = ch + } + return string(s) +} + +func (bv BitVec) Clear() { + for i := range bv.B { + bv.B[i] = 0 + } +} diff --git a/src/cmd/compile/internal/deadcode/deadcode.go b/src/cmd/compile/internal/deadcode/deadcode.go new file mode 100644 index 0000000000000000000000000000000000000000..520203787f02dc97968e4cd71c577f7fc646d24d --- /dev/null +++ b/src/cmd/compile/internal/deadcode/deadcode.go @@ -0,0 +1,152 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package deadcode + +import ( + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" +) + +func Func(fn *ir.Func) { + stmts(&fn.Body) + + if len(fn.Body) == 0 { + return + } + + for _, n := range fn.Body { + if len(n.Init()) > 0 { + return + } + switch n.Op() { + case ir.OIF: + n := n.(*ir.IfStmt) + if !ir.IsConst(n.Cond, constant.Bool) || len(n.Body) > 0 || len(n.Else) > 0 { + return + } + case ir.OFOR: + n := n.(*ir.ForStmt) + if !ir.IsConst(n.Cond, constant.Bool) || ir.BoolVal(n.Cond) { + return + } + default: + return + } + } + + fn.Body = []ir.Node{ir.NewBlockStmt(base.Pos, nil)} +} + +func stmts(nn *ir.Nodes) { + var lastLabel = -1 + for i, n := range *nn { + if n != nil && n.Op() == ir.OLABEL { + lastLabel = i + } + } + for i, n := range *nn { + // Cut is set to true when all nodes after i'th position + // should be removed. + // In other words, it marks whole slice "tail" as dead. + cut := false + if n == nil { + continue + } + if n.Op() == ir.OIF { + n := n.(*ir.IfStmt) + n.Cond = expr(n.Cond) + if ir.IsConst(n.Cond, constant.Bool) { + var body ir.Nodes + if ir.BoolVal(n.Cond) { + n.Else = ir.Nodes{} + body = n.Body + } else { + n.Body = ir.Nodes{} + body = n.Else + } + // If "then" or "else" branch ends with panic or return statement, + // it is safe to remove all statements after this node. + // isterminating is not used to avoid goto-related complications. + // We must be careful not to deadcode-remove labels, as they + // might be the target of a goto. See issue 28616. + if body := body; len(body) != 0 { + switch body[(len(body) - 1)].Op() { + case ir.ORETURN, ir.OTAILCALL, ir.OPANIC: + if i > lastLabel { + cut = true + } + } + } + } + } + + if len(n.Init()) != 0 { + stmts(n.(ir.InitNode).PtrInit()) + } + switch n.Op() { + case ir.OBLOCK: + n := n.(*ir.BlockStmt) + stmts(&n.List) + case ir.OFOR: + n := n.(*ir.ForStmt) + stmts(&n.Body) + case ir.OIF: + n := n.(*ir.IfStmt) + stmts(&n.Body) + stmts(&n.Else) + case ir.ORANGE: + n := n.(*ir.RangeStmt) + stmts(&n.Body) + case ir.OSELECT: + n := n.(*ir.SelectStmt) + for _, cas := range n.Cases { + stmts(&cas.Body) + } + case ir.OSWITCH: + n := n.(*ir.SwitchStmt) + for _, cas := range n.Cases { + stmts(&cas.Body) + } + } + + if cut { + *nn = (*nn)[:i+1] + break + } + } +} + +func expr(n ir.Node) ir.Node { + // Perform dead-code elimination on short-circuited boolean + // expressions involving constants with the intent of + // producing a constant 'if' condition. + switch n.Op() { + case ir.OANDAND: + n := n.(*ir.LogicalExpr) + n.X = expr(n.X) + n.Y = expr(n.Y) + if ir.IsConst(n.X, constant.Bool) { + if ir.BoolVal(n.X) { + return n.Y // true && x => x + } else { + return n.X // false && x => false + } + } + case ir.OOROR: + n := n.(*ir.LogicalExpr) + n.X = expr(n.X) + n.Y = expr(n.Y) + if ir.IsConst(n.X, constant.Bool) { + if ir.BoolVal(n.X) { + return n.X // true || x => true + } else { + return n.Y // false || x => x + } + } + } + return n +} diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go new file mode 100644 index 0000000000000000000000000000000000000000..60ba208d0876b119d74e10e33d0103c97fee5c8c --- /dev/null +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -0,0 +1,85 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package devirtualize implements a simple "devirtualization" +// optimization pass, which replaces interface method calls with +// direct concrete-type method calls where possible. +package devirtualize + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" +) + +// Func devirtualizes calls within fn where possible. +func Func(fn *ir.Func) { + ir.CurFunc = fn + ir.VisitList(fn.Body, func(n ir.Node) { + if call, ok := n.(*ir.CallExpr); ok { + Call(call) + } + }) +} + +// Call devirtualizes the given call if possible. +func Call(call *ir.CallExpr) { + if call.Op() != ir.OCALLINTER { + return + } + sel := call.X.(*ir.SelectorExpr) + r := ir.StaticValue(sel.X) + if r.Op() != ir.OCONVIFACE { + return + } + recv := r.(*ir.ConvExpr) + + typ := recv.X.Type() + if typ.IsInterface() { + return + } + + dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, nil) + dt.SetType(typ) + x := typecheck.Callee(ir.NewSelectorExpr(sel.Pos(), ir.OXDOT, dt, sel.Sel)) + switch x.Op() { + case ir.ODOTMETH: + x := x.(*ir.SelectorExpr) + if base.Flag.LowerM != 0 { + base.WarnfAt(call.Pos(), "devirtualizing %v to %v", sel, typ) + } + call.SetOp(ir.OCALLMETH) + call.X = x + case ir.ODOTINTER: + // Promoted method from embedded interface-typed field (#42279). + x := x.(*ir.SelectorExpr) + if base.Flag.LowerM != 0 { + base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", sel, typ) + } + call.SetOp(ir.OCALLINTER) + call.X = x + default: + // TODO(mdempsky): Turn back into Fatalf after more testing. + if base.Flag.LowerM != 0 { + base.WarnfAt(call.Pos(), "failed to devirtualize %v (%v)", x, x.Op()) + } + return + } + + // Duplicated logic from typecheck for function call return + // value types. + // + // Receiver parameter size may have changed; need to update + // call.Type to get correct stack offsets for result + // parameters. + types.CheckSize(x.Type()) + switch ft := x.Type(); ft.NumResults() { + case 0: + case 1: + call.SetType(ft.Results().Field(0).Type) + default: + call.SetType(ft.Results()) + } +} diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go new file mode 100644 index 0000000000000000000000000000000000000000..0e22b61bc3ff4344ba1ad7c3e74f65c76a620243 --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/dwarf.go @@ -0,0 +1,564 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarfgen + +import ( + "bytes" + "flag" + "fmt" + "internal/buildcfg" + "sort" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/types" + "cmd/internal/dwarf" + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/src" +) + +func Info(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) { + fn := curfn.(*ir.Func) + + if fn.Nname != nil { + expect := fn.Linksym() + if fnsym.ABI() == obj.ABI0 { + expect = fn.LinksymABI(obj.ABI0) + } + if fnsym != expect { + base.Fatalf("unexpected fnsym: %v != %v", fnsym, expect) + } + } + + // Back when there were two different *Funcs for a function, this code + // was not consistent about whether a particular *Node being processed + // was an ODCLFUNC or ONAME node. Partly this is because inlined function + // bodies have no ODCLFUNC node, which was it's own inconsistency. + // In any event, the handling of the two different nodes for DWARF purposes + // was subtly different, likely in unintended ways. CL 272253 merged the + // two nodes' Func fields, so that code sees the same *Func whether it is + // holding the ODCLFUNC or the ONAME. This resulted in changes in the + // DWARF output. To preserve the existing DWARF output and leave an + // intentional change for a future CL, this code does the following when + // fn.Op == ONAME: + // + // 1. Disallow use of createComplexVars in createDwarfVars. + // It was not possible to reach that code for an ONAME before, + // because the DebugInfo was set only on the ODCLFUNC Func. + // Calling into it in the ONAME case causes an index out of bounds panic. + // + // 2. Do not populate apdecls. fn.Func.Dcl was in the ODCLFUNC Func, + // not the ONAME Func. Populating apdecls for the ONAME case results + // in selected being populated after createSimpleVars is called in + // createDwarfVars, and then that causes the loop to skip all the entries + // in dcl, meaning that the RecordAutoType calls don't happen. + // + // These two adjustments keep toolstash -cmp working for now. + // Deciding the right answer is, as they say, future work. + // + // We can tell the difference between the old ODCLFUNC and ONAME + // cases by looking at the infosym.Name. If it's empty, DebugInfo is + // being called from (*obj.Link).populateDWARF, which used to use + // the ODCLFUNC. If it's non-empty (the name will end in $abstract), + // DebugInfo is being called from (*obj.Link).DwarfAbstractFunc, + // which used to use the ONAME form. + isODCLFUNC := infosym.Name == "" + + var apdecls []*ir.Name + // Populate decls for fn. + if isODCLFUNC { + for _, n := range fn.Dcl { + if n.Op() != ir.ONAME { // might be OTYPE or OLITERAL + continue + } + switch n.Class { + case ir.PAUTO: + if !n.Used() { + // Text == nil -> generating abstract function + if fnsym.Func().Text != nil { + base.Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)") + } + continue + } + case ir.PPARAM, ir.PPARAMOUT: + default: + continue + } + apdecls = append(apdecls, n) + fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type())) + } + } + + decls, dwarfVars := createDwarfVars(fnsym, isODCLFUNC, fn, apdecls) + + // For each type referenced by the functions auto vars but not + // already referenced by a dwarf var, attach an R_USETYPE relocation to + // the function symbol to insure that the type included in DWARF + // processing during linking. + typesyms := []*obj.LSym{} + for t, _ := range fnsym.Func().Autot { + typesyms = append(typesyms, t) + } + sort.Sort(obj.BySymName(typesyms)) + for _, sym := range typesyms { + r := obj.Addrel(infosym) + r.Sym = sym + r.Type = objabi.R_USETYPE + } + fnsym.Func().Autot = nil + + var varScopes []ir.ScopeID + for _, decl := range decls { + pos := declPos(decl) + varScopes = append(varScopes, findScope(fn.Marks, pos)) + } + + scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes) + var inlcalls dwarf.InlCalls + if base.Flag.GenDwarfInl > 0 { + inlcalls = assembleInlines(fnsym, dwarfVars) + } + return scopes, inlcalls +} + +func declPos(decl *ir.Name) src.XPos { + return decl.Canonical().Pos() +} + +// createDwarfVars process fn, returning a list of DWARF variables and the +// Nodes they represent. +func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var) { + // Collect a raw list of DWARF vars. + var vars []*dwarf.Var + var decls []*ir.Name + var selected ir.NameSet + + if base.Ctxt.Flag_locationlists && base.Ctxt.Flag_optimize && fn.DebugInfo != nil && complexOK { + decls, vars, selected = createComplexVars(fnsym, fn) + } else if fn.ABI == obj.ABIInternal && base.Flag.N != 0 && complexOK { + decls, vars, selected = createABIVars(fnsym, fn, apDecls) + } else { + decls, vars, selected = createSimpleVars(fnsym, apDecls) + } + + dcl := apDecls + if fnsym.WasInlined() { + dcl = preInliningDcls(fnsym) + } + + // If optimization is enabled, the list above will typically be + // missing some of the original pre-optimization variables in the + // function (they may have been promoted to registers, folded into + // constants, dead-coded away, etc). Input arguments not eligible + // for SSA optimization are also missing. Here we add back in entries + // for selected missing vars. Note that the recipe below creates a + // conservative location. The idea here is that we want to + // communicate to the user that "yes, there is a variable named X + // in this function, but no, I don't have enough information to + // reliably report its contents." + // For non-SSA-able arguments, however, the correct information + // is known -- they have a single home on the stack. + for _, n := range dcl { + if selected.Has(n) { + continue + } + c := n.Sym().Name[0] + if c == '.' || n.Type().IsUntyped() { + continue + } + if n.Class == ir.PPARAM && !ssagen.TypeOK(n.Type()) { + // SSA-able args get location lists, and may move in and + // out of registers, so those are handled elsewhere. + // Autos and named output params seem to get handled + // with VARDEF, which creates location lists. + // Args not of SSA-able type are treated here; they + // are homed on the stack in a single place for the + // entire call. + vars = append(vars, createSimpleVar(fnsym, n)) + decls = append(decls, n) + continue + } + typename := dwarf.InfoPrefix + types.TypeSymName(n.Type()) + decls = append(decls, n) + abbrev := dwarf.DW_ABRV_AUTO_LOCLIST + isReturnValue := (n.Class == ir.PPARAMOUT) + if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT { + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + } + if n.Esc() == ir.EscHeap { + // The variable in question has been promoted to the heap. + // Its address is in n.Heapaddr. + // TODO(thanm): generate a better location expression + } + inlIndex := 0 + if base.Flag.GenDwarfInl > 1 { + if n.InlFormal() || n.InlLocal() { + inlIndex = posInlIndex(n.Pos()) + 1 + if n.InlFormal() { + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + } + } + } + declpos := base.Ctxt.InnermostPos(n.Pos()) + vars = append(vars, &dwarf.Var{ + Name: n.Sym().Name, + IsReturnValue: isReturnValue, + Abbrev: abbrev, + StackOffset: int32(n.FrameOffset()), + Type: base.Ctxt.Lookup(typename), + DeclFile: declpos.RelFilename(), + DeclLine: declpos.RelLine(), + DeclCol: declpos.Col(), + InlIndex: int32(inlIndex), + ChildIndex: -1, + }) + // Record go type of to insure that it gets emitted by the linker. + fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type())) + } + + // Sort decls and vars. + sortDeclsAndVars(fn, decls, vars) + + return decls, vars +} + +// sortDeclsAndVars sorts the decl and dwarf var lists according to +// parameter declaration order, so as to insure that when a subprogram +// DIE is emitted, its parameter children appear in declaration order. +// Prior to the advent of the register ABI, sorting by frame offset +// would achieve this; with the register we now need to go back to the +// original function signature. +func sortDeclsAndVars(fn *ir.Func, decls []*ir.Name, vars []*dwarf.Var) { + paramOrder := make(map[*ir.Name]int) + idx := 1 + for _, selfn := range types.RecvsParamsResults { + fsl := selfn(fn.Type()).FieldSlice() + for _, f := range fsl { + if n, ok := f.Nname.(*ir.Name); ok { + paramOrder[n] = idx + idx++ + } + } + } + sort.Stable(varsAndDecls{decls, vars, paramOrder}) +} + +type varsAndDecls struct { + decls []*ir.Name + vars []*dwarf.Var + paramOrder map[*ir.Name]int +} + +func (v varsAndDecls) Len() int { + return len(v.decls) +} + +func (v varsAndDecls) Less(i, j int) bool { + nameLT := func(ni, nj *ir.Name) bool { + oi, foundi := v.paramOrder[ni] + oj, foundj := v.paramOrder[nj] + if foundi { + if foundj { + return oi < oj + } else { + return true + } + } + return false + } + return nameLT(v.decls[i], v.decls[j]) +} + +func (v varsAndDecls) Swap(i, j int) { + v.vars[i], v.vars[j] = v.vars[j], v.vars[i] + v.decls[i], v.decls[j] = v.decls[j], v.decls[i] +} + +// Given a function that was inlined at some point during the +// compilation, return a sorted list of nodes corresponding to the +// autos/locals in that function prior to inlining. If this is a +// function that is not local to the package being compiled, then the +// names of the variables may have been "versioned" to avoid conflicts +// with local vars; disregard this versioning when sorting. +func preInliningDcls(fnsym *obj.LSym) []*ir.Name { + fn := base.Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*ir.Func) + var rdcl []*ir.Name + for _, n := range fn.Inl.Dcl { + c := n.Sym().Name[0] + // Avoid reporting "_" parameters, since if there are more than + // one, it can result in a collision later on, as in #23179. + if unversion(n.Sym().Name) == "_" || c == '.' || n.Type().IsUntyped() { + continue + } + rdcl = append(rdcl, n) + } + return rdcl +} + +// createSimpleVars creates a DWARF entry for every variable declared in the +// function, claiming that they are permanently on the stack. +func createSimpleVars(fnsym *obj.LSym, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, ir.NameSet) { + var vars []*dwarf.Var + var decls []*ir.Name + var selected ir.NameSet + for _, n := range apDecls { + if ir.IsAutoTmp(n) { + continue + } + + decls = append(decls, n) + vars = append(vars, createSimpleVar(fnsym, n)) + selected.Add(n) + } + return decls, vars, selected +} + +func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var { + var abbrev int + var offs int64 + + localAutoOffset := func() int64 { + offs = n.FrameOffset() + if base.Ctxt.FixedFrameSize() == 0 { + offs -= int64(types.PtrSize) + } + if buildcfg.FramePointerEnabled { + offs -= int64(types.PtrSize) + } + return offs + } + + switch n.Class { + case ir.PAUTO: + offs = localAutoOffset() + abbrev = dwarf.DW_ABRV_AUTO + case ir.PPARAM, ir.PPARAMOUT: + abbrev = dwarf.DW_ABRV_PARAM + if n.IsOutputParamInRegisters() { + offs = localAutoOffset() + } else { + offs = n.FrameOffset() + base.Ctxt.FixedFrameSize() + } + + default: + base.Fatalf("createSimpleVar unexpected class %v for node %v", n.Class, n) + } + + typename := dwarf.InfoPrefix + types.TypeSymName(n.Type()) + delete(fnsym.Func().Autot, reflectdata.TypeLinksym(n.Type())) + inlIndex := 0 + if base.Flag.GenDwarfInl > 1 { + if n.InlFormal() || n.InlLocal() { + inlIndex = posInlIndex(n.Pos()) + 1 + if n.InlFormal() { + abbrev = dwarf.DW_ABRV_PARAM + } + } + } + declpos := base.Ctxt.InnermostPos(declPos(n)) + return &dwarf.Var{ + Name: n.Sym().Name, + IsReturnValue: n.Class == ir.PPARAMOUT, + IsInlFormal: n.InlFormal(), + Abbrev: abbrev, + StackOffset: int32(offs), + Type: base.Ctxt.Lookup(typename), + DeclFile: declpos.RelFilename(), + DeclLine: declpos.RelLine(), + DeclCol: declpos.Col(), + InlIndex: int32(inlIndex), + ChildIndex: -1, + } +} + +// createABIVars creates DWARF variables for functions in which the +// register ABI is enabled but optimization is turned off. It uses a +// hybrid approach in which register-resident input params are +// captured with location lists, and all other vars use the "simple" +// strategy. +func createABIVars(fnsym *obj.LSym, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, ir.NameSet) { + + // Invoke createComplexVars to generate dwarf vars for input parameters + // that are register-allocated according to the ABI rules. + decls, vars, selected := createComplexVars(fnsym, fn) + + // Now fill in the remainder of the variables: input parameters + // that are not register-resident, output parameters, and local + // variables. + for _, n := range apDecls { + if ir.IsAutoTmp(n) { + continue + } + if _, ok := selected[n]; ok { + // already handled + continue + } + + decls = append(decls, n) + vars = append(vars, createSimpleVar(fnsym, n)) + selected.Add(n) + } + + return decls, vars, selected +} + +// createComplexVars creates recomposed DWARF vars with location lists, +// suitable for describing optimized code. +func createComplexVars(fnsym *obj.LSym, fn *ir.Func) ([]*ir.Name, []*dwarf.Var, ir.NameSet) { + debugInfo := fn.DebugInfo.(*ssa.FuncDebug) + + // Produce a DWARF variable entry for each user variable. + var decls []*ir.Name + var vars []*dwarf.Var + var ssaVars ir.NameSet + + for varID, dvar := range debugInfo.Vars { + n := dvar + ssaVars.Add(n) + for _, slot := range debugInfo.VarSlots[varID] { + ssaVars.Add(debugInfo.Slots[slot].N) + } + + if dvar := createComplexVar(fnsym, fn, ssa.VarID(varID)); dvar != nil { + decls = append(decls, n) + vars = append(vars, dvar) + } + } + + return decls, vars, ssaVars +} + +// createComplexVar builds a single DWARF variable entry and location list. +func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID) *dwarf.Var { + debug := fn.DebugInfo.(*ssa.FuncDebug) + n := debug.Vars[varID] + + var abbrev int + switch n.Class { + case ir.PAUTO: + abbrev = dwarf.DW_ABRV_AUTO_LOCLIST + case ir.PPARAM, ir.PPARAMOUT: + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + default: + return nil + } + + gotype := reflectdata.TypeLinksym(n.Type()) + delete(fnsym.Func().Autot, gotype) + typename := dwarf.InfoPrefix + gotype.Name[len("type."):] + inlIndex := 0 + if base.Flag.GenDwarfInl > 1 { + if n.InlFormal() || n.InlLocal() { + inlIndex = posInlIndex(n.Pos()) + 1 + if n.InlFormal() { + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + } + } + } + declpos := base.Ctxt.InnermostPos(n.Pos()) + dvar := &dwarf.Var{ + Name: n.Sym().Name, + IsReturnValue: n.Class == ir.PPARAMOUT, + IsInlFormal: n.InlFormal(), + Abbrev: abbrev, + Type: base.Ctxt.Lookup(typename), + // The stack offset is used as a sorting key, so for decomposed + // variables just give it the first one. It's not used otherwise. + // This won't work well if the first slot hasn't been assigned a stack + // location, but it's not obvious how to do better. + StackOffset: ssagen.StackOffset(debug.Slots[debug.VarSlots[varID][0]]), + DeclFile: declpos.RelFilename(), + DeclLine: declpos.RelLine(), + DeclCol: declpos.Col(), + InlIndex: int32(inlIndex), + ChildIndex: -1, + } + list := debug.LocationLists[varID] + if len(list) != 0 { + dvar.PutLocationList = func(listSym, startPC dwarf.Sym) { + debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym)) + } + } + return dvar +} + +// RecordFlags records the specified command-line flags to be placed +// in the DWARF info. +func RecordFlags(flags ...string) { + if base.Ctxt.Pkgpath == "" { + // We can't record the flags if we don't know what the + // package name is. + return + } + + type BoolFlag interface { + IsBoolFlag() bool + } + type CountFlag interface { + IsCountFlag() bool + } + var cmd bytes.Buffer + for _, name := range flags { + f := flag.Lookup(name) + if f == nil { + continue + } + getter := f.Value.(flag.Getter) + if getter.String() == f.DefValue { + // Flag has default value, so omit it. + continue + } + if bf, ok := f.Value.(BoolFlag); ok && bf.IsBoolFlag() { + val, ok := getter.Get().(bool) + if ok && val { + fmt.Fprintf(&cmd, " -%s", f.Name) + continue + } + } + if cf, ok := f.Value.(CountFlag); ok && cf.IsCountFlag() { + val, ok := getter.Get().(int) + if ok && val == 1 { + fmt.Fprintf(&cmd, " -%s", f.Name) + continue + } + } + fmt.Fprintf(&cmd, " -%s=%v", f.Name, getter.Get()) + } + + // Adds flag to producer string singalling whether regabi is turned on or + // off. + // Once regabi is turned on across the board and the relative GOEXPERIMENT + // knobs no longer exist this code should be removed. + if buildcfg.Experiment.RegabiArgs { + cmd.Write([]byte(" regabi")) + } + + if cmd.Len() == 0 { + return + } + s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "producer." + base.Ctxt.Pkgpath) + s.Type = objabi.SDWARFCUINFO + // Sometimes (for example when building tests) we can link + // together two package main archives. So allow dups. + s.Set(obj.AttrDuplicateOK, true) + base.Ctxt.Data = append(base.Ctxt.Data, s) + s.P = cmd.Bytes()[1:] +} + +// RecordPackageName records the name of the package being +// compiled, so that the linker can save it in the compile unit's DIE. +func RecordPackageName() { + s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "packagename." + base.Ctxt.Pkgpath) + s.Type = objabi.SDWARFCUINFO + // Sometimes (for example when building tests) we can link + // together two package main archives. So allow dups. + s.Set(obj.AttrDuplicateOK, true) + base.Ctxt.Data = append(base.Ctxt.Data, s) + s.P = []byte(types.LocalPkg.Name) +} diff --git a/src/cmd/compile/internal/gc/dwinl.go b/src/cmd/compile/internal/dwarfgen/dwinl.go similarity index 86% rename from src/cmd/compile/internal/gc/dwinl.go rename to src/cmd/compile/internal/dwarfgen/dwinl.go index 31d076814ca5a2322df93725e2cdcc8b4ed72150..8adb36fc883da0614f760410fa8f6af8e7ccb25e 100644 --- a/src/cmd/compile/internal/gc/dwinl.go +++ b/src/cmd/compile/internal/dwarfgen/dwinl.go @@ -2,14 +2,17 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package dwarfgen import ( + "fmt" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/internal/dwarf" "cmd/internal/obj" "cmd/internal/src" - "fmt" - "strings" ) // To identify variables by original source position. @@ -26,8 +29,8 @@ type varPos struct { func assembleInlines(fnsym *obj.LSym, dwVars []*dwarf.Var) dwarf.InlCalls { var inlcalls dwarf.InlCalls - if Debug_gendwarfinl != 0 { - Ctxt.Logf("assembling DWARF inlined routine info for %v\n", fnsym.Name) + if base.Debug.DwarfInl != 0 { + base.Ctxt.Logf("assembling DWARF inlined routine info for %v\n", fnsym.Name) } // This maps inline index (from Ctxt.InlTree) to index in inlcalls.Calls @@ -106,7 +109,7 @@ func assembleInlines(fnsym *obj.LSym, dwVars []*dwarf.Var) dwarf.InlCalls { } m = makePreinlineDclMap(fnsym) } else { - ifnlsym := Ctxt.InlTree.InlinedFunction(int(ii - 1)) + ifnlsym := base.Ctxt.InlTree.InlinedFunction(int(ii - 1)) m = makePreinlineDclMap(ifnlsym) } @@ -181,7 +184,7 @@ func assembleInlines(fnsym *obj.LSym, dwVars []*dwarf.Var) dwarf.InlCalls { } // Debugging - if Debug_gendwarfinl != 0 { + if base.Debug.DwarfInl != 0 { dumpInlCalls(inlcalls) dumpInlVars(dwVars) } @@ -204,16 +207,17 @@ func assembleInlines(fnsym *obj.LSym, dwVars []*dwarf.Var) dwarf.InlCalls { // late in the compilation when it is determined that we need an // abstract function DIE for an inlined routine imported from a // previously compiled package. -func genAbstractFunc(fn *obj.LSym) { - ifn := Ctxt.DwFixups.GetPrecursorFunc(fn) +func AbstractFunc(fn *obj.LSym) { + ifn := base.Ctxt.DwFixups.GetPrecursorFunc(fn) if ifn == nil { - Ctxt.Diag("failed to locate precursor fn for %v", fn) + base.Ctxt.Diag("failed to locate precursor fn for %v", fn) return } - if Debug_gendwarfinl != 0 { - Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name) + _ = ifn.(*ir.Func) + if base.Debug.DwarfInl != 0 { + base.Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name) } - Ctxt.DwarfAbstractFunc(ifn, fn, myimportpath) + base.Ctxt.DwarfAbstractFunc(ifn, fn, base.Ctxt.Pkgpath) } // Undo any versioning performed when a name was written @@ -235,9 +239,9 @@ func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int { dcl := preInliningDcls(fnsym) m := make(map[varPos]int) for i, n := range dcl { - pos := Ctxt.InnermostPos(n.Pos) + pos := base.Ctxt.InnermostPos(n.Pos()) vp := varPos{ - DeclName: unversion(n.Sym.Name), + DeclName: unversion(n.Sym().Name), DeclFile: pos.RelFilename(), DeclLine: pos.RelLine(), DeclCol: pos.Col(), @@ -261,17 +265,17 @@ func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int { // is one. We do this first so that parents appear before their // children in the resulting table. parCallIdx := -1 - parInlIdx := Ctxt.InlTree.Parent(inlIdx) + parInlIdx := base.Ctxt.InlTree.Parent(inlIdx) if parInlIdx >= 0 { parCallIdx = insertInlCall(dwcalls, parInlIdx, imap) } // Create new entry for this inline - inlinedFn := Ctxt.InlTree.InlinedFunction(inlIdx) - callXPos := Ctxt.InlTree.CallPos(inlIdx) - absFnSym := Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn) - pb := Ctxt.PosTable.Pos(callXPos).Base() - callFileSym := Ctxt.Lookup(pb.SymFilename()) + inlinedFn := base.Ctxt.InlTree.InlinedFunction(inlIdx) + callXPos := base.Ctxt.InlTree.CallPos(inlIdx) + absFnSym := base.Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn) + pb := base.Ctxt.PosTable.Pos(callXPos).Base() + callFileSym := base.Ctxt.Lookup(pb.SymFilename()) ic := dwarf.InlCall{ InlIndex: inlIdx, CallFile: callFileSym, @@ -299,7 +303,7 @@ func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int { // the index for a node from the inlined body of D will refer to the // call to D from C. Whew. func posInlIndex(xpos src.XPos) int { - pos := Ctxt.PosTable.Pos(xpos) + pos := base.Ctxt.PosTable.Pos(xpos) if b := pos.Base(); b != nil { ii := b.InliningIndex() if ii >= 0 { @@ -325,7 +329,7 @@ func addRange(calls []dwarf.InlCall, start, end int64, ii int, imap map[int]int) // Append range to correct inlined call callIdx, found := imap[ii] if !found { - Fatalf("can't find inlIndex %d in imap for prog at %d\n", ii, start) + base.Fatalf("can't find inlIndex %d in imap for prog at %d\n", ii, start) } call := &calls[callIdx] call.Ranges = append(call.Ranges, dwarf.Range{Start: start, End: end}) @@ -333,23 +337,23 @@ func addRange(calls []dwarf.InlCall, start, end int64, ii int, imap map[int]int) func dumpInlCall(inlcalls dwarf.InlCalls, idx, ilevel int) { for i := 0; i < ilevel; i++ { - Ctxt.Logf(" ") + base.Ctxt.Logf(" ") } ic := inlcalls.Calls[idx] - callee := Ctxt.InlTree.InlinedFunction(ic.InlIndex) - Ctxt.Logf(" %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name) + callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex) + base.Ctxt.Logf(" %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name) for _, f := range ic.InlVars { - Ctxt.Logf(" %v", f.Name) + base.Ctxt.Logf(" %v", f.Name) } - Ctxt.Logf(" ) C: (") + base.Ctxt.Logf(" ) C: (") for _, k := range ic.Children { - Ctxt.Logf(" %v", k) + base.Ctxt.Logf(" %v", k) } - Ctxt.Logf(" ) R:") + base.Ctxt.Logf(" ) R:") for _, r := range ic.Ranges { - Ctxt.Logf(" [%d,%d)", r.Start, r.End) + base.Ctxt.Logf(" [%d,%d)", r.Start, r.End) } - Ctxt.Logf("\n") + base.Ctxt.Logf("\n") for _, k := range ic.Children { dumpInlCall(inlcalls, k, ilevel+1) } @@ -374,7 +378,7 @@ func dumpInlVars(dwvars []*dwarf.Var) { if dwv.IsInAbstract { ia = 1 } - Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ) + base.Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ) } } @@ -411,7 +415,7 @@ func checkInlCall(funcName string, inlCalls dwarf.InlCalls, funcSize int64, idx, // Callee ic := inlCalls.Calls[idx] - callee := Ctxt.InlTree.InlinedFunction(ic.InlIndex).Name + callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex).Name calleeRanges := ic.Ranges // Caller @@ -419,14 +423,14 @@ func checkInlCall(funcName string, inlCalls dwarf.InlCalls, funcSize int64, idx, parentRanges := []dwarf.Range{dwarf.Range{Start: int64(0), End: funcSize}} if parentIdx != -1 { pic := inlCalls.Calls[parentIdx] - caller = Ctxt.InlTree.InlinedFunction(pic.InlIndex).Name + caller = base.Ctxt.InlTree.InlinedFunction(pic.InlIndex).Name parentRanges = pic.Ranges } // Callee ranges contained in caller ranges? c, m := rangesContainsAll(parentRanges, calleeRanges) if !c { - Fatalf("** malformed inlined routine range in %s: caller %s callee %s II=%d %s\n", funcName, caller, callee, idx, m) + base.Fatalf("** malformed inlined routine range in %s: caller %s callee %s II=%d %s\n", funcName, caller, callee, idx, m) } // Now visit kids diff --git a/src/cmd/compile/internal/dwarfgen/marker.go b/src/cmd/compile/internal/dwarfgen/marker.go new file mode 100644 index 0000000000000000000000000000000000000000..ec6ce45a900bc098b97773ce16f1c33a5217e23f --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/marker.go @@ -0,0 +1,94 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarfgen + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/internal/src" +) + +// A ScopeMarker tracks scope nesting and boundaries for later use +// during DWARF generation. +type ScopeMarker struct { + parents []ir.ScopeID + marks []ir.Mark +} + +// checkPos validates the given position and returns the current scope. +func (m *ScopeMarker) checkPos(pos src.XPos) ir.ScopeID { + if !pos.IsKnown() { + base.Fatalf("unknown scope position") + } + + if len(m.marks) == 0 { + return 0 + } + + last := &m.marks[len(m.marks)-1] + if xposBefore(pos, last.Pos) { + base.FatalfAt(pos, "non-monotonic scope positions\n\t%v: previous scope position", base.FmtPos(last.Pos)) + } + return last.Scope +} + +// Push records a transition to a new child scope of the current scope. +func (m *ScopeMarker) Push(pos src.XPos) { + current := m.checkPos(pos) + + m.parents = append(m.parents, current) + child := ir.ScopeID(len(m.parents)) + + m.marks = append(m.marks, ir.Mark{Pos: pos, Scope: child}) +} + +// Pop records a transition back to the current scope's parent. +func (m *ScopeMarker) Pop(pos src.XPos) { + current := m.checkPos(pos) + + parent := m.parents[current-1] + + m.marks = append(m.marks, ir.Mark{Pos: pos, Scope: parent}) +} + +// Unpush removes the current scope, which must be empty. +func (m *ScopeMarker) Unpush() { + i := len(m.marks) - 1 + current := m.marks[i].Scope + + if current != ir.ScopeID(len(m.parents)) { + base.FatalfAt(m.marks[i].Pos, "current scope is not empty") + } + + m.parents = m.parents[:current-1] + m.marks = m.marks[:i] +} + +// WriteTo writes the recorded scope marks to the given function, +// and resets the marker for reuse. +func (m *ScopeMarker) WriteTo(fn *ir.Func) { + m.compactMarks() + + fn.Parents = make([]ir.ScopeID, len(m.parents)) + copy(fn.Parents, m.parents) + m.parents = m.parents[:0] + + fn.Marks = make([]ir.Mark, len(m.marks)) + copy(fn.Marks, m.marks) + m.marks = m.marks[:0] +} + +func (m *ScopeMarker) compactMarks() { + n := 0 + for _, next := range m.marks { + if n > 0 && next.Pos == m.marks[n-1].Pos { + m.marks[n-1].Scope = next.Scope + continue + } + m.marks[n] = next + n++ + } + m.marks = m.marks[:n] +} diff --git a/src/cmd/compile/internal/gc/scope.go b/src/cmd/compile/internal/dwarfgen/scope.go similarity index 63% rename from src/cmd/compile/internal/gc/scope.go rename to src/cmd/compile/internal/dwarfgen/scope.go index e66b859e10069142984bf528a1cf47db8d449293..b4ae69e96fa7c4f64adca5a488286a7838f1ce8e 100644 --- a/src/cmd/compile/internal/gc/scope.go +++ b/src/cmd/compile/internal/dwarfgen/scope.go @@ -2,21 +2,24 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package dwarfgen import ( + "sort" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/internal/dwarf" "cmd/internal/obj" "cmd/internal/src" - "sort" ) // See golang.org/issue/20390. func xposBefore(p, q src.XPos) bool { - return Ctxt.PosTable.Pos(p).Before(Ctxt.PosTable.Pos(q)) + return base.Ctxt.PosTable.Pos(p).Before(base.Ctxt.PosTable.Pos(q)) } -func findScope(marks []Mark, pos src.XPos) ScopeID { +func findScope(marks []ir.Mark, pos src.XPos) ir.ScopeID { i := sort.Search(len(marks), func(i int) bool { return xposBefore(pos, marks[i].Pos) }) @@ -26,21 +29,27 @@ func findScope(marks []Mark, pos src.XPos) ScopeID { return marks[i-1].Scope } -func assembleScopes(fnsym *obj.LSym, fn *Node, dwarfVars []*dwarf.Var, varScopes []ScopeID) []dwarf.Scope { +func assembleScopes(fnsym *obj.LSym, fn *ir.Func, dwarfVars []*dwarf.Var, varScopes []ir.ScopeID) []dwarf.Scope { // Initialize the DWARF scope tree based on lexical scopes. - dwarfScopes := make([]dwarf.Scope, 1+len(fn.Func.Parents)) - for i, parent := range fn.Func.Parents { + dwarfScopes := make([]dwarf.Scope, 1+len(fn.Parents)) + for i, parent := range fn.Parents { dwarfScopes[i+1].Parent = int32(parent) } - scopeVariables(dwarfVars, varScopes, dwarfScopes) - scopePCs(fnsym, fn.Func.Marks, dwarfScopes) + scopeVariables(dwarfVars, varScopes, dwarfScopes, fnsym.ABI() != obj.ABI0) + if fnsym.Func().Text != nil { + scopePCs(fnsym, fn.Marks, dwarfScopes) + } return compactScopes(dwarfScopes) } // scopeVariables assigns DWARF variable records to their scopes. -func scopeVariables(dwarfVars []*dwarf.Var, varScopes []ScopeID, dwarfScopes []dwarf.Scope) { - sort.Stable(varsByScopeAndOffset{dwarfVars, varScopes}) +func scopeVariables(dwarfVars []*dwarf.Var, varScopes []ir.ScopeID, dwarfScopes []dwarf.Scope, regabi bool) { + if regabi { + sort.Stable(varsByScope{dwarfVars, varScopes}) + } else { + sort.Stable(varsByScopeAndOffset{dwarfVars, varScopes}) + } i0 := 0 for i := range dwarfVars { @@ -56,7 +65,7 @@ func scopeVariables(dwarfVars []*dwarf.Var, varScopes []ScopeID, dwarfScopes []d } // scopePCs assigns PC ranges to their scopes. -func scopePCs(fnsym *obj.LSym, marks []Mark, dwarfScopes []dwarf.Scope) { +func scopePCs(fnsym *obj.LSym, marks []ir.Mark, dwarfScopes []dwarf.Scope) { // If there aren't any child scopes (in particular, when scope // tracking is disabled), we can skip a whole lot of work. if len(marks) == 0 { @@ -89,7 +98,7 @@ func compactScopes(dwarfScopes []dwarf.Scope) []dwarf.Scope { type varsByScopeAndOffset struct { vars []*dwarf.Var - scopes []ScopeID + scopes []ir.ScopeID } func (v varsByScopeAndOffset) Len() int { @@ -107,3 +116,21 @@ func (v varsByScopeAndOffset) Swap(i, j int) { v.vars[i], v.vars[j] = v.vars[j], v.vars[i] v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i] } + +type varsByScope struct { + vars []*dwarf.Var + scopes []ir.ScopeID +} + +func (v varsByScope) Len() int { + return len(v.vars) +} + +func (v varsByScope) Less(i, j int) bool { + return v.scopes[i] < v.scopes[j] +} + +func (v varsByScope) Swap(i, j int) { + v.vars[i], v.vars[j] = v.vars[j], v.vars[i] + v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i] +} diff --git a/src/cmd/compile/internal/gc/scope_test.go b/src/cmd/compile/internal/dwarfgen/scope_test.go similarity index 98% rename from src/cmd/compile/internal/gc/scope_test.go rename to src/cmd/compile/internal/dwarfgen/scope_test.go index b0e038d27f5db419414c71058430c76ddab267be..3df4c345c3cb5f5e9d31fc7ce36e560ac4e8b759 100644 --- a/src/cmd/compile/internal/gc/scope_test.go +++ b/src/cmd/compile/internal/dwarfgen/scope_test.go @@ -2,10 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc_test +package dwarfgen import ( - "cmd/internal/objfile" "debug/dwarf" "fmt" "internal/testenv" @@ -18,6 +17,8 @@ import ( "strconv" "strings" "testing" + + "cmd/internal/objfile" ) type testline struct { @@ -180,17 +181,17 @@ var testfile = []testline{ {line: " fi(p)", scopes: []int{1}}, {line: " }"}, {line: "}"}, - {line: "func TestCaptureVar(flag bool) func() int {"}, - {line: " a := 1", vars: []string{"arg flag bool", "arg ~r1 func() int", "var a int"}}, + {line: "var fglob func() int"}, + {line: "func TestCaptureVar(flag bool) {"}, + {line: " a := 1", vars: []string{"arg flag bool", "var a int"}}, // TODO(register args) restore "arg ~r1 func() int", {line: " if flag {"}, {line: " b := 2", scopes: []int{1}, vars: []string{"var b int", "var f func() int"}}, {line: " f := func() int {", scopes: []int{1, 0}}, {line: " return b + 1"}, {line: " }"}, - {line: " return f", scopes: []int{1}}, + {line: " fglob = f", scopes: []int{1}}, {line: " }"}, {line: " f1(a)"}, - {line: " return nil"}, {line: "}"}, {line: "func main() {"}, {line: " TestNestedFor()"}, diff --git a/src/cmd/compile/internal/escape/escape.go b/src/cmd/compile/internal/escape/escape.go new file mode 100644 index 0000000000000000000000000000000000000000..cd56f07b6147a431cf0347e65c5c91df2d98d29d --- /dev/null +++ b/src/cmd/compile/internal/escape/escape.go @@ -0,0 +1,2149 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package escape + +import ( + "fmt" + "math" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/logopt" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +// Escape analysis. +// +// Here we analyze functions to determine which Go variables +// (including implicit allocations such as calls to "new" or "make", +// composite literals, etc.) can be allocated on the stack. The two +// key invariants we have to ensure are: (1) pointers to stack objects +// cannot be stored in the heap, and (2) pointers to a stack object +// cannot outlive that object (e.g., because the declaring function +// returned and destroyed the object's stack frame, or its space is +// reused across loop iterations for logically distinct variables). +// +// We implement this with a static data-flow analysis of the AST. +// First, we construct a directed weighted graph where vertices +// (termed "locations") represent variables allocated by statements +// and expressions, and edges represent assignments between variables +// (with weights representing addressing/dereference counts). +// +// Next we walk the graph looking for assignment paths that might +// violate the invariants stated above. If a variable v's address is +// stored in the heap or elsewhere that may outlive it, then v is +// marked as requiring heap allocation. +// +// To support interprocedural analysis, we also record data-flow from +// each function's parameters to the heap and to its result +// parameters. This information is summarized as "parameter tags", +// which are used at static call sites to improve escape analysis of +// function arguments. + +// Constructing the location graph. +// +// Every allocating statement (e.g., variable declaration) or +// expression (e.g., "new" or "make") is first mapped to a unique +// "location." +// +// We also model every Go assignment as a directed edges between +// locations. The number of dereference operations minus the number of +// addressing operations is recorded as the edge's weight (termed +// "derefs"). For example: +// +// p = &q // -1 +// p = q // 0 +// p = *q // 1 +// p = **q // 2 +// +// p = **&**&q // 2 +// +// Note that the & operator can only be applied to addressable +// expressions, and the expression &x itself is not addressable, so +// derefs cannot go below -1. +// +// Every Go language construct is lowered into this representation, +// generally without sensitivity to flow, path, or context; and +// without distinguishing elements within a compound variable. For +// example: +// +// var x struct { f, g *int } +// var u []*int +// +// x.f = u[0] +// +// is modeled simply as +// +// x = *u +// +// That is, we don't distinguish x.f from x.g, or u[0] from u[1], +// u[2], etc. However, we do record the implicit dereference involved +// in indexing a slice. + +// A batch holds escape analysis state that's shared across an entire +// batch of functions being analyzed at once. +type batch struct { + allLocs []*location + closures []closure + + heapLoc location + blankLoc location +} + +// A closure holds a closure expression and its spill hole (i.e., +// where the hole representing storing into its closure record). +type closure struct { + k hole + clo *ir.ClosureExpr +} + +// An escape holds state specific to a single function being analyzed +// within a batch. +type escape struct { + *batch + + curfn *ir.Func // function being analyzed + + labels map[*types.Sym]labelState // known labels + + // loopDepth counts the current loop nesting depth within + // curfn. It increments within each "for" loop and at each + // label with a corresponding backwards "goto" (i.e., + // unstructured loop). + loopDepth int +} + +// An location represents an abstract location that stores a Go +// variable. +type location struct { + n ir.Node // represented variable or expression, if any + curfn *ir.Func // enclosing function + edges []edge // incoming edges + loopDepth int // loopDepth at declaration + + // resultIndex records the tuple index (starting at 1) for + // PPARAMOUT variables within their function's result type. + // For non-PPARAMOUT variables it's 0. + resultIndex int + + // derefs and walkgen are used during walkOne to track the + // minimal dereferences from the walk root. + derefs int // >= -1 + walkgen uint32 + + // dst and dstEdgeindex track the next immediate assignment + // destination location during walkone, along with the index + // of the edge pointing back to this location. + dst *location + dstEdgeIdx int + + // queued is used by walkAll to track whether this location is + // in the walk queue. + queued bool + + // escapes reports whether the represented variable's address + // escapes; that is, whether the variable must be heap + // allocated. + escapes bool + + // transient reports whether the represented expression's + // address does not outlive the statement; that is, whether + // its storage can be immediately reused. + transient bool + + // paramEsc records the represented parameter's leak set. + paramEsc leaks + + captured bool // has a closure captured this variable? + reassigned bool // has this variable been reassigned? + addrtaken bool // has this variable's address been taken? +} + +// An edge represents an assignment edge between two Go variables. +type edge struct { + src *location + derefs int // >= -1 + notes *note +} + +// Fmt is called from node printing to print information about escape analysis results. +func Fmt(n ir.Node) string { + text := "" + switch n.Esc() { + case ir.EscUnknown: + break + + case ir.EscHeap: + text = "esc(h)" + + case ir.EscNone: + text = "esc(no)" + + case ir.EscNever: + text = "esc(N)" + + default: + text = fmt.Sprintf("esc(%d)", n.Esc()) + } + + if n.Op() == ir.ONAME { + n := n.(*ir.Name) + if loc, ok := n.Opt.(*location); ok && loc.loopDepth != 0 { + if text != "" { + text += " " + } + text += fmt.Sprintf("ld(%d)", loc.loopDepth) + } + } + + return text +} + +// Batch performs escape analysis on a minimal batch of +// functions. +func Batch(fns []*ir.Func, recursive bool) { + for _, fn := range fns { + if fn.Op() != ir.ODCLFUNC { + base.Fatalf("unexpected node: %v", fn) + } + } + + var b batch + b.heapLoc.escapes = true + + // Construct data-flow graph from syntax trees. + for _, fn := range fns { + if base.Flag.W > 1 { + s := fmt.Sprintf("\nbefore escape %v", fn) + ir.Dump(s, fn) + } + b.initFunc(fn) + } + for _, fn := range fns { + if !fn.IsHiddenClosure() { + b.walkFunc(fn) + } + } + + // We've walked the function bodies, so we've seen everywhere a + // variable might be reassigned or have it's address taken. Now we + // can decide whether closures should capture their free variables + // by value or reference. + for _, closure := range b.closures { + b.flowClosure(closure.k, closure.clo) + } + b.closures = nil + + for _, loc := range b.allLocs { + if why := HeapAllocReason(loc.n); why != "" { + b.flow(b.heapHole().addr(loc.n, why), loc) + } + } + + b.walkAll() + b.finish(fns) +} + +func (b *batch) with(fn *ir.Func) *escape { + return &escape{ + batch: b, + curfn: fn, + loopDepth: 1, + } +} + +func (b *batch) initFunc(fn *ir.Func) { + e := b.with(fn) + if fn.Esc() != escFuncUnknown { + base.Fatalf("unexpected node: %v", fn) + } + fn.SetEsc(escFuncPlanned) + if base.Flag.LowerM > 3 { + ir.Dump("escAnalyze", fn) + } + + // Allocate locations for local variables. + for _, n := range fn.Dcl { + if n.Op() == ir.ONAME { + e.newLoc(n, false) + } + } + + // Initialize resultIndex for result parameters. + for i, f := range fn.Type().Results().FieldSlice() { + e.oldLoc(f.Nname.(*ir.Name)).resultIndex = 1 + i + } +} + +func (b *batch) walkFunc(fn *ir.Func) { + e := b.with(fn) + fn.SetEsc(escFuncStarted) + + // Identify labels that mark the head of an unstructured loop. + ir.Visit(fn, func(n ir.Node) { + switch n.Op() { + case ir.OLABEL: + n := n.(*ir.LabelStmt) + if e.labels == nil { + e.labels = make(map[*types.Sym]labelState) + } + e.labels[n.Label] = nonlooping + + case ir.OGOTO: + // If we visited the label before the goto, + // then this is a looping label. + n := n.(*ir.BranchStmt) + if e.labels[n.Label] == nonlooping { + e.labels[n.Label] = looping + } + } + }) + + e.block(fn.Body) + + if len(e.labels) != 0 { + base.FatalfAt(fn.Pos(), "leftover labels after walkFunc") + } +} + +func (b *batch) flowClosure(k hole, clo *ir.ClosureExpr) { + for _, cv := range clo.Func.ClosureVars { + n := cv.Canonical() + loc := b.oldLoc(cv) + if !loc.captured { + base.FatalfAt(cv.Pos(), "closure variable never captured: %v", cv) + } + + // Capture by value for variables <= 128 bytes that are never reassigned. + n.SetByval(!loc.addrtaken && !loc.reassigned && n.Type().Size() <= 128) + if !n.Byval() { + n.SetAddrtaken(true) + } + + if base.Flag.LowerM > 1 { + how := "ref" + if n.Byval() { + how = "value" + } + base.WarnfAt(n.Pos(), "%v capturing by %s: %v (addr=%v assign=%v width=%d)", n.Curfn, how, n, loc.addrtaken, loc.reassigned, n.Type().Size()) + } + + // Flow captured variables to closure. + k := k + if !cv.Byval() { + k = k.addr(cv, "reference") + } + b.flow(k.note(cv, "captured by a closure"), loc) + } +} + +// Below we implement the methods for walking the AST and recording +// data flow edges. Note that because a sub-expression might have +// side-effects, it's important to always visit the entire AST. +// +// For example, write either: +// +// if x { +// e.discard(n.Left) +// } else { +// e.value(k, n.Left) +// } +// +// or +// +// if x { +// k = e.discardHole() +// } +// e.value(k, n.Left) +// +// Do NOT write: +// +// // BAD: possibly loses side-effects within n.Left +// if !x { +// e.value(k, n.Left) +// } + +// stmt evaluates a single Go statement. +func (e *escape) stmt(n ir.Node) { + if n == nil { + return + } + + lno := ir.SetPos(n) + defer func() { + base.Pos = lno + }() + + if base.Flag.LowerM > 2 { + fmt.Printf("%v:[%d] %v stmt: %v\n", base.FmtPos(base.Pos), e.loopDepth, e.curfn, n) + } + + e.stmts(n.Init()) + + switch n.Op() { + default: + base.Fatalf("unexpected stmt: %v", n) + + case ir.ODCLCONST, ir.ODCLTYPE, ir.OFALL, ir.OINLMARK: + // nop + + case ir.OBREAK, ir.OCONTINUE, ir.OGOTO: + // TODO(mdempsky): Handle dead code? + + case ir.OBLOCK: + n := n.(*ir.BlockStmt) + e.stmts(n.List) + + case ir.ODCL: + // Record loop depth at declaration. + n := n.(*ir.Decl) + if !ir.IsBlank(n.X) { + e.dcl(n.X) + } + + case ir.OLABEL: + n := n.(*ir.LabelStmt) + switch e.labels[n.Label] { + case nonlooping: + if base.Flag.LowerM > 2 { + fmt.Printf("%v:%v non-looping label\n", base.FmtPos(base.Pos), n) + } + case looping: + if base.Flag.LowerM > 2 { + fmt.Printf("%v: %v looping label\n", base.FmtPos(base.Pos), n) + } + e.loopDepth++ + default: + base.Fatalf("label missing tag") + } + delete(e.labels, n.Label) + + case ir.OIF: + n := n.(*ir.IfStmt) + e.discard(n.Cond) + e.block(n.Body) + e.block(n.Else) + + case ir.OFOR, ir.OFORUNTIL: + n := n.(*ir.ForStmt) + e.loopDepth++ + e.discard(n.Cond) + e.stmt(n.Post) + e.block(n.Body) + e.loopDepth-- + + case ir.ORANGE: + // for Key, Value = range X { Body } + n := n.(*ir.RangeStmt) + + // X is evaluated outside the loop. + tmp := e.newLoc(nil, false) + e.expr(tmp.asHole(), n.X) + + e.loopDepth++ + ks := e.addrs([]ir.Node{n.Key, n.Value}) + if n.X.Type().IsArray() { + e.flow(ks[1].note(n, "range"), tmp) + } else { + e.flow(ks[1].deref(n, "range-deref"), tmp) + } + e.reassigned(ks, n) + + e.block(n.Body) + e.loopDepth-- + + case ir.OSWITCH: + n := n.(*ir.SwitchStmt) + + if guard, ok := n.Tag.(*ir.TypeSwitchGuard); ok { + var ks []hole + if guard.Tag != nil { + for _, cas := range n.Cases { + cv := cas.Var + k := e.dcl(cv) // type switch variables have no ODCL. + if cv.Type().HasPointers() { + ks = append(ks, k.dotType(cv.Type(), cas, "switch case")) + } + } + } + e.expr(e.teeHole(ks...), n.Tag.(*ir.TypeSwitchGuard).X) + } else { + e.discard(n.Tag) + } + + for _, cas := range n.Cases { + e.discards(cas.List) + e.block(cas.Body) + } + + case ir.OSELECT: + n := n.(*ir.SelectStmt) + for _, cas := range n.Cases { + e.stmt(cas.Comm) + e.block(cas.Body) + } + case ir.ORECV: + // TODO(mdempsky): Consider e.discard(n.Left). + n := n.(*ir.UnaryExpr) + e.exprSkipInit(e.discardHole(), n) // already visited n.Ninit + case ir.OSEND: + n := n.(*ir.SendStmt) + e.discard(n.Chan) + e.assignHeap(n.Value, "send", n) + + case ir.OAS: + n := n.(*ir.AssignStmt) + e.assignList([]ir.Node{n.X}, []ir.Node{n.Y}, "assign", n) + case ir.OASOP: + n := n.(*ir.AssignOpStmt) + // TODO(mdempsky): Worry about OLSH/ORSH? + e.assignList([]ir.Node{n.X}, []ir.Node{n.Y}, "assign", n) + case ir.OAS2: + n := n.(*ir.AssignListStmt) + e.assignList(n.Lhs, n.Rhs, "assign-pair", n) + + case ir.OAS2DOTTYPE: // v, ok = x.(type) + n := n.(*ir.AssignListStmt) + e.assignList(n.Lhs, n.Rhs, "assign-pair-dot-type", n) + case ir.OAS2MAPR: // v, ok = m[k] + n := n.(*ir.AssignListStmt) + e.assignList(n.Lhs, n.Rhs, "assign-pair-mapr", n) + case ir.OAS2RECV, ir.OSELRECV2: // v, ok = <-ch + n := n.(*ir.AssignListStmt) + e.assignList(n.Lhs, n.Rhs, "assign-pair-receive", n) + + case ir.OAS2FUNC: + n := n.(*ir.AssignListStmt) + e.stmts(n.Rhs[0].Init()) + ks := e.addrs(n.Lhs) + e.call(ks, n.Rhs[0], nil) + e.reassigned(ks, n) + case ir.ORETURN: + n := n.(*ir.ReturnStmt) + results := e.curfn.Type().Results().FieldSlice() + dsts := make([]ir.Node, len(results)) + for i, res := range results { + dsts[i] = res.Nname.(*ir.Name) + } + e.assignList(dsts, n.Results, "return", n) + case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER, ir.OCLOSE, ir.OCOPY, ir.ODELETE, ir.OPANIC, ir.OPRINT, ir.OPRINTN, ir.ORECOVER: + e.call(nil, n, nil) + case ir.OGO, ir.ODEFER: + n := n.(*ir.GoDeferStmt) + e.stmts(n.Call.Init()) + e.call(nil, n.Call, n) + + case ir.OTAILCALL: + // TODO(mdempsky): Treat like a normal call? esc.go used to just ignore it. + } +} + +func (e *escape) stmts(l ir.Nodes) { + for _, n := range l { + e.stmt(n) + } +} + +// block is like stmts, but preserves loopDepth. +func (e *escape) block(l ir.Nodes) { + old := e.loopDepth + e.stmts(l) + e.loopDepth = old +} + +// expr models evaluating an expression n and flowing the result into +// hole k. +func (e *escape) expr(k hole, n ir.Node) { + if n == nil { + return + } + e.stmts(n.Init()) + e.exprSkipInit(k, n) +} + +func (e *escape) exprSkipInit(k hole, n ir.Node) { + if n == nil { + return + } + + lno := ir.SetPos(n) + defer func() { + base.Pos = lno + }() + + uintptrEscapesHack := k.uintptrEscapesHack + k.uintptrEscapesHack = false + + if uintptrEscapesHack && n.Op() == ir.OCONVNOP && n.(*ir.ConvExpr).X.Type().IsUnsafePtr() { + // nop + } else if k.derefs >= 0 && !n.Type().HasPointers() { + k.dst = &e.blankLoc + } + + switch n.Op() { + default: + base.Fatalf("unexpected expr: %s %v", n.Op().String(), n) + + case ir.OLITERAL, ir.ONIL, ir.OGETG, ir.OTYPE, ir.OMETHEXPR, ir.OLINKSYMOFFSET: + // nop + + case ir.ONAME: + n := n.(*ir.Name) + if n.Class == ir.PFUNC || n.Class == ir.PEXTERN { + return + } + if n.IsClosureVar() && n.Defn == nil { + return // ".this" from method value wrapper + } + e.flow(k, e.oldLoc(n)) + + case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT: + n := n.(*ir.UnaryExpr) + e.discard(n.X) + case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.ODIV, ir.OMOD, ir.OLSH, ir.ORSH, ir.OAND, ir.OANDNOT, ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE: + n := n.(*ir.BinaryExpr) + e.discard(n.X) + e.discard(n.Y) + case ir.OANDAND, ir.OOROR: + n := n.(*ir.LogicalExpr) + e.discard(n.X) + e.discard(n.Y) + case ir.OADDR: + n := n.(*ir.AddrExpr) + e.expr(k.addr(n, "address-of"), n.X) // "address-of" + case ir.ODEREF: + n := n.(*ir.StarExpr) + e.expr(k.deref(n, "indirection"), n.X) // "indirection" + case ir.ODOT, ir.ODOTMETH, ir.ODOTINTER: + n := n.(*ir.SelectorExpr) + e.expr(k.note(n, "dot"), n.X) + case ir.ODOTPTR: + n := n.(*ir.SelectorExpr) + e.expr(k.deref(n, "dot of pointer"), n.X) // "dot of pointer" + case ir.ODOTTYPE, ir.ODOTTYPE2: + n := n.(*ir.TypeAssertExpr) + e.expr(k.dotType(n.Type(), n, "dot"), n.X) + case ir.OINDEX: + n := n.(*ir.IndexExpr) + if n.X.Type().IsArray() { + e.expr(k.note(n, "fixed-array-index-of"), n.X) + } else { + // TODO(mdempsky): Fix why reason text. + e.expr(k.deref(n, "dot of pointer"), n.X) + } + e.discard(n.Index) + case ir.OINDEXMAP: + n := n.(*ir.IndexExpr) + e.discard(n.X) + e.discard(n.Index) + case ir.OSLICE, ir.OSLICEARR, ir.OSLICE3, ir.OSLICE3ARR, ir.OSLICESTR: + n := n.(*ir.SliceExpr) + e.expr(k.note(n, "slice"), n.X) + e.discard(n.Low) + e.discard(n.High) + e.discard(n.Max) + + case ir.OCONV, ir.OCONVNOP: + n := n.(*ir.ConvExpr) + if ir.ShouldCheckPtr(e.curfn, 2) && n.Type().IsUnsafePtr() && n.X.Type().IsPtr() { + // When -d=checkptr=2 is enabled, treat + // conversions to unsafe.Pointer as an + // escaping operation. This allows better + // runtime instrumentation, since we can more + // easily detect object boundaries on the heap + // than the stack. + e.assignHeap(n.X, "conversion to unsafe.Pointer", n) + } else if n.Type().IsUnsafePtr() && n.X.Type().IsUintptr() { + e.unsafeValue(k, n.X) + } else { + e.expr(k, n.X) + } + case ir.OCONVIFACE: + n := n.(*ir.ConvExpr) + if !n.X.Type().IsInterface() && !types.IsDirectIface(n.X.Type()) { + k = e.spill(k, n) + } + e.expr(k.note(n, "interface-converted"), n.X) + case ir.OSLICE2ARRPTR: + // the slice pointer flows directly to the result + n := n.(*ir.ConvExpr) + e.expr(k, n.X) + case ir.ORECV: + n := n.(*ir.UnaryExpr) + e.discard(n.X) + + case ir.OCALLMETH, ir.OCALLFUNC, ir.OCALLINTER, ir.OLEN, ir.OCAP, ir.OCOMPLEX, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.OCOPY, ir.OUNSAFEADD, ir.OUNSAFESLICE: + e.call([]hole{k}, n, nil) + + case ir.ONEW: + n := n.(*ir.UnaryExpr) + e.spill(k, n) + + case ir.OMAKESLICE: + n := n.(*ir.MakeExpr) + e.spill(k, n) + e.discard(n.Len) + e.discard(n.Cap) + case ir.OMAKECHAN: + n := n.(*ir.MakeExpr) + e.discard(n.Len) + case ir.OMAKEMAP: + n := n.(*ir.MakeExpr) + e.spill(k, n) + e.discard(n.Len) + + case ir.ORECOVER: + // nop + + case ir.OCALLPART: + // Flow the receiver argument to both the closure and + // to the receiver parameter. + + n := n.(*ir.SelectorExpr) + closureK := e.spill(k, n) + + m := n.Selection + + // We don't know how the method value will be called + // later, so conservatively assume the result + // parameters all flow to the heap. + // + // TODO(mdempsky): Change ks into a callback, so that + // we don't have to create this slice? + var ks []hole + for i := m.Type.NumResults(); i > 0; i-- { + ks = append(ks, e.heapHole()) + } + name, _ := m.Nname.(*ir.Name) + paramK := e.tagHole(ks, name, m.Type.Recv()) + + e.expr(e.teeHole(paramK, closureK), n.X) + + case ir.OPTRLIT: + n := n.(*ir.AddrExpr) + e.expr(e.spill(k, n), n.X) + + case ir.OARRAYLIT: + n := n.(*ir.CompLitExpr) + for _, elt := range n.List { + if elt.Op() == ir.OKEY { + elt = elt.(*ir.KeyExpr).Value + } + e.expr(k.note(n, "array literal element"), elt) + } + + case ir.OSLICELIT: + n := n.(*ir.CompLitExpr) + k = e.spill(k, n) + k.uintptrEscapesHack = uintptrEscapesHack // for ...uintptr parameters + + for _, elt := range n.List { + if elt.Op() == ir.OKEY { + elt = elt.(*ir.KeyExpr).Value + } + e.expr(k.note(n, "slice-literal-element"), elt) + } + + case ir.OSTRUCTLIT: + n := n.(*ir.CompLitExpr) + for _, elt := range n.List { + e.expr(k.note(n, "struct literal element"), elt.(*ir.StructKeyExpr).Value) + } + + case ir.OMAPLIT: + n := n.(*ir.CompLitExpr) + e.spill(k, n) + + // Map keys and values are always stored in the heap. + for _, elt := range n.List { + elt := elt.(*ir.KeyExpr) + e.assignHeap(elt.Key, "map literal key", n) + e.assignHeap(elt.Value, "map literal value", n) + } + + case ir.OCLOSURE: + n := n.(*ir.ClosureExpr) + k = e.spill(k, n) + e.closures = append(e.closures, closure{k, n}) + + if fn := n.Func; fn.IsHiddenClosure() { + for _, cv := range fn.ClosureVars { + if loc := e.oldLoc(cv); !loc.captured { + loc.captured = true + + // Ignore reassignments to the variable in straightline code + // preceding the first capture by a closure. + if loc.loopDepth == e.loopDepth { + loc.reassigned = false + } + } + } + + for _, n := range fn.Dcl { + // Add locations for local variables of the + // closure, if needed, in case we're not including + // the closure func in the batch for escape + // analysis (happens for escape analysis called + // from reflectdata.methodWrapper) + if n.Op() == ir.ONAME && n.Opt == nil { + e.with(fn).newLoc(n, false) + } + } + e.walkFunc(fn) + } + + case ir.ORUNES2STR, ir.OBYTES2STR, ir.OSTR2RUNES, ir.OSTR2BYTES, ir.ORUNESTR: + n := n.(*ir.ConvExpr) + e.spill(k, n) + e.discard(n.X) + + case ir.OADDSTR: + n := n.(*ir.AddStringExpr) + e.spill(k, n) + + // Arguments of OADDSTR never escape; + // runtime.concatstrings makes sure of that. + e.discards(n.List) + } +} + +// unsafeValue evaluates a uintptr-typed arithmetic expression looking +// for conversions from an unsafe.Pointer. +func (e *escape) unsafeValue(k hole, n ir.Node) { + if n.Type().Kind() != types.TUINTPTR { + base.Fatalf("unexpected type %v for %v", n.Type(), n) + } + if k.addrtaken { + base.Fatalf("unexpected addrtaken") + } + + e.stmts(n.Init()) + + switch n.Op() { + case ir.OCONV, ir.OCONVNOP: + n := n.(*ir.ConvExpr) + if n.X.Type().IsUnsafePtr() { + e.expr(k, n.X) + } else { + e.discard(n.X) + } + case ir.ODOTPTR: + n := n.(*ir.SelectorExpr) + if ir.IsReflectHeaderDataField(n) { + e.expr(k.deref(n, "reflect.Header.Data"), n.X) + } else { + e.discard(n.X) + } + case ir.OPLUS, ir.ONEG, ir.OBITNOT: + n := n.(*ir.UnaryExpr) + e.unsafeValue(k, n.X) + case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.ODIV, ir.OMOD, ir.OAND, ir.OANDNOT: + n := n.(*ir.BinaryExpr) + e.unsafeValue(k, n.X) + e.unsafeValue(k, n.Y) + case ir.OLSH, ir.ORSH: + n := n.(*ir.BinaryExpr) + e.unsafeValue(k, n.X) + // RHS need not be uintptr-typed (#32959) and can't meaningfully + // flow pointers anyway. + e.discard(n.Y) + default: + e.exprSkipInit(e.discardHole(), n) + } +} + +// discard evaluates an expression n for side-effects, but discards +// its value. +func (e *escape) discard(n ir.Node) { + e.expr(e.discardHole(), n) +} + +func (e *escape) discards(l ir.Nodes) { + for _, n := range l { + e.discard(n) + } +} + +// addr evaluates an addressable expression n and returns a hole +// that represents storing into the represented location. +func (e *escape) addr(n ir.Node) hole { + if n == nil || ir.IsBlank(n) { + // Can happen in select case, range, maybe others. + return e.discardHole() + } + + k := e.heapHole() + + switch n.Op() { + default: + base.Fatalf("unexpected addr: %v", n) + case ir.ONAME: + n := n.(*ir.Name) + if n.Class == ir.PEXTERN { + break + } + k = e.oldLoc(n).asHole() + case ir.OLINKSYMOFFSET: + break + case ir.ODOT: + n := n.(*ir.SelectorExpr) + k = e.addr(n.X) + case ir.OINDEX: + n := n.(*ir.IndexExpr) + e.discard(n.Index) + if n.X.Type().IsArray() { + k = e.addr(n.X) + } else { + e.discard(n.X) + } + case ir.ODEREF, ir.ODOTPTR: + e.discard(n) + case ir.OINDEXMAP: + n := n.(*ir.IndexExpr) + e.discard(n.X) + e.assignHeap(n.Index, "key of map put", n) + } + + return k +} + +func (e *escape) addrs(l ir.Nodes) []hole { + var ks []hole + for _, n := range l { + ks = append(ks, e.addr(n)) + } + return ks +} + +// reassigned marks the locations associated with the given holes as +// reassigned, unless the location represents a variable declared and +// assigned exactly once by where. +func (e *escape) reassigned(ks []hole, where ir.Node) { + if as, ok := where.(*ir.AssignStmt); ok && as.Op() == ir.OAS && as.Y == nil { + if dst, ok := as.X.(*ir.Name); ok && dst.Op() == ir.ONAME && dst.Defn == nil { + // Zero-value assignment for variable declared without an + // explicit initial value. Assume this is its initialization + // statement. + return + } + } + + for _, k := range ks { + loc := k.dst + // Variables declared by range statements are assigned on every iteration. + if n, ok := loc.n.(*ir.Name); ok && n.Defn == where && where.Op() != ir.ORANGE { + continue + } + loc.reassigned = true + } +} + +// assignList evaluates the assignment dsts... = srcs.... +func (e *escape) assignList(dsts, srcs []ir.Node, why string, where ir.Node) { + ks := e.addrs(dsts) + for i, k := range ks { + var src ir.Node + if i < len(srcs) { + src = srcs[i] + } + + if dst := dsts[i]; dst != nil { + // Detect implicit conversion of uintptr to unsafe.Pointer when + // storing into reflect.{Slice,String}Header. + if dst.Op() == ir.ODOTPTR && ir.IsReflectHeaderDataField(dst) { + e.unsafeValue(e.heapHole().note(where, why), src) + continue + } + + // Filter out some no-op assignments for escape analysis. + if src != nil && isSelfAssign(dst, src) { + if base.Flag.LowerM != 0 { + base.WarnfAt(where.Pos(), "%v ignoring self-assignment in %v", e.curfn, where) + } + k = e.discardHole() + } + } + + e.expr(k.note(where, why), src) + } + + e.reassigned(ks, where) +} + +func (e *escape) assignHeap(src ir.Node, why string, where ir.Node) { + e.expr(e.heapHole().note(where, why), src) +} + +// call evaluates a call expressions, including builtin calls. ks +// should contain the holes representing where the function callee's +// results flows; where is the OGO/ODEFER context of the call, if any. +func (e *escape) call(ks []hole, call, where ir.Node) { + topLevelDefer := where != nil && where.Op() == ir.ODEFER && e.loopDepth == 1 + if topLevelDefer { + // force stack allocation of defer record, unless + // open-coded defers are used (see ssa.go) + where.SetEsc(ir.EscNever) + } + + argument := func(k hole, arg ir.Node) { + if topLevelDefer { + // Top level defers arguments don't escape to + // heap, but they do need to last until end of + // function. + k = e.later(k) + } else if where != nil { + k = e.heapHole() + } + + e.expr(k.note(call, "call parameter"), arg) + } + + switch call.Op() { + default: + ir.Dump("esc", call) + base.Fatalf("unexpected call op: %v", call.Op()) + + case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER: + call := call.(*ir.CallExpr) + typecheck.FixVariadicCall(call) + + // Pick out the function callee, if statically known. + var fn *ir.Name + switch call.Op() { + case ir.OCALLFUNC: + switch v := ir.StaticValue(call.X); { + case v.Op() == ir.ONAME && v.(*ir.Name).Class == ir.PFUNC: + fn = v.(*ir.Name) + case v.Op() == ir.OCLOSURE: + fn = v.(*ir.ClosureExpr).Func.Nname + } + case ir.OCALLMETH: + fn = ir.MethodExprName(call.X) + } + + fntype := call.X.Type() + if fn != nil { + fntype = fn.Type() + } + + if ks != nil && fn != nil && e.inMutualBatch(fn) { + for i, result := range fn.Type().Results().FieldSlice() { + e.expr(ks[i], ir.AsNode(result.Nname)) + } + } + + if r := fntype.Recv(); r != nil { + argument(e.tagHole(ks, fn, r), call.X.(*ir.SelectorExpr).X) + } else { + // Evaluate callee function expression. + argument(e.discardHole(), call.X) + } + + args := call.Args + for i, param := range fntype.Params().FieldSlice() { + argument(e.tagHole(ks, fn, param), args[i]) + } + + case ir.OAPPEND: + call := call.(*ir.CallExpr) + args := call.Args + + // Appendee slice may flow directly to the result, if + // it has enough capacity. Alternatively, a new heap + // slice might be allocated, and all slice elements + // might flow to heap. + appendeeK := ks[0] + if args[0].Type().Elem().HasPointers() { + appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice")) + } + argument(appendeeK, args[0]) + + if call.IsDDD { + appendedK := e.discardHole() + if args[1].Type().IsSlice() && args[1].Type().Elem().HasPointers() { + appendedK = e.heapHole().deref(call, "appended slice...") + } + argument(appendedK, args[1]) + } else { + for _, arg := range args[1:] { + argument(e.heapHole(), arg) + } + } + + case ir.OCOPY: + call := call.(*ir.BinaryExpr) + argument(e.discardHole(), call.X) + + copiedK := e.discardHole() + if call.Y.Type().IsSlice() && call.Y.Type().Elem().HasPointers() { + copiedK = e.heapHole().deref(call, "copied slice") + } + argument(copiedK, call.Y) + + case ir.OPANIC: + call := call.(*ir.UnaryExpr) + argument(e.heapHole(), call.X) + + case ir.OCOMPLEX: + call := call.(*ir.BinaryExpr) + argument(e.discardHole(), call.X) + argument(e.discardHole(), call.Y) + case ir.ODELETE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER: + call := call.(*ir.CallExpr) + for _, arg := range call.Args { + argument(e.discardHole(), arg) + } + case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE: + call := call.(*ir.UnaryExpr) + argument(e.discardHole(), call.X) + + case ir.OUNSAFEADD, ir.OUNSAFESLICE: + call := call.(*ir.BinaryExpr) + argument(ks[0], call.X) + argument(e.discardHole(), call.Y) + } +} + +// tagHole returns a hole for evaluating an argument passed to param. +// ks should contain the holes representing where the function +// callee's results flows. fn is the statically-known callee function, +// if any. +func (e *escape) tagHole(ks []hole, fn *ir.Name, param *types.Field) hole { + // If this is a dynamic call, we can't rely on param.Note. + if fn == nil { + return e.heapHole() + } + + if e.inMutualBatch(fn) { + return e.addr(ir.AsNode(param.Nname)) + } + + // Call to previously tagged function. + + if param.Note == UintptrEscapesNote { + k := e.heapHole() + k.uintptrEscapesHack = true + return k + } + + var tagKs []hole + + esc := parseLeaks(param.Note) + if x := esc.Heap(); x >= 0 { + tagKs = append(tagKs, e.heapHole().shift(x)) + } + + if ks != nil { + for i := 0; i < numEscResults; i++ { + if x := esc.Result(i); x >= 0 { + tagKs = append(tagKs, ks[i].shift(x)) + } + } + } + + return e.teeHole(tagKs...) +} + +// inMutualBatch reports whether function fn is in the batch of +// mutually recursive functions being analyzed. When this is true, +// fn has not yet been analyzed, so its parameters and results +// should be incorporated directly into the flow graph instead of +// relying on its escape analysis tagging. +func (e *escape) inMutualBatch(fn *ir.Name) bool { + if fn.Defn != nil && fn.Defn.Esc() < escFuncTagged { + if fn.Defn.Esc() == escFuncUnknown { + base.Fatalf("graph inconsistency: %v", fn) + } + return true + } + return false +} + +// An hole represents a context for evaluation a Go +// expression. E.g., when evaluating p in "x = **p", we'd have a hole +// with dst==x and derefs==2. +type hole struct { + dst *location + derefs int // >= -1 + notes *note + + // addrtaken indicates whether this context is taking the address of + // the expression, independent of whether the address will actually + // be stored into a variable. + addrtaken bool + + // uintptrEscapesHack indicates this context is evaluating an + // argument for a //go:uintptrescapes function. + uintptrEscapesHack bool +} + +type note struct { + next *note + where ir.Node + why string +} + +func (k hole) note(where ir.Node, why string) hole { + if where == nil || why == "" { + base.Fatalf("note: missing where/why") + } + if base.Flag.LowerM >= 2 || logopt.Enabled() { + k.notes = ¬e{ + next: k.notes, + where: where, + why: why, + } + } + return k +} + +func (k hole) shift(delta int) hole { + k.derefs += delta + if k.derefs < -1 { + base.Fatalf("derefs underflow: %v", k.derefs) + } + k.addrtaken = delta < 0 + return k +} + +func (k hole) deref(where ir.Node, why string) hole { return k.shift(1).note(where, why) } +func (k hole) addr(where ir.Node, why string) hole { return k.shift(-1).note(where, why) } + +func (k hole) dotType(t *types.Type, where ir.Node, why string) hole { + if !t.IsInterface() && !types.IsDirectIface(t) { + k = k.shift(1) + } + return k.note(where, why) +} + +// teeHole returns a new hole that flows into each hole of ks, +// similar to the Unix tee(1) command. +func (e *escape) teeHole(ks ...hole) hole { + if len(ks) == 0 { + return e.discardHole() + } + if len(ks) == 1 { + return ks[0] + } + // TODO(mdempsky): Optimize if there's only one non-discard hole? + + // Given holes "l1 = _", "l2 = **_", "l3 = *_", ..., create a + // new temporary location ltmp, wire it into place, and return + // a hole for "ltmp = _". + loc := e.newLoc(nil, true) + for _, k := range ks { + // N.B., "p = &q" and "p = &tmp; tmp = q" are not + // semantically equivalent. To combine holes like "l1 + // = _" and "l2 = &_", we'd need to wire them as "l1 = + // *ltmp" and "l2 = ltmp" and return "ltmp = &_" + // instead. + if k.derefs < 0 { + base.Fatalf("teeHole: negative derefs") + } + + e.flow(k, loc) + } + return loc.asHole() +} + +func (e *escape) dcl(n *ir.Name) hole { + if n.Curfn != e.curfn || n.IsClosureVar() { + base.Fatalf("bad declaration of %v", n) + } + loc := e.oldLoc(n) + loc.loopDepth = e.loopDepth + return loc.asHole() +} + +// spill allocates a new location associated with expression n, flows +// its address to k, and returns a hole that flows values to it. It's +// intended for use with most expressions that allocate storage. +func (e *escape) spill(k hole, n ir.Node) hole { + loc := e.newLoc(n, true) + e.flow(k.addr(n, "spill"), loc) + return loc.asHole() +} + +// later returns a new hole that flows into k, but some time later. +// Its main effect is to prevent immediate reuse of temporary +// variables introduced during Order. +func (e *escape) later(k hole) hole { + loc := e.newLoc(nil, false) + e.flow(k, loc) + return loc.asHole() +} + +func (e *escape) newLoc(n ir.Node, transient bool) *location { + if e.curfn == nil { + base.Fatalf("e.curfn isn't set") + } + if n != nil && n.Type() != nil && n.Type().NotInHeap() { + base.ErrorfAt(n.Pos(), "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type()) + } + + if n != nil && n.Op() == ir.ONAME { + n = n.(*ir.Name).Canonical() + } + loc := &location{ + n: n, + curfn: e.curfn, + loopDepth: e.loopDepth, + transient: transient, + } + e.allLocs = append(e.allLocs, loc) + if n != nil { + if n.Op() == ir.ONAME { + n := n.(*ir.Name) + if n.Curfn != e.curfn { + base.Fatalf("curfn mismatch: %v != %v for %v", n.Curfn, e.curfn, n) + } + + if n.Opt != nil { + base.Fatalf("%v already has a location", n) + } + n.Opt = loc + } + } + return loc +} + +func (b *batch) oldLoc(n *ir.Name) *location { + if n.Canonical().Opt == nil { + base.Fatalf("%v has no location", n) + } + return n.Canonical().Opt.(*location) +} + +func (l *location) asHole() hole { + return hole{dst: l} +} + +func (b *batch) flow(k hole, src *location) { + if k.addrtaken { + src.addrtaken = true + } + + dst := k.dst + if dst == &b.blankLoc { + return + } + if dst == src && k.derefs >= 0 { // dst = dst, dst = *dst, ... + return + } + if dst.escapes && k.derefs < 0 { // dst = &src + if base.Flag.LowerM >= 2 || logopt.Enabled() { + pos := base.FmtPos(src.n.Pos()) + if base.Flag.LowerM >= 2 { + fmt.Printf("%s: %v escapes to heap:\n", pos, src.n) + } + explanation := b.explainFlow(pos, dst, src, k.derefs, k.notes, []*logopt.LoggedOpt{}) + if logopt.Enabled() { + var e_curfn *ir.Func // TODO(mdempsky): Fix. + logopt.LogOpt(src.n.Pos(), "escapes", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", src.n), explanation) + } + + } + src.escapes = true + return + } + + // TODO(mdempsky): Deduplicate edges? + dst.edges = append(dst.edges, edge{src: src, derefs: k.derefs, notes: k.notes}) +} + +func (b *batch) heapHole() hole { return b.heapLoc.asHole() } +func (b *batch) discardHole() hole { return b.blankLoc.asHole() } + +// walkAll computes the minimal dereferences between all pairs of +// locations. +func (b *batch) walkAll() { + // We use a work queue to keep track of locations that we need + // to visit, and repeatedly walk until we reach a fixed point. + // + // We walk once from each location (including the heap), and + // then re-enqueue each location on its transition from + // transient->!transient and !escapes->escapes, which can each + // happen at most once. So we take Θ(len(e.allLocs)) walks. + + // LIFO queue, has enough room for e.allLocs and e.heapLoc. + todo := make([]*location, 0, len(b.allLocs)+1) + enqueue := func(loc *location) { + if !loc.queued { + todo = append(todo, loc) + loc.queued = true + } + } + + for _, loc := range b.allLocs { + enqueue(loc) + } + enqueue(&b.heapLoc) + + var walkgen uint32 + for len(todo) > 0 { + root := todo[len(todo)-1] + todo = todo[:len(todo)-1] + root.queued = false + + walkgen++ + b.walkOne(root, walkgen, enqueue) + } +} + +// walkOne computes the minimal number of dereferences from root to +// all other locations. +func (b *batch) walkOne(root *location, walkgen uint32, enqueue func(*location)) { + // The data flow graph has negative edges (from addressing + // operations), so we use the Bellman-Ford algorithm. However, + // we don't have to worry about infinite negative cycles since + // we bound intermediate dereference counts to 0. + + root.walkgen = walkgen + root.derefs = 0 + root.dst = nil + + todo := []*location{root} // LIFO queue + for len(todo) > 0 { + l := todo[len(todo)-1] + todo = todo[:len(todo)-1] + + derefs := l.derefs + + // If l.derefs < 0, then l's address flows to root. + addressOf := derefs < 0 + if addressOf { + // For a flow path like "root = &l; l = x", + // l's address flows to root, but x's does + // not. We recognize this by lower bounding + // derefs at 0. + derefs = 0 + + // If l's address flows to a non-transient + // location, then l can't be transiently + // allocated. + if !root.transient && l.transient { + l.transient = false + enqueue(l) + } + } + + if b.outlives(root, l) { + // l's value flows to root. If l is a function + // parameter and root is the heap or a + // corresponding result parameter, then record + // that value flow for tagging the function + // later. + if l.isName(ir.PPARAM) { + if (logopt.Enabled() || base.Flag.LowerM >= 2) && !l.escapes { + if base.Flag.LowerM >= 2 { + fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", base.FmtPos(l.n.Pos()), l.n, b.explainLoc(root), derefs) + } + explanation := b.explainPath(root, l) + if logopt.Enabled() { + var e_curfn *ir.Func // TODO(mdempsky): Fix. + logopt.LogOpt(l.n.Pos(), "leak", "escape", ir.FuncName(e_curfn), + fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, b.explainLoc(root), derefs), explanation) + } + } + l.leakTo(root, derefs) + } + + // If l's address flows somewhere that + // outlives it, then l needs to be heap + // allocated. + if addressOf && !l.escapes { + if logopt.Enabled() || base.Flag.LowerM >= 2 { + if base.Flag.LowerM >= 2 { + fmt.Printf("%s: %v escapes to heap:\n", base.FmtPos(l.n.Pos()), l.n) + } + explanation := b.explainPath(root, l) + if logopt.Enabled() { + var e_curfn *ir.Func // TODO(mdempsky): Fix. + logopt.LogOpt(l.n.Pos(), "escape", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", l.n), explanation) + } + } + l.escapes = true + enqueue(l) + continue + } + } + + for i, edge := range l.edges { + if edge.src.escapes { + continue + } + d := derefs + edge.derefs + if edge.src.walkgen != walkgen || edge.src.derefs > d { + edge.src.walkgen = walkgen + edge.src.derefs = d + edge.src.dst = l + edge.src.dstEdgeIdx = i + todo = append(todo, edge.src) + } + } + } +} + +// explainPath prints an explanation of how src flows to the walk root. +func (b *batch) explainPath(root, src *location) []*logopt.LoggedOpt { + visited := make(map[*location]bool) + pos := base.FmtPos(src.n.Pos()) + var explanation []*logopt.LoggedOpt + for { + // Prevent infinite loop. + if visited[src] { + if base.Flag.LowerM >= 2 { + fmt.Printf("%s: warning: truncated explanation due to assignment cycle; see golang.org/issue/35518\n", pos) + } + break + } + visited[src] = true + dst := src.dst + edge := &dst.edges[src.dstEdgeIdx] + if edge.src != src { + base.Fatalf("path inconsistency: %v != %v", edge.src, src) + } + + explanation = b.explainFlow(pos, dst, src, edge.derefs, edge.notes, explanation) + + if dst == root { + break + } + src = dst + } + + return explanation +} + +func (b *batch) explainFlow(pos string, dst, srcloc *location, derefs int, notes *note, explanation []*logopt.LoggedOpt) []*logopt.LoggedOpt { + ops := "&" + if derefs >= 0 { + ops = strings.Repeat("*", derefs) + } + print := base.Flag.LowerM >= 2 + + flow := fmt.Sprintf(" flow: %s = %s%v:", b.explainLoc(dst), ops, b.explainLoc(srcloc)) + if print { + fmt.Printf("%s:%s\n", pos, flow) + } + if logopt.Enabled() { + var epos src.XPos + if notes != nil { + epos = notes.where.Pos() + } else if srcloc != nil && srcloc.n != nil { + epos = srcloc.n.Pos() + } + var e_curfn *ir.Func // TODO(mdempsky): Fix. + explanation = append(explanation, logopt.NewLoggedOpt(epos, "escflow", "escape", ir.FuncName(e_curfn), flow)) + } + + for note := notes; note != nil; note = note.next { + if print { + fmt.Printf("%s: from %v (%v) at %s\n", pos, note.where, note.why, base.FmtPos(note.where.Pos())) + } + if logopt.Enabled() { + var e_curfn *ir.Func // TODO(mdempsky): Fix. + explanation = append(explanation, logopt.NewLoggedOpt(note.where.Pos(), "escflow", "escape", ir.FuncName(e_curfn), + fmt.Sprintf(" from %v (%v)", note.where, note.why))) + } + } + return explanation +} + +func (b *batch) explainLoc(l *location) string { + if l == &b.heapLoc { + return "{heap}" + } + if l.n == nil { + // TODO(mdempsky): Omit entirely. + return "{temp}" + } + if l.n.Op() == ir.ONAME { + return fmt.Sprintf("%v", l.n) + } + return fmt.Sprintf("{storage for %v}", l.n) +} + +// outlives reports whether values stored in l may survive beyond +// other's lifetime if stack allocated. +func (b *batch) outlives(l, other *location) bool { + // The heap outlives everything. + if l.escapes { + return true + } + + // We don't know what callers do with returned values, so + // pessimistically we need to assume they flow to the heap and + // outlive everything too. + if l.isName(ir.PPARAMOUT) { + // Exception: Directly called closures can return + // locations allocated outside of them without forcing + // them to the heap. For example: + // + // var u int // okay to stack allocate + // *(func() *int { return &u }()) = 42 + if containsClosure(other.curfn, l.curfn) && l.curfn.ClosureCalled() { + return false + } + + return true + } + + // If l and other are within the same function, then l + // outlives other if it was declared outside other's loop + // scope. For example: + // + // var l *int + // for { + // l = new(int) + // } + if l.curfn == other.curfn && l.loopDepth < other.loopDepth { + return true + } + + // If other is declared within a child closure of where l is + // declared, then l outlives it. For example: + // + // var l *int + // func() { + // l = new(int) + // } + if containsClosure(l.curfn, other.curfn) { + return true + } + + return false +} + +// containsClosure reports whether c is a closure contained within f. +func containsClosure(f, c *ir.Func) bool { + // Common case. + if f == c { + return false + } + + // Closures within function Foo are named like "Foo.funcN..." + // TODO(mdempsky): Better way to recognize this. + fn := f.Sym().Name + cn := c.Sym().Name + return len(cn) > len(fn) && cn[:len(fn)] == fn && cn[len(fn)] == '.' +} + +// leak records that parameter l leaks to sink. +func (l *location) leakTo(sink *location, derefs int) { + // If sink is a result parameter that doesn't escape (#44614) + // and we can fit return bits into the escape analysis tag, + // then record as a result leak. + if !sink.escapes && sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn { + ri := sink.resultIndex - 1 + if ri < numEscResults { + // Leak to result parameter. + l.paramEsc.AddResult(ri, derefs) + return + } + } + + // Otherwise, record as heap leak. + l.paramEsc.AddHeap(derefs) +} + +func (b *batch) finish(fns []*ir.Func) { + // Record parameter tags for package export data. + for _, fn := range fns { + fn.SetEsc(escFuncTagged) + + narg := 0 + for _, fs := range &types.RecvsParams { + for _, f := range fs(fn.Type()).Fields().Slice() { + narg++ + f.Note = b.paramTag(fn, narg, f) + } + } + } + + for _, loc := range b.allLocs { + n := loc.n + if n == nil { + continue + } + if n.Op() == ir.ONAME { + n := n.(*ir.Name) + n.Opt = nil + } + + // Update n.Esc based on escape analysis results. + + if loc.escapes { + if n.Op() == ir.ONAME { + if base.Flag.CompilingRuntime { + base.ErrorfAt(n.Pos(), "%v escapes to heap, not allowed in runtime", n) + } + if base.Flag.LowerM != 0 { + base.WarnfAt(n.Pos(), "moved to heap: %v", n) + } + } else { + if base.Flag.LowerM != 0 { + base.WarnfAt(n.Pos(), "%v escapes to heap", n) + } + if logopt.Enabled() { + var e_curfn *ir.Func // TODO(mdempsky): Fix. + logopt.LogOpt(n.Pos(), "escape", "escape", ir.FuncName(e_curfn)) + } + } + n.SetEsc(ir.EscHeap) + } else { + if base.Flag.LowerM != 0 && n.Op() != ir.ONAME { + base.WarnfAt(n.Pos(), "%v does not escape", n) + } + n.SetEsc(ir.EscNone) + if loc.transient { + switch n.Op() { + case ir.OCLOSURE: + n := n.(*ir.ClosureExpr) + n.SetTransient(true) + case ir.OCALLPART: + n := n.(*ir.SelectorExpr) + n.SetTransient(true) + case ir.OSLICELIT: + n := n.(*ir.CompLitExpr) + n.SetTransient(true) + } + } + } + } +} + +func (l *location) isName(c ir.Class) bool { + return l.n != nil && l.n.Op() == ir.ONAME && l.n.(*ir.Name).Class == c +} + +const numEscResults = 7 + +// An leaks represents a set of assignment flows from a parameter +// to the heap or to any of its function's (first numEscResults) +// result parameters. +type leaks [1 + numEscResults]uint8 + +// Empty reports whether l is an empty set (i.e., no assignment flows). +func (l leaks) Empty() bool { return l == leaks{} } + +// Heap returns the minimum deref count of any assignment flow from l +// to the heap. If no such flows exist, Heap returns -1. +func (l leaks) Heap() int { return l.get(0) } + +// Result returns the minimum deref count of any assignment flow from +// l to its function's i'th result parameter. If no such flows exist, +// Result returns -1. +func (l leaks) Result(i int) int { return l.get(1 + i) } + +// AddHeap adds an assignment flow from l to the heap. +func (l *leaks) AddHeap(derefs int) { l.add(0, derefs) } + +// AddResult adds an assignment flow from l to its function's i'th +// result parameter. +func (l *leaks) AddResult(i, derefs int) { l.add(1+i, derefs) } + +func (l *leaks) setResult(i, derefs int) { l.set(1+i, derefs) } + +func (l leaks) get(i int) int { return int(l[i]) - 1 } + +func (l *leaks) add(i, derefs int) { + if old := l.get(i); old < 0 || derefs < old { + l.set(i, derefs) + } +} + +func (l *leaks) set(i, derefs int) { + v := derefs + 1 + if v < 0 { + base.Fatalf("invalid derefs count: %v", derefs) + } + if v > math.MaxUint8 { + v = math.MaxUint8 + } + + l[i] = uint8(v) +} + +// Optimize removes result flow paths that are equal in length or +// longer than the shortest heap flow path. +func (l *leaks) Optimize() { + // If we have a path to the heap, then there's no use in + // keeping equal or longer paths elsewhere. + if x := l.Heap(); x >= 0 { + for i := 0; i < numEscResults; i++ { + if l.Result(i) >= x { + l.setResult(i, -1) + } + } + } +} + +var leakTagCache = map[leaks]string{} + +// Encode converts l into a binary string for export data. +func (l leaks) Encode() string { + if l.Heap() == 0 { + // Space optimization: empty string encodes more + // efficiently in export data. + return "" + } + if s, ok := leakTagCache[l]; ok { + return s + } + + n := len(l) + for n > 0 && l[n-1] == 0 { + n-- + } + s := "esc:" + string(l[:n]) + leakTagCache[l] = s + return s +} + +// parseLeaks parses a binary string representing a leaks +func parseLeaks(s string) leaks { + var l leaks + if !strings.HasPrefix(s, "esc:") { + l.AddHeap(0) + return l + } + copy(l[:], s[4:]) + return l +} + +func Funcs(all []ir.Node) { + ir.VisitFuncsBottomUp(all, Batch) +} + +const ( + escFuncUnknown = 0 + iota + escFuncPlanned + escFuncStarted + escFuncTagged +) + +// Mark labels that have no backjumps to them as not increasing e.loopdepth. +type labelState int + +const ( + looping labelState = 1 + iota + nonlooping +) + +func isSliceSelfAssign(dst, src ir.Node) bool { + // Detect the following special case. + // + // func (b *Buffer) Foo() { + // n, m := ... + // b.buf = b.buf[n:m] + // } + // + // This assignment is a no-op for escape analysis, + // it does not store any new pointers into b that were not already there. + // However, without this special case b will escape, because we assign to OIND/ODOTPTR. + // Here we assume that the statement will not contain calls, + // that is, that order will move any calls to init. + // Otherwise base ONAME value could change between the moments + // when we evaluate it for dst and for src. + + // dst is ONAME dereference. + var dstX ir.Node + switch dst.Op() { + default: + return false + case ir.ODEREF: + dst := dst.(*ir.StarExpr) + dstX = dst.X + case ir.ODOTPTR: + dst := dst.(*ir.SelectorExpr) + dstX = dst.X + } + if dstX.Op() != ir.ONAME { + return false + } + // src is a slice operation. + switch src.Op() { + case ir.OSLICE, ir.OSLICE3, ir.OSLICESTR: + // OK. + case ir.OSLICEARR, ir.OSLICE3ARR: + // Since arrays are embedded into containing object, + // slice of non-pointer array will introduce a new pointer into b that was not already there + // (pointer to b itself). After such assignment, if b contents escape, + // b escapes as well. If we ignore such OSLICEARR, we will conclude + // that b does not escape when b contents do. + // + // Pointer to an array is OK since it's not stored inside b directly. + // For slicing an array (not pointer to array), there is an implicit OADDR. + // We check that to determine non-pointer array slicing. + src := src.(*ir.SliceExpr) + if src.X.Op() == ir.OADDR { + return false + } + default: + return false + } + // slice is applied to ONAME dereference. + var baseX ir.Node + switch base := src.(*ir.SliceExpr).X; base.Op() { + default: + return false + case ir.ODEREF: + base := base.(*ir.StarExpr) + baseX = base.X + case ir.ODOTPTR: + base := base.(*ir.SelectorExpr) + baseX = base.X + } + if baseX.Op() != ir.ONAME { + return false + } + // dst and src reference the same base ONAME. + return dstX.(*ir.Name) == baseX.(*ir.Name) +} + +// isSelfAssign reports whether assignment from src to dst can +// be ignored by the escape analysis as it's effectively a self-assignment. +func isSelfAssign(dst, src ir.Node) bool { + if isSliceSelfAssign(dst, src) { + return true + } + + // Detect trivial assignments that assign back to the same object. + // + // It covers these cases: + // val.x = val.y + // val.x[i] = val.y[j] + // val.x1.x2 = val.x1.y2 + // ... etc + // + // These assignments do not change assigned object lifetime. + + if dst == nil || src == nil || dst.Op() != src.Op() { + return false + } + + // The expression prefix must be both "safe" and identical. + switch dst.Op() { + case ir.ODOT, ir.ODOTPTR: + // Safe trailing accessors that are permitted to differ. + dst := dst.(*ir.SelectorExpr) + src := src.(*ir.SelectorExpr) + return ir.SameSafeExpr(dst.X, src.X) + case ir.OINDEX: + dst := dst.(*ir.IndexExpr) + src := src.(*ir.IndexExpr) + if mayAffectMemory(dst.Index) || mayAffectMemory(src.Index) { + return false + } + return ir.SameSafeExpr(dst.X, src.X) + default: + return false + } +} + +// mayAffectMemory reports whether evaluation of n may affect the program's +// memory state. If the expression can't affect memory state, then it can be +// safely ignored by the escape analysis. +func mayAffectMemory(n ir.Node) bool { + // We may want to use a list of "memory safe" ops instead of generally + // "side-effect free", which would include all calls and other ops that can + // allocate or change global state. For now, it's safer to start with the latter. + // + // We're ignoring things like division by zero, index out of range, + // and nil pointer dereference here. + + // TODO(rsc): It seems like it should be possible to replace this with + // an ir.Any looking for any op that's not the ones in the case statement. + // But that produces changes in the compiled output detected by buildall. + switch n.Op() { + case ir.ONAME, ir.OLITERAL, ir.ONIL: + return false + + case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.OLSH, ir.ORSH, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OMOD: + n := n.(*ir.BinaryExpr) + return mayAffectMemory(n.X) || mayAffectMemory(n.Y) + + case ir.OINDEX: + n := n.(*ir.IndexExpr) + return mayAffectMemory(n.X) || mayAffectMemory(n.Index) + + case ir.OCONVNOP, ir.OCONV: + n := n.(*ir.ConvExpr) + return mayAffectMemory(n.X) + + case ir.OLEN, ir.OCAP, ir.ONOT, ir.OBITNOT, ir.OPLUS, ir.ONEG, ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF: + n := n.(*ir.UnaryExpr) + return mayAffectMemory(n.X) + + case ir.ODOT, ir.ODOTPTR: + n := n.(*ir.SelectorExpr) + return mayAffectMemory(n.X) + + case ir.ODEREF: + n := n.(*ir.StarExpr) + return mayAffectMemory(n.X) + + default: + return true + } +} + +// HeapAllocReason returns the reason the given Node must be heap +// allocated, or the empty string if it doesn't. +func HeapAllocReason(n ir.Node) string { + if n == nil || n.Type() == nil { + return "" + } + + // Parameters are always passed via the stack. + if n.Op() == ir.ONAME { + n := n.(*ir.Name) + if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT { + return "" + } + } + + if n.Type().Width > ir.MaxStackVarSize { + return "too large for stack" + } + + if (n.Op() == ir.ONEW || n.Op() == ir.OPTRLIT) && n.Type().Elem().Width > ir.MaxImplicitStackVarSize { + return "too large for stack" + } + + if n.Op() == ir.OCLOSURE && typecheck.ClosureType(n.(*ir.ClosureExpr)).Size() > ir.MaxImplicitStackVarSize { + return "too large for stack" + } + if n.Op() == ir.OCALLPART && typecheck.PartialCallType(n.(*ir.SelectorExpr)).Size() > ir.MaxImplicitStackVarSize { + return "too large for stack" + } + + if n.Op() == ir.OMAKESLICE { + n := n.(*ir.MakeExpr) + r := n.Cap + if r == nil { + r = n.Len + } + if !ir.IsSmallIntConst(r) { + return "non-constant size" + } + if t := n.Type(); t.Elem().Width != 0 && ir.Int64Val(r) > ir.MaxImplicitStackVarSize/t.Elem().Width { + return "too large for stack" + } + } + + return "" +} + +// This special tag is applied to uintptr variables +// that we believe may hold unsafe.Pointers for +// calls into assembly functions. +const UnsafeUintptrNote = "unsafe-uintptr" + +// This special tag is applied to uintptr parameters of functions +// marked go:uintptrescapes. +const UintptrEscapesNote = "uintptr-escapes" + +func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string { + name := func() string { + if f.Sym != nil { + return f.Sym.Name + } + return fmt.Sprintf("arg#%d", narg) + } + + if len(fn.Body) == 0 { + // Assume that uintptr arguments must be held live across the call. + // This is most important for syscall.Syscall. + // See golang.org/issue/13372. + // This really doesn't have much to do with escape analysis per se, + // but we are reusing the ability to annotate an individual function + // argument and pass those annotations along to importing code. + if f.Type.IsUintptr() { + if base.Flag.LowerM != 0 { + base.WarnfAt(f.Pos, "assuming %v is unsafe uintptr", name()) + } + return UnsafeUintptrNote + } + + if !f.Type.HasPointers() { // don't bother tagging for scalars + return "" + } + + var esc leaks + + // External functions are assumed unsafe, unless + // //go:noescape is given before the declaration. + if fn.Pragma&ir.Noescape != 0 { + if base.Flag.LowerM != 0 && f.Sym != nil { + base.WarnfAt(f.Pos, "%v does not escape", name()) + } + } else { + if base.Flag.LowerM != 0 && f.Sym != nil { + base.WarnfAt(f.Pos, "leaking param: %v", name()) + } + esc.AddHeap(0) + } + + return esc.Encode() + } + + if fn.Pragma&ir.UintptrEscapes != 0 { + if f.Type.IsUintptr() { + if base.Flag.LowerM != 0 { + base.WarnfAt(f.Pos, "marking %v as escaping uintptr", name()) + } + return UintptrEscapesNote + } + if f.IsDDD() && f.Type.Elem().IsUintptr() { + // final argument is ...uintptr. + if base.Flag.LowerM != 0 { + base.WarnfAt(f.Pos, "marking %v as escaping ...uintptr", name()) + } + return UintptrEscapesNote + } + } + + if !f.Type.HasPointers() { // don't bother tagging for scalars + return "" + } + + // Unnamed parameters are unused and therefore do not escape. + if f.Sym == nil || f.Sym.IsBlank() { + var esc leaks + return esc.Encode() + } + + n := f.Nname.(*ir.Name) + loc := b.oldLoc(n) + esc := loc.paramEsc + esc.Optimize() + + if base.Flag.LowerM != 0 && !loc.escapes { + if esc.Empty() { + base.WarnfAt(f.Pos, "%v does not escape", name()) + } + if x := esc.Heap(); x >= 0 { + if x == 0 { + base.WarnfAt(f.Pos, "leaking param: %v", name()) + } else { + // TODO(mdempsky): Mention level=x like below? + base.WarnfAt(f.Pos, "leaking param content: %v", name()) + } + } + for i := 0; i < numEscResults; i++ { + if x := esc.Result(i); x >= 0 { + res := fn.Type().Results().Field(i).Sym + base.WarnfAt(f.Pos, "leaking param: %v to result %v level=%d", name(), res, x) + } + } + } + + return esc.Encode() +} diff --git a/src/cmd/compile/internal/gc/alg.go b/src/cmd/compile/internal/gc/alg.go deleted file mode 100644 index 2f7fa27bb9100ab6571feac54e0da5b70fe18947..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/alg.go +++ /dev/null @@ -1,959 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/obj" - "fmt" - "sort" -) - -// AlgKind describes the kind of algorithms used for comparing and -// hashing a Type. -type AlgKind int - -//go:generate stringer -type AlgKind -trimprefix A - -const ( - // These values are known by runtime. - ANOEQ AlgKind = iota - AMEM0 - AMEM8 - AMEM16 - AMEM32 - AMEM64 - AMEM128 - ASTRING - AINTER - ANILINTER - AFLOAT32 - AFLOAT64 - ACPLX64 - ACPLX128 - - // Type can be compared/hashed as regular memory. - AMEM AlgKind = 100 - - // Type needs special comparison/hashing functions. - ASPECIAL AlgKind = -1 -) - -// IsComparable reports whether t is a comparable type. -func IsComparable(t *types.Type) bool { - a, _ := algtype1(t) - return a != ANOEQ -} - -// IsRegularMemory reports whether t can be compared/hashed as regular memory. -func IsRegularMemory(t *types.Type) bool { - a, _ := algtype1(t) - return a == AMEM -} - -// IncomparableField returns an incomparable Field of struct Type t, if any. -func IncomparableField(t *types.Type) *types.Field { - for _, f := range t.FieldSlice() { - if !IsComparable(f.Type) { - return f - } - } - return nil -} - -// EqCanPanic reports whether == on type t could panic (has an interface somewhere). -// t must be comparable. -func EqCanPanic(t *types.Type) bool { - switch t.Etype { - default: - return false - case TINTER: - return true - case TARRAY: - return EqCanPanic(t.Elem()) - case TSTRUCT: - for _, f := range t.FieldSlice() { - if !f.Sym.IsBlank() && EqCanPanic(f.Type) { - return true - } - } - return false - } -} - -// algtype is like algtype1, except it returns the fixed-width AMEMxx variants -// instead of the general AMEM kind when possible. -func algtype(t *types.Type) AlgKind { - a, _ := algtype1(t) - if a == AMEM { - switch t.Width { - case 0: - return AMEM0 - case 1: - return AMEM8 - case 2: - return AMEM16 - case 4: - return AMEM32 - case 8: - return AMEM64 - case 16: - return AMEM128 - } - } - - return a -} - -// algtype1 returns the AlgKind used for comparing and hashing Type t. -// If it returns ANOEQ, it also returns the component type of t that -// makes it incomparable. -func algtype1(t *types.Type) (AlgKind, *types.Type) { - if t.Broke() { - return AMEM, nil - } - if t.Noalg() { - return ANOEQ, t - } - - switch t.Etype { - case TANY, TFORW: - // will be defined later. - return ANOEQ, t - - case TINT8, TUINT8, TINT16, TUINT16, - TINT32, TUINT32, TINT64, TUINT64, - TINT, TUINT, TUINTPTR, - TBOOL, TPTR, - TCHAN, TUNSAFEPTR: - return AMEM, nil - - case TFUNC, TMAP: - return ANOEQ, t - - case TFLOAT32: - return AFLOAT32, nil - - case TFLOAT64: - return AFLOAT64, nil - - case TCOMPLEX64: - return ACPLX64, nil - - case TCOMPLEX128: - return ACPLX128, nil - - case TSTRING: - return ASTRING, nil - - case TINTER: - if t.IsEmptyInterface() { - return ANILINTER, nil - } - return AINTER, nil - - case TSLICE: - return ANOEQ, t - - case TARRAY: - a, bad := algtype1(t.Elem()) - switch a { - case AMEM: - return AMEM, nil - case ANOEQ: - return ANOEQ, bad - } - - switch t.NumElem() { - case 0: - // We checked above that the element type is comparable. - return AMEM, nil - case 1: - // Single-element array is same as its lone element. - return a, nil - } - - return ASPECIAL, nil - - case TSTRUCT: - fields := t.FieldSlice() - - // One-field struct is same as that one field alone. - if len(fields) == 1 && !fields[0].Sym.IsBlank() { - return algtype1(fields[0].Type) - } - - ret := AMEM - for i, f := range fields { - // All fields must be comparable. - a, bad := algtype1(f.Type) - if a == ANOEQ { - return ANOEQ, bad - } - - // Blank fields, padded fields, fields with non-memory - // equality need special compare. - if a != AMEM || f.Sym.IsBlank() || ispaddedfield(t, i) { - ret = ASPECIAL - } - } - - return ret, nil - } - - Fatalf("algtype1: unexpected type %v", t) - return 0, nil -} - -// genhash returns a symbol which is the closure used to compute -// the hash of a value of type t. -// Note: the generated function must match runtime.typehash exactly. -func genhash(t *types.Type) *obj.LSym { - switch algtype(t) { - default: - // genhash is only called for types that have equality - Fatalf("genhash %v", t) - case AMEM0: - return sysClosure("memhash0") - case AMEM8: - return sysClosure("memhash8") - case AMEM16: - return sysClosure("memhash16") - case AMEM32: - return sysClosure("memhash32") - case AMEM64: - return sysClosure("memhash64") - case AMEM128: - return sysClosure("memhash128") - case ASTRING: - return sysClosure("strhash") - case AINTER: - return sysClosure("interhash") - case ANILINTER: - return sysClosure("nilinterhash") - case AFLOAT32: - return sysClosure("f32hash") - case AFLOAT64: - return sysClosure("f64hash") - case ACPLX64: - return sysClosure("c64hash") - case ACPLX128: - return sysClosure("c128hash") - case AMEM: - // For other sizes of plain memory, we build a closure - // that calls memhash_varlen. The size of the memory is - // encoded in the first slot of the closure. - closure := typeLookup(fmt.Sprintf(".hashfunc%d", t.Width)).Linksym() - if len(closure.P) > 0 { // already generated - return closure - } - if memhashvarlen == nil { - memhashvarlen = sysfunc("memhash_varlen") - } - ot := 0 - ot = dsymptr(closure, ot, memhashvarlen, 0) - ot = duintptr(closure, ot, uint64(t.Width)) // size encoded in closure - ggloblsym(closure, int32(ot), obj.DUPOK|obj.RODATA) - return closure - case ASPECIAL: - break - } - - closure := typesymprefix(".hashfunc", t).Linksym() - if len(closure.P) > 0 { // already generated - return closure - } - - // Generate hash functions for subtypes. - // There are cases where we might not use these hashes, - // but in that case they will get dead-code eliminated. - // (And the closure generated by genhash will also get - // dead-code eliminated, as we call the subtype hashers - // directly.) - switch t.Etype { - case types.TARRAY: - genhash(t.Elem()) - case types.TSTRUCT: - for _, f := range t.FieldSlice() { - genhash(f.Type) - } - } - - sym := typesymprefix(".hash", t) - if Debug.r != 0 { - fmt.Printf("genhash %v %v %v\n", closure, sym, t) - } - - lineno = autogeneratedPos // less confusing than end of input - dclcontext = PEXTERN - - // func sym(p *T, h uintptr) uintptr - tfn := nod(OTFUNC, nil, nil) - tfn.List.Set2( - namedfield("p", types.NewPtr(t)), - namedfield("h", types.Types[TUINTPTR]), - ) - tfn.Rlist.Set1(anonfield(types.Types[TUINTPTR])) - - fn := dclfunc(sym, tfn) - np := asNode(tfn.Type.Params().Field(0).Nname) - nh := asNode(tfn.Type.Params().Field(1).Nname) - - switch t.Etype { - case types.TARRAY: - // An array of pure memory would be handled by the - // standard algorithm, so the element type must not be - // pure memory. - hashel := hashfor(t.Elem()) - - n := nod(ORANGE, nil, nod(ODEREF, np, nil)) - ni := newname(lookup("i")) - ni.Type = types.Types[TINT] - n.List.Set1(ni) - n.SetColas(true) - colasdefn(n.List.Slice(), n) - ni = n.List.First() - - // h = hashel(&p[i], h) - call := nod(OCALL, hashel, nil) - - nx := nod(OINDEX, np, ni) - nx.SetBounded(true) - na := nod(OADDR, nx, nil) - call.List.Append(na) - call.List.Append(nh) - n.Nbody.Append(nod(OAS, nh, call)) - - fn.Nbody.Append(n) - - case types.TSTRUCT: - // Walk the struct using memhash for runs of AMEM - // and calling specific hash functions for the others. - for i, fields := 0, t.FieldSlice(); i < len(fields); { - f := fields[i] - - // Skip blank fields. - if f.Sym.IsBlank() { - i++ - continue - } - - // Hash non-memory fields with appropriate hash function. - if !IsRegularMemory(f.Type) { - hashel := hashfor(f.Type) - call := nod(OCALL, hashel, nil) - nx := nodSym(OXDOT, np, f.Sym) // TODO: fields from other packages? - na := nod(OADDR, nx, nil) - call.List.Append(na) - call.List.Append(nh) - fn.Nbody.Append(nod(OAS, nh, call)) - i++ - continue - } - - // Otherwise, hash a maximal length run of raw memory. - size, next := memrun(t, i) - - // h = hashel(&p.first, size, h) - hashel := hashmem(f.Type) - call := nod(OCALL, hashel, nil) - nx := nodSym(OXDOT, np, f.Sym) // TODO: fields from other packages? - na := nod(OADDR, nx, nil) - call.List.Append(na) - call.List.Append(nh) - call.List.Append(nodintconst(size)) - fn.Nbody.Append(nod(OAS, nh, call)) - - i = next - } - } - - r := nod(ORETURN, nil, nil) - r.List.Append(nh) - fn.Nbody.Append(r) - - if Debug.r != 0 { - dumplist("genhash body", fn.Nbody) - } - - funcbody() - - fn.Func.SetDupok(true) - fn = typecheck(fn, ctxStmt) - - Curfn = fn - typecheckslice(fn.Nbody.Slice(), ctxStmt) - Curfn = nil - - if debug_dclstack != 0 { - testdclstack() - } - - fn.Func.SetNilCheckDisabled(true) - xtop = append(xtop, fn) - - // Build closure. It doesn't close over any variables, so - // it contains just the function pointer. - dsymptr(closure, 0, sym.Linksym(), 0) - ggloblsym(closure, int32(Widthptr), obj.DUPOK|obj.RODATA) - - return closure -} - -func hashfor(t *types.Type) *Node { - var sym *types.Sym - - switch a, _ := algtype1(t); a { - case AMEM: - Fatalf("hashfor with AMEM type") - case AINTER: - sym = Runtimepkg.Lookup("interhash") - case ANILINTER: - sym = Runtimepkg.Lookup("nilinterhash") - case ASTRING: - sym = Runtimepkg.Lookup("strhash") - case AFLOAT32: - sym = Runtimepkg.Lookup("f32hash") - case AFLOAT64: - sym = Runtimepkg.Lookup("f64hash") - case ACPLX64: - sym = Runtimepkg.Lookup("c64hash") - case ACPLX128: - sym = Runtimepkg.Lookup("c128hash") - default: - // Note: the caller of hashfor ensured that this symbol - // exists and has a body by calling genhash for t. - sym = typesymprefix(".hash", t) - } - - n := newname(sym) - setNodeNameFunc(n) - n.Type = functype(nil, []*Node{ - anonfield(types.NewPtr(t)), - anonfield(types.Types[TUINTPTR]), - }, []*Node{ - anonfield(types.Types[TUINTPTR]), - }) - return n -} - -// sysClosure returns a closure which will call the -// given runtime function (with no closed-over variables). -func sysClosure(name string) *obj.LSym { - s := sysvar(name + "·f") - if len(s.P) == 0 { - f := sysfunc(name) - dsymptr(s, 0, f, 0) - ggloblsym(s, int32(Widthptr), obj.DUPOK|obj.RODATA) - } - return s -} - -// geneq returns a symbol which is the closure used to compute -// equality for two objects of type t. -func geneq(t *types.Type) *obj.LSym { - switch algtype(t) { - case ANOEQ: - // The runtime will panic if it tries to compare - // a type with a nil equality function. - return nil - case AMEM0: - return sysClosure("memequal0") - case AMEM8: - return sysClosure("memequal8") - case AMEM16: - return sysClosure("memequal16") - case AMEM32: - return sysClosure("memequal32") - case AMEM64: - return sysClosure("memequal64") - case AMEM128: - return sysClosure("memequal128") - case ASTRING: - return sysClosure("strequal") - case AINTER: - return sysClosure("interequal") - case ANILINTER: - return sysClosure("nilinterequal") - case AFLOAT32: - return sysClosure("f32equal") - case AFLOAT64: - return sysClosure("f64equal") - case ACPLX64: - return sysClosure("c64equal") - case ACPLX128: - return sysClosure("c128equal") - case AMEM: - // make equality closure. The size of the type - // is encoded in the closure. - closure := typeLookup(fmt.Sprintf(".eqfunc%d", t.Width)).Linksym() - if len(closure.P) != 0 { - return closure - } - if memequalvarlen == nil { - memequalvarlen = sysvar("memequal_varlen") // asm func - } - ot := 0 - ot = dsymptr(closure, ot, memequalvarlen, 0) - ot = duintptr(closure, ot, uint64(t.Width)) - ggloblsym(closure, int32(ot), obj.DUPOK|obj.RODATA) - return closure - case ASPECIAL: - break - } - - closure := typesymprefix(".eqfunc", t).Linksym() - if len(closure.P) > 0 { // already generated - return closure - } - sym := typesymprefix(".eq", t) - if Debug.r != 0 { - fmt.Printf("geneq %v\n", t) - } - - // Autogenerate code for equality of structs and arrays. - - lineno = autogeneratedPos // less confusing than end of input - dclcontext = PEXTERN - - // func sym(p, q *T) bool - tfn := nod(OTFUNC, nil, nil) - tfn.List.Set2( - namedfield("p", types.NewPtr(t)), - namedfield("q", types.NewPtr(t)), - ) - tfn.Rlist.Set1(namedfield("r", types.Types[TBOOL])) - - fn := dclfunc(sym, tfn) - np := asNode(tfn.Type.Params().Field(0).Nname) - nq := asNode(tfn.Type.Params().Field(1).Nname) - nr := asNode(tfn.Type.Results().Field(0).Nname) - - // Label to jump to if an equality test fails. - neq := autolabel(".neq") - - // We reach here only for types that have equality but - // cannot be handled by the standard algorithms, - // so t must be either an array or a struct. - switch t.Etype { - default: - Fatalf("geneq %v", t) - - case TARRAY: - nelem := t.NumElem() - - // checkAll generates code to check the equality of all array elements. - // If unroll is greater than nelem, checkAll generates: - // - // if eq(p[0], q[0]) && eq(p[1], q[1]) && ... { - // } else { - // return - // } - // - // And so on. - // - // Otherwise it generates: - // - // for i := 0; i < nelem; i++ { - // if eq(p[i], q[i]) { - // } else { - // goto neq - // } - // } - // - // TODO(josharian): consider doing some loop unrolling - // for larger nelem as well, processing a few elements at a time in a loop. - checkAll := func(unroll int64, last bool, eq func(pi, qi *Node) *Node) { - // checkIdx generates a node to check for equality at index i. - checkIdx := func(i *Node) *Node { - // pi := p[i] - pi := nod(OINDEX, np, i) - pi.SetBounded(true) - pi.Type = t.Elem() - // qi := q[i] - qi := nod(OINDEX, nq, i) - qi.SetBounded(true) - qi.Type = t.Elem() - return eq(pi, qi) - } - - if nelem <= unroll { - if last { - // Do last comparison in a different manner. - nelem-- - } - // Generate a series of checks. - for i := int64(0); i < nelem; i++ { - // if check {} else { goto neq } - nif := nod(OIF, checkIdx(nodintconst(i)), nil) - nif.Rlist.Append(nodSym(OGOTO, nil, neq)) - fn.Nbody.Append(nif) - } - if last { - fn.Nbody.Append(nod(OAS, nr, checkIdx(nodintconst(nelem)))) - } - } else { - // Generate a for loop. - // for i := 0; i < nelem; i++ - i := temp(types.Types[TINT]) - init := nod(OAS, i, nodintconst(0)) - cond := nod(OLT, i, nodintconst(nelem)) - post := nod(OAS, i, nod(OADD, i, nodintconst(1))) - loop := nod(OFOR, cond, post) - loop.Ninit.Append(init) - // if eq(pi, qi) {} else { goto neq } - nif := nod(OIF, checkIdx(i), nil) - nif.Rlist.Append(nodSym(OGOTO, nil, neq)) - loop.Nbody.Append(nif) - fn.Nbody.Append(loop) - if last { - fn.Nbody.Append(nod(OAS, nr, nodbool(true))) - } - } - } - - switch t.Elem().Etype { - case TSTRING: - // Do two loops. First, check that all the lengths match (cheap). - // Second, check that all the contents match (expensive). - // TODO: when the array size is small, unroll the length match checks. - checkAll(3, false, func(pi, qi *Node) *Node { - // Compare lengths. - eqlen, _ := eqstring(pi, qi) - return eqlen - }) - checkAll(1, true, func(pi, qi *Node) *Node { - // Compare contents. - _, eqmem := eqstring(pi, qi) - return eqmem - }) - case TFLOAT32, TFLOAT64: - checkAll(2, true, func(pi, qi *Node) *Node { - // p[i] == q[i] - return nod(OEQ, pi, qi) - }) - // TODO: pick apart structs, do them piecemeal too - default: - checkAll(1, true, func(pi, qi *Node) *Node { - // p[i] == q[i] - return nod(OEQ, pi, qi) - }) - } - - case TSTRUCT: - // Build a list of conditions to satisfy. - // The conditions are a list-of-lists. Conditions are reorderable - // within each inner list. The outer lists must be evaluated in order. - var conds [][]*Node - conds = append(conds, []*Node{}) - and := func(n *Node) { - i := len(conds) - 1 - conds[i] = append(conds[i], n) - } - - // Walk the struct using memequal for runs of AMEM - // and calling specific equality tests for the others. - for i, fields := 0, t.FieldSlice(); i < len(fields); { - f := fields[i] - - // Skip blank-named fields. - if f.Sym.IsBlank() { - i++ - continue - } - - // Compare non-memory fields with field equality. - if !IsRegularMemory(f.Type) { - if EqCanPanic(f.Type) { - // Enforce ordering by starting a new set of reorderable conditions. - conds = append(conds, []*Node{}) - } - p := nodSym(OXDOT, np, f.Sym) - q := nodSym(OXDOT, nq, f.Sym) - switch { - case f.Type.IsString(): - eqlen, eqmem := eqstring(p, q) - and(eqlen) - and(eqmem) - default: - and(nod(OEQ, p, q)) - } - if EqCanPanic(f.Type) { - // Also enforce ordering after something that can panic. - conds = append(conds, []*Node{}) - } - i++ - continue - } - - // Find maximal length run of memory-only fields. - size, next := memrun(t, i) - - // TODO(rsc): All the calls to newname are wrong for - // cross-package unexported fields. - if s := fields[i:next]; len(s) <= 2 { - // Two or fewer fields: use plain field equality. - for _, f := range s { - and(eqfield(np, nq, f.Sym)) - } - } else { - // More than two fields: use memequal. - and(eqmem(np, nq, f.Sym, size)) - } - i = next - } - - // Sort conditions to put runtime calls last. - // Preserve the rest of the ordering. - var flatConds []*Node - for _, c := range conds { - isCall := func(n *Node) bool { - return n.Op == OCALL || n.Op == OCALLFUNC - } - sort.SliceStable(c, func(i, j int) bool { - return !isCall(c[i]) && isCall(c[j]) - }) - flatConds = append(flatConds, c...) - } - - if len(flatConds) == 0 { - fn.Nbody.Append(nod(OAS, nr, nodbool(true))) - } else { - for _, c := range flatConds[:len(flatConds)-1] { - // if cond {} else { goto neq } - n := nod(OIF, c, nil) - n.Rlist.Append(nodSym(OGOTO, nil, neq)) - fn.Nbody.Append(n) - } - fn.Nbody.Append(nod(OAS, nr, flatConds[len(flatConds)-1])) - } - } - - // ret: - // return - ret := autolabel(".ret") - fn.Nbody.Append(nodSym(OLABEL, nil, ret)) - fn.Nbody.Append(nod(ORETURN, nil, nil)) - - // neq: - // r = false - // return (or goto ret) - fn.Nbody.Append(nodSym(OLABEL, nil, neq)) - fn.Nbody.Append(nod(OAS, nr, nodbool(false))) - if EqCanPanic(t) || hasCall(fn) { - // Epilogue is large, so share it with the equal case. - fn.Nbody.Append(nodSym(OGOTO, nil, ret)) - } else { - // Epilogue is small, so don't bother sharing. - fn.Nbody.Append(nod(ORETURN, nil, nil)) - } - // TODO(khr): the epilogue size detection condition above isn't perfect. - // We should really do a generic CL that shares epilogues across - // the board. See #24936. - - if Debug.r != 0 { - dumplist("geneq body", fn.Nbody) - } - - funcbody() - - fn.Func.SetDupok(true) - fn = typecheck(fn, ctxStmt) - - Curfn = fn - typecheckslice(fn.Nbody.Slice(), ctxStmt) - Curfn = nil - - if debug_dclstack != 0 { - testdclstack() - } - - // Disable checknils while compiling this code. - // We are comparing a struct or an array, - // neither of which can be nil, and our comparisons - // are shallow. - fn.Func.SetNilCheckDisabled(true) - xtop = append(xtop, fn) - - // Generate a closure which points at the function we just generated. - dsymptr(closure, 0, sym.Linksym(), 0) - ggloblsym(closure, int32(Widthptr), obj.DUPOK|obj.RODATA) - return closure -} - -func hasCall(n *Node) bool { - if n.Op == OCALL || n.Op == OCALLFUNC { - return true - } - if n.Left != nil && hasCall(n.Left) { - return true - } - if n.Right != nil && hasCall(n.Right) { - return true - } - for _, x := range n.Ninit.Slice() { - if hasCall(x) { - return true - } - } - for _, x := range n.Nbody.Slice() { - if hasCall(x) { - return true - } - } - for _, x := range n.List.Slice() { - if hasCall(x) { - return true - } - } - for _, x := range n.Rlist.Slice() { - if hasCall(x) { - return true - } - } - return false -} - -// eqfield returns the node -// p.field == q.field -func eqfield(p *Node, q *Node, field *types.Sym) *Node { - nx := nodSym(OXDOT, p, field) - ny := nodSym(OXDOT, q, field) - ne := nod(OEQ, nx, ny) - return ne -} - -// eqstring returns the nodes -// len(s) == len(t) -// and -// memequal(s.ptr, t.ptr, len(s)) -// which can be used to construct string equality comparison. -// eqlen must be evaluated before eqmem, and shortcircuiting is required. -func eqstring(s, t *Node) (eqlen, eqmem *Node) { - s = conv(s, types.Types[TSTRING]) - t = conv(t, types.Types[TSTRING]) - sptr := nod(OSPTR, s, nil) - tptr := nod(OSPTR, t, nil) - slen := conv(nod(OLEN, s, nil), types.Types[TUINTPTR]) - tlen := conv(nod(OLEN, t, nil), types.Types[TUINTPTR]) - - fn := syslook("memequal") - fn = substArgTypes(fn, types.Types[TUINT8], types.Types[TUINT8]) - call := nod(OCALL, fn, nil) - call.List.Append(sptr, tptr, slen.copy()) - call = typecheck(call, ctxExpr|ctxMultiOK) - - cmp := nod(OEQ, slen, tlen) - cmp = typecheck(cmp, ctxExpr) - cmp.Type = types.Types[TBOOL] - return cmp, call -} - -// eqinterface returns the nodes -// s.tab == t.tab (or s.typ == t.typ, as appropriate) -// and -// ifaceeq(s.tab, s.data, t.data) (or efaceeq(s.typ, s.data, t.data), as appropriate) -// which can be used to construct interface equality comparison. -// eqtab must be evaluated before eqdata, and shortcircuiting is required. -func eqinterface(s, t *Node) (eqtab, eqdata *Node) { - if !types.Identical(s.Type, t.Type) { - Fatalf("eqinterface %v %v", s.Type, t.Type) - } - // func ifaceeq(tab *uintptr, x, y unsafe.Pointer) (ret bool) - // func efaceeq(typ *uintptr, x, y unsafe.Pointer) (ret bool) - var fn *Node - if s.Type.IsEmptyInterface() { - fn = syslook("efaceeq") - } else { - fn = syslook("ifaceeq") - } - - stab := nod(OITAB, s, nil) - ttab := nod(OITAB, t, nil) - sdata := nod(OIDATA, s, nil) - tdata := nod(OIDATA, t, nil) - sdata.Type = types.Types[TUNSAFEPTR] - tdata.Type = types.Types[TUNSAFEPTR] - sdata.SetTypecheck(1) - tdata.SetTypecheck(1) - - call := nod(OCALL, fn, nil) - call.List.Append(stab, sdata, tdata) - call = typecheck(call, ctxExpr|ctxMultiOK) - - cmp := nod(OEQ, stab, ttab) - cmp = typecheck(cmp, ctxExpr) - cmp.Type = types.Types[TBOOL] - return cmp, call -} - -// eqmem returns the node -// memequal(&p.field, &q.field [, size]) -func eqmem(p *Node, q *Node, field *types.Sym, size int64) *Node { - nx := nod(OADDR, nodSym(OXDOT, p, field), nil) - ny := nod(OADDR, nodSym(OXDOT, q, field), nil) - nx = typecheck(nx, ctxExpr) - ny = typecheck(ny, ctxExpr) - - fn, needsize := eqmemfunc(size, nx.Type.Elem()) - call := nod(OCALL, fn, nil) - call.List.Append(nx) - call.List.Append(ny) - if needsize { - call.List.Append(nodintconst(size)) - } - - return call -} - -func eqmemfunc(size int64, t *types.Type) (fn *Node, needsize bool) { - switch size { - default: - fn = syslook("memequal") - needsize = true - case 1, 2, 4, 8, 16: - buf := fmt.Sprintf("memequal%d", int(size)*8) - fn = syslook(buf) - } - - fn = substArgTypes(fn, t, t) - return fn, needsize -} - -// memrun finds runs of struct fields for which memory-only algs are appropriate. -// t is the parent struct type, and start is the field index at which to start the run. -// size is the length in bytes of the memory included in the run. -// next is the index just after the end of the memory run. -func memrun(t *types.Type, start int) (size int64, next int) { - next = start - for { - next++ - if next == t.NumFields() { - break - } - // Stop run after a padded field. - if ispaddedfield(t, next-1) { - break - } - // Also, stop before a blank or non-memory field. - if f := t.Field(next); f.Sym.IsBlank() || !IsRegularMemory(f.Type) { - break - } - } - return t.Field(next-1).End() - t.Field(start).Offset, next -} - -// ispaddedfield reports whether the i'th field of struct type t is followed -// by padding. -func ispaddedfield(t *types.Type, i int) bool { - if !t.IsStruct() { - Fatalf("ispaddedfield called non-struct %v", t) - } - end := t.Width - if i+1 < t.NumFields() { - end = t.Field(i + 1).Offset - } - return t.Field(i).End() != end -} diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go deleted file mode 100644 index 10f21f86df581ab103a151587418d2c62eb2b73d..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/bexport.go +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" -) - -type exporter struct { - marked map[*types.Type]bool // types already seen by markType -} - -// markType recursively visits types reachable from t to identify -// functions whose inline bodies may be needed. -func (p *exporter) markType(t *types.Type) { - if p.marked[t] { - return - } - p.marked[t] = true - - // If this is a named type, mark all of its associated - // methods. Skip interface types because t.Methods contains - // only their unexpanded method set (i.e., exclusive of - // interface embeddings), and the switch statement below - // handles their full method set. - if t.Sym != nil && t.Etype != TINTER { - for _, m := range t.Methods().Slice() { - if types.IsExported(m.Sym.Name) { - p.markType(m.Type) - } - } - } - - // Recursively mark any types that can be produced given a - // value of type t: dereferencing a pointer; indexing or - // iterating over an array, slice, or map; receiving from a - // channel; accessing a struct field or interface method; or - // calling a function. - // - // Notably, we don't mark function parameter types, because - // the user already needs some way to construct values of - // those types. - switch t.Etype { - case TPTR, TARRAY, TSLICE: - p.markType(t.Elem()) - - case TCHAN: - if t.ChanDir().CanRecv() { - p.markType(t.Elem()) - } - - case TMAP: - p.markType(t.Key()) - p.markType(t.Elem()) - - case TSTRUCT: - for _, f := range t.FieldSlice() { - if types.IsExported(f.Sym.Name) || f.Embedded != 0 { - p.markType(f.Type) - } - } - - case TFUNC: - // If t is the type of a function or method, then - // t.Nname() is its ONAME. Mark its inline body and - // any recursively called functions for export. - inlFlood(asNode(t.Nname())) - - for _, f := range t.Results().FieldSlice() { - p.markType(f.Type) - } - - case TINTER: - for _, f := range t.FieldSlice() { - if types.IsExported(f.Sym.Name) { - p.markType(f.Type) - } - } - } -} - -// ---------------------------------------------------------------------------- -// Export format - -// Tags. Must be < 0. -const ( - // Objects - packageTag = -(iota + 1) - constTag - typeTag - varTag - funcTag - endTag - - // Types - namedTag - arrayTag - sliceTag - dddTag - structTag - pointerTag - signatureTag - interfaceTag - mapTag - chanTag - - // Values - falseTag - trueTag - int64Tag - floatTag - fractionTag // not used by gc - complexTag - stringTag - nilTag - unknownTag // not used by gc (only appears in packages with errors) - - // Type aliases - aliasTag -) - -var predecl []*types.Type // initialized lazily - -func predeclared() []*types.Type { - if predecl == nil { - // initialize lazily to be sure that all - // elements have been initialized before - predecl = []*types.Type{ - // basic types - types.Types[TBOOL], - types.Types[TINT], - types.Types[TINT8], - types.Types[TINT16], - types.Types[TINT32], - types.Types[TINT64], - types.Types[TUINT], - types.Types[TUINT8], - types.Types[TUINT16], - types.Types[TUINT32], - types.Types[TUINT64], - types.Types[TUINTPTR], - types.Types[TFLOAT32], - types.Types[TFLOAT64], - types.Types[TCOMPLEX64], - types.Types[TCOMPLEX128], - types.Types[TSTRING], - - // basic type aliases - types.Bytetype, - types.Runetype, - - // error - types.Errortype, - - // untyped types - types.UntypedBool, - types.UntypedInt, - types.UntypedRune, - types.UntypedFloat, - types.UntypedComplex, - types.UntypedString, - types.Types[TNIL], - - // package unsafe - types.Types[TUNSAFEPTR], - - // invalid type (package contains errors) - types.Types[Txxx], - - // any type, for builtin export data - types.Types[TANY], - } - } - return predecl -} diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go deleted file mode 100644 index 911ac4c0dc7b6d83e9a5c5cb1b4d5ee67d37673e..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/bimport.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/internal/src" -) - -// numImport tracks how often a package with a given name is imported. -// It is used to provide a better error message (by using the package -// path to disambiguate) if a package that appears multiple times with -// the same name appears in an error message. -var numImport = make(map[string]int) - -func npos(pos src.XPos, n *Node) *Node { - n.Pos = pos - return n -} - -func builtinCall(op Op) *Node { - return nod(OCALL, mkname(builtinpkg.Lookup(goopnames[op])), nil) -} diff --git a/src/cmd/compile/internal/gc/bootstrap.go b/src/cmd/compile/internal/gc/bootstrap.go index 967f75a9ac3a7534e3f8f8182761c4e97526ffa1..37b0d59ede7a1da511a06a734c21143b809df93b 100644 --- a/src/cmd/compile/internal/gc/bootstrap.go +++ b/src/cmd/compile/internal/gc/bootstrap.go @@ -2,12 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !go1.8 // +build !go1.8 package gc -import "runtime" +import ( + "cmd/compile/internal/base" + "runtime" +) func startMutexProfiling() { - Fatalf("mutex profiling unavailable in version %v", runtime.Version()) + base.Fatalf("mutex profiling unavailable in version %v", runtime.Version()) } diff --git a/src/cmd/compile/internal/gc/builtin.go b/src/cmd/compile/internal/gc/builtin.go deleted file mode 100644 index e04f23e2294ed6bfbd06681db61169e09f9e069e..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/builtin.go +++ /dev/null @@ -1,340 +0,0 @@ -// Code generated by mkbuiltin.go. DO NOT EDIT. - -package gc - -import "cmd/compile/internal/types" - -var runtimeDecls = [...]struct { - name string - tag int - typ int -}{ - {"newobject", funcTag, 4}, - {"mallocgc", funcTag, 8}, - {"panicdivide", funcTag, 9}, - {"panicshift", funcTag, 9}, - {"panicmakeslicelen", funcTag, 9}, - {"panicmakeslicecap", funcTag, 9}, - {"throwinit", funcTag, 9}, - {"panicwrap", funcTag, 9}, - {"gopanic", funcTag, 11}, - {"gorecover", funcTag, 14}, - {"goschedguarded", funcTag, 9}, - {"goPanicIndex", funcTag, 16}, - {"goPanicIndexU", funcTag, 18}, - {"goPanicSliceAlen", funcTag, 16}, - {"goPanicSliceAlenU", funcTag, 18}, - {"goPanicSliceAcap", funcTag, 16}, - {"goPanicSliceAcapU", funcTag, 18}, - {"goPanicSliceB", funcTag, 16}, - {"goPanicSliceBU", funcTag, 18}, - {"goPanicSlice3Alen", funcTag, 16}, - {"goPanicSlice3AlenU", funcTag, 18}, - {"goPanicSlice3Acap", funcTag, 16}, - {"goPanicSlice3AcapU", funcTag, 18}, - {"goPanicSlice3B", funcTag, 16}, - {"goPanicSlice3BU", funcTag, 18}, - {"goPanicSlice3C", funcTag, 16}, - {"goPanicSlice3CU", funcTag, 18}, - {"printbool", funcTag, 19}, - {"printfloat", funcTag, 21}, - {"printint", funcTag, 23}, - {"printhex", funcTag, 25}, - {"printuint", funcTag, 25}, - {"printcomplex", funcTag, 27}, - {"printstring", funcTag, 29}, - {"printpointer", funcTag, 30}, - {"printuintptr", funcTag, 31}, - {"printiface", funcTag, 30}, - {"printeface", funcTag, 30}, - {"printslice", funcTag, 30}, - {"printnl", funcTag, 9}, - {"printsp", funcTag, 9}, - {"printlock", funcTag, 9}, - {"printunlock", funcTag, 9}, - {"concatstring2", funcTag, 34}, - {"concatstring3", funcTag, 35}, - {"concatstring4", funcTag, 36}, - {"concatstring5", funcTag, 37}, - {"concatstrings", funcTag, 39}, - {"cmpstring", funcTag, 40}, - {"intstring", funcTag, 43}, - {"slicebytetostring", funcTag, 44}, - {"slicebytetostringtmp", funcTag, 45}, - {"slicerunetostring", funcTag, 48}, - {"stringtoslicebyte", funcTag, 50}, - {"stringtoslicerune", funcTag, 53}, - {"slicecopy", funcTag, 54}, - {"decoderune", funcTag, 55}, - {"countrunes", funcTag, 56}, - {"convI2I", funcTag, 57}, - {"convT16", funcTag, 58}, - {"convT32", funcTag, 58}, - {"convT64", funcTag, 58}, - {"convTstring", funcTag, 58}, - {"convTslice", funcTag, 58}, - {"convT2E", funcTag, 59}, - {"convT2Enoptr", funcTag, 59}, - {"convT2I", funcTag, 59}, - {"convT2Inoptr", funcTag, 59}, - {"assertE2I", funcTag, 57}, - {"assertE2I2", funcTag, 60}, - {"assertI2I", funcTag, 57}, - {"assertI2I2", funcTag, 60}, - {"panicdottypeE", funcTag, 61}, - {"panicdottypeI", funcTag, 61}, - {"panicnildottype", funcTag, 62}, - {"ifaceeq", funcTag, 64}, - {"efaceeq", funcTag, 64}, - {"fastrand", funcTag, 66}, - {"makemap64", funcTag, 68}, - {"makemap", funcTag, 69}, - {"makemap_small", funcTag, 70}, - {"mapaccess1", funcTag, 71}, - {"mapaccess1_fast32", funcTag, 72}, - {"mapaccess1_fast64", funcTag, 72}, - {"mapaccess1_faststr", funcTag, 72}, - {"mapaccess1_fat", funcTag, 73}, - {"mapaccess2", funcTag, 74}, - {"mapaccess2_fast32", funcTag, 75}, - {"mapaccess2_fast64", funcTag, 75}, - {"mapaccess2_faststr", funcTag, 75}, - {"mapaccess2_fat", funcTag, 76}, - {"mapassign", funcTag, 71}, - {"mapassign_fast32", funcTag, 72}, - {"mapassign_fast32ptr", funcTag, 72}, - {"mapassign_fast64", funcTag, 72}, - {"mapassign_fast64ptr", funcTag, 72}, - {"mapassign_faststr", funcTag, 72}, - {"mapiterinit", funcTag, 77}, - {"mapdelete", funcTag, 77}, - {"mapdelete_fast32", funcTag, 78}, - {"mapdelete_fast64", funcTag, 78}, - {"mapdelete_faststr", funcTag, 78}, - {"mapiternext", funcTag, 79}, - {"mapclear", funcTag, 80}, - {"makechan64", funcTag, 82}, - {"makechan", funcTag, 83}, - {"chanrecv1", funcTag, 85}, - {"chanrecv2", funcTag, 86}, - {"chansend1", funcTag, 88}, - {"closechan", funcTag, 30}, - {"writeBarrier", varTag, 90}, - {"typedmemmove", funcTag, 91}, - {"typedmemclr", funcTag, 92}, - {"typedslicecopy", funcTag, 93}, - {"selectnbsend", funcTag, 94}, - {"selectnbrecv", funcTag, 95}, - {"selectnbrecv2", funcTag, 97}, - {"selectsetpc", funcTag, 98}, - {"selectgo", funcTag, 99}, - {"block", funcTag, 9}, - {"makeslice", funcTag, 100}, - {"makeslice64", funcTag, 101}, - {"makeslicecopy", funcTag, 102}, - {"growslice", funcTag, 104}, - {"memmove", funcTag, 105}, - {"memclrNoHeapPointers", funcTag, 106}, - {"memclrHasPointers", funcTag, 106}, - {"memequal", funcTag, 107}, - {"memequal0", funcTag, 108}, - {"memequal8", funcTag, 108}, - {"memequal16", funcTag, 108}, - {"memequal32", funcTag, 108}, - {"memequal64", funcTag, 108}, - {"memequal128", funcTag, 108}, - {"f32equal", funcTag, 109}, - {"f64equal", funcTag, 109}, - {"c64equal", funcTag, 109}, - {"c128equal", funcTag, 109}, - {"strequal", funcTag, 109}, - {"interequal", funcTag, 109}, - {"nilinterequal", funcTag, 109}, - {"memhash", funcTag, 110}, - {"memhash0", funcTag, 111}, - {"memhash8", funcTag, 111}, - {"memhash16", funcTag, 111}, - {"memhash32", funcTag, 111}, - {"memhash64", funcTag, 111}, - {"memhash128", funcTag, 111}, - {"f32hash", funcTag, 111}, - {"f64hash", funcTag, 111}, - {"c64hash", funcTag, 111}, - {"c128hash", funcTag, 111}, - {"strhash", funcTag, 111}, - {"interhash", funcTag, 111}, - {"nilinterhash", funcTag, 111}, - {"int64div", funcTag, 112}, - {"uint64div", funcTag, 113}, - {"int64mod", funcTag, 112}, - {"uint64mod", funcTag, 113}, - {"float64toint64", funcTag, 114}, - {"float64touint64", funcTag, 115}, - {"float64touint32", funcTag, 116}, - {"int64tofloat64", funcTag, 117}, - {"uint64tofloat64", funcTag, 118}, - {"uint32tofloat64", funcTag, 119}, - {"complex128div", funcTag, 120}, - {"racefuncenter", funcTag, 31}, - {"racefuncenterfp", funcTag, 9}, - {"racefuncexit", funcTag, 9}, - {"raceread", funcTag, 31}, - {"racewrite", funcTag, 31}, - {"racereadrange", funcTag, 121}, - {"racewriterange", funcTag, 121}, - {"msanread", funcTag, 121}, - {"msanwrite", funcTag, 121}, - {"msanmove", funcTag, 122}, - {"checkptrAlignment", funcTag, 123}, - {"checkptrArithmetic", funcTag, 125}, - {"libfuzzerTraceCmp1", funcTag, 127}, - {"libfuzzerTraceCmp2", funcTag, 129}, - {"libfuzzerTraceCmp4", funcTag, 130}, - {"libfuzzerTraceCmp8", funcTag, 131}, - {"libfuzzerTraceConstCmp1", funcTag, 127}, - {"libfuzzerTraceConstCmp2", funcTag, 129}, - {"libfuzzerTraceConstCmp4", funcTag, 130}, - {"libfuzzerTraceConstCmp8", funcTag, 131}, - {"x86HasPOPCNT", varTag, 6}, - {"x86HasSSE41", varTag, 6}, - {"x86HasFMA", varTag, 6}, - {"armHasVFPv4", varTag, 6}, - {"arm64HasATOMICS", varTag, 6}, -} - -func runtimeTypes() []*types.Type { - var typs [132]*types.Type - typs[0] = types.Bytetype - typs[1] = types.NewPtr(typs[0]) - typs[2] = types.Types[TANY] - typs[3] = types.NewPtr(typs[2]) - typs[4] = functype(nil, []*Node{anonfield(typs[1])}, []*Node{anonfield(typs[3])}) - typs[5] = types.Types[TUINTPTR] - typs[6] = types.Types[TBOOL] - typs[7] = types.Types[TUNSAFEPTR] - typs[8] = functype(nil, []*Node{anonfield(typs[5]), anonfield(typs[1]), anonfield(typs[6])}, []*Node{anonfield(typs[7])}) - typs[9] = functype(nil, nil, nil) - typs[10] = types.Types[TINTER] - typs[11] = functype(nil, []*Node{anonfield(typs[10])}, nil) - typs[12] = types.Types[TINT32] - typs[13] = types.NewPtr(typs[12]) - typs[14] = functype(nil, []*Node{anonfield(typs[13])}, []*Node{anonfield(typs[10])}) - typs[15] = types.Types[TINT] - typs[16] = functype(nil, []*Node{anonfield(typs[15]), anonfield(typs[15])}, nil) - typs[17] = types.Types[TUINT] - typs[18] = functype(nil, []*Node{anonfield(typs[17]), anonfield(typs[15])}, nil) - typs[19] = functype(nil, []*Node{anonfield(typs[6])}, nil) - typs[20] = types.Types[TFLOAT64] - typs[21] = functype(nil, []*Node{anonfield(typs[20])}, nil) - typs[22] = types.Types[TINT64] - typs[23] = functype(nil, []*Node{anonfield(typs[22])}, nil) - typs[24] = types.Types[TUINT64] - typs[25] = functype(nil, []*Node{anonfield(typs[24])}, nil) - typs[26] = types.Types[TCOMPLEX128] - typs[27] = functype(nil, []*Node{anonfield(typs[26])}, nil) - typs[28] = types.Types[TSTRING] - typs[29] = functype(nil, []*Node{anonfield(typs[28])}, nil) - typs[30] = functype(nil, []*Node{anonfield(typs[2])}, nil) - typs[31] = functype(nil, []*Node{anonfield(typs[5])}, nil) - typs[32] = types.NewArray(typs[0], 32) - typs[33] = types.NewPtr(typs[32]) - typs[34] = functype(nil, []*Node{anonfield(typs[33]), anonfield(typs[28]), anonfield(typs[28])}, []*Node{anonfield(typs[28])}) - typs[35] = functype(nil, []*Node{anonfield(typs[33]), anonfield(typs[28]), anonfield(typs[28]), anonfield(typs[28])}, []*Node{anonfield(typs[28])}) - typs[36] = functype(nil, []*Node{anonfield(typs[33]), anonfield(typs[28]), anonfield(typs[28]), anonfield(typs[28]), anonfield(typs[28])}, []*Node{anonfield(typs[28])}) - typs[37] = functype(nil, []*Node{anonfield(typs[33]), anonfield(typs[28]), anonfield(typs[28]), anonfield(typs[28]), anonfield(typs[28]), anonfield(typs[28])}, []*Node{anonfield(typs[28])}) - typs[38] = types.NewSlice(typs[28]) - typs[39] = functype(nil, []*Node{anonfield(typs[33]), anonfield(typs[38])}, []*Node{anonfield(typs[28])}) - typs[40] = functype(nil, []*Node{anonfield(typs[28]), anonfield(typs[28])}, []*Node{anonfield(typs[15])}) - typs[41] = types.NewArray(typs[0], 4) - typs[42] = types.NewPtr(typs[41]) - typs[43] = functype(nil, []*Node{anonfield(typs[42]), anonfield(typs[22])}, []*Node{anonfield(typs[28])}) - typs[44] = functype(nil, []*Node{anonfield(typs[33]), anonfield(typs[1]), anonfield(typs[15])}, []*Node{anonfield(typs[28])}) - typs[45] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15])}, []*Node{anonfield(typs[28])}) - typs[46] = types.Runetype - typs[47] = types.NewSlice(typs[46]) - typs[48] = functype(nil, []*Node{anonfield(typs[33]), anonfield(typs[47])}, []*Node{anonfield(typs[28])}) - typs[49] = types.NewSlice(typs[0]) - typs[50] = functype(nil, []*Node{anonfield(typs[33]), anonfield(typs[28])}, []*Node{anonfield(typs[49])}) - typs[51] = types.NewArray(typs[46], 32) - typs[52] = types.NewPtr(typs[51]) - typs[53] = functype(nil, []*Node{anonfield(typs[52]), anonfield(typs[28])}, []*Node{anonfield(typs[47])}) - typs[54] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[15]), anonfield(typs[3]), anonfield(typs[15]), anonfield(typs[5])}, []*Node{anonfield(typs[15])}) - typs[55] = functype(nil, []*Node{anonfield(typs[28]), anonfield(typs[15])}, []*Node{anonfield(typs[46]), anonfield(typs[15])}) - typs[56] = functype(nil, []*Node{anonfield(typs[28])}, []*Node{anonfield(typs[15])}) - typs[57] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[2])}, []*Node{anonfield(typs[2])}) - typs[58] = functype(nil, []*Node{anonfield(typs[2])}, []*Node{anonfield(typs[7])}) - typs[59] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3])}, []*Node{anonfield(typs[2])}) - typs[60] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[2])}, []*Node{anonfield(typs[2]), anonfield(typs[6])}) - typs[61] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[1]), anonfield(typs[1])}, nil) - typs[62] = functype(nil, []*Node{anonfield(typs[1])}, nil) - typs[63] = types.NewPtr(typs[5]) - typs[64] = functype(nil, []*Node{anonfield(typs[63]), anonfield(typs[7]), anonfield(typs[7])}, []*Node{anonfield(typs[6])}) - typs[65] = types.Types[TUINT32] - typs[66] = functype(nil, nil, []*Node{anonfield(typs[65])}) - typs[67] = types.NewMap(typs[2], typs[2]) - typs[68] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[22]), anonfield(typs[3])}, []*Node{anonfield(typs[67])}) - typs[69] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[3])}, []*Node{anonfield(typs[67])}) - typs[70] = functype(nil, nil, []*Node{anonfield(typs[67])}) - typs[71] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[67]), anonfield(typs[3])}, []*Node{anonfield(typs[3])}) - typs[72] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[67]), anonfield(typs[2])}, []*Node{anonfield(typs[3])}) - typs[73] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[67]), anonfield(typs[3]), anonfield(typs[1])}, []*Node{anonfield(typs[3])}) - typs[74] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[67]), anonfield(typs[3])}, []*Node{anonfield(typs[3]), anonfield(typs[6])}) - typs[75] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[67]), anonfield(typs[2])}, []*Node{anonfield(typs[3]), anonfield(typs[6])}) - typs[76] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[67]), anonfield(typs[3]), anonfield(typs[1])}, []*Node{anonfield(typs[3]), anonfield(typs[6])}) - typs[77] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[67]), anonfield(typs[3])}, nil) - typs[78] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[67]), anonfield(typs[2])}, nil) - typs[79] = functype(nil, []*Node{anonfield(typs[3])}, nil) - typs[80] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[67])}, nil) - typs[81] = types.NewChan(typs[2], types.Cboth) - typs[82] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[22])}, []*Node{anonfield(typs[81])}) - typs[83] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15])}, []*Node{anonfield(typs[81])}) - typs[84] = types.NewChan(typs[2], types.Crecv) - typs[85] = functype(nil, []*Node{anonfield(typs[84]), anonfield(typs[3])}, nil) - typs[86] = functype(nil, []*Node{anonfield(typs[84]), anonfield(typs[3])}, []*Node{anonfield(typs[6])}) - typs[87] = types.NewChan(typs[2], types.Csend) - typs[88] = functype(nil, []*Node{anonfield(typs[87]), anonfield(typs[3])}, nil) - typs[89] = types.NewArray(typs[0], 3) - typs[90] = tostruct([]*Node{namedfield("enabled", typs[6]), namedfield("pad", typs[89]), namedfield("needed", typs[6]), namedfield("cgo", typs[6]), namedfield("alignme", typs[24])}) - typs[91] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3]), anonfield(typs[3])}, nil) - typs[92] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3])}, nil) - typs[93] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3]), anonfield(typs[15]), anonfield(typs[3]), anonfield(typs[15])}, []*Node{anonfield(typs[15])}) - typs[94] = functype(nil, []*Node{anonfield(typs[87]), anonfield(typs[3])}, []*Node{anonfield(typs[6])}) - typs[95] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[84])}, []*Node{anonfield(typs[6])}) - typs[96] = types.NewPtr(typs[6]) - typs[97] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[96]), anonfield(typs[84])}, []*Node{anonfield(typs[6])}) - typs[98] = functype(nil, []*Node{anonfield(typs[63])}, nil) - typs[99] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[1]), anonfield(typs[63]), anonfield(typs[15]), anonfield(typs[15]), anonfield(typs[6])}, []*Node{anonfield(typs[15]), anonfield(typs[6])}) - typs[100] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[15])}, []*Node{anonfield(typs[7])}) - typs[101] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[22]), anonfield(typs[22])}, []*Node{anonfield(typs[7])}) - typs[102] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[15]), anonfield(typs[7])}, []*Node{anonfield(typs[7])}) - typs[103] = types.NewSlice(typs[2]) - typs[104] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[103]), anonfield(typs[15])}, []*Node{anonfield(typs[103])}) - typs[105] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[3]), anonfield(typs[5])}, nil) - typs[106] = functype(nil, []*Node{anonfield(typs[7]), anonfield(typs[5])}, nil) - typs[107] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[3]), anonfield(typs[5])}, []*Node{anonfield(typs[6])}) - typs[108] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[3])}, []*Node{anonfield(typs[6])}) - typs[109] = functype(nil, []*Node{anonfield(typs[7]), anonfield(typs[7])}, []*Node{anonfield(typs[6])}) - typs[110] = functype(nil, []*Node{anonfield(typs[7]), anonfield(typs[5]), anonfield(typs[5])}, []*Node{anonfield(typs[5])}) - typs[111] = functype(nil, []*Node{anonfield(typs[7]), anonfield(typs[5])}, []*Node{anonfield(typs[5])}) - typs[112] = functype(nil, []*Node{anonfield(typs[22]), anonfield(typs[22])}, []*Node{anonfield(typs[22])}) - typs[113] = functype(nil, []*Node{anonfield(typs[24]), anonfield(typs[24])}, []*Node{anonfield(typs[24])}) - typs[114] = functype(nil, []*Node{anonfield(typs[20])}, []*Node{anonfield(typs[22])}) - typs[115] = functype(nil, []*Node{anonfield(typs[20])}, []*Node{anonfield(typs[24])}) - typs[116] = functype(nil, []*Node{anonfield(typs[20])}, []*Node{anonfield(typs[65])}) - typs[117] = functype(nil, []*Node{anonfield(typs[22])}, []*Node{anonfield(typs[20])}) - typs[118] = functype(nil, []*Node{anonfield(typs[24])}, []*Node{anonfield(typs[20])}) - typs[119] = functype(nil, []*Node{anonfield(typs[65])}, []*Node{anonfield(typs[20])}) - typs[120] = functype(nil, []*Node{anonfield(typs[26]), anonfield(typs[26])}, []*Node{anonfield(typs[26])}) - typs[121] = functype(nil, []*Node{anonfield(typs[5]), anonfield(typs[5])}, nil) - typs[122] = functype(nil, []*Node{anonfield(typs[5]), anonfield(typs[5]), anonfield(typs[5])}, nil) - typs[123] = functype(nil, []*Node{anonfield(typs[7]), anonfield(typs[1]), anonfield(typs[5])}, nil) - typs[124] = types.NewSlice(typs[7]) - typs[125] = functype(nil, []*Node{anonfield(typs[7]), anonfield(typs[124])}, nil) - typs[126] = types.Types[TUINT8] - typs[127] = functype(nil, []*Node{anonfield(typs[126]), anonfield(typs[126])}, nil) - typs[128] = types.Types[TUINT16] - typs[129] = functype(nil, []*Node{anonfield(typs[128]), anonfield(typs[128])}, nil) - typs[130] = functype(nil, []*Node{anonfield(typs[65]), anonfield(typs[65])}, nil) - typs[131] = functype(nil, []*Node{anonfield(typs[24]), anonfield(typs[24])}, nil) - return typs[:] -} diff --git a/src/cmd/compile/internal/gc/bv.go b/src/cmd/compile/internal/gc/bv.go deleted file mode 100644 index e32ab97ad52d2df06b3fe0f6cd9c0c9ac8ff6523..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/bv.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "math/bits" -) - -const ( - wordBits = 32 - wordMask = wordBits - 1 - wordShift = 5 -) - -// A bvec is a bit vector. -type bvec struct { - n int32 // number of bits in vector - b []uint32 // words holding bits -} - -func bvalloc(n int32) bvec { - nword := (n + wordBits - 1) / wordBits - return bvec{n, make([]uint32, nword)} -} - -type bulkBvec struct { - words []uint32 - nbit int32 - nword int32 -} - -func bvbulkalloc(nbit int32, count int32) bulkBvec { - nword := (nbit + wordBits - 1) / wordBits - size := int64(nword) * int64(count) - if int64(int32(size*4)) != size*4 { - Fatalf("bvbulkalloc too big: nbit=%d count=%d nword=%d size=%d", nbit, count, nword, size) - } - return bulkBvec{ - words: make([]uint32, size), - nbit: nbit, - nword: nword, - } -} - -func (b *bulkBvec) next() bvec { - out := bvec{b.nbit, b.words[:b.nword]} - b.words = b.words[b.nword:] - return out -} - -func (bv1 bvec) Eq(bv2 bvec) bool { - if bv1.n != bv2.n { - Fatalf("bvequal: lengths %d and %d are not equal", bv1.n, bv2.n) - } - for i, x := range bv1.b { - if x != bv2.b[i] { - return false - } - } - return true -} - -func (dst bvec) Copy(src bvec) { - copy(dst.b, src.b) -} - -func (bv bvec) Get(i int32) bool { - if i < 0 || i >= bv.n { - Fatalf("bvget: index %d is out of bounds with length %d\n", i, bv.n) - } - mask := uint32(1 << uint(i%wordBits)) - return bv.b[i>>wordShift]&mask != 0 -} - -func (bv bvec) Set(i int32) { - if i < 0 || i >= bv.n { - Fatalf("bvset: index %d is out of bounds with length %d\n", i, bv.n) - } - mask := uint32(1 << uint(i%wordBits)) - bv.b[i/wordBits] |= mask -} - -func (bv bvec) Unset(i int32) { - if i < 0 || i >= bv.n { - Fatalf("bvunset: index %d is out of bounds with length %d\n", i, bv.n) - } - mask := uint32(1 << uint(i%wordBits)) - bv.b[i/wordBits] &^= mask -} - -// bvnext returns the smallest index >= i for which bvget(bv, i) == 1. -// If there is no such index, bvnext returns -1. -func (bv bvec) Next(i int32) int32 { - if i >= bv.n { - return -1 - } - - // Jump i ahead to next word with bits. - if bv.b[i>>wordShift]>>uint(i&wordMask) == 0 { - i &^= wordMask - i += wordBits - for i < bv.n && bv.b[i>>wordShift] == 0 { - i += wordBits - } - } - - if i >= bv.n { - return -1 - } - - // Find 1 bit. - w := bv.b[i>>wordShift] >> uint(i&wordMask) - i += int32(bits.TrailingZeros32(w)) - - return i -} - -func (bv bvec) IsEmpty() bool { - for _, x := range bv.b { - if x != 0 { - return false - } - } - return true -} - -func (bv bvec) Not() { - for i, x := range bv.b { - bv.b[i] = ^x - } -} - -// union -func (dst bvec) Or(src1, src2 bvec) { - if len(src1.b) == 0 { - return - } - _, _ = dst.b[len(src1.b)-1], src2.b[len(src1.b)-1] // hoist bounds checks out of the loop - - for i, x := range src1.b { - dst.b[i] = x | src2.b[i] - } -} - -// intersection -func (dst bvec) And(src1, src2 bvec) { - if len(src1.b) == 0 { - return - } - _, _ = dst.b[len(src1.b)-1], src2.b[len(src1.b)-1] // hoist bounds checks out of the loop - - for i, x := range src1.b { - dst.b[i] = x & src2.b[i] - } -} - -// difference -func (dst bvec) AndNot(src1, src2 bvec) { - if len(src1.b) == 0 { - return - } - _, _ = dst.b[len(src1.b)-1], src2.b[len(src1.b)-1] // hoist bounds checks out of the loop - - for i, x := range src1.b { - dst.b[i] = x &^ src2.b[i] - } -} - -func (bv bvec) String() string { - s := make([]byte, 2+bv.n) - copy(s, "#*") - for i := int32(0); i < bv.n; i++ { - ch := byte('0') - if bv.Get(i) { - ch = '1' - } - s[2+i] = ch - } - return string(s) -} - -func (bv bvec) Clear() { - for i := range bv.b { - bv.b[i] = 0 - } -} - -// FNV-1 hash function constants. -const ( - H0 = 2166136261 - Hp = 16777619 -) - -func hashbitmap(h uint32, bv bvec) uint32 { - n := int((bv.n + 31) / 32) - for i := 0; i < n; i++ { - w := bv.b[i] - h = (h * Hp) ^ (w & 0xff) - h = (h * Hp) ^ ((w >> 8) & 0xff) - h = (h * Hp) ^ ((w >> 16) & 0xff) - h = (h * Hp) ^ ((w >> 24) & 0xff) - } - - return h -} - -// bvecSet is a set of bvecs, in initial insertion order. -type bvecSet struct { - index []int // hash -> uniq index. -1 indicates empty slot. - uniq []bvec // unique bvecs, in insertion order -} - -func (m *bvecSet) grow() { - // Allocate new index. - n := len(m.index) * 2 - if n == 0 { - n = 32 - } - newIndex := make([]int, n) - for i := range newIndex { - newIndex[i] = -1 - } - - // Rehash into newIndex. - for i, bv := range m.uniq { - h := hashbitmap(H0, bv) % uint32(len(newIndex)) - for { - j := newIndex[h] - if j < 0 { - newIndex[h] = i - break - } - h++ - if h == uint32(len(newIndex)) { - h = 0 - } - } - } - m.index = newIndex -} - -// add adds bv to the set and returns its index in m.extractUniqe. -// The caller must not modify bv after this. -func (m *bvecSet) add(bv bvec) int { - if len(m.uniq)*4 >= len(m.index) { - m.grow() - } - - index := m.index - h := hashbitmap(H0, bv) % uint32(len(index)) - for { - j := index[h] - if j < 0 { - // New bvec. - index[h] = len(m.uniq) - m.uniq = append(m.uniq, bv) - return len(m.uniq) - 1 - } - jlive := m.uniq[j] - if bv.Eq(jlive) { - // Existing bvec. - return j - } - - h++ - if h == uint32(len(index)) { - h = 0 - } - } -} - -// extractUniqe returns this slice of unique bit vectors in m, as -// indexed by the result of bvecSet.add. -func (m *bvecSet) extractUniqe() []bvec { - return m.uniq -} diff --git a/src/cmd/compile/internal/gc/closure.go b/src/cmd/compile/internal/gc/closure.go deleted file mode 100644 index bd350f696e8cecbbaccbfdbc23b63a3451c4ce3b..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/closure.go +++ /dev/null @@ -1,594 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/syntax" - "cmd/compile/internal/types" - "fmt" -) - -func (p *noder) funcLit(expr *syntax.FuncLit) *Node { - xtype := p.typeExpr(expr.Type) - ntype := p.typeExpr(expr.Type) - - xfunc := p.nod(expr, ODCLFUNC, nil, nil) - xfunc.Func.SetIsHiddenClosure(Curfn != nil) - xfunc.Func.Nname = newfuncnamel(p.pos(expr), nblank.Sym) // filled in by typecheckclosure - xfunc.Func.Nname.Name.Param.Ntype = xtype - xfunc.Func.Nname.Name.Defn = xfunc - - clo := p.nod(expr, OCLOSURE, nil, nil) - clo.Func.Ntype = ntype - - xfunc.Func.Closure = clo - clo.Func.Closure = xfunc - - p.funcBody(xfunc, expr.Body) - - // closure-specific variables are hanging off the - // ordinary ones in the symbol table; see oldname. - // unhook them. - // make the list of pointers for the closure call. - for _, v := range xfunc.Func.Cvars.Slice() { - // Unlink from v1; see comment in syntax.go type Param for these fields. - v1 := v.Name.Defn - v1.Name.Param.Innermost = v.Name.Param.Outer - - // If the closure usage of v is not dense, - // we need to make it dense; now that we're out - // of the function in which v appeared, - // look up v.Sym in the enclosing function - // and keep it around for use in the compiled code. - // - // That is, suppose we just finished parsing the innermost - // closure f4 in this code: - // - // func f() { - // v := 1 - // func() { // f2 - // use(v) - // func() { // f3 - // func() { // f4 - // use(v) - // }() - // }() - // }() - // } - // - // At this point v.Outer is f2's v; there is no f3's v. - // To construct the closure f4 from within f3, - // we need to use f3's v and in this case we need to create f3's v. - // We are now in the context of f3, so calling oldname(v.Sym) - // obtains f3's v, creating it if necessary (as it is in the example). - // - // capturevars will decide whether to use v directly or &v. - v.Name.Param.Outer = oldname(v.Sym) - } - - return clo -} - -// typecheckclosure typechecks an OCLOSURE node. It also creates the named -// function associated with the closure. -// TODO: This creation of the named function should probably really be done in a -// separate pass from type-checking. -func typecheckclosure(clo *Node, top int) { - xfunc := clo.Func.Closure - // Set current associated iota value, so iota can be used inside - // function in ConstSpec, see issue #22344 - if x := getIotaValue(); x >= 0 { - xfunc.SetIota(x) - } - - clo.Func.Ntype = typecheck(clo.Func.Ntype, ctxType) - clo.Type = clo.Func.Ntype.Type - clo.Func.Top = top - - // Do not typecheck xfunc twice, otherwise, we will end up pushing - // xfunc to xtop multiple times, causing initLSym called twice. - // See #30709 - if xfunc.Typecheck() == 1 { - return - } - - for _, ln := range xfunc.Func.Cvars.Slice() { - n := ln.Name.Defn - if !n.Name.Captured() { - n.Name.SetCaptured(true) - if n.Name.Decldepth == 0 { - Fatalf("typecheckclosure: var %S does not have decldepth assigned", n) - } - - // Ignore assignments to the variable in straightline code - // preceding the first capturing by a closure. - if n.Name.Decldepth == decldepth { - n.Name.SetAssigned(false) - } - } - } - - xfunc.Func.Nname.Sym = closurename(Curfn) - setNodeNameFunc(xfunc.Func.Nname) - xfunc = typecheck(xfunc, ctxStmt) - - // Type check the body now, but only if we're inside a function. - // At top level (in a variable initialization: curfn==nil) we're not - // ready to type check code yet; we'll check it later, because the - // underlying closure function we create is added to xtop. - if Curfn != nil && clo.Type != nil { - oldfn := Curfn - Curfn = xfunc - olddd := decldepth - decldepth = 1 - typecheckslice(xfunc.Nbody.Slice(), ctxStmt) - decldepth = olddd - Curfn = oldfn - } - - xtop = append(xtop, xfunc) -} - -// globClosgen is like Func.Closgen, but for the global scope. -var globClosgen int - -// closurename generates a new unique name for a closure within -// outerfunc. -func closurename(outerfunc *Node) *types.Sym { - outer := "glob." - prefix := "func" - gen := &globClosgen - - if outerfunc != nil { - if outerfunc.Func.Closure != nil { - prefix = "" - } - - outer = outerfunc.funcname() - - // There may be multiple functions named "_". In those - // cases, we can't use their individual Closgens as it - // would lead to name clashes. - if !outerfunc.Func.Nname.isBlank() { - gen = &outerfunc.Func.Closgen - } - } - - *gen++ - return lookup(fmt.Sprintf("%s.%s%d", outer, prefix, *gen)) -} - -// capturevarscomplete is set to true when the capturevars phase is done. -var capturevarscomplete bool - -// capturevars is called in a separate phase after all typechecking is done. -// It decides whether each variable captured by a closure should be captured -// by value or by reference. -// We use value capturing for values <= 128 bytes that are never reassigned -// after capturing (effectively constant). -func capturevars(xfunc *Node) { - lno := lineno - lineno = xfunc.Pos - - clo := xfunc.Func.Closure - cvars := xfunc.Func.Cvars.Slice() - out := cvars[:0] - for _, v := range cvars { - if v.Type == nil { - // If v.Type is nil, it means v looked like it - // was going to be used in the closure, but - // isn't. This happens in struct literals like - // s{f: x} where we can't distinguish whether - // f is a field identifier or expression until - // resolving s. - continue - } - out = append(out, v) - - // type check the & of closed variables outside the closure, - // so that the outer frame also grabs them and knows they escape. - dowidth(v.Type) - - outer := v.Name.Param.Outer - outermost := v.Name.Defn - - // out parameters will be assigned to implicitly upon return. - if outermost.Class() != PPARAMOUT && !outermost.Name.Addrtaken() && !outermost.Name.Assigned() && v.Type.Width <= 128 { - v.Name.SetByval(true) - } else { - outermost.Name.SetAddrtaken(true) - outer = nod(OADDR, outer, nil) - } - - if Debug.m > 1 { - var name *types.Sym - if v.Name.Curfn != nil && v.Name.Curfn.Func.Nname != nil { - name = v.Name.Curfn.Func.Nname.Sym - } - how := "ref" - if v.Name.Byval() { - how = "value" - } - Warnl(v.Pos, "%v capturing by %s: %v (addr=%v assign=%v width=%d)", name, how, v.Sym, outermost.Name.Addrtaken(), outermost.Name.Assigned(), int32(v.Type.Width)) - } - - outer = typecheck(outer, ctxExpr) - clo.Func.Enter.Append(outer) - } - - xfunc.Func.Cvars.Set(out) - lineno = lno -} - -// transformclosure is called in a separate phase after escape analysis. -// It transform closure bodies to properly reference captured variables. -func transformclosure(xfunc *Node) { - lno := lineno - lineno = xfunc.Pos - clo := xfunc.Func.Closure - - if clo.Func.Top&ctxCallee != 0 { - // If the closure is directly called, we transform it to a plain function call - // with variables passed as args. This avoids allocation of a closure object. - // Here we do only a part of the transformation. Walk of OCALLFUNC(OCLOSURE) - // will complete the transformation later. - // For illustration, the following closure: - // func(a int) { - // println(byval) - // byref++ - // }(42) - // becomes: - // func(byval int, &byref *int, a int) { - // println(byval) - // (*&byref)++ - // }(byval, &byref, 42) - - // f is ONAME of the actual function. - f := xfunc.Func.Nname - - // We are going to insert captured variables before input args. - var params []*types.Field - var decls []*Node - for _, v := range xfunc.Func.Cvars.Slice() { - if !v.Name.Byval() { - // If v of type T is captured by reference, - // we introduce function param &v *T - // and v remains PAUTOHEAP with &v heapaddr - // (accesses will implicitly deref &v). - addr := newname(lookup("&" + v.Sym.Name)) - addr.Type = types.NewPtr(v.Type) - v.Name.Param.Heapaddr = addr - v = addr - } - - v.SetClass(PPARAM) - decls = append(decls, v) - - fld := types.NewField() - fld.Nname = asTypesNode(v) - fld.Type = v.Type - fld.Sym = v.Sym - params = append(params, fld) - } - - if len(params) > 0 { - // Prepend params and decls. - f.Type.Params().SetFields(append(params, f.Type.Params().FieldSlice()...)) - xfunc.Func.Dcl = append(decls, xfunc.Func.Dcl...) - } - - dowidth(f.Type) - xfunc.Type = f.Type // update type of ODCLFUNC - } else { - // The closure is not called, so it is going to stay as closure. - var body []*Node - offset := int64(Widthptr) - for _, v := range xfunc.Func.Cvars.Slice() { - // cv refers to the field inside of closure OSTRUCTLIT. - cv := nod(OCLOSUREVAR, nil, nil) - - cv.Type = v.Type - if !v.Name.Byval() { - cv.Type = types.NewPtr(v.Type) - } - offset = Rnd(offset, int64(cv.Type.Align)) - cv.Xoffset = offset - offset += cv.Type.Width - - if v.Name.Byval() && v.Type.Width <= int64(2*Widthptr) { - // If it is a small variable captured by value, downgrade it to PAUTO. - v.SetClass(PAUTO) - xfunc.Func.Dcl = append(xfunc.Func.Dcl, v) - body = append(body, nod(OAS, v, cv)) - } else { - // Declare variable holding addresses taken from closure - // and initialize in entry prologue. - addr := newname(lookup("&" + v.Sym.Name)) - addr.Type = types.NewPtr(v.Type) - addr.SetClass(PAUTO) - addr.Name.SetUsed(true) - addr.Name.Curfn = xfunc - xfunc.Func.Dcl = append(xfunc.Func.Dcl, addr) - v.Name.Param.Heapaddr = addr - if v.Name.Byval() { - cv = nod(OADDR, cv, nil) - } - body = append(body, nod(OAS, addr, cv)) - } - } - - if len(body) > 0 { - typecheckslice(body, ctxStmt) - xfunc.Func.Enter.Set(body) - xfunc.Func.SetNeedctxt(true) - } - } - - lineno = lno -} - -// hasemptycvars reports whether closure clo has an -// empty list of captured vars. -func hasemptycvars(clo *Node) bool { - xfunc := clo.Func.Closure - return xfunc.Func.Cvars.Len() == 0 -} - -// closuredebugruntimecheck applies boilerplate checks for debug flags -// and compiling runtime -func closuredebugruntimecheck(clo *Node) { - if Debug_closure > 0 { - xfunc := clo.Func.Closure - if clo.Esc == EscHeap { - Warnl(clo.Pos, "heap closure, captured vars = %v", xfunc.Func.Cvars) - } else { - Warnl(clo.Pos, "stack closure, captured vars = %v", xfunc.Func.Cvars) - } - } - if compiling_runtime && clo.Esc == EscHeap { - yyerrorl(clo.Pos, "heap-allocated closure, not allowed in runtime") - } -} - -// closureType returns the struct type used to hold all the information -// needed in the closure for clo (clo must be a OCLOSURE node). -// The address of a variable of the returned type can be cast to a func. -func closureType(clo *Node) *types.Type { - // Create closure in the form of a composite literal. - // supposing the closure captures an int i and a string s - // and has one float64 argument and no results, - // the generated code looks like: - // - // clos = &struct{.F uintptr; i *int; s *string}{func.1, &i, &s} - // - // The use of the struct provides type information to the garbage - // collector so that it can walk the closure. We could use (in this case) - // [3]unsafe.Pointer instead, but that would leave the gc in the dark. - // The information appears in the binary in the form of type descriptors; - // the struct is unnamed so that closures in multiple packages with the - // same struct type can share the descriptor. - fields := []*Node{ - namedfield(".F", types.Types[TUINTPTR]), - } - for _, v := range clo.Func.Closure.Func.Cvars.Slice() { - typ := v.Type - if !v.Name.Byval() { - typ = types.NewPtr(typ) - } - fields = append(fields, symfield(v.Sym, typ)) - } - typ := tostruct(fields) - typ.SetNoalg(true) - return typ -} - -func walkclosure(clo *Node, init *Nodes) *Node { - xfunc := clo.Func.Closure - - // If no closure vars, don't bother wrapping. - if hasemptycvars(clo) { - if Debug_closure > 0 { - Warnl(clo.Pos, "closure converted to global") - } - return xfunc.Func.Nname - } - closuredebugruntimecheck(clo) - - typ := closureType(clo) - - clos := nod(OCOMPLIT, nil, typenod(typ)) - clos.Esc = clo.Esc - clos.List.Set(append([]*Node{nod(OCFUNC, xfunc.Func.Nname, nil)}, clo.Func.Enter.Slice()...)) - - clos = nod(OADDR, clos, nil) - clos.Esc = clo.Esc - - // Force type conversion from *struct to the func type. - clos = convnop(clos, clo.Type) - - // non-escaping temp to use, if any. - if x := prealloc[clo]; x != nil { - if !types.Identical(typ, x.Type) { - panic("closure type does not match order's assigned type") - } - clos.Left.Right = x - delete(prealloc, clo) - } - - return walkexpr(clos, init) -} - -func typecheckpartialcall(fn *Node, sym *types.Sym) { - switch fn.Op { - case ODOTINTER, ODOTMETH: - break - - default: - Fatalf("invalid typecheckpartialcall") - } - - // Create top-level function. - xfunc := makepartialcall(fn, fn.Type, sym) - fn.Func = xfunc.Func - fn.Func.SetWrapper(true) - fn.Right = newname(sym) - fn.Op = OCALLPART - fn.Type = xfunc.Type -} - -// makepartialcall returns a DCLFUNC node representing the wrapper function (*-fm) needed -// for partial calls. -func makepartialcall(fn *Node, t0 *types.Type, meth *types.Sym) *Node { - rcvrtype := fn.Left.Type - sym := methodSymSuffix(rcvrtype, meth, "-fm") - - if sym.Uniq() { - return asNode(sym.Def) - } - sym.SetUniq(true) - - savecurfn := Curfn - saveLineNo := lineno - Curfn = nil - - // Set line number equal to the line number where the method is declared. - var m *types.Field - if lookdot0(meth, rcvrtype, &m, false) == 1 && m.Pos.IsKnown() { - lineno = m.Pos - } - // Note: !m.Pos.IsKnown() happens for method expressions where - // the method is implicitly declared. The Error method of the - // built-in error type is one such method. We leave the line - // number at the use of the method expression in this - // case. See issue 29389. - - tfn := nod(OTFUNC, nil, nil) - tfn.List.Set(structargs(t0.Params(), true)) - tfn.Rlist.Set(structargs(t0.Results(), false)) - - xfunc := dclfunc(sym, tfn) - xfunc.Func.SetDupok(true) - xfunc.Func.SetNeedctxt(true) - - tfn.Type.SetPkg(t0.Pkg()) - - // Declare and initialize variable holding receiver. - - cv := nod(OCLOSUREVAR, nil, nil) - cv.Type = rcvrtype - cv.Xoffset = Rnd(int64(Widthptr), int64(cv.Type.Align)) - - ptr := newname(lookup(".this")) - declare(ptr, PAUTO) - ptr.Name.SetUsed(true) - var body []*Node - if rcvrtype.IsPtr() || rcvrtype.IsInterface() { - ptr.Type = rcvrtype - body = append(body, nod(OAS, ptr, cv)) - } else { - ptr.Type = types.NewPtr(rcvrtype) - body = append(body, nod(OAS, ptr, nod(OADDR, cv, nil))) - } - - call := nod(OCALL, nodSym(OXDOT, ptr, meth), nil) - call.List.Set(paramNnames(tfn.Type)) - call.SetIsDDD(tfn.Type.IsVariadic()) - if t0.NumResults() != 0 { - n := nod(ORETURN, nil, nil) - n.List.Set1(call) - call = n - } - body = append(body, call) - - xfunc.Nbody.Set(body) - funcbody() - - xfunc = typecheck(xfunc, ctxStmt) - // Need to typecheck the body of the just-generated wrapper. - // typecheckslice() requires that Curfn is set when processing an ORETURN. - Curfn = xfunc - typecheckslice(xfunc.Nbody.Slice(), ctxStmt) - sym.Def = asTypesNode(xfunc) - xtop = append(xtop, xfunc) - Curfn = savecurfn - lineno = saveLineNo - - return xfunc -} - -// partialCallType returns the struct type used to hold all the information -// needed in the closure for n (n must be a OCALLPART node). -// The address of a variable of the returned type can be cast to a func. -func partialCallType(n *Node) *types.Type { - t := tostruct([]*Node{ - namedfield("F", types.Types[TUINTPTR]), - namedfield("R", n.Left.Type), - }) - t.SetNoalg(true) - return t -} - -func walkpartialcall(n *Node, init *Nodes) *Node { - // Create closure in the form of a composite literal. - // For x.M with receiver (x) type T, the generated code looks like: - // - // clos = &struct{F uintptr; R T}{T.M·f, x} - // - // Like walkclosure above. - - if n.Left.Type.IsInterface() { - // Trigger panic for method on nil interface now. - // Otherwise it happens in the wrapper and is confusing. - n.Left = cheapexpr(n.Left, init) - n.Left = walkexpr(n.Left, nil) - - tab := nod(OITAB, n.Left, nil) - tab = typecheck(tab, ctxExpr) - - c := nod(OCHECKNIL, tab, nil) - c.SetTypecheck(1) - init.Append(c) - } - - typ := partialCallType(n) - - clos := nod(OCOMPLIT, nil, typenod(typ)) - clos.Esc = n.Esc - clos.List.Set2(nod(OCFUNC, n.Func.Nname, nil), n.Left) - - clos = nod(OADDR, clos, nil) - clos.Esc = n.Esc - - // Force type conversion from *struct to the func type. - clos = convnop(clos, n.Type) - - // non-escaping temp to use, if any. - if x := prealloc[n]; x != nil { - if !types.Identical(typ, x.Type) { - panic("partial call type does not match order's assigned type") - } - clos.Left.Right = x - delete(prealloc, n) - } - - return walkexpr(clos, init) -} - -// callpartMethod returns the *types.Field representing the method -// referenced by method value n. -func callpartMethod(n *Node) *types.Field { - if n.Op != OCALLPART { - Fatalf("expected OCALLPART, got %v", n) - } - - // TODO(mdempsky): Optimize this. If necessary, - // makepartialcall could save m for us somewhere. - var m *types.Field - if lookdot0(n.Right.Sym, n.Left.Type, &m, false) != 1 { - Fatalf("failed to find field for OCALLPART") - } - - return m -} diff --git a/src/cmd/compile/internal/gc/compile.go b/src/cmd/compile/internal/gc/compile.go new file mode 100644 index 0000000000000000000000000000000000000000..00504451a881c9562285321ecbce03f4ec30b764 --- /dev/null +++ b/src/cmd/compile/internal/gc/compile.go @@ -0,0 +1,169 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gc + +import ( + "internal/race" + "math/rand" + "sort" + "sync" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/liveness" + "cmd/compile/internal/objw" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/compile/internal/walk" + "cmd/internal/obj" +) + +// "Portable" code generation. + +var ( + compilequeue []*ir.Func // functions waiting to be compiled +) + +func enqueueFunc(fn *ir.Func) { + if ir.CurFunc != nil { + base.FatalfAt(fn.Pos(), "enqueueFunc %v inside %v", fn, ir.CurFunc) + } + + if ir.FuncName(fn) == "_" { + // Skip compiling blank functions. + // Frontend already reported any spec-mandated errors (#29870). + return + } + + if clo := fn.OClosure; clo != nil && !ir.IsTrivialClosure(clo) { + return // we'll get this as part of its enclosing function + } + + if len(fn.Body) == 0 { + // Initialize ABI wrappers if necessary. + ssagen.InitLSym(fn, false) + types.CalcSize(fn.Type()) + a := ssagen.AbiForBodylessFuncStackMap(fn) + abiInfo := a.ABIAnalyzeFuncType(fn.Type().FuncType()) // abiInfo has spill/home locations for wrapper + liveness.WriteFuncMap(fn, abiInfo) + if fn.ABI == obj.ABI0 { + x := ssagen.EmitArgInfo(fn, abiInfo) + objw.Global(x, int32(len(x.P)), obj.RODATA|obj.LOCAL) + } + return + } + + errorsBefore := base.Errors() + + todo := []*ir.Func{fn} + for len(todo) > 0 { + next := todo[len(todo)-1] + todo = todo[:len(todo)-1] + + prepareFunc(next) + todo = append(todo, next.Closures...) + } + + if base.Errors() > errorsBefore { + return + } + + // Enqueue just fn itself. compileFunctions will handle + // scheduling compilation of its closures after it's done. + compilequeue = append(compilequeue, fn) +} + +// prepareFunc handles any remaining frontend compilation tasks that +// aren't yet safe to perform concurrently. +func prepareFunc(fn *ir.Func) { + // Set up the function's LSym early to avoid data races with the assemblers. + // Do this before walk, as walk needs the LSym to set attributes/relocations + // (e.g. in MarkTypeUsedInInterface). + ssagen.InitLSym(fn, true) + + // Calculate parameter offsets. + types.CalcSize(fn.Type()) + + typecheck.DeclContext = ir.PAUTO + ir.CurFunc = fn + walk.Walk(fn) + ir.CurFunc = nil // enforce no further uses of CurFunc + typecheck.DeclContext = ir.PEXTERN +} + +// compileFunctions compiles all functions in compilequeue. +// It fans out nBackendWorkers to do the work +// and waits for them to complete. +func compileFunctions() { + if len(compilequeue) == 0 { + return + } + + if race.Enabled { + // Randomize compilation order to try to shake out races. + tmp := make([]*ir.Func, len(compilequeue)) + perm := rand.Perm(len(compilequeue)) + for i, v := range perm { + tmp[v] = compilequeue[i] + } + copy(compilequeue, tmp) + } else { + // Compile the longest functions first, + // since they're most likely to be the slowest. + // This helps avoid stragglers. + sort.Slice(compilequeue, func(i, j int) bool { + return len(compilequeue[i].Body) > len(compilequeue[j].Body) + }) + } + + // By default, we perform work right away on the current goroutine + // as the solo worker. + queue := func(work func(int)) { + work(0) + } + + if nWorkers := base.Flag.LowerC; nWorkers > 1 { + // For concurrent builds, we create a goroutine per task, but + // require them to hold a unique worker ID while performing work + // to limit parallelism. + workerIDs := make(chan int, nWorkers) + for i := 0; i < nWorkers; i++ { + workerIDs <- i + } + + queue = func(work func(int)) { + go func() { + worker := <-workerIDs + work(worker) + workerIDs <- worker + }() + } + } + + var wg sync.WaitGroup + var compile func([]*ir.Func) + compile = func(fns []*ir.Func) { + wg.Add(len(fns)) + for _, fn := range fns { + fn := fn + queue(func(worker int) { + ssagen.Compile(fn, worker) + compile(fn.Closures) + wg.Done() + }) + } + } + + types.CalcSizeDisabled = true // not safe to calculate sizes concurrently + base.Ctxt.InParallel = true + + compile(compilequeue) + compilequeue = nil + wg.Wait() + + base.Ctxt.InParallel = false + types.CalcSizeDisabled = false +} diff --git a/src/cmd/compile/internal/gc/const.go b/src/cmd/compile/internal/gc/const.go deleted file mode 100644 index b92c8d66b5afb1039c2fff5e0e8b73864ac6135b..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/const.go +++ /dev/null @@ -1,1323 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/src" - "fmt" - "math/big" - "strings" -) - -// Ctype describes the constant kind of an "ideal" (untyped) constant. -type Ctype uint8 - -const ( - CTxxx Ctype = iota - - CTINT - CTRUNE - CTFLT - CTCPLX - CTSTR - CTBOOL - CTNIL -) - -type Val struct { - // U contains one of: - // bool bool when Ctype() == CTBOOL - // *Mpint int when Ctype() == CTINT, rune when Ctype() == CTRUNE - // *Mpflt float when Ctype() == CTFLT - // *Mpcplx pair of floats when Ctype() == CTCPLX - // string string when Ctype() == CTSTR - // *Nilval when Ctype() == CTNIL - U interface{} -} - -func (v Val) Ctype() Ctype { - switch x := v.U.(type) { - default: - Fatalf("unexpected Ctype for %T", v.U) - panic("unreachable") - case nil: - return CTxxx - case *NilVal: - return CTNIL - case bool: - return CTBOOL - case *Mpint: - if x.Rune { - return CTRUNE - } - return CTINT - case *Mpflt: - return CTFLT - case *Mpcplx: - return CTCPLX - case string: - return CTSTR - } -} - -func eqval(a, b Val) bool { - if a.Ctype() != b.Ctype() { - return false - } - switch x := a.U.(type) { - default: - Fatalf("unexpected Ctype for %T", a.U) - panic("unreachable") - case *NilVal: - return true - case bool: - y := b.U.(bool) - return x == y - case *Mpint: - y := b.U.(*Mpint) - return x.Cmp(y) == 0 - case *Mpflt: - y := b.U.(*Mpflt) - return x.Cmp(y) == 0 - case *Mpcplx: - y := b.U.(*Mpcplx) - return x.Real.Cmp(&y.Real) == 0 && x.Imag.Cmp(&y.Imag) == 0 - case string: - y := b.U.(string) - return x == y - } -} - -// Interface returns the constant value stored in v as an interface{}. -// It returns int64s for ints and runes, float64s for floats, -// complex128s for complex values, and nil for constant nils. -func (v Val) Interface() interface{} { - switch x := v.U.(type) { - default: - Fatalf("unexpected Interface for %T", v.U) - panic("unreachable") - case *NilVal: - return nil - case bool, string: - return x - case *Mpint: - return x.Int64() - case *Mpflt: - return x.Float64() - case *Mpcplx: - return complex(x.Real.Float64(), x.Imag.Float64()) - } -} - -type NilVal struct{} - -// Int64Val returns n as an int64. -// n must be an integer or rune constant. -func (n *Node) Int64Val() int64 { - if !Isconst(n, CTINT) { - Fatalf("Int64Val(%v)", n) - } - return n.Val().U.(*Mpint).Int64() -} - -// CanInt64 reports whether it is safe to call Int64Val() on n. -func (n *Node) CanInt64() bool { - if !Isconst(n, CTINT) { - return false - } - - // if the value inside n cannot be represented as an int64, the - // return value of Int64 is undefined - return n.Val().U.(*Mpint).CmpInt64(n.Int64Val()) == 0 -} - -// BoolVal returns n as a bool. -// n must be a boolean constant. -func (n *Node) BoolVal() bool { - if !Isconst(n, CTBOOL) { - Fatalf("BoolVal(%v)", n) - } - return n.Val().U.(bool) -} - -// StringVal returns the value of a literal string Node as a string. -// n must be a string constant. -func (n *Node) StringVal() string { - if !Isconst(n, CTSTR) { - Fatalf("StringVal(%v)", n) - } - return n.Val().U.(string) -} - -// truncate float literal fv to 32-bit or 64-bit precision -// according to type; return truncated value. -func truncfltlit(oldv *Mpflt, t *types.Type) *Mpflt { - if t == nil { - return oldv - } - - if overflow(Val{oldv}, t) { - // If there was overflow, simply continuing would set the - // value to Inf which in turn would lead to spurious follow-on - // errors. Avoid this by returning the existing value. - return oldv - } - - fv := newMpflt() - - // convert large precision literal floating - // into limited precision (float64 or float32) - switch t.Etype { - case types.TFLOAT32: - fv.SetFloat64(oldv.Float32()) - case types.TFLOAT64: - fv.SetFloat64(oldv.Float64()) - default: - Fatalf("truncfltlit: unexpected Etype %v", t.Etype) - } - - return fv -} - -// truncate Real and Imag parts of Mpcplx to 32-bit or 64-bit -// precision, according to type; return truncated value. In case of -// overflow, calls yyerror but does not truncate the input value. -func trunccmplxlit(oldv *Mpcplx, t *types.Type) *Mpcplx { - if t == nil { - return oldv - } - - if overflow(Val{oldv}, t) { - // If there was overflow, simply continuing would set the - // value to Inf which in turn would lead to spurious follow-on - // errors. Avoid this by returning the existing value. - return oldv - } - - cv := newMpcmplx() - - switch t.Etype { - case types.TCOMPLEX64: - cv.Real.SetFloat64(oldv.Real.Float32()) - cv.Imag.SetFloat64(oldv.Imag.Float32()) - case types.TCOMPLEX128: - cv.Real.SetFloat64(oldv.Real.Float64()) - cv.Imag.SetFloat64(oldv.Imag.Float64()) - default: - Fatalf("trunccplxlit: unexpected Etype %v", t.Etype) - } - - return cv -} - -// TODO(mdempsky): Replace these with better APIs. -func convlit(n *Node, t *types.Type) *Node { return convlit1(n, t, false, nil) } -func defaultlit(n *Node, t *types.Type) *Node { return convlit1(n, t, false, nil) } - -// convlit1 converts an untyped expression n to type t. If n already -// has a type, convlit1 has no effect. -// -// For explicit conversions, t must be non-nil, and integer-to-string -// conversions are allowed. -// -// For implicit conversions (e.g., assignments), t may be nil; if so, -// n is converted to its default type. -// -// If there's an error converting n to t, context is used in the error -// message. -func convlit1(n *Node, t *types.Type, explicit bool, context func() string) *Node { - if explicit && t == nil { - Fatalf("explicit conversion missing type") - } - if t != nil && t.IsUntyped() { - Fatalf("bad conversion to untyped: %v", t) - } - - if n == nil || n.Type == nil { - // Allow sloppy callers. - return n - } - if !n.Type.IsUntyped() { - // Already typed; nothing to do. - return n - } - - if n.Op == OLITERAL { - // Can't always set n.Type directly on OLITERAL nodes. - // See discussion on CL 20813. - n = n.rawcopy() - } - - // Nil is technically not a constant, so handle it specially. - if n.Type.Etype == TNIL { - if t == nil { - yyerror("use of untyped nil") - n.SetDiag(true) - n.Type = nil - return n - } - - if !t.HasNil() { - // Leave for caller to handle. - return n - } - - n.Type = t - return n - } - - if t == nil || !okforconst[t.Etype] { - t = defaultType(n.Type) - } - - switch n.Op { - default: - Fatalf("unexpected untyped expression: %v", n) - - case OLITERAL: - v := convertVal(n.Val(), t, explicit) - if v.U == nil { - break - } - n.SetVal(v) - n.Type = t - return n - - case OPLUS, ONEG, OBITNOT, ONOT, OREAL, OIMAG: - ot := operandType(n.Op, t) - if ot == nil { - n = defaultlit(n, nil) - break - } - - n.Left = convlit(n.Left, ot) - if n.Left.Type == nil { - n.Type = nil - return n - } - n.Type = t - return n - - case OADD, OSUB, OMUL, ODIV, OMOD, OOR, OXOR, OAND, OANDNOT, OOROR, OANDAND, OCOMPLEX: - ot := operandType(n.Op, t) - if ot == nil { - n = defaultlit(n, nil) - break - } - - n.Left = convlit(n.Left, ot) - n.Right = convlit(n.Right, ot) - if n.Left.Type == nil || n.Right.Type == nil { - n.Type = nil - return n - } - if !types.Identical(n.Left.Type, n.Right.Type) { - yyerror("invalid operation: %v (mismatched types %v and %v)", n, n.Left.Type, n.Right.Type) - n.Type = nil - return n - } - - n.Type = t - return n - - case OEQ, ONE, OLT, OLE, OGT, OGE: - if !t.IsBoolean() { - break - } - n.Type = t - return n - - case OLSH, ORSH: - n.Left = convlit1(n.Left, t, explicit, nil) - n.Type = n.Left.Type - if n.Type != nil && !n.Type.IsInteger() { - yyerror("invalid operation: %v (shift of type %v)", n, n.Type) - n.Type = nil - } - return n - } - - if !n.Diag() { - if !t.Broke() { - if explicit { - yyerror("cannot convert %L to type %v", n, t) - } else if context != nil { - yyerror("cannot use %L as type %v in %s", n, t, context()) - } else { - yyerror("cannot use %L as type %v", n, t) - } - } - n.SetDiag(true) - } - n.Type = nil - return n -} - -func operandType(op Op, t *types.Type) *types.Type { - switch op { - case OCOMPLEX: - if t.IsComplex() { - return floatForComplex(t) - } - case OREAL, OIMAG: - if t.IsFloat() { - return complexForFloat(t) - } - default: - if okfor[op][t.Etype] { - return t - } - } - return nil -} - -// convertVal converts v into a representation appropriate for t. If -// no such representation exists, it returns Val{} instead. -// -// If explicit is true, then conversions from integer to string are -// also allowed. -func convertVal(v Val, t *types.Type, explicit bool) Val { - switch ct := v.Ctype(); ct { - case CTBOOL: - if t.IsBoolean() { - return v - } - - case CTSTR: - if t.IsString() { - return v - } - - case CTINT, CTRUNE: - if explicit && t.IsString() { - return tostr(v) - } - fallthrough - case CTFLT, CTCPLX: - switch { - case t.IsInteger(): - v = toint(v) - overflow(v, t) - return v - case t.IsFloat(): - v = toflt(v) - v = Val{truncfltlit(v.U.(*Mpflt), t)} - return v - case t.IsComplex(): - v = tocplx(v) - v = Val{trunccmplxlit(v.U.(*Mpcplx), t)} - return v - } - } - - return Val{} -} - -func tocplx(v Val) Val { - switch u := v.U.(type) { - case *Mpint: - c := newMpcmplx() - c.Real.SetInt(u) - c.Imag.SetFloat64(0.0) - v.U = c - - case *Mpflt: - c := newMpcmplx() - c.Real.Set(u) - c.Imag.SetFloat64(0.0) - v.U = c - } - - return v -} - -func toflt(v Val) Val { - switch u := v.U.(type) { - case *Mpint: - f := newMpflt() - f.SetInt(u) - v.U = f - - case *Mpcplx: - f := newMpflt() - f.Set(&u.Real) - if u.Imag.CmpFloat64(0) != 0 { - yyerror("constant %v truncated to real", u.GoString()) - } - v.U = f - } - - return v -} - -func toint(v Val) Val { - switch u := v.U.(type) { - case *Mpint: - if u.Rune { - i := new(Mpint) - i.Set(u) - v.U = i - } - - case *Mpflt: - i := new(Mpint) - if !i.SetFloat(u) { - if i.checkOverflow(0) { - yyerror("integer too large") - } else { - // The value of u cannot be represented as an integer; - // so we need to print an error message. - // Unfortunately some float values cannot be - // reasonably formatted for inclusion in an error - // message (example: 1 + 1e-100), so first we try to - // format the float; if the truncation resulted in - // something that looks like an integer we omit the - // value from the error message. - // (See issue #11371). - var t big.Float - t.Parse(u.GoString(), 10) - if t.IsInt() { - yyerror("constant truncated to integer") - } else { - yyerror("constant %v truncated to integer", u.GoString()) - } - } - } - v.U = i - - case *Mpcplx: - i := new(Mpint) - if !i.SetFloat(&u.Real) || u.Imag.CmpFloat64(0) != 0 { - yyerror("constant %v truncated to integer", u.GoString()) - } - - v.U = i - } - - return v -} - -func doesoverflow(v Val, t *types.Type) bool { - switch u := v.U.(type) { - case *Mpint: - if !t.IsInteger() { - Fatalf("overflow: %v integer constant", t) - } - return u.Cmp(minintval[t.Etype]) < 0 || u.Cmp(maxintval[t.Etype]) > 0 - - case *Mpflt: - if !t.IsFloat() { - Fatalf("overflow: %v floating-point constant", t) - } - return u.Cmp(minfltval[t.Etype]) <= 0 || u.Cmp(maxfltval[t.Etype]) >= 0 - - case *Mpcplx: - if !t.IsComplex() { - Fatalf("overflow: %v complex constant", t) - } - return u.Real.Cmp(minfltval[t.Etype]) <= 0 || u.Real.Cmp(maxfltval[t.Etype]) >= 0 || - u.Imag.Cmp(minfltval[t.Etype]) <= 0 || u.Imag.Cmp(maxfltval[t.Etype]) >= 0 - } - - return false -} - -func overflow(v Val, t *types.Type) bool { - // v has already been converted - // to appropriate form for t. - if t == nil || t.Etype == TIDEAL { - return false - } - - // Only uintptrs may be converted to pointers, which cannot overflow. - if t.IsPtr() || t.IsUnsafePtr() { - return false - } - - if doesoverflow(v, t) { - yyerror("constant %v overflows %v", v, t) - return true - } - - return false - -} - -func tostr(v Val) Val { - switch u := v.U.(type) { - case *Mpint: - var r rune = 0xFFFD - if u.Cmp(minintval[TINT32]) >= 0 && u.Cmp(maxintval[TINT32]) <= 0 { - r = rune(u.Int64()) - } - v.U = string(r) - } - - return v -} - -func consttype(n *Node) Ctype { - if n == nil || n.Op != OLITERAL { - return CTxxx - } - return n.Val().Ctype() -} - -func Isconst(n *Node, ct Ctype) bool { - t := consttype(n) - - // If the caller is asking for CTINT, allow CTRUNE too. - // Makes life easier for back ends. - return t == ct || (ct == CTINT && t == CTRUNE) -} - -// evconst rewrites constant expressions into OLITERAL nodes. -func evconst(n *Node) { - nl, nr := n.Left, n.Right - - // Pick off just the opcodes that can be constant evaluated. - switch op := n.Op; op { - case OPLUS, ONEG, OBITNOT, ONOT: - if nl.Op == OLITERAL { - setconst(n, unaryOp(op, nl.Val(), n.Type)) - } - - case OADD, OSUB, OMUL, ODIV, OMOD, OOR, OXOR, OAND, OANDNOT, OOROR, OANDAND: - if nl.Op == OLITERAL && nr.Op == OLITERAL { - setconst(n, binaryOp(nl.Val(), op, nr.Val())) - } - - case OEQ, ONE, OLT, OLE, OGT, OGE: - if nl.Op == OLITERAL && nr.Op == OLITERAL { - setboolconst(n, compareOp(nl.Val(), op, nr.Val())) - } - - case OLSH, ORSH: - if nl.Op == OLITERAL && nr.Op == OLITERAL { - setconst(n, shiftOp(nl.Val(), op, nr.Val())) - } - - case OCONV, ORUNESTR: - if okforconst[n.Type.Etype] && nl.Op == OLITERAL { - setconst(n, convertVal(nl.Val(), n.Type, true)) - } - - case OCONVNOP: - if okforconst[n.Type.Etype] && nl.Op == OLITERAL { - // set so n.Orig gets OCONV instead of OCONVNOP - n.Op = OCONV - setconst(n, nl.Val()) - } - - case OADDSTR: - // Merge adjacent constants in the argument list. - s := n.List.Slice() - for i1 := 0; i1 < len(s); i1++ { - if Isconst(s[i1], CTSTR) && i1+1 < len(s) && Isconst(s[i1+1], CTSTR) { - // merge from i1 up to but not including i2 - var strs []string - i2 := i1 - for i2 < len(s) && Isconst(s[i2], CTSTR) { - strs = append(strs, s[i2].StringVal()) - i2++ - } - - nl := *s[i1] - nl.Orig = &nl - nl.SetVal(Val{strings.Join(strs, "")}) - s[i1] = &nl - s = append(s[:i1+1], s[i2:]...) - } - } - - if len(s) == 1 && Isconst(s[0], CTSTR) { - n.Op = OLITERAL - n.SetVal(s[0].Val()) - } else { - n.List.Set(s) - } - - case OCAP, OLEN: - switch nl.Type.Etype { - case TSTRING: - if Isconst(nl, CTSTR) { - setintconst(n, int64(len(nl.StringVal()))) - } - case TARRAY: - if !hascallchan(nl) { - setintconst(n, nl.Type.NumElem()) - } - } - - case OALIGNOF, OOFFSETOF, OSIZEOF: - setintconst(n, evalunsafe(n)) - - case OREAL, OIMAG: - if nl.Op == OLITERAL { - var re, im *Mpflt - switch u := nl.Val().U.(type) { - case *Mpint: - re = newMpflt() - re.SetInt(u) - // im = 0 - case *Mpflt: - re = u - // im = 0 - case *Mpcplx: - re = &u.Real - im = &u.Imag - default: - Fatalf("impossible") - } - if n.Op == OIMAG { - if im == nil { - im = newMpflt() - } - re = im - } - setconst(n, Val{re}) - } - - case OCOMPLEX: - if nl.Op == OLITERAL && nr.Op == OLITERAL { - // make it a complex literal - c := newMpcmplx() - c.Real.Set(toflt(nl.Val()).U.(*Mpflt)) - c.Imag.Set(toflt(nr.Val()).U.(*Mpflt)) - setconst(n, Val{c}) - } - } -} - -func match(x, y Val) (Val, Val) { - switch { - case x.Ctype() == CTCPLX || y.Ctype() == CTCPLX: - return tocplx(x), tocplx(y) - case x.Ctype() == CTFLT || y.Ctype() == CTFLT: - return toflt(x), toflt(y) - } - - // Mixed int/rune are fine. - return x, y -} - -func compareOp(x Val, op Op, y Val) bool { - x, y = match(x, y) - - switch x.Ctype() { - case CTBOOL: - x, y := x.U.(bool), y.U.(bool) - switch op { - case OEQ: - return x == y - case ONE: - return x != y - } - - case CTINT, CTRUNE: - x, y := x.U.(*Mpint), y.U.(*Mpint) - return cmpZero(x.Cmp(y), op) - - case CTFLT: - x, y := x.U.(*Mpflt), y.U.(*Mpflt) - return cmpZero(x.Cmp(y), op) - - case CTCPLX: - x, y := x.U.(*Mpcplx), y.U.(*Mpcplx) - eq := x.Real.Cmp(&y.Real) == 0 && x.Imag.Cmp(&y.Imag) == 0 - switch op { - case OEQ: - return eq - case ONE: - return !eq - } - - case CTSTR: - x, y := x.U.(string), y.U.(string) - switch op { - case OEQ: - return x == y - case ONE: - return x != y - case OLT: - return x < y - case OLE: - return x <= y - case OGT: - return x > y - case OGE: - return x >= y - } - } - - Fatalf("compareOp: bad comparison: %v %v %v", x, op, y) - panic("unreachable") -} - -func cmpZero(x int, op Op) bool { - switch op { - case OEQ: - return x == 0 - case ONE: - return x != 0 - case OLT: - return x < 0 - case OLE: - return x <= 0 - case OGT: - return x > 0 - case OGE: - return x >= 0 - } - - Fatalf("cmpZero: want comparison operator, got %v", op) - panic("unreachable") -} - -func binaryOp(x Val, op Op, y Val) Val { - x, y = match(x, y) - -Outer: - switch x.Ctype() { - case CTBOOL: - x, y := x.U.(bool), y.U.(bool) - switch op { - case OANDAND: - return Val{U: x && y} - case OOROR: - return Val{U: x || y} - } - - case CTINT, CTRUNE: - x, y := x.U.(*Mpint), y.U.(*Mpint) - - u := new(Mpint) - u.Rune = x.Rune || y.Rune - u.Set(x) - switch op { - case OADD: - u.Add(y) - case OSUB: - u.Sub(y) - case OMUL: - u.Mul(y) - case ODIV: - if y.CmpInt64(0) == 0 { - yyerror("division by zero") - return Val{} - } - u.Quo(y) - case OMOD: - if y.CmpInt64(0) == 0 { - yyerror("division by zero") - return Val{} - } - u.Rem(y) - case OOR: - u.Or(y) - case OAND: - u.And(y) - case OANDNOT: - u.AndNot(y) - case OXOR: - u.Xor(y) - default: - break Outer - } - return Val{U: u} - - case CTFLT: - x, y := x.U.(*Mpflt), y.U.(*Mpflt) - - u := newMpflt() - u.Set(x) - switch op { - case OADD: - u.Add(y) - case OSUB: - u.Sub(y) - case OMUL: - u.Mul(y) - case ODIV: - if y.CmpFloat64(0) == 0 { - yyerror("division by zero") - return Val{} - } - u.Quo(y) - default: - break Outer - } - return Val{U: u} - - case CTCPLX: - x, y := x.U.(*Mpcplx), y.U.(*Mpcplx) - - u := newMpcmplx() - u.Real.Set(&x.Real) - u.Imag.Set(&x.Imag) - switch op { - case OADD: - u.Real.Add(&y.Real) - u.Imag.Add(&y.Imag) - case OSUB: - u.Real.Sub(&y.Real) - u.Imag.Sub(&y.Imag) - case OMUL: - u.Mul(y) - case ODIV: - if !u.Div(y) { - yyerror("complex division by zero") - return Val{} - } - default: - break Outer - } - return Val{U: u} - } - - Fatalf("binaryOp: bad operation: %v %v %v", x, op, y) - panic("unreachable") -} - -func unaryOp(op Op, x Val, t *types.Type) Val { - switch op { - case OPLUS: - switch x.Ctype() { - case CTINT, CTRUNE, CTFLT, CTCPLX: - return x - } - - case ONEG: - switch x.Ctype() { - case CTINT, CTRUNE: - x := x.U.(*Mpint) - u := new(Mpint) - u.Rune = x.Rune - u.Set(x) - u.Neg() - return Val{U: u} - - case CTFLT: - x := x.U.(*Mpflt) - u := newMpflt() - u.Set(x) - u.Neg() - return Val{U: u} - - case CTCPLX: - x := x.U.(*Mpcplx) - u := newMpcmplx() - u.Real.Set(&x.Real) - u.Imag.Set(&x.Imag) - u.Real.Neg() - u.Imag.Neg() - return Val{U: u} - } - - case OBITNOT: - switch x.Ctype() { - case CTINT, CTRUNE: - x := x.U.(*Mpint) - - u := new(Mpint) - u.Rune = x.Rune - if t.IsSigned() || t.IsUntyped() { - // Signed values change sign. - u.SetInt64(-1) - } else { - // Unsigned values invert their bits. - u.Set(maxintval[t.Etype]) - } - u.Xor(x) - return Val{U: u} - } - - case ONOT: - return Val{U: !x.U.(bool)} - } - - Fatalf("unaryOp: bad operation: %v %v", op, x) - panic("unreachable") -} - -func shiftOp(x Val, op Op, y Val) Val { - if x.Ctype() != CTRUNE { - x = toint(x) - } - y = toint(y) - - u := new(Mpint) - u.Set(x.U.(*Mpint)) - u.Rune = x.U.(*Mpint).Rune - switch op { - case OLSH: - u.Lsh(y.U.(*Mpint)) - case ORSH: - u.Rsh(y.U.(*Mpint)) - default: - Fatalf("shiftOp: bad operator: %v", op) - panic("unreachable") - } - return Val{U: u} -} - -// setconst rewrites n as an OLITERAL with value v. -func setconst(n *Node, v Val) { - // If constant folding failed, mark n as broken and give up. - if v.U == nil { - n.Type = nil - return - } - - // Ensure n.Orig still points to a semantically-equivalent - // expression after we rewrite n into a constant. - if n.Orig == n { - n.Orig = n.sepcopy() - } - - *n = Node{ - Op: OLITERAL, - Pos: n.Pos, - Orig: n.Orig, - Type: n.Type, - Xoffset: BADWIDTH, - } - n.SetVal(v) - if vt := idealType(v.Ctype()); n.Type.IsUntyped() && n.Type != vt { - Fatalf("untyped type mismatch, have: %v, want: %v", n.Type, vt) - } - - // Check range. - lno := setlineno(n) - overflow(v, n.Type) - lineno = lno - - if !n.Type.IsUntyped() { - switch v.Ctype() { - // Truncate precision for non-ideal float. - case CTFLT: - n.SetVal(Val{truncfltlit(v.U.(*Mpflt), n.Type)}) - // Truncate precision for non-ideal complex. - case CTCPLX: - n.SetVal(Val{trunccmplxlit(v.U.(*Mpcplx), n.Type)}) - } - } -} - -func setboolconst(n *Node, v bool) { - setconst(n, Val{U: v}) -} - -func setintconst(n *Node, v int64) { - u := new(Mpint) - u.SetInt64(v) - setconst(n, Val{u}) -} - -// nodlit returns a new untyped constant with value v. -func nodlit(v Val) *Node { - n := nod(OLITERAL, nil, nil) - n.SetVal(v) - n.Type = idealType(v.Ctype()) - return n -} - -func idealType(ct Ctype) *types.Type { - switch ct { - case CTSTR: - return types.UntypedString - case CTBOOL: - return types.UntypedBool - case CTINT: - return types.UntypedInt - case CTRUNE: - return types.UntypedRune - case CTFLT: - return types.UntypedFloat - case CTCPLX: - return types.UntypedComplex - case CTNIL: - return types.Types[TNIL] - } - Fatalf("unexpected Ctype: %v", ct) - return nil -} - -// defaultlit on both nodes simultaneously; -// if they're both ideal going in they better -// get the same type going out. -// force means must assign concrete (non-ideal) type. -// The results of defaultlit2 MUST be assigned back to l and r, e.g. -// n.Left, n.Right = defaultlit2(n.Left, n.Right, force) -func defaultlit2(l *Node, r *Node, force bool) (*Node, *Node) { - if l.Type == nil || r.Type == nil { - return l, r - } - if !l.Type.IsUntyped() { - r = convlit(r, l.Type) - return l, r - } - - if !r.Type.IsUntyped() { - l = convlit(l, r.Type) - return l, r - } - - if !force { - return l, r - } - - // Can't mix bool with non-bool, string with non-string, or nil with anything (untyped). - if l.Type.IsBoolean() != r.Type.IsBoolean() { - return l, r - } - if l.Type.IsString() != r.Type.IsString() { - return l, r - } - if l.isNil() || r.isNil() { - return l, r - } - - t := defaultType(mixUntyped(l.Type, r.Type)) - l = convlit(l, t) - r = convlit(r, t) - return l, r -} - -func ctype(t *types.Type) Ctype { - switch t { - case types.UntypedBool: - return CTBOOL - case types.UntypedString: - return CTSTR - case types.UntypedInt: - return CTINT - case types.UntypedRune: - return CTRUNE - case types.UntypedFloat: - return CTFLT - case types.UntypedComplex: - return CTCPLX - } - Fatalf("bad type %v", t) - panic("unreachable") -} - -func mixUntyped(t1, t2 *types.Type) *types.Type { - t := t1 - if ctype(t2) > ctype(t1) { - t = t2 - } - return t -} - -func defaultType(t *types.Type) *types.Type { - if !t.IsUntyped() || t.Etype == TNIL { - return t - } - - switch t { - case types.UntypedBool: - return types.Types[TBOOL] - case types.UntypedString: - return types.Types[TSTRING] - case types.UntypedInt: - return types.Types[TINT] - case types.UntypedRune: - return types.Runetype - case types.UntypedFloat: - return types.Types[TFLOAT64] - case types.UntypedComplex: - return types.Types[TCOMPLEX128] - } - - Fatalf("bad type %v", t) - return nil -} - -func smallintconst(n *Node) bool { - if n.Op == OLITERAL && Isconst(n, CTINT) && n.Type != nil { - switch simtype[n.Type.Etype] { - case TINT8, - TUINT8, - TINT16, - TUINT16, - TINT32, - TUINT32, - TBOOL: - return true - - case TIDEAL, TINT64, TUINT64, TPTR: - v, ok := n.Val().U.(*Mpint) - if ok && v.Cmp(minintval[TINT32]) >= 0 && v.Cmp(maxintval[TINT32]) <= 0 { - return true - } - } - } - - return false -} - -// indexconst checks if Node n contains a constant expression -// representable as a non-negative int and returns its value. -// If n is not a constant expression, not representable as an -// integer, or negative, it returns -1. If n is too large, it -// returns -2. -func indexconst(n *Node) int64 { - if n.Op != OLITERAL { - return -1 - } - - v := toint(n.Val()) // toint returns argument unchanged if not representable as an *Mpint - vi, ok := v.U.(*Mpint) - if !ok || vi.CmpInt64(0) < 0 { - return -1 - } - if vi.Cmp(maxintval[TINT]) > 0 { - return -2 - } - - return vi.Int64() -} - -// isGoConst reports whether n is a Go language constant (as opposed to a -// compile-time constant). -// -// Expressions derived from nil, like string([]byte(nil)), while they -// may be known at compile time, are not Go language constants. -func (n *Node) isGoConst() bool { - return n.Op == OLITERAL && n.Val().Ctype() != CTNIL -} - -func hascallchan(n *Node) bool { - if n == nil { - return false - } - switch n.Op { - case OAPPEND, - OCALL, - OCALLFUNC, - OCALLINTER, - OCALLMETH, - OCAP, - OCLOSE, - OCOMPLEX, - OCOPY, - ODELETE, - OIMAG, - OLEN, - OMAKE, - ONEW, - OPANIC, - OPRINT, - OPRINTN, - OREAL, - ORECOVER, - ORECV: - return true - } - - if hascallchan(n.Left) || hascallchan(n.Right) { - return true - } - for _, n1 := range n.List.Slice() { - if hascallchan(n1) { - return true - } - } - for _, n2 := range n.Rlist.Slice() { - if hascallchan(n2) { - return true - } - } - - return false -} - -// A constSet represents a set of Go constant expressions. -type constSet struct { - m map[constSetKey]src.XPos -} - -type constSetKey struct { - typ *types.Type - val interface{} -} - -// add adds constant expression n to s. If a constant expression of -// equal value and identical type has already been added, then add -// reports an error about the duplicate value. -// -// pos provides position information for where expression n occurred -// (in case n does not have its own position information). what and -// where are used in the error message. -// -// n must not be an untyped constant. -func (s *constSet) add(pos src.XPos, n *Node, what, where string) { - if n.Op == OCONVIFACE && n.Implicit() { - n = n.Left - } - - if !n.isGoConst() { - return - } - if n.Type.IsUntyped() { - Fatalf("%v is untyped", n) - } - - // Consts are only duplicates if they have the same value and - // identical types. - // - // In general, we have to use types.Identical to test type - // identity, because == gives false negatives for anonymous - // types and the byte/uint8 and rune/int32 builtin type - // aliases. However, this is not a problem here, because - // constant expressions are always untyped or have a named - // type, and we explicitly handle the builtin type aliases - // below. - // - // This approach may need to be revisited though if we fix - // #21866 by treating all type aliases like byte/uint8 and - // rune/int32. - - typ := n.Type - switch typ { - case types.Bytetype: - typ = types.Types[TUINT8] - case types.Runetype: - typ = types.Types[TINT32] - } - k := constSetKey{typ, n.Val().Interface()} - - if hasUniquePos(n) { - pos = n.Pos - } - - if s.m == nil { - s.m = make(map[constSetKey]src.XPos) - } - - if prevPos, isDup := s.m[k]; isDup { - yyerrorl(pos, "duplicate %s %s in %s\n\tprevious %s at %v", - what, nodeAndVal(n), where, - what, linestr(prevPos)) - } else { - s.m[k] = pos - } -} - -// nodeAndVal reports both an expression and its constant value, if -// the latter is non-obvious. -// -// TODO(mdempsky): This could probably be a fmt.go flag. -func nodeAndVal(n *Node) string { - show := n.String() - val := n.Val().Interface() - if s := fmt.Sprintf("%#v", val); show != s { - show += " (value " + s + ")" - } - return show -} diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go deleted file mode 100644 index 6e90eb4d65bcd66ab882068a0e1186c1f99939e1..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/dcl.go +++ /dev/null @@ -1,1185 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "bytes" - "cmd/compile/internal/types" - "cmd/internal/obj" - "cmd/internal/src" - "fmt" - "strings" -) - -// Declaration stack & operations - -var externdcl []*Node - -func testdclstack() { - if !types.IsDclstackValid() { - if nerrors != 0 { - errorexit() - } - Fatalf("mark left on the dclstack") - } -} - -// redeclare emits a diagnostic about symbol s being redeclared at pos. -func redeclare(pos src.XPos, s *types.Sym, where string) { - if !s.Lastlineno.IsKnown() { - pkg := s.Origpkg - if pkg == nil { - pkg = s.Pkg - } - yyerrorl(pos, "%v redeclared %s\n"+ - "\tprevious declaration during import %q", s, where, pkg.Path) - } else { - prevPos := s.Lastlineno - - // When an import and a declaration collide in separate files, - // present the import as the "redeclared", because the declaration - // is visible where the import is, but not vice versa. - // See issue 4510. - if s.Def == nil { - pos, prevPos = prevPos, pos - } - - yyerrorl(pos, "%v redeclared %s\n"+ - "\tprevious declaration at %v", s, where, linestr(prevPos)) - } -} - -var vargen int - -// declare individual names - var, typ, const - -var declare_typegen int - -// declare records that Node n declares symbol n.Sym in the specified -// declaration context. -func declare(n *Node, ctxt Class) { - if n.isBlank() { - return - } - - if n.Name == nil { - // named OLITERAL needs Name; most OLITERALs don't. - n.Name = new(Name) - } - - s := n.Sym - - // kludgy: typecheckok means we're past parsing. Eg genwrapper may declare out of package names later. - if !inimport && !typecheckok && s.Pkg != localpkg { - yyerrorl(n.Pos, "cannot declare name %v", s) - } - - gen := 0 - if ctxt == PEXTERN { - if s.Name == "init" { - yyerrorl(n.Pos, "cannot declare init - must be func") - } - if s.Name == "main" && s.Pkg.Name == "main" { - yyerrorl(n.Pos, "cannot declare main - must be func") - } - externdcl = append(externdcl, n) - } else { - if Curfn == nil && ctxt == PAUTO { - lineno = n.Pos - Fatalf("automatic outside function") - } - if Curfn != nil && ctxt != PFUNC { - Curfn.Func.Dcl = append(Curfn.Func.Dcl, n) - } - if n.Op == OTYPE { - declare_typegen++ - gen = declare_typegen - } else if n.Op == ONAME && ctxt == PAUTO && !strings.Contains(s.Name, "·") { - vargen++ - gen = vargen - } - types.Pushdcl(s) - n.Name.Curfn = Curfn - } - - if ctxt == PAUTO { - n.Xoffset = 0 - } - - if s.Block == types.Block { - // functype will print errors about duplicate function arguments. - // Don't repeat the error here. - if ctxt != PPARAM && ctxt != PPARAMOUT { - redeclare(n.Pos, s, "in this block") - } - } - - s.Block = types.Block - s.Lastlineno = lineno - s.Def = asTypesNode(n) - n.Name.Vargen = int32(gen) - n.SetClass(ctxt) - if ctxt == PFUNC { - n.Sym.SetFunc(true) - } - - autoexport(n, ctxt) -} - -func addvar(n *Node, t *types.Type, ctxt Class) { - if n == nil || n.Sym == nil || (n.Op != ONAME && n.Op != ONONAME) || t == nil { - Fatalf("addvar: n=%v t=%v nil", n, t) - } - - n.Op = ONAME - declare(n, ctxt) - n.Type = t -} - -// declare variables from grammar -// new_name_list (type | [type] = expr_list) -func variter(vl []*Node, t *Node, el []*Node) []*Node { - var init []*Node - doexpr := len(el) > 0 - - if len(el) == 1 && len(vl) > 1 { - e := el[0] - as2 := nod(OAS2, nil, nil) - as2.List.Set(vl) - as2.Rlist.Set1(e) - for _, v := range vl { - v.Op = ONAME - declare(v, dclcontext) - v.Name.Param.Ntype = t - v.Name.Defn = as2 - if Curfn != nil { - init = append(init, nod(ODCL, v, nil)) - } - } - - return append(init, as2) - } - - nel := len(el) - for _, v := range vl { - var e *Node - if doexpr { - if len(el) == 0 { - yyerror("assignment mismatch: %d variables but %d values", len(vl), nel) - break - } - e = el[0] - el = el[1:] - } - - v.Op = ONAME - declare(v, dclcontext) - v.Name.Param.Ntype = t - - if e != nil || Curfn != nil || v.isBlank() { - if Curfn != nil { - init = append(init, nod(ODCL, v, nil)) - } - e = nod(OAS, v, e) - init = append(init, e) - if e.Right != nil { - v.Name.Defn = e - } - } - } - - if len(el) != 0 { - yyerror("assignment mismatch: %d variables but %d values", len(vl), nel) - } - return init -} - -// newnoname returns a new ONONAME Node associated with symbol s. -func newnoname(s *types.Sym) *Node { - if s == nil { - Fatalf("newnoname nil") - } - n := nod(ONONAME, nil, nil) - n.Sym = s - n.Xoffset = 0 - return n -} - -// newfuncnamel generates a new name node for a function or method. -// TODO(rsc): Use an ODCLFUNC node instead. See comment in CL 7360. -func newfuncnamel(pos src.XPos, s *types.Sym) *Node { - n := newnamel(pos, s) - n.Func = new(Func) - n.Func.SetIsHiddenClosure(Curfn != nil) - return n -} - -// this generates a new name node for a name -// being declared. -func dclname(s *types.Sym) *Node { - n := newname(s) - n.Op = ONONAME // caller will correct it - return n -} - -func typenod(t *types.Type) *Node { - return typenodl(src.NoXPos, t) -} - -func typenodl(pos src.XPos, t *types.Type) *Node { - // if we copied another type with *t = *u - // then t->nod might be out of date, so - // check t->nod->type too - if asNode(t.Nod) == nil || asNode(t.Nod).Type != t { - t.Nod = asTypesNode(nodl(pos, OTYPE, nil, nil)) - asNode(t.Nod).Type = t - asNode(t.Nod).Sym = t.Sym - } - - return asNode(t.Nod) -} - -func anonfield(typ *types.Type) *Node { - return symfield(nil, typ) -} - -func namedfield(s string, typ *types.Type) *Node { - return symfield(lookup(s), typ) -} - -func symfield(s *types.Sym, typ *types.Type) *Node { - n := nodSym(ODCLFIELD, nil, s) - n.Type = typ - return n -} - -// oldname returns the Node that declares symbol s in the current scope. -// If no such Node currently exists, an ONONAME Node is returned instead. -// Automatically creates a new closure variable if the referenced symbol was -// declared in a different (containing) function. -func oldname(s *types.Sym) *Node { - n := asNode(s.Def) - if n == nil { - // Maybe a top-level declaration will come along later to - // define s. resolve will check s.Def again once all input - // source has been processed. - return newnoname(s) - } - - if Curfn != nil && n.Op == ONAME && n.Name.Curfn != nil && n.Name.Curfn != Curfn { - // Inner func is referring to var in outer func. - // - // TODO(rsc): If there is an outer variable x and we - // are parsing x := 5 inside the closure, until we get to - // the := it looks like a reference to the outer x so we'll - // make x a closure variable unnecessarily. - c := n.Name.Param.Innermost - if c == nil || c.Name.Curfn != Curfn { - // Do not have a closure var for the active closure yet; make one. - c = newname(s) - c.SetClass(PAUTOHEAP) - c.Name.SetIsClosureVar(true) - c.SetIsDDD(n.IsDDD()) - c.Name.Defn = n - - // Link into list of active closure variables. - // Popped from list in func funcLit. - c.Name.Param.Outer = n.Name.Param.Innermost - n.Name.Param.Innermost = c - - Curfn.Func.Cvars.Append(c) - } - - // return ref to closure var, not original - return c - } - - return n -} - -// importName is like oldname, but it reports an error if sym is from another package and not exported. -func importName(sym *types.Sym) *Node { - n := oldname(sym) - if !types.IsExported(sym.Name) && sym.Pkg != localpkg { - n.SetDiag(true) - yyerror("cannot refer to unexported name %s.%s", sym.Pkg.Name, sym.Name) - } - return n -} - -// := declarations -func colasname(n *Node) bool { - switch n.Op { - case ONAME, - ONONAME, - OPACK, - OTYPE, - OLITERAL: - return n.Sym != nil - } - - return false -} - -func colasdefn(left []*Node, defn *Node) { - for _, n := range left { - if n.Sym != nil { - n.Sym.SetUniq(true) - } - } - - var nnew, nerr int - for i, n := range left { - if n.isBlank() { - continue - } - if !colasname(n) { - yyerrorl(defn.Pos, "non-name %v on left side of :=", n) - nerr++ - continue - } - - if !n.Sym.Uniq() { - yyerrorl(defn.Pos, "%v repeated on left side of :=", n.Sym) - n.SetDiag(true) - nerr++ - continue - } - - n.Sym.SetUniq(false) - if n.Sym.Block == types.Block { - continue - } - - nnew++ - n = newname(n.Sym) - declare(n, dclcontext) - n.Name.Defn = defn - defn.Ninit.Append(nod(ODCL, n, nil)) - left[i] = n - } - - if nnew == 0 && nerr == 0 { - yyerrorl(defn.Pos, "no new variables on left side of :=") - } -} - -// declare the arguments in an -// interface field declaration. -func ifacedcl(n *Node) { - if n.Op != ODCLFIELD || n.Left == nil { - Fatalf("ifacedcl") - } - - if n.Sym.IsBlank() { - yyerror("methods must have a unique non-blank name") - } -} - -// declare the function proper -// and declare the arguments. -// called in extern-declaration context -// returns in auto-declaration context. -func funchdr(n *Node) { - // change the declaration context from extern to auto - funcStack = append(funcStack, funcStackEnt{Curfn, dclcontext}) - Curfn = n - dclcontext = PAUTO - - types.Markdcl() - - if n.Func.Nname != nil { - funcargs(n.Func.Nname.Name.Param.Ntype) - } else if n.Func.Ntype != nil { - funcargs(n.Func.Ntype) - } else { - funcargs2(n.Type) - } -} - -func funcargs(nt *Node) { - if nt.Op != OTFUNC { - Fatalf("funcargs %v", nt.Op) - } - - // re-start the variable generation number - // we want to use small numbers for the return variables, - // so let them have the chunk starting at 1. - // - // TODO(mdempsky): This is ugly, and only necessary because - // esc.go uses Vargen to figure out result parameters' index - // within the result tuple. - vargen = nt.Rlist.Len() - - // declare the receiver and in arguments. - if nt.Left != nil { - funcarg(nt.Left, PPARAM) - } - for _, n := range nt.List.Slice() { - funcarg(n, PPARAM) - } - - oldvargen := vargen - vargen = 0 - - // declare the out arguments. - gen := nt.List.Len() - for _, n := range nt.Rlist.Slice() { - if n.Sym == nil { - // Name so that escape analysis can track it. ~r stands for 'result'. - n.Sym = lookupN("~r", gen) - gen++ - } - if n.Sym.IsBlank() { - // Give it a name so we can assign to it during return. ~b stands for 'blank'. - // The name must be different from ~r above because if you have - // func f() (_ int) - // func g() int - // f is allowed to use a plain 'return' with no arguments, while g is not. - // So the two cases must be distinguished. - n.Sym = lookupN("~b", gen) - gen++ - } - - funcarg(n, PPARAMOUT) - } - - vargen = oldvargen -} - -func funcarg(n *Node, ctxt Class) { - if n.Op != ODCLFIELD { - Fatalf("funcarg %v", n.Op) - } - if n.Sym == nil { - return - } - - n.Right = newnamel(n.Pos, n.Sym) - n.Right.Name.Param.Ntype = n.Left - n.Right.SetIsDDD(n.IsDDD()) - declare(n.Right, ctxt) - - vargen++ - n.Right.Name.Vargen = int32(vargen) -} - -// Same as funcargs, except run over an already constructed TFUNC. -// This happens during import, where the hidden_fndcl rule has -// used functype directly to parse the function's type. -func funcargs2(t *types.Type) { - if t.Etype != TFUNC { - Fatalf("funcargs2 %v", t) - } - - for _, f := range t.Recvs().Fields().Slice() { - funcarg2(f, PPARAM) - } - for _, f := range t.Params().Fields().Slice() { - funcarg2(f, PPARAM) - } - for _, f := range t.Results().Fields().Slice() { - funcarg2(f, PPARAMOUT) - } -} - -func funcarg2(f *types.Field, ctxt Class) { - if f.Sym == nil { - return - } - n := newnamel(f.Pos, f.Sym) - f.Nname = asTypesNode(n) - n.Type = f.Type - n.SetIsDDD(f.IsDDD()) - declare(n, ctxt) -} - -var funcStack []funcStackEnt // stack of previous values of Curfn/dclcontext - -type funcStackEnt struct { - curfn *Node - dclcontext Class -} - -// finish the body. -// called in auto-declaration context. -// returns in extern-declaration context. -func funcbody() { - // change the declaration context from auto to previous context - types.Popdcl() - var e funcStackEnt - funcStack, e = funcStack[:len(funcStack)-1], funcStack[len(funcStack)-1] - Curfn, dclcontext = e.curfn, e.dclcontext -} - -// structs, functions, and methods. -// they don't belong here, but where do they belong? -func checkembeddedtype(t *types.Type) { - if t == nil { - return - } - - if t.Sym == nil && t.IsPtr() { - t = t.Elem() - if t.IsInterface() { - yyerror("embedded type cannot be a pointer to interface") - } - } - - if t.IsPtr() || t.IsUnsafePtr() { - yyerror("embedded type cannot be a pointer") - } else if t.Etype == TFORW && !t.ForwardType().Embedlineno.IsKnown() { - t.ForwardType().Embedlineno = lineno - } -} - -func structfield(n *Node) *types.Field { - lno := lineno - lineno = n.Pos - - if n.Op != ODCLFIELD { - Fatalf("structfield: oops %v\n", n) - } - - f := types.NewField() - f.Pos = n.Pos - f.Sym = n.Sym - - if n.Left != nil { - n.Left = typecheck(n.Left, ctxType) - n.Type = n.Left.Type - n.Left = nil - } - - f.Type = n.Type - if f.Type == nil { - f.SetBroke(true) - } - - if n.Embedded() { - checkembeddedtype(n.Type) - f.Embedded = 1 - } else { - f.Embedded = 0 - } - - switch u := n.Val().U.(type) { - case string: - f.Note = u - default: - yyerror("field tag must be a string") - case nil: - // no-op - } - - lineno = lno - return f -} - -// checkdupfields emits errors for duplicately named fields or methods in -// a list of struct or interface types. -func checkdupfields(what string, fss ...[]*types.Field) { - seen := make(map[*types.Sym]bool) - for _, fs := range fss { - for _, f := range fs { - if f.Sym == nil || f.Sym.IsBlank() { - continue - } - if seen[f.Sym] { - yyerrorl(f.Pos, "duplicate %s %s", what, f.Sym.Name) - continue - } - seen[f.Sym] = true - } - } -} - -// convert a parsed id/type list into -// a type for struct/interface/arglist -func tostruct(l []*Node) *types.Type { - t := types.New(TSTRUCT) - - fields := make([]*types.Field, len(l)) - for i, n := range l { - f := structfield(n) - if f.Broke() { - t.SetBroke(true) - } - fields[i] = f - } - t.SetFields(fields) - - checkdupfields("field", t.FieldSlice()) - - if !t.Broke() { - checkwidth(t) - } - - return t -} - -func tofunargs(l []*Node, funarg types.Funarg) *types.Type { - t := types.New(TSTRUCT) - t.StructType().Funarg = funarg - - fields := make([]*types.Field, len(l)) - for i, n := range l { - f := structfield(n) - f.SetIsDDD(n.IsDDD()) - if n.Right != nil { - n.Right.Type = f.Type - f.Nname = asTypesNode(n.Right) - } - if f.Broke() { - t.SetBroke(true) - } - fields[i] = f - } - t.SetFields(fields) - return t -} - -func tofunargsfield(fields []*types.Field, funarg types.Funarg) *types.Type { - t := types.New(TSTRUCT) - t.StructType().Funarg = funarg - t.SetFields(fields) - return t -} - -func interfacefield(n *Node) *types.Field { - lno := lineno - lineno = n.Pos - - if n.Op != ODCLFIELD { - Fatalf("interfacefield: oops %v\n", n) - } - - if n.Val().Ctype() != CTxxx { - yyerror("interface method cannot have annotation") - } - - // MethodSpec = MethodName Signature | InterfaceTypeName . - // - // If Sym != nil, then Sym is MethodName and Left is Signature. - // Otherwise, Left is InterfaceTypeName. - - if n.Left != nil { - n.Left = typecheck(n.Left, ctxType) - n.Type = n.Left.Type - n.Left = nil - } - - f := types.NewField() - f.Pos = n.Pos - f.Sym = n.Sym - f.Type = n.Type - if f.Type == nil { - f.SetBroke(true) - } - - lineno = lno - return f -} - -func tointerface(l []*Node) *types.Type { - if len(l) == 0 { - return types.Types[TINTER] - } - t := types.New(TINTER) - var fields []*types.Field - for _, n := range l { - f := interfacefield(n) - if f.Broke() { - t.SetBroke(true) - } - fields = append(fields, f) - } - t.SetInterface(fields) - return t -} - -func fakeRecv() *Node { - return anonfield(types.FakeRecvType()) -} - -func fakeRecvField() *types.Field { - f := types.NewField() - f.Type = types.FakeRecvType() - return f -} - -// isifacemethod reports whether (field) m is -// an interface method. Such methods have the -// special receiver type types.FakeRecvType(). -func isifacemethod(f *types.Type) bool { - return f.Recv().Type == types.FakeRecvType() -} - -// turn a parsed function declaration into a type -func functype(this *Node, in, out []*Node) *types.Type { - t := types.New(TFUNC) - - var rcvr []*Node - if this != nil { - rcvr = []*Node{this} - } - t.FuncType().Receiver = tofunargs(rcvr, types.FunargRcvr) - t.FuncType().Params = tofunargs(in, types.FunargParams) - t.FuncType().Results = tofunargs(out, types.FunargResults) - - checkdupfields("argument", t.Recvs().FieldSlice(), t.Params().FieldSlice(), t.Results().FieldSlice()) - - if t.Recvs().Broke() || t.Results().Broke() || t.Params().Broke() { - t.SetBroke(true) - } - - t.FuncType().Outnamed = t.NumResults() > 0 && origSym(t.Results().Field(0).Sym) != nil - - return t -} - -func functypefield(this *types.Field, in, out []*types.Field) *types.Type { - t := types.New(TFUNC) - - var rcvr []*types.Field - if this != nil { - rcvr = []*types.Field{this} - } - t.FuncType().Receiver = tofunargsfield(rcvr, types.FunargRcvr) - t.FuncType().Params = tofunargsfield(in, types.FunargParams) - t.FuncType().Results = tofunargsfield(out, types.FunargResults) - - t.FuncType().Outnamed = t.NumResults() > 0 && origSym(t.Results().Field(0).Sym) != nil - - return t -} - -// origSym returns the original symbol written by the user. -func origSym(s *types.Sym) *types.Sym { - if s == nil { - return nil - } - - if len(s.Name) > 1 && s.Name[0] == '~' { - switch s.Name[1] { - case 'r': // originally an unnamed result - return nil - case 'b': // originally the blank identifier _ - // TODO(mdempsky): Does s.Pkg matter here? - return nblank.Sym - } - return s - } - - if strings.HasPrefix(s.Name, ".anon") { - // originally an unnamed or _ name (see subr.go: structargs) - return nil - } - - return s -} - -// methodSym returns the method symbol representing a method name -// associated with a specific receiver type. -// -// Method symbols can be used to distinguish the same method appearing -// in different method sets. For example, T.M and (*T).M have distinct -// method symbols. -// -// The returned symbol will be marked as a function. -func methodSym(recv *types.Type, msym *types.Sym) *types.Sym { - sym := methodSymSuffix(recv, msym, "") - sym.SetFunc(true) - return sym -} - -// methodSymSuffix is like methodsym, but allows attaching a -// distinguisher suffix. To avoid collisions, the suffix must not -// start with a letter, number, or period. -func methodSymSuffix(recv *types.Type, msym *types.Sym, suffix string) *types.Sym { - if msym.IsBlank() { - Fatalf("blank method name") - } - - rsym := recv.Sym - if recv.IsPtr() { - if rsym != nil { - Fatalf("declared pointer receiver type: %v", recv) - } - rsym = recv.Elem().Sym - } - - // Find the package the receiver type appeared in. For - // anonymous receiver types (i.e., anonymous structs with - // embedded fields), use the "go" pseudo-package instead. - rpkg := gopkg - if rsym != nil { - rpkg = rsym.Pkg - } - - var b bytes.Buffer - if recv.IsPtr() { - // The parentheses aren't really necessary, but - // they're pretty traditional at this point. - fmt.Fprintf(&b, "(%-S)", recv) - } else { - fmt.Fprintf(&b, "%-S", recv) - } - - // A particular receiver type may have multiple non-exported - // methods with the same name. To disambiguate them, include a - // package qualifier for names that came from a different - // package than the receiver type. - if !types.IsExported(msym.Name) && msym.Pkg != rpkg { - b.WriteString(".") - b.WriteString(msym.Pkg.Prefix) - } - - b.WriteString(".") - b.WriteString(msym.Name) - b.WriteString(suffix) - - return rpkg.LookupBytes(b.Bytes()) -} - -// Add a method, declared as a function. -// - msym is the method symbol -// - t is function type (with receiver) -// Returns a pointer to the existing or added Field; or nil if there's an error. -func addmethod(msym *types.Sym, t *types.Type, local, nointerface bool) *types.Field { - if msym == nil { - Fatalf("no method symbol") - } - - // get parent type sym - rf := t.Recv() // ptr to this structure - if rf == nil { - yyerror("missing receiver") - return nil - } - - mt := methtype(rf.Type) - if mt == nil || mt.Sym == nil { - pa := rf.Type - t := pa - if t != nil && t.IsPtr() { - if t.Sym != nil { - yyerror("invalid receiver type %v (%v is a pointer type)", pa, t) - return nil - } - t = t.Elem() - } - - switch { - case t == nil || t.Broke(): - // rely on typecheck having complained before - case t.Sym == nil: - yyerror("invalid receiver type %v (%v is not a defined type)", pa, t) - case t.IsPtr(): - yyerror("invalid receiver type %v (%v is a pointer type)", pa, t) - case t.IsInterface(): - yyerror("invalid receiver type %v (%v is an interface type)", pa, t) - default: - // Should have picked off all the reasons above, - // but just in case, fall back to generic error. - yyerror("invalid receiver type %v (%L / %L)", pa, pa, t) - } - return nil - } - - if local && mt.Sym.Pkg != localpkg { - yyerror("cannot define new methods on non-local type %v", mt) - return nil - } - - if msym.IsBlank() { - return nil - } - - if mt.IsStruct() { - for _, f := range mt.Fields().Slice() { - if f.Sym == msym { - yyerror("type %v has both field and method named %v", mt, msym) - f.SetBroke(true) - return nil - } - } - } - - for _, f := range mt.Methods().Slice() { - if msym.Name != f.Sym.Name { - continue - } - // types.Identical only checks that incoming and result parameters match, - // so explicitly check that the receiver parameters match too. - if !types.Identical(t, f.Type) || !types.Identical(t.Recv().Type, f.Type.Recv().Type) { - yyerror("method redeclared: %v.%v\n\t%v\n\t%v", mt, msym, f.Type, t) - } - return f - } - - f := types.NewField() - f.Pos = lineno - f.Sym = msym - f.Type = t - f.SetNointerface(nointerface) - - mt.Methods().Append(f) - return f -} - -func funcsymname(s *types.Sym) string { - return s.Name + "·f" -} - -// funcsym returns s·f. -func funcsym(s *types.Sym) *types.Sym { - // funcsymsmu here serves to protect not just mutations of funcsyms (below), - // but also the package lookup of the func sym name, - // since this function gets called concurrently from the backend. - // There are no other concurrent package lookups in the backend, - // except for the types package, which is protected separately. - // Reusing funcsymsmu to also cover this package lookup - // avoids a general, broader, expensive package lookup mutex. - // Note makefuncsym also does package look-up of func sym names, - // but that it is only called serially, from the front end. - funcsymsmu.Lock() - sf, existed := s.Pkg.LookupOK(funcsymname(s)) - // Don't export s·f when compiling for dynamic linking. - // When dynamically linking, the necessary function - // symbols will be created explicitly with makefuncsym. - // See the makefuncsym comment for details. - if !Ctxt.Flag_dynlink && !existed { - funcsyms = append(funcsyms, s) - } - funcsymsmu.Unlock() - return sf -} - -// makefuncsym ensures that s·f is exported. -// It is only used with -dynlink. -// When not compiling for dynamic linking, -// the funcsyms are created as needed by -// the packages that use them. -// Normally we emit the s·f stubs as DUPOK syms, -// but DUPOK doesn't work across shared library boundaries. -// So instead, when dynamic linking, we only create -// the s·f stubs in s's package. -func makefuncsym(s *types.Sym) { - if !Ctxt.Flag_dynlink { - Fatalf("makefuncsym dynlink") - } - if s.IsBlank() { - return - } - if compiling_runtime && (s.Name == "getg" || s.Name == "getclosureptr" || s.Name == "getcallerpc" || s.Name == "getcallersp") { - // runtime.getg(), getclosureptr(), getcallerpc(), and - // getcallersp() are not real functions and so do not - // get funcsyms. - return - } - if _, existed := s.Pkg.LookupOK(funcsymname(s)); !existed { - funcsyms = append(funcsyms, s) - } -} - -// setNodeNameFunc marks a node as a function. -func setNodeNameFunc(n *Node) { - if n.Op != ONAME || n.Class() != Pxxx { - Fatalf("expected ONAME/Pxxx node, got %v", n) - } - - n.SetClass(PFUNC) - n.Sym.SetFunc(true) -} - -func dclfunc(sym *types.Sym, tfn *Node) *Node { - if tfn.Op != OTFUNC { - Fatalf("expected OTFUNC node, got %v", tfn) - } - - fn := nod(ODCLFUNC, nil, nil) - fn.Func.Nname = newfuncnamel(lineno, sym) - fn.Func.Nname.Name.Defn = fn - fn.Func.Nname.Name.Param.Ntype = tfn - setNodeNameFunc(fn.Func.Nname) - funchdr(fn) - fn.Func.Nname.Name.Param.Ntype = typecheck(fn.Func.Nname.Name.Param.Ntype, ctxType) - return fn -} - -type nowritebarrierrecChecker struct { - // extraCalls contains extra function calls that may not be - // visible during later analysis. It maps from the ODCLFUNC of - // the caller to a list of callees. - extraCalls map[*Node][]nowritebarrierrecCall - - // curfn is the current function during AST walks. - curfn *Node -} - -type nowritebarrierrecCall struct { - target *Node // ODCLFUNC of caller or callee - lineno src.XPos // line of call -} - -type nowritebarrierrecCallSym struct { - target *obj.LSym // LSym of callee - lineno src.XPos // line of call -} - -// newNowritebarrierrecChecker creates a nowritebarrierrecChecker. It -// must be called before transformclosure and walk. -func newNowritebarrierrecChecker() *nowritebarrierrecChecker { - c := &nowritebarrierrecChecker{ - extraCalls: make(map[*Node][]nowritebarrierrecCall), - } - - // Find all systemstack calls and record their targets. In - // general, flow analysis can't see into systemstack, but it's - // important to handle it for this check, so we model it - // directly. This has to happen before transformclosure since - // it's a lot harder to work out the argument after. - for _, n := range xtop { - if n.Op != ODCLFUNC { - continue - } - c.curfn = n - inspect(n, c.findExtraCalls) - } - c.curfn = nil - return c -} - -func (c *nowritebarrierrecChecker) findExtraCalls(n *Node) bool { - if n.Op != OCALLFUNC { - return true - } - fn := n.Left - if fn == nil || fn.Op != ONAME || fn.Class() != PFUNC || fn.Name.Defn == nil { - return true - } - if !isRuntimePkg(fn.Sym.Pkg) || fn.Sym.Name != "systemstack" { - return true - } - - var callee *Node - arg := n.List.First() - switch arg.Op { - case ONAME: - callee = arg.Name.Defn - case OCLOSURE: - callee = arg.Func.Closure - default: - Fatalf("expected ONAME or OCLOSURE node, got %+v", arg) - } - if callee.Op != ODCLFUNC { - Fatalf("expected ODCLFUNC node, got %+v", callee) - } - c.extraCalls[c.curfn] = append(c.extraCalls[c.curfn], nowritebarrierrecCall{callee, n.Pos}) - return true -} - -// recordCall records a call from ODCLFUNC node "from", to function -// symbol "to" at position pos. -// -// This should be done as late as possible during compilation to -// capture precise call graphs. The target of the call is an LSym -// because that's all we know after we start SSA. -// -// This can be called concurrently for different from Nodes. -func (c *nowritebarrierrecChecker) recordCall(from *Node, to *obj.LSym, pos src.XPos) { - if from.Op != ODCLFUNC { - Fatalf("expected ODCLFUNC, got %v", from) - } - // We record this information on the *Func so this is - // concurrent-safe. - fn := from.Func - if fn.nwbrCalls == nil { - fn.nwbrCalls = new([]nowritebarrierrecCallSym) - } - *fn.nwbrCalls = append(*fn.nwbrCalls, nowritebarrierrecCallSym{to, pos}) -} - -func (c *nowritebarrierrecChecker) check() { - // We walk the call graph as late as possible so we can - // capture all calls created by lowering, but this means we - // only get to see the obj.LSyms of calls. symToFunc lets us - // get back to the ODCLFUNCs. - symToFunc := make(map[*obj.LSym]*Node) - // funcs records the back-edges of the BFS call graph walk. It - // maps from the ODCLFUNC of each function that must not have - // write barriers to the call that inhibits them. Functions - // that are directly marked go:nowritebarrierrec are in this - // map with a zero-valued nowritebarrierrecCall. This also - // acts as the set of marks for the BFS of the call graph. - funcs := make(map[*Node]nowritebarrierrecCall) - // q is the queue of ODCLFUNC Nodes to visit in BFS order. - var q nodeQueue - - for _, n := range xtop { - if n.Op != ODCLFUNC { - continue - } - - symToFunc[n.Func.lsym] = n - - // Make nowritebarrierrec functions BFS roots. - if n.Func.Pragma&Nowritebarrierrec != 0 { - funcs[n] = nowritebarrierrecCall{} - q.pushRight(n) - } - // Check go:nowritebarrier functions. - if n.Func.Pragma&Nowritebarrier != 0 && n.Func.WBPos.IsKnown() { - yyerrorl(n.Func.WBPos, "write barrier prohibited") - } - } - - // Perform a BFS of the call graph from all - // go:nowritebarrierrec functions. - enqueue := func(src, target *Node, pos src.XPos) { - if target.Func.Pragma&Yeswritebarrierrec != 0 { - // Don't flow into this function. - return - } - if _, ok := funcs[target]; ok { - // Already found a path to target. - return - } - - // Record the path. - funcs[target] = nowritebarrierrecCall{target: src, lineno: pos} - q.pushRight(target) - } - for !q.empty() { - fn := q.popLeft() - - // Check fn. - if fn.Func.WBPos.IsKnown() { - var err bytes.Buffer - call := funcs[fn] - for call.target != nil { - fmt.Fprintf(&err, "\n\t%v: called by %v", linestr(call.lineno), call.target.Func.Nname) - call = funcs[call.target] - } - yyerrorl(fn.Func.WBPos, "write barrier prohibited by caller; %v%s", fn.Func.Nname, err.String()) - continue - } - - // Enqueue fn's calls. - for _, callee := range c.extraCalls[fn] { - enqueue(fn, callee.target, callee.lineno) - } - if fn.Func.nwbrCalls == nil { - continue - } - for _, callee := range *fn.Func.nwbrCalls { - target := symToFunc[callee.target] - if target != nil { - enqueue(fn, target, callee.lineno) - } - } - } -} diff --git a/src/cmd/compile/internal/gc/embed.go b/src/cmd/compile/internal/gc/embed.go deleted file mode 100644 index f45796cc1d80679e040adce10fd1332989204309..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/embed.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/syntax" - "cmd/compile/internal/types" - "cmd/internal/obj" - "encoding/json" - "io/ioutil" - "log" - "path" - "sort" - "strconv" - "strings" -) - -var embedlist []*Node - -var embedCfg struct { - Patterns map[string][]string - Files map[string]string -} - -func readEmbedCfg(file string) { - data, err := ioutil.ReadFile(file) - if err != nil { - log.Fatalf("-embedcfg: %v", err) - } - if err := json.Unmarshal(data, &embedCfg); err != nil { - log.Fatalf("%s: %v", file, err) - } - if embedCfg.Patterns == nil { - log.Fatalf("%s: invalid embedcfg: missing Patterns", file) - } - if embedCfg.Files == nil { - log.Fatalf("%s: invalid embedcfg: missing Files", file) - } -} - -const ( - embedUnknown = iota - embedBytes - embedString - embedFiles -) - -func varEmbed(p *noder, names []*Node, typ *Node, exprs []*Node, embeds []PragmaEmbed) { - haveEmbed := false - for _, decl := range p.file.DeclList { - imp, ok := decl.(*syntax.ImportDecl) - if !ok { - // imports always come first - break - } - path, _ := strconv.Unquote(imp.Path.Value) - if path == "embed" { - haveEmbed = true - break - } - } - - pos := embeds[0].Pos - if !haveEmbed { - p.yyerrorpos(pos, "invalid go:embed: missing import \"embed\"") - return - } - if len(names) > 1 { - p.yyerrorpos(pos, "go:embed cannot apply to multiple vars") - return - } - if len(exprs) > 0 { - p.yyerrorpos(pos, "go:embed cannot apply to var with initializer") - return - } - if typ == nil { - // Should not happen, since len(exprs) == 0 now. - p.yyerrorpos(pos, "go:embed cannot apply to var without type") - return - } - if dclcontext != PEXTERN { - p.yyerrorpos(pos, "go:embed cannot apply to var inside func") - return - } - - var list []irEmbed - for _, e := range embeds { - list = append(list, irEmbed{Pos: p.makeXPos(e.Pos), Patterns: e.Patterns}) - } - v := names[0] - v.Name.Param.SetEmbedList(list) - embedlist = append(embedlist, v) -} - -func embedFileList(v *Node, kind int) []string { - // Build list of files to store. - have := make(map[string]bool) - var list []string - for _, e := range v.Name.Param.EmbedList() { - for _, pattern := range e.Patterns { - files, ok := embedCfg.Patterns[pattern] - if !ok { - yyerrorl(e.Pos, "invalid go:embed: build system did not map pattern: %s", pattern) - } - for _, file := range files { - if embedCfg.Files[file] == "" { - yyerrorl(e.Pos, "invalid go:embed: build system did not map file: %s", file) - continue - } - if !have[file] { - have[file] = true - list = append(list, file) - } - if kind == embedFiles { - for dir := path.Dir(file); dir != "." && !have[dir]; dir = path.Dir(dir) { - have[dir] = true - list = append(list, dir+"/") - } - } - } - } - } - sort.Slice(list, func(i, j int) bool { - return embedFileLess(list[i], list[j]) - }) - - if kind == embedString || kind == embedBytes { - if len(list) > 1 { - yyerrorl(v.Pos, "invalid go:embed: multiple files for type %v", v.Type) - return nil - } - } - - return list -} - -// embedKind determines the kind of embedding variable. -func embedKind(typ *types.Type) int { - if typ.Sym != nil && typ.Sym.Name == "FS" && (typ.Sym.Pkg.Path == "embed" || (typ.Sym.Pkg == localpkg && myimportpath == "embed")) { - return embedFiles - } - if typ.Etype == types.TSTRING { - return embedString - } - if typ.Etype == types.TSLICE && typ.Elem().Etype == types.TUINT8 { - return embedBytes - } - return embedUnknown -} - -func embedFileNameSplit(name string) (dir, elem string, isDir bool) { - if name[len(name)-1] == '/' { - isDir = true - name = name[:len(name)-1] - } - i := len(name) - 1 - for i >= 0 && name[i] != '/' { - i-- - } - if i < 0 { - return ".", name, isDir - } - return name[:i], name[i+1:], isDir -} - -// embedFileLess implements the sort order for a list of embedded files. -// See the comment inside ../../../../embed/embed.go's Files struct for rationale. -func embedFileLess(x, y string) bool { - xdir, xelem, _ := embedFileNameSplit(x) - ydir, yelem, _ := embedFileNameSplit(y) - return xdir < ydir || xdir == ydir && xelem < yelem -} - -func dumpembeds() { - for _, v := range embedlist { - initEmbed(v) - } -} - -// initEmbed emits the init data for a //go:embed variable, -// which is either a string, a []byte, or an embed.FS. -func initEmbed(v *Node) { - commentPos := v.Name.Param.EmbedList()[0].Pos - if !langSupported(1, 16, localpkg) { - lno := lineno - lineno = commentPos - yyerrorv("go1.16", "go:embed") - lineno = lno - return - } - if embedCfg.Patterns == nil { - yyerrorl(commentPos, "invalid go:embed: build system did not supply embed configuration") - return - } - kind := embedKind(v.Type) - if kind == embedUnknown { - yyerrorl(v.Pos, "go:embed cannot apply to var of type %v", v.Type) - return - } - - files := embedFileList(v, kind) - switch kind { - case embedString, embedBytes: - file := files[0] - fsym, size, err := fileStringSym(v.Pos, embedCfg.Files[file], kind == embedString, nil) - if err != nil { - yyerrorl(v.Pos, "embed %s: %v", file, err) - } - sym := v.Sym.Linksym() - off := 0 - off = dsymptr(sym, off, fsym, 0) // data string - off = duintptr(sym, off, uint64(size)) // len - if kind == embedBytes { - duintptr(sym, off, uint64(size)) // cap for slice - } - - case embedFiles: - slicedata := Ctxt.Lookup(`"".` + v.Sym.Name + `.files`) - off := 0 - // []files pointed at by Files - off = dsymptr(slicedata, off, slicedata, 3*Widthptr) // []file, pointing just past slice - off = duintptr(slicedata, off, uint64(len(files))) - off = duintptr(slicedata, off, uint64(len(files))) - - // embed/embed.go type file is: - // name string - // data string - // hash [16]byte - // Emit one of these per file in the set. - const hashSize = 16 - hash := make([]byte, hashSize) - for _, file := range files { - off = dsymptr(slicedata, off, stringsym(v.Pos, file), 0) // file string - off = duintptr(slicedata, off, uint64(len(file))) - if strings.HasSuffix(file, "/") { - // entry for directory - no data - off = duintptr(slicedata, off, 0) - off = duintptr(slicedata, off, 0) - off += hashSize - } else { - fsym, size, err := fileStringSym(v.Pos, embedCfg.Files[file], true, hash) - if err != nil { - yyerrorl(v.Pos, "embed %s: %v", file, err) - } - off = dsymptr(slicedata, off, fsym, 0) // data string - off = duintptr(slicedata, off, uint64(size)) - off = int(slicedata.WriteBytes(Ctxt, int64(off), hash)) - } - } - ggloblsym(slicedata, int32(off), obj.RODATA|obj.LOCAL) - sym := v.Sym.Linksym() - dsymptr(sym, 0, slicedata, 0) - } -} diff --git a/src/cmd/compile/internal/gc/esc.go b/src/cmd/compile/internal/gc/esc.go deleted file mode 100644 index 6f328ab5ea9362da3eb44b0c8670459657c6c5d0..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/esc.go +++ /dev/null @@ -1,472 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "fmt" -) - -func escapes(all []*Node) { - visitBottomUp(all, escapeFuncs) -} - -const ( - EscFuncUnknown = 0 + iota - EscFuncPlanned - EscFuncStarted - EscFuncTagged -) - -func min8(a, b int8) int8 { - if a < b { - return a - } - return b -} - -func max8(a, b int8) int8 { - if a > b { - return a - } - return b -} - -const ( - EscUnknown = iota - EscNone // Does not escape to heap, result, or parameters. - EscHeap // Reachable from the heap - EscNever // By construction will not escape. -) - -// funcSym returns fn.Func.Nname.Sym if no nils are encountered along the way. -func funcSym(fn *Node) *types.Sym { - if fn == nil || fn.Func.Nname == nil { - return nil - } - return fn.Func.Nname.Sym -} - -// Mark labels that have no backjumps to them as not increasing e.loopdepth. -// Walk hasn't generated (goto|label).Left.Sym.Label yet, so we'll cheat -// and set it to one of the following two. Then in esc we'll clear it again. -var ( - looping Node - nonlooping Node -) - -func isSliceSelfAssign(dst, src *Node) bool { - // Detect the following special case. - // - // func (b *Buffer) Foo() { - // n, m := ... - // b.buf = b.buf[n:m] - // } - // - // This assignment is a no-op for escape analysis, - // it does not store any new pointers into b that were not already there. - // However, without this special case b will escape, because we assign to OIND/ODOTPTR. - // Here we assume that the statement will not contain calls, - // that is, that order will move any calls to init. - // Otherwise base ONAME value could change between the moments - // when we evaluate it for dst and for src. - - // dst is ONAME dereference. - if dst.Op != ODEREF && dst.Op != ODOTPTR || dst.Left.Op != ONAME { - return false - } - // src is a slice operation. - switch src.Op { - case OSLICE, OSLICE3, OSLICESTR: - // OK. - case OSLICEARR, OSLICE3ARR: - // Since arrays are embedded into containing object, - // slice of non-pointer array will introduce a new pointer into b that was not already there - // (pointer to b itself). After such assignment, if b contents escape, - // b escapes as well. If we ignore such OSLICEARR, we will conclude - // that b does not escape when b contents do. - // - // Pointer to an array is OK since it's not stored inside b directly. - // For slicing an array (not pointer to array), there is an implicit OADDR. - // We check that to determine non-pointer array slicing. - if src.Left.Op == OADDR { - return false - } - default: - return false - } - // slice is applied to ONAME dereference. - if src.Left.Op != ODEREF && src.Left.Op != ODOTPTR || src.Left.Left.Op != ONAME { - return false - } - // dst and src reference the same base ONAME. - return dst.Left == src.Left.Left -} - -// isSelfAssign reports whether assignment from src to dst can -// be ignored by the escape analysis as it's effectively a self-assignment. -func isSelfAssign(dst, src *Node) bool { - if isSliceSelfAssign(dst, src) { - return true - } - - // Detect trivial assignments that assign back to the same object. - // - // It covers these cases: - // val.x = val.y - // val.x[i] = val.y[j] - // val.x1.x2 = val.x1.y2 - // ... etc - // - // These assignments do not change assigned object lifetime. - - if dst == nil || src == nil || dst.Op != src.Op { - return false - } - - switch dst.Op { - case ODOT, ODOTPTR: - // Safe trailing accessors that are permitted to differ. - case OINDEX: - if mayAffectMemory(dst.Right) || mayAffectMemory(src.Right) { - return false - } - default: - return false - } - - // The expression prefix must be both "safe" and identical. - return samesafeexpr(dst.Left, src.Left) -} - -// mayAffectMemory reports whether evaluation of n may affect the program's -// memory state. If the expression can't affect memory state, then it can be -// safely ignored by the escape analysis. -func mayAffectMemory(n *Node) bool { - // We may want to use a list of "memory safe" ops instead of generally - // "side-effect free", which would include all calls and other ops that can - // allocate or change global state. For now, it's safer to start with the latter. - // - // We're ignoring things like division by zero, index out of range, - // and nil pointer dereference here. - switch n.Op { - case ONAME, OCLOSUREVAR, OLITERAL: - return false - - // Left+Right group. - case OINDEX, OADD, OSUB, OOR, OXOR, OMUL, OLSH, ORSH, OAND, OANDNOT, ODIV, OMOD: - return mayAffectMemory(n.Left) || mayAffectMemory(n.Right) - - // Left group. - case ODOT, ODOTPTR, ODEREF, OCONVNOP, OCONV, OLEN, OCAP, - ONOT, OBITNOT, OPLUS, ONEG, OALIGNOF, OOFFSETOF, OSIZEOF: - return mayAffectMemory(n.Left) - - default: - return true - } -} - -// heapAllocReason returns the reason the given Node must be heap -// allocated, or the empty string if it doesn't. -func heapAllocReason(n *Node) string { - if n.Type == nil { - return "" - } - - // Parameters are always passed via the stack. - if n.Op == ONAME && (n.Class() == PPARAM || n.Class() == PPARAMOUT) { - return "" - } - - if n.Type.Width > maxStackVarSize { - return "too large for stack" - } - - if (n.Op == ONEW || n.Op == OPTRLIT) && n.Type.Elem().Width >= maxImplicitStackVarSize { - return "too large for stack" - } - - if n.Op == OCLOSURE && closureType(n).Size() >= maxImplicitStackVarSize { - return "too large for stack" - } - if n.Op == OCALLPART && partialCallType(n).Size() >= maxImplicitStackVarSize { - return "too large for stack" - } - - if n.Op == OMAKESLICE { - r := n.Right - if r == nil { - r = n.Left - } - if !smallintconst(r) { - return "non-constant size" - } - if t := n.Type; t.Elem().Width != 0 && r.Int64Val() >= maxImplicitStackVarSize/t.Elem().Width { - return "too large for stack" - } - } - - return "" -} - -// addrescapes tags node n as having had its address taken -// by "increasing" the "value" of n.Esc to EscHeap. -// Storage is allocated as necessary to allow the address -// to be taken. -func addrescapes(n *Node) { - switch n.Op { - default: - // Unexpected Op, probably due to a previous type error. Ignore. - - case ODEREF, ODOTPTR: - // Nothing to do. - - case ONAME: - if n == nodfp { - break - } - - // if this is a tmpname (PAUTO), it was tagged by tmpname as not escaping. - // on PPARAM it means something different. - if n.Class() == PAUTO && n.Esc == EscNever { - break - } - - // If a closure reference escapes, mark the outer variable as escaping. - if n.Name.IsClosureVar() { - addrescapes(n.Name.Defn) - break - } - - if n.Class() != PPARAM && n.Class() != PPARAMOUT && n.Class() != PAUTO { - break - } - - // This is a plain parameter or local variable that needs to move to the heap, - // but possibly for the function outside the one we're compiling. - // That is, if we have: - // - // func f(x int) { - // func() { - // global = &x - // } - // } - // - // then we're analyzing the inner closure but we need to move x to the - // heap in f, not in the inner closure. Flip over to f before calling moveToHeap. - oldfn := Curfn - Curfn = n.Name.Curfn - if Curfn.Func.Closure != nil && Curfn.Op == OCLOSURE { - Curfn = Curfn.Func.Closure - } - ln := lineno - lineno = Curfn.Pos - moveToHeap(n) - Curfn = oldfn - lineno = ln - - // ODOTPTR has already been introduced, - // so these are the non-pointer ODOT and OINDEX. - // In &x[0], if x is a slice, then x does not - // escape--the pointer inside x does, but that - // is always a heap pointer anyway. - case ODOT, OINDEX, OPAREN, OCONVNOP: - if !n.Left.Type.IsSlice() { - addrescapes(n.Left) - } - } -} - -// moveToHeap records the parameter or local variable n as moved to the heap. -func moveToHeap(n *Node) { - if Debug.r != 0 { - Dump("MOVE", n) - } - if compiling_runtime { - yyerror("%v escapes to heap, not allowed in runtime", n) - } - if n.Class() == PAUTOHEAP { - Dump("n", n) - Fatalf("double move to heap") - } - - // Allocate a local stack variable to hold the pointer to the heap copy. - // temp will add it to the function declaration list automatically. - heapaddr := temp(types.NewPtr(n.Type)) - heapaddr.Sym = lookup("&" + n.Sym.Name) - heapaddr.Orig.Sym = heapaddr.Sym - heapaddr.Pos = n.Pos - - // Unset AutoTemp to persist the &foo variable name through SSA to - // liveness analysis. - // TODO(mdempsky/drchase): Cleaner solution? - heapaddr.Name.SetAutoTemp(false) - - // Parameters have a local stack copy used at function start/end - // in addition to the copy in the heap that may live longer than - // the function. - if n.Class() == PPARAM || n.Class() == PPARAMOUT { - if n.Xoffset == BADWIDTH { - Fatalf("addrescapes before param assignment") - } - - // We rewrite n below to be a heap variable (indirection of heapaddr). - // Preserve a copy so we can still write code referring to the original, - // and substitute that copy into the function declaration list - // so that analyses of the local (on-stack) variables use it. - stackcopy := newname(n.Sym) - stackcopy.Type = n.Type - stackcopy.Xoffset = n.Xoffset - stackcopy.SetClass(n.Class()) - stackcopy.Name.Param.Heapaddr = heapaddr - if n.Class() == PPARAMOUT { - // Make sure the pointer to the heap copy is kept live throughout the function. - // The function could panic at any point, and then a defer could recover. - // Thus, we need the pointer to the heap copy always available so the - // post-deferreturn code can copy the return value back to the stack. - // See issue 16095. - heapaddr.Name.SetIsOutputParamHeapAddr(true) - } - n.Name.Param.Stackcopy = stackcopy - - // Substitute the stackcopy into the function variable list so that - // liveness and other analyses use the underlying stack slot - // and not the now-pseudo-variable n. - found := false - for i, d := range Curfn.Func.Dcl { - if d == n { - Curfn.Func.Dcl[i] = stackcopy - found = true - break - } - // Parameters are before locals, so can stop early. - // This limits the search even in functions with many local variables. - if d.Class() == PAUTO { - break - } - } - if !found { - Fatalf("cannot find %v in local variable list", n) - } - Curfn.Func.Dcl = append(Curfn.Func.Dcl, n) - } - - // Modify n in place so that uses of n now mean indirection of the heapaddr. - n.SetClass(PAUTOHEAP) - n.Xoffset = 0 - n.Name.Param.Heapaddr = heapaddr - n.Esc = EscHeap - if Debug.m != 0 { - Warnl(n.Pos, "moved to heap: %v", n) - } -} - -// This special tag is applied to uintptr variables -// that we believe may hold unsafe.Pointers for -// calls into assembly functions. -const unsafeUintptrTag = "unsafe-uintptr" - -// This special tag is applied to uintptr parameters of functions -// marked go:uintptrescapes. -const uintptrEscapesTag = "uintptr-escapes" - -func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string { - name := func() string { - if f.Sym != nil { - return f.Sym.Name - } - return fmt.Sprintf("arg#%d", narg) - } - - if fn.Nbody.Len() == 0 { - // Assume that uintptr arguments must be held live across the call. - // This is most important for syscall.Syscall. - // See golang.org/issue/13372. - // This really doesn't have much to do with escape analysis per se, - // but we are reusing the ability to annotate an individual function - // argument and pass those annotations along to importing code. - if f.Type.IsUintptr() { - if Debug.m != 0 { - Warnl(f.Pos, "assuming %v is unsafe uintptr", name()) - } - return unsafeUintptrTag - } - - if !f.Type.HasPointers() { // don't bother tagging for scalars - return "" - } - - var esc EscLeaks - - // External functions are assumed unsafe, unless - // //go:noescape is given before the declaration. - if fn.Func.Pragma&Noescape != 0 { - if Debug.m != 0 && f.Sym != nil { - Warnl(f.Pos, "%v does not escape", name()) - } - } else { - if Debug.m != 0 && f.Sym != nil { - Warnl(f.Pos, "leaking param: %v", name()) - } - esc.AddHeap(0) - } - - return esc.Encode() - } - - if fn.Func.Pragma&UintptrEscapes != 0 { - if f.Type.IsUintptr() { - if Debug.m != 0 { - Warnl(f.Pos, "marking %v as escaping uintptr", name()) - } - return uintptrEscapesTag - } - if f.IsDDD() && f.Type.Elem().IsUintptr() { - // final argument is ...uintptr. - if Debug.m != 0 { - Warnl(f.Pos, "marking %v as escaping ...uintptr", name()) - } - return uintptrEscapesTag - } - } - - if !f.Type.HasPointers() { // don't bother tagging for scalars - return "" - } - - // Unnamed parameters are unused and therefore do not escape. - if f.Sym == nil || f.Sym.IsBlank() { - var esc EscLeaks - return esc.Encode() - } - - n := asNode(f.Nname) - loc := e.oldLoc(n) - esc := loc.paramEsc - esc.Optimize() - - if Debug.m != 0 && !loc.escapes { - if esc.Empty() { - Warnl(f.Pos, "%v does not escape", name()) - } - if x := esc.Heap(); x >= 0 { - if x == 0 { - Warnl(f.Pos, "leaking param: %v", name()) - } else { - // TODO(mdempsky): Mention level=x like below? - Warnl(f.Pos, "leaking param content: %v", name()) - } - } - for i := 0; i < numEscResults; i++ { - if x := esc.Result(i); x >= 0 { - res := fn.Type.Results().Field(i).Sym - Warnl(f.Pos, "leaking param: %v to result %v level=%d", name(), res, x) - } - } - } - - return esc.Encode() -} diff --git a/src/cmd/compile/internal/gc/escape.go b/src/cmd/compile/internal/gc/escape.go deleted file mode 100644 index f719892d17a1cdd793a81c5ae588a47c07b7e5f8..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/escape.go +++ /dev/null @@ -1,1539 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/logopt" - "cmd/compile/internal/types" - "cmd/internal/src" - "fmt" - "math" - "strings" -) - -// Escape analysis. -// -// Here we analyze functions to determine which Go variables -// (including implicit allocations such as calls to "new" or "make", -// composite literals, etc.) can be allocated on the stack. The two -// key invariants we have to ensure are: (1) pointers to stack objects -// cannot be stored in the heap, and (2) pointers to a stack object -// cannot outlive that object (e.g., because the declaring function -// returned and destroyed the object's stack frame, or its space is -// reused across loop iterations for logically distinct variables). -// -// We implement this with a static data-flow analysis of the AST. -// First, we construct a directed weighted graph where vertices -// (termed "locations") represent variables allocated by statements -// and expressions, and edges represent assignments between variables -// (with weights representing addressing/dereference counts). -// -// Next we walk the graph looking for assignment paths that might -// violate the invariants stated above. If a variable v's address is -// stored in the heap or elsewhere that may outlive it, then v is -// marked as requiring heap allocation. -// -// To support interprocedural analysis, we also record data-flow from -// each function's parameters to the heap and to its result -// parameters. This information is summarized as "parameter tags", -// which are used at static call sites to improve escape analysis of -// function arguments. - -// Constructing the location graph. -// -// Every allocating statement (e.g., variable declaration) or -// expression (e.g., "new" or "make") is first mapped to a unique -// "location." -// -// We also model every Go assignment as a directed edges between -// locations. The number of dereference operations minus the number of -// addressing operations is recorded as the edge's weight (termed -// "derefs"). For example: -// -// p = &q // -1 -// p = q // 0 -// p = *q // 1 -// p = **q // 2 -// -// p = **&**&q // 2 -// -// Note that the & operator can only be applied to addressable -// expressions, and the expression &x itself is not addressable, so -// derefs cannot go below -1. -// -// Every Go language construct is lowered into this representation, -// generally without sensitivity to flow, path, or context; and -// without distinguishing elements within a compound variable. For -// example: -// -// var x struct { f, g *int } -// var u []*int -// -// x.f = u[0] -// -// is modeled simply as -// -// x = *u -// -// That is, we don't distinguish x.f from x.g, or u[0] from u[1], -// u[2], etc. However, we do record the implicit dereference involved -// in indexing a slice. - -type Escape struct { - allLocs []*EscLocation - - curfn *Node - - // loopDepth counts the current loop nesting depth within - // curfn. It increments within each "for" loop and at each - // label with a corresponding backwards "goto" (i.e., - // unstructured loop). - loopDepth int - - heapLoc EscLocation - blankLoc EscLocation -} - -// An EscLocation represents an abstract location that stores a Go -// variable. -type EscLocation struct { - n *Node // represented variable or expression, if any - curfn *Node // enclosing function - edges []EscEdge // incoming edges - loopDepth int // loopDepth at declaration - - // derefs and walkgen are used during walkOne to track the - // minimal dereferences from the walk root. - derefs int // >= -1 - walkgen uint32 - - // dst and dstEdgeindex track the next immediate assignment - // destination location during walkone, along with the index - // of the edge pointing back to this location. - dst *EscLocation - dstEdgeIdx int - - // queued is used by walkAll to track whether this location is - // in the walk queue. - queued bool - - // escapes reports whether the represented variable's address - // escapes; that is, whether the variable must be heap - // allocated. - escapes bool - - // transient reports whether the represented expression's - // address does not outlive the statement; that is, whether - // its storage can be immediately reused. - transient bool - - // paramEsc records the represented parameter's leak set. - paramEsc EscLeaks -} - -// An EscEdge represents an assignment edge between two Go variables. -type EscEdge struct { - src *EscLocation - derefs int // >= -1 - notes *EscNote -} - -// escapeFuncs performs escape analysis on a minimal batch of -// functions. -func escapeFuncs(fns []*Node, recursive bool) { - for _, fn := range fns { - if fn.Op != ODCLFUNC { - Fatalf("unexpected node: %v", fn) - } - } - - var e Escape - e.heapLoc.escapes = true - - // Construct data-flow graph from syntax trees. - for _, fn := range fns { - e.initFunc(fn) - } - for _, fn := range fns { - e.walkFunc(fn) - } - e.curfn = nil - - e.walkAll() - e.finish(fns) -} - -func (e *Escape) initFunc(fn *Node) { - if fn.Op != ODCLFUNC || fn.Esc != EscFuncUnknown { - Fatalf("unexpected node: %v", fn) - } - fn.Esc = EscFuncPlanned - if Debug.m > 3 { - Dump("escAnalyze", fn) - } - - e.curfn = fn - e.loopDepth = 1 - - // Allocate locations for local variables. - for _, dcl := range fn.Func.Dcl { - if dcl.Op == ONAME { - e.newLoc(dcl, false) - } - } -} - -func (e *Escape) walkFunc(fn *Node) { - fn.Esc = EscFuncStarted - - // Identify labels that mark the head of an unstructured loop. - inspectList(fn.Nbody, func(n *Node) bool { - switch n.Op { - case OLABEL: - n.Sym.Label = asTypesNode(&nonlooping) - - case OGOTO: - // If we visited the label before the goto, - // then this is a looping label. - if n.Sym.Label == asTypesNode(&nonlooping) { - n.Sym.Label = asTypesNode(&looping) - } - } - - return true - }) - - e.curfn = fn - e.loopDepth = 1 - e.block(fn.Nbody) -} - -// Below we implement the methods for walking the AST and recording -// data flow edges. Note that because a sub-expression might have -// side-effects, it's important to always visit the entire AST. -// -// For example, write either: -// -// if x { -// e.discard(n.Left) -// } else { -// e.value(k, n.Left) -// } -// -// or -// -// if x { -// k = e.discardHole() -// } -// e.value(k, n.Left) -// -// Do NOT write: -// -// // BAD: possibly loses side-effects within n.Left -// if !x { -// e.value(k, n.Left) -// } - -// stmt evaluates a single Go statement. -func (e *Escape) stmt(n *Node) { - if n == nil { - return - } - - lno := setlineno(n) - defer func() { - lineno = lno - }() - - if Debug.m > 2 { - fmt.Printf("%v:[%d] %v stmt: %v\n", linestr(lineno), e.loopDepth, funcSym(e.curfn), n) - } - - e.stmts(n.Ninit) - - switch n.Op { - default: - Fatalf("unexpected stmt: %v", n) - - case ODCLCONST, ODCLTYPE, OEMPTY, OFALL, OINLMARK: - // nop - - case OBREAK, OCONTINUE, OGOTO: - // TODO(mdempsky): Handle dead code? - - case OBLOCK: - e.stmts(n.List) - - case ODCL: - // Record loop depth at declaration. - if !n.Left.isBlank() { - e.dcl(n.Left) - } - - case OLABEL: - switch asNode(n.Sym.Label) { - case &nonlooping: - if Debug.m > 2 { - fmt.Printf("%v:%v non-looping label\n", linestr(lineno), n) - } - case &looping: - if Debug.m > 2 { - fmt.Printf("%v: %v looping label\n", linestr(lineno), n) - } - e.loopDepth++ - default: - Fatalf("label missing tag") - } - n.Sym.Label = nil - - case OIF: - e.discard(n.Left) - e.block(n.Nbody) - e.block(n.Rlist) - - case OFOR, OFORUNTIL: - e.loopDepth++ - e.discard(n.Left) - e.stmt(n.Right) - e.block(n.Nbody) - e.loopDepth-- - - case ORANGE: - // for List = range Right { Nbody } - e.loopDepth++ - ks := e.addrs(n.List) - e.block(n.Nbody) - e.loopDepth-- - - // Right is evaluated outside the loop. - k := e.discardHole() - if len(ks) >= 2 { - if n.Right.Type.IsArray() { - k = ks[1].note(n, "range") - } else { - k = ks[1].deref(n, "range-deref") - } - } - e.expr(e.later(k), n.Right) - - case OSWITCH: - typesw := n.Left != nil && n.Left.Op == OTYPESW - - var ks []EscHole - for _, cas := range n.List.Slice() { // cases - if typesw && n.Left.Left != nil { - cv := cas.Rlist.First() - k := e.dcl(cv) // type switch variables have no ODCL. - if cv.Type.HasPointers() { - ks = append(ks, k.dotType(cv.Type, cas, "switch case")) - } - } - - e.discards(cas.List) - e.block(cas.Nbody) - } - - if typesw { - e.expr(e.teeHole(ks...), n.Left.Right) - } else { - e.discard(n.Left) - } - - case OSELECT: - for _, cas := range n.List.Slice() { - e.stmt(cas.Left) - e.block(cas.Nbody) - } - case OSELRECV: - e.assign(n.Left, n.Right, "selrecv", n) - case OSELRECV2: - e.assign(n.Left, n.Right, "selrecv", n) - e.assign(n.List.First(), nil, "selrecv", n) - case ORECV: - // TODO(mdempsky): Consider e.discard(n.Left). - e.exprSkipInit(e.discardHole(), n) // already visited n.Ninit - case OSEND: - e.discard(n.Left) - e.assignHeap(n.Right, "send", n) - - case OAS, OASOP: - e.assign(n.Left, n.Right, "assign", n) - - case OAS2: - for i, nl := range n.List.Slice() { - e.assign(nl, n.Rlist.Index(i), "assign-pair", n) - } - - case OAS2DOTTYPE: // v, ok = x.(type) - e.assign(n.List.First(), n.Right, "assign-pair-dot-type", n) - e.assign(n.List.Second(), nil, "assign-pair-dot-type", n) - case OAS2MAPR: // v, ok = m[k] - e.assign(n.List.First(), n.Right, "assign-pair-mapr", n) - e.assign(n.List.Second(), nil, "assign-pair-mapr", n) - case OAS2RECV: // v, ok = <-ch - e.assign(n.List.First(), n.Right, "assign-pair-receive", n) - e.assign(n.List.Second(), nil, "assign-pair-receive", n) - - case OAS2FUNC: - e.stmts(n.Right.Ninit) - e.call(e.addrs(n.List), n.Right, nil) - case ORETURN: - results := e.curfn.Type.Results().FieldSlice() - for i, v := range n.List.Slice() { - e.assign(asNode(results[i].Nname), v, "return", n) - } - case OCALLFUNC, OCALLMETH, OCALLINTER, OCLOSE, OCOPY, ODELETE, OPANIC, OPRINT, OPRINTN, ORECOVER: - e.call(nil, n, nil) - case OGO, ODEFER: - e.stmts(n.Left.Ninit) - e.call(nil, n.Left, n) - - case ORETJMP: - // TODO(mdempsky): What do? esc.go just ignores it. - } -} - -func (e *Escape) stmts(l Nodes) { - for _, n := range l.Slice() { - e.stmt(n) - } -} - -// block is like stmts, but preserves loopDepth. -func (e *Escape) block(l Nodes) { - old := e.loopDepth - e.stmts(l) - e.loopDepth = old -} - -// expr models evaluating an expression n and flowing the result into -// hole k. -func (e *Escape) expr(k EscHole, n *Node) { - if n == nil { - return - } - e.stmts(n.Ninit) - e.exprSkipInit(k, n) -} - -func (e *Escape) exprSkipInit(k EscHole, n *Node) { - if n == nil { - return - } - - lno := setlineno(n) - defer func() { - lineno = lno - }() - - uintptrEscapesHack := k.uintptrEscapesHack - k.uintptrEscapesHack = false - - if uintptrEscapesHack && n.Op == OCONVNOP && n.Left.Type.IsUnsafePtr() { - // nop - } else if k.derefs >= 0 && !n.Type.HasPointers() { - k = e.discardHole() - } - - switch n.Op { - default: - Fatalf("unexpected expr: %v", n) - - case OLITERAL, OGETG, OCLOSUREVAR, OTYPE: - // nop - - case ONAME: - if n.Class() == PFUNC || n.Class() == PEXTERN { - return - } - e.flow(k, e.oldLoc(n)) - - case OPLUS, ONEG, OBITNOT, ONOT: - e.discard(n.Left) - case OADD, OSUB, OOR, OXOR, OMUL, ODIV, OMOD, OLSH, ORSH, OAND, OANDNOT, OEQ, ONE, OLT, OLE, OGT, OGE, OANDAND, OOROR: - e.discard(n.Left) - e.discard(n.Right) - - case OADDR: - e.expr(k.addr(n, "address-of"), n.Left) // "address-of" - case ODEREF: - e.expr(k.deref(n, "indirection"), n.Left) // "indirection" - case ODOT, ODOTMETH, ODOTINTER: - e.expr(k.note(n, "dot"), n.Left) - case ODOTPTR: - e.expr(k.deref(n, "dot of pointer"), n.Left) // "dot of pointer" - case ODOTTYPE, ODOTTYPE2: - e.expr(k.dotType(n.Type, n, "dot"), n.Left) - case OINDEX: - if n.Left.Type.IsArray() { - e.expr(k.note(n, "fixed-array-index-of"), n.Left) - } else { - // TODO(mdempsky): Fix why reason text. - e.expr(k.deref(n, "dot of pointer"), n.Left) - } - e.discard(n.Right) - case OINDEXMAP: - e.discard(n.Left) - e.discard(n.Right) - case OSLICE, OSLICEARR, OSLICE3, OSLICE3ARR, OSLICESTR: - e.expr(k.note(n, "slice"), n.Left) - low, high, max := n.SliceBounds() - e.discard(low) - e.discard(high) - e.discard(max) - - case OCONV, OCONVNOP: - if checkPtr(e.curfn, 2) && n.Type.IsUnsafePtr() && n.Left.Type.IsPtr() { - // When -d=checkptr=2 is enabled, treat - // conversions to unsafe.Pointer as an - // escaping operation. This allows better - // runtime instrumentation, since we can more - // easily detect object boundaries on the heap - // than the stack. - e.assignHeap(n.Left, "conversion to unsafe.Pointer", n) - } else if n.Type.IsUnsafePtr() && n.Left.Type.IsUintptr() { - e.unsafeValue(k, n.Left) - } else { - e.expr(k, n.Left) - } - case OCONVIFACE: - if !n.Left.Type.IsInterface() && !isdirectiface(n.Left.Type) { - k = e.spill(k, n) - } - e.expr(k.note(n, "interface-converted"), n.Left) - - case ORECV: - e.discard(n.Left) - - case OCALLMETH, OCALLFUNC, OCALLINTER, OLEN, OCAP, OCOMPLEX, OREAL, OIMAG, OAPPEND, OCOPY: - e.call([]EscHole{k}, n, nil) - - case ONEW: - e.spill(k, n) - - case OMAKESLICE: - e.spill(k, n) - e.discard(n.Left) - e.discard(n.Right) - case OMAKECHAN: - e.discard(n.Left) - case OMAKEMAP: - e.spill(k, n) - e.discard(n.Left) - - case ORECOVER: - // nop - - case OCALLPART: - // Flow the receiver argument to both the closure and - // to the receiver parameter. - - closureK := e.spill(k, n) - - m := callpartMethod(n) - - // We don't know how the method value will be called - // later, so conservatively assume the result - // parameters all flow to the heap. - // - // TODO(mdempsky): Change ks into a callback, so that - // we don't have to create this dummy slice? - var ks []EscHole - for i := m.Type.NumResults(); i > 0; i-- { - ks = append(ks, e.heapHole()) - } - paramK := e.tagHole(ks, asNode(m.Type.Nname()), m.Type.Recv()) - - e.expr(e.teeHole(paramK, closureK), n.Left) - - case OPTRLIT: - e.expr(e.spill(k, n), n.Left) - - case OARRAYLIT: - for _, elt := range n.List.Slice() { - if elt.Op == OKEY { - elt = elt.Right - } - e.expr(k.note(n, "array literal element"), elt) - } - - case OSLICELIT: - k = e.spill(k, n) - k.uintptrEscapesHack = uintptrEscapesHack // for ...uintptr parameters - - for _, elt := range n.List.Slice() { - if elt.Op == OKEY { - elt = elt.Right - } - e.expr(k.note(n, "slice-literal-element"), elt) - } - - case OSTRUCTLIT: - for _, elt := range n.List.Slice() { - e.expr(k.note(n, "struct literal element"), elt.Left) - } - - case OMAPLIT: - e.spill(k, n) - - // Map keys and values are always stored in the heap. - for _, elt := range n.List.Slice() { - e.assignHeap(elt.Left, "map literal key", n) - e.assignHeap(elt.Right, "map literal value", n) - } - - case OCLOSURE: - k = e.spill(k, n) - - // Link addresses of captured variables to closure. - for _, v := range n.Func.Closure.Func.Cvars.Slice() { - if v.Op == OXXX { // unnamed out argument; see dcl.go:/^funcargs - continue - } - - k := k - if !v.Name.Byval() { - k = k.addr(v, "reference") - } - - e.expr(k.note(n, "captured by a closure"), v.Name.Defn) - } - - case ORUNES2STR, OBYTES2STR, OSTR2RUNES, OSTR2BYTES, ORUNESTR: - e.spill(k, n) - e.discard(n.Left) - - case OADDSTR: - e.spill(k, n) - - // Arguments of OADDSTR never escape; - // runtime.concatstrings makes sure of that. - e.discards(n.List) - } -} - -// unsafeValue evaluates a uintptr-typed arithmetic expression looking -// for conversions from an unsafe.Pointer. -func (e *Escape) unsafeValue(k EscHole, n *Node) { - if n.Type.Etype != TUINTPTR { - Fatalf("unexpected type %v for %v", n.Type, n) - } - - e.stmts(n.Ninit) - - switch n.Op { - case OCONV, OCONVNOP: - if n.Left.Type.IsUnsafePtr() { - e.expr(k, n.Left) - } else { - e.discard(n.Left) - } - case ODOTPTR: - if isReflectHeaderDataField(n) { - e.expr(k.deref(n, "reflect.Header.Data"), n.Left) - } else { - e.discard(n.Left) - } - case OPLUS, ONEG, OBITNOT: - e.unsafeValue(k, n.Left) - case OADD, OSUB, OOR, OXOR, OMUL, ODIV, OMOD, OAND, OANDNOT: - e.unsafeValue(k, n.Left) - e.unsafeValue(k, n.Right) - case OLSH, ORSH: - e.unsafeValue(k, n.Left) - // RHS need not be uintptr-typed (#32959) and can't meaningfully - // flow pointers anyway. - e.discard(n.Right) - default: - e.exprSkipInit(e.discardHole(), n) - } -} - -// discard evaluates an expression n for side-effects, but discards -// its value. -func (e *Escape) discard(n *Node) { - e.expr(e.discardHole(), n) -} - -func (e *Escape) discards(l Nodes) { - for _, n := range l.Slice() { - e.discard(n) - } -} - -// addr evaluates an addressable expression n and returns an EscHole -// that represents storing into the represented location. -func (e *Escape) addr(n *Node) EscHole { - if n == nil || n.isBlank() { - // Can happen at least in OSELRECV. - // TODO(mdempsky): Anywhere else? - return e.discardHole() - } - - k := e.heapHole() - - switch n.Op { - default: - Fatalf("unexpected addr: %v", n) - case ONAME: - if n.Class() == PEXTERN { - break - } - k = e.oldLoc(n).asHole() - case ODOT: - k = e.addr(n.Left) - case OINDEX: - e.discard(n.Right) - if n.Left.Type.IsArray() { - k = e.addr(n.Left) - } else { - e.discard(n.Left) - } - case ODEREF, ODOTPTR: - e.discard(n) - case OINDEXMAP: - e.discard(n.Left) - e.assignHeap(n.Right, "key of map put", n) - } - - if !n.Type.HasPointers() { - k = e.discardHole() - } - - return k -} - -func (e *Escape) addrs(l Nodes) []EscHole { - var ks []EscHole - for _, n := range l.Slice() { - ks = append(ks, e.addr(n)) - } - return ks -} - -// assign evaluates the assignment dst = src. -func (e *Escape) assign(dst, src *Node, why string, where *Node) { - // Filter out some no-op assignments for escape analysis. - ignore := dst != nil && src != nil && isSelfAssign(dst, src) - if ignore && Debug.m != 0 { - Warnl(where.Pos, "%v ignoring self-assignment in %S", funcSym(e.curfn), where) - } - - k := e.addr(dst) - if dst != nil && dst.Op == ODOTPTR && isReflectHeaderDataField(dst) { - e.unsafeValue(e.heapHole().note(where, why), src) - } else { - if ignore { - k = e.discardHole() - } - e.expr(k.note(where, why), src) - } -} - -func (e *Escape) assignHeap(src *Node, why string, where *Node) { - e.expr(e.heapHole().note(where, why), src) -} - -// call evaluates a call expressions, including builtin calls. ks -// should contain the holes representing where the function callee's -// results flows; where is the OGO/ODEFER context of the call, if any. -func (e *Escape) call(ks []EscHole, call, where *Node) { - topLevelDefer := where != nil && where.Op == ODEFER && e.loopDepth == 1 - if topLevelDefer { - // force stack allocation of defer record, unless - // open-coded defers are used (see ssa.go) - where.Esc = EscNever - } - - argument := func(k EscHole, arg *Node) { - if topLevelDefer { - // Top level defers arguments don't escape to - // heap, but they do need to last until end of - // function. - k = e.later(k) - } else if where != nil { - k = e.heapHole() - } - - e.expr(k.note(call, "call parameter"), arg) - } - - switch call.Op { - default: - Fatalf("unexpected call op: %v", call.Op) - - case OCALLFUNC, OCALLMETH, OCALLINTER: - fixVariadicCall(call) - - // Pick out the function callee, if statically known. - var fn *Node - switch call.Op { - case OCALLFUNC: - switch v := staticValue(call.Left); { - case v.Op == ONAME && v.Class() == PFUNC: - fn = v - case v.Op == OCLOSURE: - fn = v.Func.Closure.Func.Nname - } - case OCALLMETH: - fn = asNode(call.Left.Type.FuncType().Nname) - } - - fntype := call.Left.Type - if fn != nil { - fntype = fn.Type - } - - if ks != nil && fn != nil && e.inMutualBatch(fn) { - for i, result := range fn.Type.Results().FieldSlice() { - e.expr(ks[i], asNode(result.Nname)) - } - } - - if r := fntype.Recv(); r != nil { - argument(e.tagHole(ks, fn, r), call.Left.Left) - } else { - // Evaluate callee function expression. - argument(e.discardHole(), call.Left) - } - - args := call.List.Slice() - for i, param := range fntype.Params().FieldSlice() { - argument(e.tagHole(ks, fn, param), args[i]) - } - - case OAPPEND: - args := call.List.Slice() - - // Appendee slice may flow directly to the result, if - // it has enough capacity. Alternatively, a new heap - // slice might be allocated, and all slice elements - // might flow to heap. - appendeeK := ks[0] - if args[0].Type.Elem().HasPointers() { - appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice")) - } - argument(appendeeK, args[0]) - - if call.IsDDD() { - appendedK := e.discardHole() - if args[1].Type.IsSlice() && args[1].Type.Elem().HasPointers() { - appendedK = e.heapHole().deref(call, "appended slice...") - } - argument(appendedK, args[1]) - } else { - for _, arg := range args[1:] { - argument(e.heapHole(), arg) - } - } - - case OCOPY: - argument(e.discardHole(), call.Left) - - copiedK := e.discardHole() - if call.Right.Type.IsSlice() && call.Right.Type.Elem().HasPointers() { - copiedK = e.heapHole().deref(call, "copied slice") - } - argument(copiedK, call.Right) - - case OPANIC: - argument(e.heapHole(), call.Left) - - case OCOMPLEX: - argument(e.discardHole(), call.Left) - argument(e.discardHole(), call.Right) - case ODELETE, OPRINT, OPRINTN, ORECOVER: - for _, arg := range call.List.Slice() { - argument(e.discardHole(), arg) - } - case OLEN, OCAP, OREAL, OIMAG, OCLOSE: - argument(e.discardHole(), call.Left) - } -} - -// tagHole returns a hole for evaluating an argument passed to param. -// ks should contain the holes representing where the function -// callee's results flows. fn is the statically-known callee function, -// if any. -func (e *Escape) tagHole(ks []EscHole, fn *Node, param *types.Field) EscHole { - // If this is a dynamic call, we can't rely on param.Note. - if fn == nil { - return e.heapHole() - } - - if e.inMutualBatch(fn) { - return e.addr(asNode(param.Nname)) - } - - // Call to previously tagged function. - - if param.Note == uintptrEscapesTag { - k := e.heapHole() - k.uintptrEscapesHack = true - return k - } - - var tagKs []EscHole - - esc := ParseLeaks(param.Note) - if x := esc.Heap(); x >= 0 { - tagKs = append(tagKs, e.heapHole().shift(x)) - } - - if ks != nil { - for i := 0; i < numEscResults; i++ { - if x := esc.Result(i); x >= 0 { - tagKs = append(tagKs, ks[i].shift(x)) - } - } - } - - return e.teeHole(tagKs...) -} - -// inMutualBatch reports whether function fn is in the batch of -// mutually recursive functions being analyzed. When this is true, -// fn has not yet been analyzed, so its parameters and results -// should be incorporated directly into the flow graph instead of -// relying on its escape analysis tagging. -func (e *Escape) inMutualBatch(fn *Node) bool { - if fn.Name.Defn != nil && fn.Name.Defn.Esc < EscFuncTagged { - if fn.Name.Defn.Esc == EscFuncUnknown { - Fatalf("graph inconsistency") - } - return true - } - return false -} - -// An EscHole represents a context for evaluation a Go -// expression. E.g., when evaluating p in "x = **p", we'd have a hole -// with dst==x and derefs==2. -type EscHole struct { - dst *EscLocation - derefs int // >= -1 - notes *EscNote - - // uintptrEscapesHack indicates this context is evaluating an - // argument for a //go:uintptrescapes function. - uintptrEscapesHack bool -} - -type EscNote struct { - next *EscNote - where *Node - why string -} - -func (k EscHole) note(where *Node, why string) EscHole { - if where == nil || why == "" { - Fatalf("note: missing where/why") - } - if Debug.m >= 2 || logopt.Enabled() { - k.notes = &EscNote{ - next: k.notes, - where: where, - why: why, - } - } - return k -} - -func (k EscHole) shift(delta int) EscHole { - k.derefs += delta - if k.derefs < -1 { - Fatalf("derefs underflow: %v", k.derefs) - } - return k -} - -func (k EscHole) deref(where *Node, why string) EscHole { return k.shift(1).note(where, why) } -func (k EscHole) addr(where *Node, why string) EscHole { return k.shift(-1).note(where, why) } - -func (k EscHole) dotType(t *types.Type, where *Node, why string) EscHole { - if !t.IsInterface() && !isdirectiface(t) { - k = k.shift(1) - } - return k.note(where, why) -} - -// teeHole returns a new hole that flows into each hole of ks, -// similar to the Unix tee(1) command. -func (e *Escape) teeHole(ks ...EscHole) EscHole { - if len(ks) == 0 { - return e.discardHole() - } - if len(ks) == 1 { - return ks[0] - } - // TODO(mdempsky): Optimize if there's only one non-discard hole? - - // Given holes "l1 = _", "l2 = **_", "l3 = *_", ..., create a - // new temporary location ltmp, wire it into place, and return - // a hole for "ltmp = _". - loc := e.newLoc(nil, true) - for _, k := range ks { - // N.B., "p = &q" and "p = &tmp; tmp = q" are not - // semantically equivalent. To combine holes like "l1 - // = _" and "l2 = &_", we'd need to wire them as "l1 = - // *ltmp" and "l2 = ltmp" and return "ltmp = &_" - // instead. - if k.derefs < 0 { - Fatalf("teeHole: negative derefs") - } - - e.flow(k, loc) - } - return loc.asHole() -} - -func (e *Escape) dcl(n *Node) EscHole { - loc := e.oldLoc(n) - loc.loopDepth = e.loopDepth - return loc.asHole() -} - -// spill allocates a new location associated with expression n, flows -// its address to k, and returns a hole that flows values to it. It's -// intended for use with most expressions that allocate storage. -func (e *Escape) spill(k EscHole, n *Node) EscHole { - loc := e.newLoc(n, true) - e.flow(k.addr(n, "spill"), loc) - return loc.asHole() -} - -// later returns a new hole that flows into k, but some time later. -// Its main effect is to prevent immediate reuse of temporary -// variables introduced during Order. -func (e *Escape) later(k EscHole) EscHole { - loc := e.newLoc(nil, false) - e.flow(k, loc) - return loc.asHole() -} - -// canonicalNode returns the canonical *Node that n logically -// represents. -func canonicalNode(n *Node) *Node { - if n != nil && n.Op == ONAME && n.Name.IsClosureVar() { - n = n.Name.Defn - if n.Name.IsClosureVar() { - Fatalf("still closure var") - } - } - - return n -} - -func (e *Escape) newLoc(n *Node, transient bool) *EscLocation { - if e.curfn == nil { - Fatalf("e.curfn isn't set") - } - if n != nil && n.Type != nil && n.Type.NotInHeap() { - yyerrorl(n.Pos, "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type) - } - - n = canonicalNode(n) - loc := &EscLocation{ - n: n, - curfn: e.curfn, - loopDepth: e.loopDepth, - transient: transient, - } - e.allLocs = append(e.allLocs, loc) - if n != nil { - if n.Op == ONAME && n.Name.Curfn != e.curfn { - Fatalf("curfn mismatch: %v != %v", n.Name.Curfn, e.curfn) - } - - if n.HasOpt() { - Fatalf("%v already has a location", n) - } - n.SetOpt(loc) - - if why := heapAllocReason(n); why != "" { - e.flow(e.heapHole().addr(n, why), loc) - } - } - return loc -} - -func (e *Escape) oldLoc(n *Node) *EscLocation { - n = canonicalNode(n) - return n.Opt().(*EscLocation) -} - -func (l *EscLocation) asHole() EscHole { - return EscHole{dst: l} -} - -func (e *Escape) flow(k EscHole, src *EscLocation) { - dst := k.dst - if dst == &e.blankLoc { - return - } - if dst == src && k.derefs >= 0 { // dst = dst, dst = *dst, ... - return - } - if dst.escapes && k.derefs < 0 { // dst = &src - if Debug.m >= 2 || logopt.Enabled() { - pos := linestr(src.n.Pos) - if Debug.m >= 2 { - fmt.Printf("%s: %v escapes to heap:\n", pos, src.n) - } - explanation := e.explainFlow(pos, dst, src, k.derefs, k.notes, []*logopt.LoggedOpt{}) - if logopt.Enabled() { - logopt.LogOpt(src.n.Pos, "escapes", "escape", e.curfn.funcname(), fmt.Sprintf("%v escapes to heap", src.n), explanation) - } - - } - src.escapes = true - return - } - - // TODO(mdempsky): Deduplicate edges? - dst.edges = append(dst.edges, EscEdge{src: src, derefs: k.derefs, notes: k.notes}) -} - -func (e *Escape) heapHole() EscHole { return e.heapLoc.asHole() } -func (e *Escape) discardHole() EscHole { return e.blankLoc.asHole() } - -// walkAll computes the minimal dereferences between all pairs of -// locations. -func (e *Escape) walkAll() { - // We use a work queue to keep track of locations that we need - // to visit, and repeatedly walk until we reach a fixed point. - // - // We walk once from each location (including the heap), and - // then re-enqueue each location on its transition from - // transient->!transient and !escapes->escapes, which can each - // happen at most once. So we take Θ(len(e.allLocs)) walks. - - // LIFO queue, has enough room for e.allLocs and e.heapLoc. - todo := make([]*EscLocation, 0, len(e.allLocs)+1) - enqueue := func(loc *EscLocation) { - if !loc.queued { - todo = append(todo, loc) - loc.queued = true - } - } - - for _, loc := range e.allLocs { - enqueue(loc) - } - enqueue(&e.heapLoc) - - var walkgen uint32 - for len(todo) > 0 { - root := todo[len(todo)-1] - todo = todo[:len(todo)-1] - root.queued = false - - walkgen++ - e.walkOne(root, walkgen, enqueue) - } -} - -// walkOne computes the minimal number of dereferences from root to -// all other locations. -func (e *Escape) walkOne(root *EscLocation, walkgen uint32, enqueue func(*EscLocation)) { - // The data flow graph has negative edges (from addressing - // operations), so we use the Bellman-Ford algorithm. However, - // we don't have to worry about infinite negative cycles since - // we bound intermediate dereference counts to 0. - - root.walkgen = walkgen - root.derefs = 0 - root.dst = nil - - todo := []*EscLocation{root} // LIFO queue - for len(todo) > 0 { - l := todo[len(todo)-1] - todo = todo[:len(todo)-1] - - base := l.derefs - - // If l.derefs < 0, then l's address flows to root. - addressOf := base < 0 - if addressOf { - // For a flow path like "root = &l; l = x", - // l's address flows to root, but x's does - // not. We recognize this by lower bounding - // base at 0. - base = 0 - - // If l's address flows to a non-transient - // location, then l can't be transiently - // allocated. - if !root.transient && l.transient { - l.transient = false - enqueue(l) - } - } - - if e.outlives(root, l) { - // l's value flows to root. If l is a function - // parameter and root is the heap or a - // corresponding result parameter, then record - // that value flow for tagging the function - // later. - if l.isName(PPARAM) { - if (logopt.Enabled() || Debug.m >= 2) && !l.escapes { - if Debug.m >= 2 { - fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", linestr(l.n.Pos), l.n, e.explainLoc(root), base) - } - explanation := e.explainPath(root, l) - if logopt.Enabled() { - logopt.LogOpt(l.n.Pos, "leak", "escape", e.curfn.funcname(), - fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, e.explainLoc(root), base), explanation) - } - } - l.leakTo(root, base) - } - - // If l's address flows somewhere that - // outlives it, then l needs to be heap - // allocated. - if addressOf && !l.escapes { - if logopt.Enabled() || Debug.m >= 2 { - if Debug.m >= 2 { - fmt.Printf("%s: %v escapes to heap:\n", linestr(l.n.Pos), l.n) - } - explanation := e.explainPath(root, l) - if logopt.Enabled() { - logopt.LogOpt(l.n.Pos, "escape", "escape", e.curfn.funcname(), fmt.Sprintf("%v escapes to heap", l.n), explanation) - } - } - l.escapes = true - enqueue(l) - continue - } - } - - for i, edge := range l.edges { - if edge.src.escapes { - continue - } - derefs := base + edge.derefs - if edge.src.walkgen != walkgen || edge.src.derefs > derefs { - edge.src.walkgen = walkgen - edge.src.derefs = derefs - edge.src.dst = l - edge.src.dstEdgeIdx = i - todo = append(todo, edge.src) - } - } - } -} - -// explainPath prints an explanation of how src flows to the walk root. -func (e *Escape) explainPath(root, src *EscLocation) []*logopt.LoggedOpt { - visited := make(map[*EscLocation]bool) - pos := linestr(src.n.Pos) - var explanation []*logopt.LoggedOpt - for { - // Prevent infinite loop. - if visited[src] { - if Debug.m >= 2 { - fmt.Printf("%s: warning: truncated explanation due to assignment cycle; see golang.org/issue/35518\n", pos) - } - break - } - visited[src] = true - dst := src.dst - edge := &dst.edges[src.dstEdgeIdx] - if edge.src != src { - Fatalf("path inconsistency: %v != %v", edge.src, src) - } - - explanation = e.explainFlow(pos, dst, src, edge.derefs, edge.notes, explanation) - - if dst == root { - break - } - src = dst - } - - return explanation -} - -func (e *Escape) explainFlow(pos string, dst, srcloc *EscLocation, derefs int, notes *EscNote, explanation []*logopt.LoggedOpt) []*logopt.LoggedOpt { - ops := "&" - if derefs >= 0 { - ops = strings.Repeat("*", derefs) - } - print := Debug.m >= 2 - - flow := fmt.Sprintf(" flow: %s = %s%v:", e.explainLoc(dst), ops, e.explainLoc(srcloc)) - if print { - fmt.Printf("%s:%s\n", pos, flow) - } - if logopt.Enabled() { - var epos src.XPos - if notes != nil { - epos = notes.where.Pos - } else if srcloc != nil && srcloc.n != nil { - epos = srcloc.n.Pos - } - explanation = append(explanation, logopt.NewLoggedOpt(epos, "escflow", "escape", e.curfn.funcname(), flow)) - } - - for note := notes; note != nil; note = note.next { - if print { - fmt.Printf("%s: from %v (%v) at %s\n", pos, note.where, note.why, linestr(note.where.Pos)) - } - if logopt.Enabled() { - explanation = append(explanation, logopt.NewLoggedOpt(note.where.Pos, "escflow", "escape", e.curfn.funcname(), - fmt.Sprintf(" from %v (%v)", note.where, note.why))) - } - } - return explanation -} - -func (e *Escape) explainLoc(l *EscLocation) string { - if l == &e.heapLoc { - return "{heap}" - } - if l.n == nil { - // TODO(mdempsky): Omit entirely. - return "{temp}" - } - if l.n.Op == ONAME { - return fmt.Sprintf("%v", l.n) - } - return fmt.Sprintf("{storage for %v}", l.n) -} - -// outlives reports whether values stored in l may survive beyond -// other's lifetime if stack allocated. -func (e *Escape) outlives(l, other *EscLocation) bool { - // The heap outlives everything. - if l.escapes { - return true - } - - // We don't know what callers do with returned values, so - // pessimistically we need to assume they flow to the heap and - // outlive everything too. - if l.isName(PPARAMOUT) { - // Exception: Directly called closures can return - // locations allocated outside of them without forcing - // them to the heap. For example: - // - // var u int // okay to stack allocate - // *(func() *int { return &u }()) = 42 - if containsClosure(other.curfn, l.curfn) && l.curfn.Func.Closure.Func.Top&ctxCallee != 0 { - return false - } - - return true - } - - // If l and other are within the same function, then l - // outlives other if it was declared outside other's loop - // scope. For example: - // - // var l *int - // for { - // l = new(int) - // } - if l.curfn == other.curfn && l.loopDepth < other.loopDepth { - return true - } - - // If other is declared within a child closure of where l is - // declared, then l outlives it. For example: - // - // var l *int - // func() { - // l = new(int) - // } - if containsClosure(l.curfn, other.curfn) { - return true - } - - return false -} - -// containsClosure reports whether c is a closure contained within f. -func containsClosure(f, c *Node) bool { - if f.Op != ODCLFUNC || c.Op != ODCLFUNC { - Fatalf("bad containsClosure: %v, %v", f, c) - } - - // Common case. - if f == c { - return false - } - - // Closures within function Foo are named like "Foo.funcN..." - // TODO(mdempsky): Better way to recognize this. - fn := f.Func.Nname.Sym.Name - cn := c.Func.Nname.Sym.Name - return len(cn) > len(fn) && cn[:len(fn)] == fn && cn[len(fn)] == '.' -} - -// leak records that parameter l leaks to sink. -func (l *EscLocation) leakTo(sink *EscLocation, derefs int) { - // If sink is a result parameter that doesn't escape (#44614) - // and we can fit return bits into the escape analysis tag, - // then record as a result leak. - if !sink.escapes && sink.isName(PPARAMOUT) && sink.curfn == l.curfn { - // TODO(mdempsky): Eliminate dependency on Vargen here. - ri := int(sink.n.Name.Vargen) - 1 - if ri < numEscResults { - // Leak to result parameter. - l.paramEsc.AddResult(ri, derefs) - return - } - } - - // Otherwise, record as heap leak. - l.paramEsc.AddHeap(derefs) -} - -func (e *Escape) finish(fns []*Node) { - // Record parameter tags for package export data. - for _, fn := range fns { - fn.Esc = EscFuncTagged - - narg := 0 - for _, fs := range &types.RecvsParams { - for _, f := range fs(fn.Type).Fields().Slice() { - narg++ - f.Note = e.paramTag(fn, narg, f) - } - } - } - - for _, loc := range e.allLocs { - n := loc.n - if n == nil { - continue - } - n.SetOpt(nil) - - // Update n.Esc based on escape analysis results. - - if loc.escapes { - if n.Op != ONAME { - if Debug.m != 0 { - Warnl(n.Pos, "%S escapes to heap", n) - } - if logopt.Enabled() { - logopt.LogOpt(n.Pos, "escape", "escape", e.curfn.funcname()) - } - } - n.Esc = EscHeap - addrescapes(n) - } else { - if Debug.m != 0 && n.Op != ONAME { - Warnl(n.Pos, "%S does not escape", n) - } - n.Esc = EscNone - if loc.transient { - n.SetTransient(true) - } - } - } -} - -func (l *EscLocation) isName(c Class) bool { - return l.n != nil && l.n.Op == ONAME && l.n.Class() == c -} - -const numEscResults = 7 - -// An EscLeaks represents a set of assignment flows from a parameter -// to the heap or to any of its function's (first numEscResults) -// result parameters. -type EscLeaks [1 + numEscResults]uint8 - -// Empty reports whether l is an empty set (i.e., no assignment flows). -func (l EscLeaks) Empty() bool { return l == EscLeaks{} } - -// Heap returns the minimum deref count of any assignment flow from l -// to the heap. If no such flows exist, Heap returns -1. -func (l EscLeaks) Heap() int { return l.get(0) } - -// Result returns the minimum deref count of any assignment flow from -// l to its function's i'th result parameter. If no such flows exist, -// Result returns -1. -func (l EscLeaks) Result(i int) int { return l.get(1 + i) } - -// AddHeap adds an assignment flow from l to the heap. -func (l *EscLeaks) AddHeap(derefs int) { l.add(0, derefs) } - -// AddResult adds an assignment flow from l to its function's i'th -// result parameter. -func (l *EscLeaks) AddResult(i, derefs int) { l.add(1+i, derefs) } - -func (l *EscLeaks) setResult(i, derefs int) { l.set(1+i, derefs) } - -func (l EscLeaks) get(i int) int { return int(l[i]) - 1 } - -func (l *EscLeaks) add(i, derefs int) { - if old := l.get(i); old < 0 || derefs < old { - l.set(i, derefs) - } -} - -func (l *EscLeaks) set(i, derefs int) { - v := derefs + 1 - if v < 0 { - Fatalf("invalid derefs count: %v", derefs) - } - if v > math.MaxUint8 { - v = math.MaxUint8 - } - - l[i] = uint8(v) -} - -// Optimize removes result flow paths that are equal in length or -// longer than the shortest heap flow path. -func (l *EscLeaks) Optimize() { - // If we have a path to the heap, then there's no use in - // keeping equal or longer paths elsewhere. - if x := l.Heap(); x >= 0 { - for i := 0; i < numEscResults; i++ { - if l.Result(i) >= x { - l.setResult(i, -1) - } - } - } -} - -var leakTagCache = map[EscLeaks]string{} - -// Encode converts l into a binary string for export data. -func (l EscLeaks) Encode() string { - if l.Heap() == 0 { - // Space optimization: empty string encodes more - // efficiently in export data. - return "" - } - if s, ok := leakTagCache[l]; ok { - return s - } - - n := len(l) - for n > 0 && l[n-1] == 0 { - n-- - } - s := "esc:" + string(l[:n]) - leakTagCache[l] = s - return s -} - -// ParseLeaks parses a binary string representing an EscLeaks. -func ParseLeaks(s string) EscLeaks { - var l EscLeaks - if !strings.HasPrefix(s, "esc:") { - l.AddHeap(0) - return l - } - copy(l[:], s[4:]) - return l -} diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index c6917e0f810ddb66ee9e8edef76f8e172b9a8cb4..2137f1d1961abf8987383d0cd835459fb37fc2c5 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -5,229 +5,157 @@ package gc import ( + "cmd/compile/internal/base" + "cmd/compile/internal/inline" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/bio" - "cmd/internal/src" "fmt" -) - -var ( - Debug_export int // if set, print debugging information about export data + "go/constant" ) func exportf(bout *bio.Writer, format string, args ...interface{}) { fmt.Fprintf(bout, format, args...) - if Debug_export != 0 { + if base.Debug.Export != 0 { fmt.Printf(format, args...) } } -var asmlist []*Node - -// exportsym marks n for export (or reexport). -func exportsym(n *Node) { - if n.Sym.OnExportList() { - return - } - n.Sym.SetOnExportList(true) - - if Debug.E != 0 { - fmt.Printf("export symbol %v\n", n.Sym) - } - - exportlist = append(exportlist, n) -} - -func initname(s string) bool { - return s == "init" -} - -func autoexport(n *Node, ctxt Class) { - if n.Sym.Pkg != localpkg { - return - } - if (ctxt != PEXTERN && ctxt != PFUNC) || dclcontext != PEXTERN { - return - } - if n.Type != nil && n.Type.IsKind(TFUNC) && n.IsMethod() { - return - } - - if types.IsExported(n.Sym.Name) || initname(n.Sym.Name) { - exportsym(n) - } - if asmhdr != "" && !n.Sym.Asm() { - n.Sym.SetAsm(true) - asmlist = append(asmlist, n) +func dumpexport(bout *bio.Writer) { + p := &exporter{marked: make(map[*types.Type]bool)} + for _, n := range typecheck.Target.Exports { + // Must catch it here rather than Export(), because the type can be + // not fully set (still TFORW) when Export() is called. + if n.Type() != nil && n.Type().HasTParam() { + base.Fatalf("Cannot (yet) export a generic type: %v", n) + } + p.markObject(n) } -} -func dumpexport(bout *bio.Writer) { // The linker also looks for the $$ marker - use char after $$ to distinguish format. exportf(bout, "\n$$B\n") // indicate binary export format off := bout.Offset() - iexport(bout.Writer) + typecheck.WriteExports(bout.Writer) size := bout.Offset() - off exportf(bout, "\n$$\n") - if Debug_export != 0 { - fmt.Printf("BenchmarkExportSize:%s 1 %d bytes\n", myimportpath, size) + if base.Debug.Export != 0 { + fmt.Printf("BenchmarkExportSize:%s 1 %d bytes\n", base.Ctxt.Pkgpath, size) } } -func importsym(ipkg *types.Pkg, s *types.Sym, op Op) *Node { - n := asNode(s.PkgDef()) - if n == nil { - // iimport should have created a stub ONONAME - // declaration for all imported symbols. The exception - // is declarations for Runtimepkg, which are populated - // by loadsys instead. - if s.Pkg != Runtimepkg { - Fatalf("missing ONONAME for %v\n", s) - } - - n = dclname(s) - s.SetPkgDef(asTypesNode(n)) - s.Importdef = ipkg - } - if n.Op != ONONAME && n.Op != op { - redeclare(lineno, s, fmt.Sprintf("during import %q", ipkg.Path)) - } - return n -} - -// importtype returns the named type declared by symbol s. -// If no such type has been declared yet, a forward declaration is returned. -// ipkg is the package being imported -func importtype(ipkg *types.Pkg, pos src.XPos, s *types.Sym) *types.Type { - n := importsym(ipkg, s, OTYPE) - if n.Op != OTYPE { - t := types.New(TFORW) - t.Sym = s - t.Nod = asTypesNode(n) - - n.Op = OTYPE - n.Pos = pos - n.Type = t - n.SetClass(PEXTERN) - } - - t := n.Type - if t == nil { - Fatalf("importtype %v", s) +func dumpasmhdr() { + b, err := bio.Create(base.Flag.AsmHdr) + if err != nil { + base.Fatalf("%v", err) } - return t -} + fmt.Fprintf(b, "// generated by compile -asmhdr from package %s\n\n", types.LocalPkg.Name) + for _, n := range typecheck.Target.Asms { + if n.Sym().IsBlank() { + continue + } + switch n.Op() { + case ir.OLITERAL: + t := n.Val().Kind() + if t == constant.Float || t == constant.Complex { + break + } + fmt.Fprintf(b, "#define const_%s %#v\n", n.Sym().Name, n.Val()) -// importobj declares symbol s as an imported object representable by op. -// ipkg is the package being imported -func importobj(ipkg *types.Pkg, pos src.XPos, s *types.Sym, op Op, ctxt Class, t *types.Type) *Node { - n := importsym(ipkg, s, op) - if n.Op != ONONAME { - if n.Op == op && (n.Class() != ctxt || !types.Identical(n.Type, t)) { - redeclare(lineno, s, fmt.Sprintf("during import %q", ipkg.Path)) + case ir.OTYPE: + t := n.Type() + if !t.IsStruct() || t.StructType().Map != nil || t.IsFuncArgStruct() { + break + } + fmt.Fprintf(b, "#define %s__size %d\n", n.Sym().Name, int(t.Width)) + for _, f := range t.Fields().Slice() { + if !f.Sym.IsBlank() { + fmt.Fprintf(b, "#define %s_%s %d\n", n.Sym().Name, f.Sym.Name, int(f.Offset)) + } + } } - return nil } - n.Op = op - n.Pos = pos - n.SetClass(ctxt) - if ctxt == PFUNC { - n.Sym.SetFunc(true) - } - n.Type = t - return n + b.Close() } -// importconst declares symbol s as an imported constant with type t and value val. -// ipkg is the package being imported -func importconst(ipkg *types.Pkg, pos src.XPos, s *types.Sym, t *types.Type, val Val) { - n := importobj(ipkg, pos, s, OLITERAL, PEXTERN, t) - if n == nil { // TODO: Check that value matches. - return - } - - n.SetVal(val) - - if Debug.E != 0 { - fmt.Printf("import const %v %L = %v\n", s, t, val) - } +type exporter struct { + marked map[*types.Type]bool // types already seen by markType } -// importfunc declares symbol s as an imported function with type t. -// ipkg is the package being imported -func importfunc(ipkg *types.Pkg, pos src.XPos, s *types.Sym, t *types.Type) { - n := importobj(ipkg, pos, s, ONAME, PFUNC, t) - if n == nil { - return +// markObject visits a reachable object. +func (p *exporter) markObject(n ir.Node) { + if n.Op() == ir.ONAME { + n := n.(*ir.Name) + if n.Class == ir.PFUNC { + inline.Inline_Flood(n, typecheck.Export) + } } - n.Func = new(Func) - t.SetNname(asTypesNode(n)) - - if Debug.E != 0 { - fmt.Printf("import func %v%S\n", s, t) - } + p.markType(n.Type()) } -// importvar declares symbol s as an imported variable with type t. -// ipkg is the package being imported -func importvar(ipkg *types.Pkg, pos src.XPos, s *types.Sym, t *types.Type) { - n := importobj(ipkg, pos, s, ONAME, PEXTERN, t) - if n == nil { +// markType recursively visits types reachable from t to identify +// functions whose inline bodies may be needed. +func (p *exporter) markType(t *types.Type) { + if p.marked[t] { return } - - if Debug.E != 0 { - fmt.Printf("import var %v %L\n", s, t) + p.marked[t] = true + + // If this is a named type, mark all of its associated + // methods. Skip interface types because t.Methods contains + // only their unexpanded method set (i.e., exclusive of + // interface embeddings), and the switch statement below + // handles their full method set. + if t.Sym() != nil && t.Kind() != types.TINTER { + for _, m := range t.Methods().Slice() { + if types.IsExported(m.Sym.Name) { + p.markObject(ir.AsNode(m.Nname)) + } + } } -} -// importalias declares symbol s as an imported type alias with type t. -// ipkg is the package being imported -func importalias(ipkg *types.Pkg, pos src.XPos, s *types.Sym, t *types.Type) { - n := importobj(ipkg, pos, s, OTYPE, PEXTERN, t) - if n == nil { - return - } + // Recursively mark any types that can be produced given a + // value of type t: dereferencing a pointer; indexing or + // iterating over an array, slice, or map; receiving from a + // channel; accessing a struct field or interface method; or + // calling a function. + // + // Notably, we don't mark function parameter types, because + // the user already needs some way to construct values of + // those types. + switch t.Kind() { + case types.TPTR, types.TARRAY, types.TSLICE: + p.markType(t.Elem()) + + case types.TCHAN: + if t.ChanDir().CanRecv() { + p.markType(t.Elem()) + } - if Debug.E != 0 { - fmt.Printf("import type %v = %L\n", s, t) - } -} + case types.TMAP: + p.markType(t.Key()) + p.markType(t.Elem()) -func dumpasmhdr() { - b, err := bio.Create(asmhdr) - if err != nil { - Fatalf("%v", err) - } - fmt.Fprintf(b, "// generated by compile -asmhdr from package %s\n\n", localpkg.Name) - for _, n := range asmlist { - if n.Sym.IsBlank() { - continue - } - switch n.Op { - case OLITERAL: - t := n.Val().Ctype() - if t == CTFLT || t == CTCPLX { - break + case types.TSTRUCT: + for _, f := range t.FieldSlice() { + if types.IsExported(f.Sym.Name) || f.Embedded != 0 { + p.markType(f.Type) } - fmt.Fprintf(b, "#define const_%s %#v\n", n.Sym.Name, n.Val()) + } - case OTYPE: - t := n.Type - if !t.IsStruct() || t.StructType().Map != nil || t.IsFuncArgStruct() { - break - } - fmt.Fprintf(b, "#define %s__size %d\n", n.Sym.Name, int(t.Width)) - for _, f := range t.Fields().Slice() { - if !f.Sym.IsBlank() { - fmt.Fprintf(b, "#define %s_%s %d\n", n.Sym.Name, f.Sym.Name, int(f.Offset)) - } + case types.TFUNC: + for _, f := range t.Results().FieldSlice() { + p.markType(f.Type) + } + + case types.TINTER: + for _, f := range t.AllMethods().Slice() { + if types.IsExported(f.Sym.Name) { + p.markType(f.Type) } } } - - b.Close() } diff --git a/src/cmd/compile/internal/gc/fmt.go b/src/cmd/compile/internal/gc/fmt.go deleted file mode 100644 index f92f5d0e884a7ad53748bf42f7c5423bc9130c1b..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/fmt.go +++ /dev/null @@ -1,1986 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "bytes" - "cmd/compile/internal/types" - "cmd/internal/src" - "fmt" - "io" - "strconv" - "strings" - "sync" - "unicode/utf8" -) - -// A FmtFlag value is a set of flags (or 0). -// They control how the Xconv functions format their values. -// See the respective function's documentation for details. -type FmtFlag int - -const ( // fmt.Format flag/prec or verb - FmtLeft FmtFlag = 1 << iota // '-' - FmtSharp // '#' - FmtSign // '+' - FmtUnsigned // internal use only (historic: u flag) - FmtShort // verb == 'S' (historic: h flag) - FmtLong // verb == 'L' (historic: l flag) - FmtComma // '.' (== hasPrec) (historic: , flag) - FmtByte // '0' (historic: hh flag) -) - -// fmtFlag computes the (internal) FmtFlag -// value given the fmt.State and format verb. -func fmtFlag(s fmt.State, verb rune) FmtFlag { - var flag FmtFlag - if s.Flag('-') { - flag |= FmtLeft - } - if s.Flag('#') { - flag |= FmtSharp - } - if s.Flag('+') { - flag |= FmtSign - } - if s.Flag(' ') { - Fatalf("FmtUnsigned in format string") - } - if _, ok := s.Precision(); ok { - flag |= FmtComma - } - if s.Flag('0') { - flag |= FmtByte - } - switch verb { - case 'S': - flag |= FmtShort - case 'L': - flag |= FmtLong - } - return flag -} - -// Format conversions: -// TODO(gri) verify these; eliminate those not used anymore -// -// %v Op Node opcodes -// Flags: #: print Go syntax (automatic unless mode == FDbg) -// -// %j *Node Node details -// Flags: 0: suppresses things not relevant until walk -// -// %v *Val Constant values -// -// %v *types.Sym Symbols -// %S unqualified identifier in any mode -// Flags: +,- #: mode (see below) -// 0: in export mode: unqualified identifier if exported, qualified if not -// -// %v *types.Type Types -// %S omit "func" and receiver in function types -// %L definition instead of name. -// Flags: +,- #: mode (see below) -// ' ' (only in -/Sym mode) print type identifiers wit package name instead of prefix. -// -// %v *Node Nodes -// %S (only in +/debug mode) suppress recursion -// %L (only in Error mode) print "foo (type Bar)" -// Flags: +,- #: mode (see below) -// -// %v Nodes Node lists -// Flags: those of *Node -// .: separate items with ',' instead of ';' - -// *types.Sym, *types.Type, and *Node types use the flags below to set the format mode -const ( - FErr fmtMode = iota - FDbg - FTypeId - FTypeIdName // same as FTypeId, but use package name instead of prefix -) - -// The mode flags '+', '-', and '#' are sticky; they persist through -// recursions of *Node, *types.Type, and *types.Sym values. The ' ' flag is -// sticky only on *types.Type recursions and only used in %-/*types.Sym mode. -// -// Example: given a *types.Sym: %+v %#v %-v print an identifier properly qualified for debug/export/internal mode - -// Useful format combinations: -// TODO(gri): verify these -// -// *Node, Nodes: -// %+v multiline recursive debug dump of *Node/Nodes -// %+S non-recursive debug dump -// -// *Node: -// %#v Go format -// %L "foo (type Bar)" for error messages -// -// *types.Type: -// %#v Go format -// %#L type definition instead of name -// %#S omit "func" and receiver in function signature -// -// %-v type identifiers -// %-S type identifiers without "func" and arg names in type signatures (methodsym) -// %- v type identifiers with package name instead of prefix (typesym, dcommontype, typehash) - -// update returns the results of applying f to mode. -func (f FmtFlag) update(mode fmtMode) (FmtFlag, fmtMode) { - switch { - case f&FmtSign != 0: - mode = FDbg - case f&FmtSharp != 0: - // ignore (textual export format no longer supported) - case f&FmtUnsigned != 0: - mode = FTypeIdName - case f&FmtLeft != 0: - mode = FTypeId - } - - f &^= FmtSharp | FmtLeft | FmtSign - return f, mode -} - -var goopnames = []string{ - OADDR: "&", - OADD: "+", - OADDSTR: "+", - OALIGNOF: "unsafe.Alignof", - OANDAND: "&&", - OANDNOT: "&^", - OAND: "&", - OAPPEND: "append", - OAS: "=", - OAS2: "=", - OBREAK: "break", - OCALL: "function call", // not actual syntax - OCAP: "cap", - OCASE: "case", - OCLOSE: "close", - OCOMPLEX: "complex", - OBITNOT: "^", - OCONTINUE: "continue", - OCOPY: "copy", - ODELETE: "delete", - ODEFER: "defer", - ODIV: "/", - OEQ: "==", - OFALL: "fallthrough", - OFOR: "for", - OFORUNTIL: "foruntil", // not actual syntax; used to avoid off-end pointer live on backedge.892 - OGE: ">=", - OGOTO: "goto", - OGT: ">", - OIF: "if", - OIMAG: "imag", - OINLMARK: "inlmark", - ODEREF: "*", - OLEN: "len", - OLE: "<=", - OLSH: "<<", - OLT: "<", - OMAKE: "make", - ONEG: "-", - OMOD: "%", - OMUL: "*", - ONEW: "new", - ONE: "!=", - ONOT: "!", - OOFFSETOF: "unsafe.Offsetof", - OOROR: "||", - OOR: "|", - OPANIC: "panic", - OPLUS: "+", - OPRINTN: "println", - OPRINT: "print", - ORANGE: "range", - OREAL: "real", - ORECV: "<-", - ORECOVER: "recover", - ORETURN: "return", - ORSH: ">>", - OSELECT: "select", - OSEND: "<-", - OSIZEOF: "unsafe.Sizeof", - OSUB: "-", - OSWITCH: "switch", - OXOR: "^", -} - -func (o Op) GoString() string { - return fmt.Sprintf("%#v", o) -} - -func (o Op) format(s fmt.State, verb rune, mode fmtMode) { - switch verb { - case 'v': - o.oconv(s, fmtFlag(s, verb), mode) - - default: - fmt.Fprintf(s, "%%!%c(Op=%d)", verb, int(o)) - } -} - -func (o Op) oconv(s fmt.State, flag FmtFlag, mode fmtMode) { - if flag&FmtSharp != 0 || mode != FDbg { - if int(o) < len(goopnames) && goopnames[o] != "" { - fmt.Fprint(s, goopnames[o]) - return - } - } - - // 'o.String()' instead of just 'o' to avoid infinite recursion - fmt.Fprint(s, o.String()) -} - -type ( - fmtMode int - - fmtNodeErr Node - fmtNodeDbg Node - fmtNodeTypeId Node - fmtNodeTypeIdName Node - - fmtOpErr Op - fmtOpDbg Op - fmtOpTypeId Op - fmtOpTypeIdName Op - - fmtTypeErr types.Type - fmtTypeDbg types.Type - fmtTypeTypeId types.Type - fmtTypeTypeIdName types.Type - - fmtSymErr types.Sym - fmtSymDbg types.Sym - fmtSymTypeId types.Sym - fmtSymTypeIdName types.Sym - - fmtNodesErr Nodes - fmtNodesDbg Nodes - fmtNodesTypeId Nodes - fmtNodesTypeIdName Nodes -) - -func (n *fmtNodeErr) Format(s fmt.State, verb rune) { (*Node)(n).format(s, verb, FErr) } -func (n *fmtNodeDbg) Format(s fmt.State, verb rune) { (*Node)(n).format(s, verb, FDbg) } -func (n *fmtNodeTypeId) Format(s fmt.State, verb rune) { (*Node)(n).format(s, verb, FTypeId) } -func (n *fmtNodeTypeIdName) Format(s fmt.State, verb rune) { (*Node)(n).format(s, verb, FTypeIdName) } -func (n *Node) Format(s fmt.State, verb rune) { n.format(s, verb, FErr) } - -func (o fmtOpErr) Format(s fmt.State, verb rune) { Op(o).format(s, verb, FErr) } -func (o fmtOpDbg) Format(s fmt.State, verb rune) { Op(o).format(s, verb, FDbg) } -func (o fmtOpTypeId) Format(s fmt.State, verb rune) { Op(o).format(s, verb, FTypeId) } -func (o fmtOpTypeIdName) Format(s fmt.State, verb rune) { Op(o).format(s, verb, FTypeIdName) } -func (o Op) Format(s fmt.State, verb rune) { o.format(s, verb, FErr) } - -func (t *fmtTypeErr) Format(s fmt.State, verb rune) { typeFormat((*types.Type)(t), s, verb, FErr) } -func (t *fmtTypeDbg) Format(s fmt.State, verb rune) { typeFormat((*types.Type)(t), s, verb, FDbg) } -func (t *fmtTypeTypeId) Format(s fmt.State, verb rune) { - typeFormat((*types.Type)(t), s, verb, FTypeId) -} -func (t *fmtTypeTypeIdName) Format(s fmt.State, verb rune) { - typeFormat((*types.Type)(t), s, verb, FTypeIdName) -} - -// func (t *types.Type) Format(s fmt.State, verb rune) // in package types - -func (y *fmtSymErr) Format(s fmt.State, verb rune) { symFormat((*types.Sym)(y), s, verb, FErr) } -func (y *fmtSymDbg) Format(s fmt.State, verb rune) { symFormat((*types.Sym)(y), s, verb, FDbg) } -func (y *fmtSymTypeId) Format(s fmt.State, verb rune) { symFormat((*types.Sym)(y), s, verb, FTypeId) } -func (y *fmtSymTypeIdName) Format(s fmt.State, verb rune) { - symFormat((*types.Sym)(y), s, verb, FTypeIdName) -} - -// func (y *types.Sym) Format(s fmt.State, verb rune) // in package types { y.format(s, verb, FErr) } - -func (n fmtNodesErr) Format(s fmt.State, verb rune) { (Nodes)(n).format(s, verb, FErr) } -func (n fmtNodesDbg) Format(s fmt.State, verb rune) { (Nodes)(n).format(s, verb, FDbg) } -func (n fmtNodesTypeId) Format(s fmt.State, verb rune) { (Nodes)(n).format(s, verb, FTypeId) } -func (n fmtNodesTypeIdName) Format(s fmt.State, verb rune) { (Nodes)(n).format(s, verb, FTypeIdName) } -func (n Nodes) Format(s fmt.State, verb rune) { n.format(s, verb, FErr) } - -func (m fmtMode) Fprintf(s fmt.State, format string, args ...interface{}) { - m.prepareArgs(args) - fmt.Fprintf(s, format, args...) -} - -func (m fmtMode) Sprintf(format string, args ...interface{}) string { - m.prepareArgs(args) - return fmt.Sprintf(format, args...) -} - -func (m fmtMode) Sprint(args ...interface{}) string { - m.prepareArgs(args) - return fmt.Sprint(args...) -} - -func (m fmtMode) prepareArgs(args []interface{}) { - switch m { - case FErr: - for i, arg := range args { - switch arg := arg.(type) { - case Op: - args[i] = fmtOpErr(arg) - case *Node: - args[i] = (*fmtNodeErr)(arg) - case *types.Type: - args[i] = (*fmtTypeErr)(arg) - case *types.Sym: - args[i] = (*fmtSymErr)(arg) - case Nodes: - args[i] = fmtNodesErr(arg) - case Val, int32, int64, string, types.EType: - // OK: printing these types doesn't depend on mode - default: - Fatalf("mode.prepareArgs type %T", arg) - } - } - case FDbg: - for i, arg := range args { - switch arg := arg.(type) { - case Op: - args[i] = fmtOpDbg(arg) - case *Node: - args[i] = (*fmtNodeDbg)(arg) - case *types.Type: - args[i] = (*fmtTypeDbg)(arg) - case *types.Sym: - args[i] = (*fmtSymDbg)(arg) - case Nodes: - args[i] = fmtNodesDbg(arg) - case Val, int32, int64, string, types.EType: - // OK: printing these types doesn't depend on mode - default: - Fatalf("mode.prepareArgs type %T", arg) - } - } - case FTypeId: - for i, arg := range args { - switch arg := arg.(type) { - case Op: - args[i] = fmtOpTypeId(arg) - case *Node: - args[i] = (*fmtNodeTypeId)(arg) - case *types.Type: - args[i] = (*fmtTypeTypeId)(arg) - case *types.Sym: - args[i] = (*fmtSymTypeId)(arg) - case Nodes: - args[i] = fmtNodesTypeId(arg) - case Val, int32, int64, string, types.EType: - // OK: printing these types doesn't depend on mode - default: - Fatalf("mode.prepareArgs type %T", arg) - } - } - case FTypeIdName: - for i, arg := range args { - switch arg := arg.(type) { - case Op: - args[i] = fmtOpTypeIdName(arg) - case *Node: - args[i] = (*fmtNodeTypeIdName)(arg) - case *types.Type: - args[i] = (*fmtTypeTypeIdName)(arg) - case *types.Sym: - args[i] = (*fmtSymTypeIdName)(arg) - case Nodes: - args[i] = fmtNodesTypeIdName(arg) - case Val, int32, int64, string, types.EType: - // OK: printing these types doesn't depend on mode - default: - Fatalf("mode.prepareArgs type %T", arg) - } - } - default: - Fatalf("mode.prepareArgs mode %d", m) - } -} - -func (n *Node) format(s fmt.State, verb rune, mode fmtMode) { - switch verb { - case 'v', 'S', 'L': - n.nconv(s, fmtFlag(s, verb), mode) - - case 'j': - n.jconv(s, fmtFlag(s, verb)) - - default: - fmt.Fprintf(s, "%%!%c(*Node=%p)", verb, n) - } -} - -// *Node details -func (n *Node) jconv(s fmt.State, flag FmtFlag) { - c := flag & FmtShort - - // Useful to see which nodes in a Node Dump/dumplist are actually identical - if Debug_dumpptrs != 0 { - fmt.Fprintf(s, " p(%p)", n) - } - if c == 0 && n.Name != nil && n.Name.Vargen != 0 { - fmt.Fprintf(s, " g(%d)", n.Name.Vargen) - } - - if Debug_dumpptrs != 0 && c == 0 && n.Name != nil && n.Name.Defn != nil { - // Useful to see where Defn is set and what node it points to - fmt.Fprintf(s, " defn(%p)", n.Name.Defn) - } - - if n.Pos.IsKnown() { - pfx := "" - switch n.Pos.IsStmt() { - case src.PosNotStmt: - pfx = "_" // "-" would be confusing - case src.PosIsStmt: - pfx = "+" - } - fmt.Fprintf(s, " l(%s%d)", pfx, n.Pos.Line()) - } - - if c == 0 && n.Xoffset != BADWIDTH { - fmt.Fprintf(s, " x(%d)", n.Xoffset) - } - - if n.Class() != 0 { - fmt.Fprintf(s, " class(%v)", n.Class()) - } - - if n.Colas() { - fmt.Fprintf(s, " colas(%v)", n.Colas()) - } - - switch n.Esc { - case EscUnknown: - break - - case EscHeap: - fmt.Fprint(s, " esc(h)") - - case EscNone: - fmt.Fprint(s, " esc(no)") - - case EscNever: - if c == 0 { - fmt.Fprint(s, " esc(N)") - } - - default: - fmt.Fprintf(s, " esc(%d)", n.Esc) - } - - if e, ok := n.Opt().(*EscLocation); ok && e.loopDepth != 0 { - fmt.Fprintf(s, " ld(%d)", e.loopDepth) - } - - if c == 0 && n.Typecheck() != 0 { - fmt.Fprintf(s, " tc(%d)", n.Typecheck()) - } - - if n.IsDDD() { - fmt.Fprintf(s, " isddd(%v)", n.IsDDD()) - } - - if n.Implicit() { - fmt.Fprintf(s, " implicit(%v)", n.Implicit()) - } - - if n.Embedded() { - fmt.Fprintf(s, " embedded") - } - - if n.Op == ONAME { - if n.Name.Addrtaken() { - fmt.Fprint(s, " addrtaken") - } - if n.Name.Assigned() { - fmt.Fprint(s, " assigned") - } - if n.Name.IsClosureVar() { - fmt.Fprint(s, " closurevar") - } - if n.Name.Captured() { - fmt.Fprint(s, " captured") - } - if n.Name.IsOutputParamHeapAddr() { - fmt.Fprint(s, " outputparamheapaddr") - } - } - if n.Bounded() { - fmt.Fprint(s, " bounded") - } - if n.NonNil() { - fmt.Fprint(s, " nonnil") - } - - if c == 0 && n.HasCall() { - fmt.Fprint(s, " hascall") - } - - if c == 0 && n.Name != nil && n.Name.Used() { - fmt.Fprint(s, " used") - } -} - -func (v Val) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - v.vconv(s, fmtFlag(s, verb)) - - default: - fmt.Fprintf(s, "%%!%c(Val=%T)", verb, v) - } -} - -func (v Val) vconv(s fmt.State, flag FmtFlag) { - switch u := v.U.(type) { - case *Mpint: - if !u.Rune { - if flag&FmtSharp != 0 { - fmt.Fprint(s, u.String()) - return - } - fmt.Fprint(s, u.GoString()) - return - } - - switch x := u.Int64(); { - case ' ' <= x && x < utf8.RuneSelf && x != '\\' && x != '\'': - fmt.Fprintf(s, "'%c'", int(x)) - - case 0 <= x && x < 1<<16: - fmt.Fprintf(s, "'\\u%04x'", uint(int(x))) - - case 0 <= x && x <= utf8.MaxRune: - fmt.Fprintf(s, "'\\U%08x'", uint64(x)) - - default: - fmt.Fprintf(s, "('\\x00' + %v)", u) - } - - case *Mpflt: - if flag&FmtSharp != 0 { - fmt.Fprint(s, u.String()) - return - } - fmt.Fprint(s, u.GoString()) - return - - case *Mpcplx: - if flag&FmtSharp != 0 { - fmt.Fprint(s, u.String()) - return - } - fmt.Fprint(s, u.GoString()) - return - - case string: - fmt.Fprint(s, strconv.Quote(u)) - - case bool: - fmt.Fprint(s, u) - - case *NilVal: - fmt.Fprint(s, "nil") - - default: - fmt.Fprintf(s, "", v.Ctype()) - } -} - -/* -s%,%,\n%g -s%\n+%\n%g -s%^[ ]*T%%g -s%,.*%%g -s%.+% [T&] = "&",%g -s%^ ........*\]%&~%g -s%~ %%g -*/ - -func symfmt(b *bytes.Buffer, s *types.Sym, flag FmtFlag, mode fmtMode) { - if flag&FmtShort == 0 { - switch mode { - case FErr: // This is for the user - if s.Pkg == builtinpkg || s.Pkg == localpkg { - b.WriteString(s.Name) - return - } - - // If the name was used by multiple packages, display the full path, - if s.Pkg.Name != "" && numImport[s.Pkg.Name] > 1 { - fmt.Fprintf(b, "%q.%s", s.Pkg.Path, s.Name) - return - } - b.WriteString(s.Pkg.Name) - b.WriteByte('.') - b.WriteString(s.Name) - return - - case FDbg: - b.WriteString(s.Pkg.Name) - b.WriteByte('.') - b.WriteString(s.Name) - return - - case FTypeIdName: - // dcommontype, typehash - b.WriteString(s.Pkg.Name) - b.WriteByte('.') - b.WriteString(s.Name) - return - - case FTypeId: - // (methodsym), typesym, weaksym - b.WriteString(s.Pkg.Prefix) - b.WriteByte('.') - b.WriteString(s.Name) - return - } - } - - if flag&FmtByte != 0 { - // FmtByte (hh) implies FmtShort (h) - // skip leading "type." in method name - name := s.Name - if i := strings.LastIndex(name, "."); i >= 0 { - name = name[i+1:] - } - - if mode == FDbg { - fmt.Fprintf(b, "@%q.%s", s.Pkg.Path, name) - return - } - - b.WriteString(name) - return - } - - b.WriteString(s.Name) -} - -var basicnames = []string{ - TINT: "int", - TUINT: "uint", - TINT8: "int8", - TUINT8: "uint8", - TINT16: "int16", - TUINT16: "uint16", - TINT32: "int32", - TUINT32: "uint32", - TINT64: "int64", - TUINT64: "uint64", - TUINTPTR: "uintptr", - TFLOAT32: "float32", - TFLOAT64: "float64", - TCOMPLEX64: "complex64", - TCOMPLEX128: "complex128", - TBOOL: "bool", - TANY: "any", - TSTRING: "string", - TNIL: "nil", - TIDEAL: "untyped number", - TBLANK: "blank", -} - -var fmtBufferPool = sync.Pool{ - New: func() interface{} { - return new(bytes.Buffer) - }, -} - -func tconv(t *types.Type, flag FmtFlag, mode fmtMode) string { - buf := fmtBufferPool.Get().(*bytes.Buffer) - buf.Reset() - defer fmtBufferPool.Put(buf) - - tconv2(buf, t, flag, mode, nil) - return types.InternString(buf.Bytes()) -} - -// tconv2 writes a string representation of t to b. -// flag and mode control exactly what is printed. -// Any types x that are already in the visited map get printed as @%d where %d=visited[x]. -// See #16897 before changing the implementation of tconv. -func tconv2(b *bytes.Buffer, t *types.Type, flag FmtFlag, mode fmtMode, visited map[*types.Type]int) { - if off, ok := visited[t]; ok { - // We've seen this type before, so we're trying to print it recursively. - // Print a reference to it instead. - fmt.Fprintf(b, "@%d", off) - return - } - if t == nil { - b.WriteString("") - return - } - if t.Etype == types.TSSA { - b.WriteString(t.Extra.(string)) - return - } - if t.Etype == types.TTUPLE { - b.WriteString(t.FieldType(0).String()) - b.WriteByte(',') - b.WriteString(t.FieldType(1).String()) - return - } - - if t.Etype == types.TRESULTS { - tys := t.Extra.(*types.Results).Types - for i, et := range tys { - if i > 0 { - b.WriteByte(',') - } - b.WriteString(et.String()) - } - return - } - - flag, mode = flag.update(mode) - if mode == FTypeIdName { - flag |= FmtUnsigned - } - if t == types.Bytetype || t == types.Runetype { - // in %-T mode collapse rune and byte with their originals. - switch mode { - case FTypeIdName, FTypeId: - t = types.Types[t.Etype] - default: - sconv2(b, t.Sym, FmtShort, mode) - return - } - } - if t == types.Errortype { - b.WriteString("error") - return - } - - // Unless the 'L' flag was specified, if the type has a name, just print that name. - if flag&FmtLong == 0 && t.Sym != nil && t != types.Types[t.Etype] { - switch mode { - case FTypeId, FTypeIdName: - if flag&FmtShort != 0 { - if t.Vargen != 0 { - sconv2(b, t.Sym, FmtShort, mode) - fmt.Fprintf(b, "·%d", t.Vargen) - return - } - sconv2(b, t.Sym, FmtShort, mode) - return - } - - if mode == FTypeIdName { - sconv2(b, t.Sym, FmtUnsigned, mode) - return - } - - if t.Sym.Pkg == localpkg && t.Vargen != 0 { - b.WriteString(mode.Sprintf("%v·%d", t.Sym, t.Vargen)) - return - } - } - - sconv2(b, t.Sym, 0, mode) - return - } - - if int(t.Etype) < len(basicnames) && basicnames[t.Etype] != "" { - var name string - switch t { - case types.UntypedBool: - name = "untyped bool" - case types.UntypedString: - name = "untyped string" - case types.UntypedInt: - name = "untyped int" - case types.UntypedRune: - name = "untyped rune" - case types.UntypedFloat: - name = "untyped float" - case types.UntypedComplex: - name = "untyped complex" - default: - name = basicnames[t.Etype] - } - b.WriteString(name) - return - } - - if mode == FDbg { - b.WriteString(t.Etype.String()) - b.WriteByte('-') - tconv2(b, t, flag, FErr, visited) - return - } - - // At this point, we might call tconv2 recursively. Add the current type to the visited list so we don't - // try to print it recursively. - // We record the offset in the result buffer where the type's text starts. This offset serves as a reference - // point for any later references to the same type. - // Note that we remove the type from the visited map as soon as the recursive call is done. - // This prevents encoding types like map[*int]*int as map[*int]@4. (That encoding would work, - // but I'd like to use the @ notation only when strictly necessary.) - if visited == nil { - visited = map[*types.Type]int{} - } - visited[t] = b.Len() - defer delete(visited, t) - - switch t.Etype { - case TPTR: - b.WriteByte('*') - switch mode { - case FTypeId, FTypeIdName: - if flag&FmtShort != 0 { - tconv2(b, t.Elem(), FmtShort, mode, visited) - return - } - } - tconv2(b, t.Elem(), 0, mode, visited) - - case TARRAY: - b.WriteByte('[') - b.WriteString(strconv.FormatInt(t.NumElem(), 10)) - b.WriteByte(']') - tconv2(b, t.Elem(), 0, mode, visited) - - case TSLICE: - b.WriteString("[]") - tconv2(b, t.Elem(), 0, mode, visited) - - case TCHAN: - switch t.ChanDir() { - case types.Crecv: - b.WriteString("<-chan ") - tconv2(b, t.Elem(), 0, mode, visited) - case types.Csend: - b.WriteString("chan<- ") - tconv2(b, t.Elem(), 0, mode, visited) - default: - b.WriteString("chan ") - if t.Elem() != nil && t.Elem().IsChan() && t.Elem().Sym == nil && t.Elem().ChanDir() == types.Crecv { - b.WriteByte('(') - tconv2(b, t.Elem(), 0, mode, visited) - b.WriteByte(')') - } else { - tconv2(b, t.Elem(), 0, mode, visited) - } - } - - case TMAP: - b.WriteString("map[") - tconv2(b, t.Key(), 0, mode, visited) - b.WriteByte(']') - tconv2(b, t.Elem(), 0, mode, visited) - - case TINTER: - if t.IsEmptyInterface() { - b.WriteString("interface {}") - break - } - b.WriteString("interface {") - for i, f := range t.Fields().Slice() { - if i != 0 { - b.WriteByte(';') - } - b.WriteByte(' ') - switch { - case f.Sym == nil: - // Check first that a symbol is defined for this type. - // Wrong interface definitions may have types lacking a symbol. - break - case types.IsExported(f.Sym.Name): - sconv2(b, f.Sym, FmtShort, mode) - default: - flag1 := FmtLeft - if flag&FmtUnsigned != 0 { - flag1 = FmtUnsigned - } - sconv2(b, f.Sym, flag1, mode) - } - tconv2(b, f.Type, FmtShort, mode, visited) - } - if t.NumFields() != 0 { - b.WriteByte(' ') - } - b.WriteByte('}') - - case TFUNC: - if flag&FmtShort != 0 { - // no leading func - } else { - if t.Recv() != nil { - b.WriteString("method") - tconv2(b, t.Recvs(), 0, mode, visited) - b.WriteByte(' ') - } - b.WriteString("func") - } - tconv2(b, t.Params(), 0, mode, visited) - - switch t.NumResults() { - case 0: - // nothing to do - - case 1: - b.WriteByte(' ') - tconv2(b, t.Results().Field(0).Type, 0, mode, visited) // struct->field->field's type - - default: - b.WriteByte(' ') - tconv2(b, t.Results(), 0, mode, visited) - } - - case TSTRUCT: - if m := t.StructType().Map; m != nil { - mt := m.MapType() - // Format the bucket struct for map[x]y as map.bucket[x]y. - // This avoids a recursive print that generates very long names. - switch t { - case mt.Bucket: - b.WriteString("map.bucket[") - case mt.Hmap: - b.WriteString("map.hdr[") - case mt.Hiter: - b.WriteString("map.iter[") - default: - Fatalf("unknown internal map type") - } - tconv2(b, m.Key(), 0, mode, visited) - b.WriteByte(']') - tconv2(b, m.Elem(), 0, mode, visited) - break - } - - if funarg := t.StructType().Funarg; funarg != types.FunargNone { - b.WriteByte('(') - var flag1 FmtFlag - switch mode { - case FTypeId, FTypeIdName, FErr: - // no argument names on function signature, and no "noescape"/"nosplit" tags - flag1 = FmtShort - } - for i, f := range t.Fields().Slice() { - if i != 0 { - b.WriteString(", ") - } - fldconv(b, f, flag1, mode, visited, funarg) - } - b.WriteByte(')') - } else { - b.WriteString("struct {") - for i, f := range t.Fields().Slice() { - if i != 0 { - b.WriteByte(';') - } - b.WriteByte(' ') - fldconv(b, f, FmtLong, mode, visited, funarg) - } - if t.NumFields() != 0 { - b.WriteByte(' ') - } - b.WriteByte('}') - } - - case TFORW: - b.WriteString("undefined") - if t.Sym != nil { - b.WriteByte(' ') - sconv2(b, t.Sym, 0, mode) - } - - case TUNSAFEPTR: - b.WriteString("unsafe.Pointer") - - case Txxx: - b.WriteString("Txxx") - default: - // Don't know how to handle - fall back to detailed prints. - b.WriteString(mode.Sprintf("%v <%v>", t.Etype, t.Sym)) - } -} - -// Statements which may be rendered with a simplestmt as init. -func stmtwithinit(op Op) bool { - switch op { - case OIF, OFOR, OFORUNTIL, OSWITCH: - return true - } - - return false -} - -func (n *Node) stmtfmt(s fmt.State, mode fmtMode) { - // some statements allow for an init, but at most one, - // but we may have an arbitrary number added, eg by typecheck - // and inlining. If it doesn't fit the syntax, emit an enclosing - // block starting with the init statements. - - // if we can just say "for" n->ninit; ... then do so - simpleinit := n.Ninit.Len() == 1 && n.Ninit.First().Ninit.Len() == 0 && stmtwithinit(n.Op) - - // otherwise, print the inits as separate statements - complexinit := n.Ninit.Len() != 0 && !simpleinit && (mode != FErr) - - // but if it was for if/for/switch, put in an extra surrounding block to limit the scope - extrablock := complexinit && stmtwithinit(n.Op) - - if extrablock { - fmt.Fprint(s, "{") - } - - if complexinit { - mode.Fprintf(s, " %v; ", n.Ninit) - } - - switch n.Op { - case ODCL: - mode.Fprintf(s, "var %v %v", n.Left.Sym, n.Left.Type) - - case ODCLFIELD: - if n.Sym != nil { - mode.Fprintf(s, "%v %v", n.Sym, n.Left) - } else { - mode.Fprintf(s, "%v", n.Left) - } - - // Don't export "v = " initializing statements, hope they're always - // preceded by the DCL which will be re-parsed and typechecked to reproduce - // the "v = " again. - case OAS: - if n.Colas() && !complexinit { - mode.Fprintf(s, "%v := %v", n.Left, n.Right) - } else { - mode.Fprintf(s, "%v = %v", n.Left, n.Right) - } - - case OASOP: - if n.Implicit() { - if n.SubOp() == OADD { - mode.Fprintf(s, "%v++", n.Left) - } else { - mode.Fprintf(s, "%v--", n.Left) - } - break - } - - mode.Fprintf(s, "%v %#v= %v", n.Left, n.SubOp(), n.Right) - - case OAS2: - if n.Colas() && !complexinit { - mode.Fprintf(s, "%.v := %.v", n.List, n.Rlist) - break - } - fallthrough - - case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: - mode.Fprintf(s, "%.v = %v", n.List, n.Right) - - case ORETURN: - mode.Fprintf(s, "return %.v", n.List) - - case ORETJMP: - mode.Fprintf(s, "retjmp %v", n.Sym) - - case OINLMARK: - mode.Fprintf(s, "inlmark %d", n.Xoffset) - - case OGO: - mode.Fprintf(s, "go %v", n.Left) - - case ODEFER: - mode.Fprintf(s, "defer %v", n.Left) - - case OIF: - if simpleinit { - mode.Fprintf(s, "if %v; %v { %v }", n.Ninit.First(), n.Left, n.Nbody) - } else { - mode.Fprintf(s, "if %v { %v }", n.Left, n.Nbody) - } - if n.Rlist.Len() != 0 { - mode.Fprintf(s, " else { %v }", n.Rlist) - } - - case OFOR, OFORUNTIL: - opname := "for" - if n.Op == OFORUNTIL { - opname = "foruntil" - } - if mode == FErr { // TODO maybe only if FmtShort, same below - fmt.Fprintf(s, "%s loop", opname) - break - } - - fmt.Fprint(s, opname) - if simpleinit { - mode.Fprintf(s, " %v;", n.Ninit.First()) - } else if n.Right != nil { - fmt.Fprint(s, " ;") - } - - if n.Left != nil { - mode.Fprintf(s, " %v", n.Left) - } - - if n.Right != nil { - mode.Fprintf(s, "; %v", n.Right) - } else if simpleinit { - fmt.Fprint(s, ";") - } - - if n.Op == OFORUNTIL && n.List.Len() != 0 { - mode.Fprintf(s, "; %v", n.List) - } - - mode.Fprintf(s, " { %v }", n.Nbody) - - case ORANGE: - if mode == FErr { - fmt.Fprint(s, "for loop") - break - } - - if n.List.Len() == 0 { - mode.Fprintf(s, "for range %v { %v }", n.Right, n.Nbody) - break - } - - mode.Fprintf(s, "for %.v = range %v { %v }", n.List, n.Right, n.Nbody) - - case OSELECT, OSWITCH: - if mode == FErr { - mode.Fprintf(s, "%v statement", n.Op) - break - } - - mode.Fprintf(s, "%#v", n.Op) - if simpleinit { - mode.Fprintf(s, " %v;", n.Ninit.First()) - } - if n.Left != nil { - mode.Fprintf(s, " %v ", n.Left) - } - - mode.Fprintf(s, " { %v }", n.List) - - case OCASE: - if n.List.Len() != 0 { - mode.Fprintf(s, "case %.v", n.List) - } else { - fmt.Fprint(s, "default") - } - mode.Fprintf(s, ": %v", n.Nbody) - - case OBREAK, OCONTINUE, OGOTO, OFALL: - if n.Sym != nil { - mode.Fprintf(s, "%#v %v", n.Op, n.Sym) - } else { - mode.Fprintf(s, "%#v", n.Op) - } - - case OEMPTY: - break - - case OLABEL: - mode.Fprintf(s, "%v: ", n.Sym) - } - - if extrablock { - fmt.Fprint(s, "}") - } -} - -var opprec = []int{ - OALIGNOF: 8, - OAPPEND: 8, - OBYTES2STR: 8, - OARRAYLIT: 8, - OSLICELIT: 8, - ORUNES2STR: 8, - OCALLFUNC: 8, - OCALLINTER: 8, - OCALLMETH: 8, - OCALL: 8, - OCAP: 8, - OCLOSE: 8, - OCONVIFACE: 8, - OCONVNOP: 8, - OCONV: 8, - OCOPY: 8, - ODELETE: 8, - OGETG: 8, - OLEN: 8, - OLITERAL: 8, - OMAKESLICE: 8, - OMAKESLICECOPY: 8, - OMAKE: 8, - OMAPLIT: 8, - ONAME: 8, - ONEW: 8, - ONONAME: 8, - OOFFSETOF: 8, - OPACK: 8, - OPANIC: 8, - OPAREN: 8, - OPRINTN: 8, - OPRINT: 8, - ORUNESTR: 8, - OSIZEOF: 8, - OSTR2BYTES: 8, - OSTR2RUNES: 8, - OSTRUCTLIT: 8, - OTARRAY: 8, - OTCHAN: 8, - OTFUNC: 8, - OTINTER: 8, - OTMAP: 8, - OTSTRUCT: 8, - OINDEXMAP: 8, - OINDEX: 8, - OSLICE: 8, - OSLICESTR: 8, - OSLICEARR: 8, - OSLICE3: 8, - OSLICE3ARR: 8, - OSLICEHEADER: 8, - ODOTINTER: 8, - ODOTMETH: 8, - ODOTPTR: 8, - ODOTTYPE2: 8, - ODOTTYPE: 8, - ODOT: 8, - OXDOT: 8, - OCALLPART: 8, - OPLUS: 7, - ONOT: 7, - OBITNOT: 7, - ONEG: 7, - OADDR: 7, - ODEREF: 7, - ORECV: 7, - OMUL: 6, - ODIV: 6, - OMOD: 6, - OLSH: 6, - ORSH: 6, - OAND: 6, - OANDNOT: 6, - OADD: 5, - OSUB: 5, - OOR: 5, - OXOR: 5, - OEQ: 4, - OLT: 4, - OLE: 4, - OGE: 4, - OGT: 4, - ONE: 4, - OSEND: 3, - OANDAND: 2, - OOROR: 1, - - // Statements handled by stmtfmt - OAS: -1, - OAS2: -1, - OAS2DOTTYPE: -1, - OAS2FUNC: -1, - OAS2MAPR: -1, - OAS2RECV: -1, - OASOP: -1, - OBREAK: -1, - OCASE: -1, - OCONTINUE: -1, - ODCL: -1, - ODCLFIELD: -1, - ODEFER: -1, - OEMPTY: -1, - OFALL: -1, - OFOR: -1, - OFORUNTIL: -1, - OGOTO: -1, - OIF: -1, - OLABEL: -1, - OGO: -1, - ORANGE: -1, - ORETURN: -1, - OSELECT: -1, - OSWITCH: -1, - - OEND: 0, -} - -func (n *Node) exprfmt(s fmt.State, prec int, mode fmtMode) { - for n != nil && n.Implicit() && (n.Op == ODEREF || n.Op == OADDR) { - n = n.Left - } - - if n == nil { - fmt.Fprint(s, "") - return - } - - nprec := opprec[n.Op] - if n.Op == OTYPE && n.Sym != nil { - nprec = 8 - } - - if prec > nprec { - mode.Fprintf(s, "(%v)", n) - return - } - - switch n.Op { - case OPAREN: - mode.Fprintf(s, "(%v)", n.Left) - - case OLITERAL: // this is a bit of a mess - if mode == FErr { - if n.Orig != nil && n.Orig != n { - n.Orig.exprfmt(s, prec, mode) - return - } - if n.Sym != nil { - fmt.Fprint(s, smodeString(n.Sym, mode)) - return - } - } - if n.Val().Ctype() == CTNIL && n.Orig != nil && n.Orig != n { - n.Orig.exprfmt(s, prec, mode) - return - } - if n.Type != nil && !n.Type.IsUntyped() { - // Need parens when type begins with what might - // be misinterpreted as a unary operator: * or <-. - if n.Type.IsPtr() || (n.Type.IsChan() && n.Type.ChanDir() == types.Crecv) { - mode.Fprintf(s, "(%v)(%v)", n.Type, n.Val()) - return - } else { - mode.Fprintf(s, "%v(%v)", n.Type, n.Val()) - return - } - } - - mode.Fprintf(s, "%v", n.Val()) - - // Special case: name used as local variable in export. - // _ becomes ~b%d internally; print as _ for export - case ONAME: - if mode == FErr && n.Sym != nil && n.Sym.Name[0] == '~' && n.Sym.Name[1] == 'b' { - fmt.Fprint(s, "_") - return - } - fallthrough - case OPACK, ONONAME: - fmt.Fprint(s, smodeString(n.Sym, mode)) - - case OTYPE: - if n.Type == nil && n.Sym != nil { - fmt.Fprint(s, smodeString(n.Sym, mode)) - return - } - mode.Fprintf(s, "%v", n.Type) - - case OTARRAY: - if n.Left != nil { - mode.Fprintf(s, "[%v]%v", n.Left, n.Right) - return - } - mode.Fprintf(s, "[]%v", n.Right) // happens before typecheck - - case OTMAP: - mode.Fprintf(s, "map[%v]%v", n.Left, n.Right) - - case OTCHAN: - switch n.TChanDir() { - case types.Crecv: - mode.Fprintf(s, "<-chan %v", n.Left) - - case types.Csend: - mode.Fprintf(s, "chan<- %v", n.Left) - - default: - if n.Left != nil && n.Left.Op == OTCHAN && n.Left.Sym == nil && n.Left.TChanDir() == types.Crecv { - mode.Fprintf(s, "chan (%v)", n.Left) - } else { - mode.Fprintf(s, "chan %v", n.Left) - } - } - - case OTSTRUCT: - fmt.Fprint(s, "") - - case OTINTER: - fmt.Fprint(s, "") - - case OTFUNC: - fmt.Fprint(s, "") - - case OCLOSURE: - if mode == FErr { - fmt.Fprint(s, "func literal") - return - } - if n.Nbody.Len() != 0 { - mode.Fprintf(s, "%v { %v }", n.Type, n.Nbody) - return - } - mode.Fprintf(s, "%v { %v }", n.Type, n.Func.Closure.Nbody) - - case OCOMPLIT: - if mode == FErr { - if n.Implicit() { - mode.Fprintf(s, "... argument") - return - } - if n.Right != nil { - mode.Fprintf(s, "%v{%s}", n.Right, ellipsisIf(n.List.Len() != 0)) - return - } - - fmt.Fprint(s, "composite literal") - return - } - mode.Fprintf(s, "(%v{ %.v })", n.Right, n.List) - - case OPTRLIT: - mode.Fprintf(s, "&%v", n.Left) - - case OSTRUCTLIT, OARRAYLIT, OSLICELIT, OMAPLIT: - if mode == FErr { - mode.Fprintf(s, "%v{%s}", n.Type, ellipsisIf(n.List.Len() != 0)) - return - } - mode.Fprintf(s, "(%v{ %.v })", n.Type, n.List) - - case OKEY: - if n.Left != nil && n.Right != nil { - mode.Fprintf(s, "%v:%v", n.Left, n.Right) - return - } - - if n.Left == nil && n.Right != nil { - mode.Fprintf(s, ":%v", n.Right) - return - } - if n.Left != nil && n.Right == nil { - mode.Fprintf(s, "%v:", n.Left) - return - } - fmt.Fprint(s, ":") - - case OSTRUCTKEY: - mode.Fprintf(s, "%v:%v", n.Sym, n.Left) - - case OCALLPART: - n.Left.exprfmt(s, nprec, mode) - if n.Right == nil || n.Right.Sym == nil { - fmt.Fprint(s, ".") - return - } - mode.Fprintf(s, ".%0S", n.Right.Sym) - - case OXDOT, ODOT, ODOTPTR, ODOTINTER, ODOTMETH: - n.Left.exprfmt(s, nprec, mode) - if n.Sym == nil { - fmt.Fprint(s, ".") - return - } - mode.Fprintf(s, ".%0S", n.Sym) - - case ODOTTYPE, ODOTTYPE2: - n.Left.exprfmt(s, nprec, mode) - if n.Right != nil { - mode.Fprintf(s, ".(%v)", n.Right) - return - } - mode.Fprintf(s, ".(%v)", n.Type) - - case OINDEX, OINDEXMAP: - n.Left.exprfmt(s, nprec, mode) - mode.Fprintf(s, "[%v]", n.Right) - - case OSLICE, OSLICESTR, OSLICEARR, OSLICE3, OSLICE3ARR: - n.Left.exprfmt(s, nprec, mode) - fmt.Fprint(s, "[") - low, high, max := n.SliceBounds() - if low != nil { - fmt.Fprint(s, low.modeString(mode)) - } - fmt.Fprint(s, ":") - if high != nil { - fmt.Fprint(s, high.modeString(mode)) - } - if n.Op.IsSlice3() { - fmt.Fprint(s, ":") - if max != nil { - fmt.Fprint(s, max.modeString(mode)) - } - } - fmt.Fprint(s, "]") - - case OSLICEHEADER: - if n.List.Len() != 2 { - Fatalf("bad OSLICEHEADER list length %d", n.List.Len()) - } - mode.Fprintf(s, "sliceheader{%v,%v,%v}", n.Left, n.List.First(), n.List.Second()) - - case OCOMPLEX, OCOPY: - if n.Left != nil { - mode.Fprintf(s, "%#v(%v, %v)", n.Op, n.Left, n.Right) - } else { - mode.Fprintf(s, "%#v(%.v)", n.Op, n.List) - } - - case OCONV, - OCONVIFACE, - OCONVNOP, - OBYTES2STR, - ORUNES2STR, - OSTR2BYTES, - OSTR2RUNES, - ORUNESTR: - if n.Type == nil || n.Type.Sym == nil { - mode.Fprintf(s, "(%v)", n.Type) - } else { - mode.Fprintf(s, "%v", n.Type) - } - if n.Left != nil { - mode.Fprintf(s, "(%v)", n.Left) - } else { - mode.Fprintf(s, "(%.v)", n.List) - } - - case OREAL, - OIMAG, - OAPPEND, - OCAP, - OCLOSE, - ODELETE, - OLEN, - OMAKE, - ONEW, - OPANIC, - ORECOVER, - OALIGNOF, - OOFFSETOF, - OSIZEOF, - OPRINT, - OPRINTN: - if n.Left != nil { - mode.Fprintf(s, "%#v(%v)", n.Op, n.Left) - return - } - if n.IsDDD() { - mode.Fprintf(s, "%#v(%.v...)", n.Op, n.List) - return - } - mode.Fprintf(s, "%#v(%.v)", n.Op, n.List) - - case OCALL, OCALLFUNC, OCALLINTER, OCALLMETH, OGETG: - n.Left.exprfmt(s, nprec, mode) - if n.IsDDD() { - mode.Fprintf(s, "(%.v...)", n.List) - return - } - mode.Fprintf(s, "(%.v)", n.List) - - case OMAKEMAP, OMAKECHAN, OMAKESLICE: - if n.List.Len() != 0 { // pre-typecheck - mode.Fprintf(s, "make(%v, %.v)", n.Type, n.List) - return - } - if n.Right != nil { - mode.Fprintf(s, "make(%v, %v, %v)", n.Type, n.Left, n.Right) - return - } - if n.Left != nil && (n.Op == OMAKESLICE || !n.Left.Type.IsUntyped()) { - mode.Fprintf(s, "make(%v, %v)", n.Type, n.Left) - return - } - mode.Fprintf(s, "make(%v)", n.Type) - - case OMAKESLICECOPY: - mode.Fprintf(s, "makeslicecopy(%v, %v, %v)", n.Type, n.Left, n.Right) - - case OPLUS, ONEG, OADDR, OBITNOT, ODEREF, ONOT, ORECV: - // Unary - mode.Fprintf(s, "%#v", n.Op) - if n.Left != nil && n.Left.Op == n.Op { - fmt.Fprint(s, " ") - } - n.Left.exprfmt(s, nprec+1, mode) - - // Binary - case OADD, - OAND, - OANDAND, - OANDNOT, - ODIV, - OEQ, - OGE, - OGT, - OLE, - OLT, - OLSH, - OMOD, - OMUL, - ONE, - OOR, - OOROR, - ORSH, - OSEND, - OSUB, - OXOR: - n.Left.exprfmt(s, nprec, mode) - mode.Fprintf(s, " %#v ", n.Op) - n.Right.exprfmt(s, nprec+1, mode) - - case OADDSTR: - for i, n1 := range n.List.Slice() { - if i != 0 { - fmt.Fprint(s, " + ") - } - n1.exprfmt(s, nprec, mode) - } - case ODDD: - mode.Fprintf(s, "...") - default: - mode.Fprintf(s, "", n.Op) - } -} - -func (n *Node) nodefmt(s fmt.State, flag FmtFlag, mode fmtMode) { - t := n.Type - - // We almost always want the original. - // TODO(gri) Why the special case for OLITERAL? - if n.Op != OLITERAL && n.Orig != nil { - n = n.Orig - } - - if flag&FmtLong != 0 && t != nil { - if t.Etype == TNIL { - fmt.Fprint(s, "nil") - } else if n.Op == ONAME && n.Name.AutoTemp() { - mode.Fprintf(s, "%v value", t) - } else { - mode.Fprintf(s, "%v (type %v)", n, t) - } - return - } - - // TODO inlining produces expressions with ninits. we can't print these yet. - - if opprec[n.Op] < 0 { - n.stmtfmt(s, mode) - return - } - - n.exprfmt(s, 0, mode) -} - -func (n *Node) nodedump(s fmt.State, flag FmtFlag, mode fmtMode) { - recur := flag&FmtShort == 0 - - if recur { - indent(s) - if dumpdepth > 40 { - fmt.Fprint(s, "...") - return - } - - if n.Ninit.Len() != 0 { - mode.Fprintf(s, "%v-init%v", n.Op, n.Ninit) - indent(s) - } - } - - switch n.Op { - default: - mode.Fprintf(s, "%v%j", n.Op, n) - - case OLITERAL: - mode.Fprintf(s, "%v-%v%j", n.Op, n.Val(), n) - - case ONAME, ONONAME: - if n.Sym != nil { - mode.Fprintf(s, "%v-%v%j", n.Op, n.Sym, n) - } else { - mode.Fprintf(s, "%v%j", n.Op, n) - } - if recur && n.Type == nil && n.Name != nil && n.Name.Param != nil && n.Name.Param.Ntype != nil { - indent(s) - mode.Fprintf(s, "%v-ntype%v", n.Op, n.Name.Param.Ntype) - } - - case OASOP: - mode.Fprintf(s, "%v-%v%j", n.Op, n.SubOp(), n) - - case OTYPE: - mode.Fprintf(s, "%v %v%j type=%v", n.Op, n.Sym, n, n.Type) - if recur && n.Type == nil && n.Name != nil && n.Name.Param != nil && n.Name.Param.Ntype != nil { - indent(s) - mode.Fprintf(s, "%v-ntype%v", n.Op, n.Name.Param.Ntype) - } - } - - if n.Op == OCLOSURE && n.Func.Closure != nil && n.Func.Closure.Func.Nname.Sym != nil { - mode.Fprintf(s, " fnName %v", n.Func.Closure.Func.Nname.Sym) - } - if n.Sym != nil && n.Op != ONAME { - mode.Fprintf(s, " %v", n.Sym) - } - - if n.Type != nil { - mode.Fprintf(s, " %v", n.Type) - } - - if recur { - if n.Left != nil { - mode.Fprintf(s, "%v", n.Left) - } - if n.Right != nil { - mode.Fprintf(s, "%v", n.Right) - } - if n.Func != nil && n.Func.Closure != nil && n.Func.Closure.Nbody.Len() != 0 { - indent(s) - // The function associated with a closure - mode.Fprintf(s, "%v-clofunc%v", n.Op, n.Func.Closure) - } - if n.Func != nil && n.Func.Dcl != nil && len(n.Func.Dcl) != 0 { - indent(s) - // The dcls for a func or closure - mode.Fprintf(s, "%v-dcl%v", n.Op, asNodes(n.Func.Dcl)) - } - if n.List.Len() != 0 { - indent(s) - mode.Fprintf(s, "%v-list%v", n.Op, n.List) - } - - if n.Rlist.Len() != 0 { - indent(s) - mode.Fprintf(s, "%v-rlist%v", n.Op, n.Rlist) - } - - if n.Nbody.Len() != 0 { - indent(s) - mode.Fprintf(s, "%v-body%v", n.Op, n.Nbody) - } - } -} - -// "%S" suppresses qualifying with package -func symFormat(s *types.Sym, f fmt.State, verb rune, mode fmtMode) { - switch verb { - case 'v', 'S': - fmt.Fprint(f, sconv(s, fmtFlag(f, verb), mode)) - - default: - fmt.Fprintf(f, "%%!%c(*types.Sym=%p)", verb, s) - } -} - -func smodeString(s *types.Sym, mode fmtMode) string { return sconv(s, 0, mode) } - -// See #16897 before changing the implementation of sconv. -func sconv(s *types.Sym, flag FmtFlag, mode fmtMode) string { - if flag&FmtLong != 0 { - panic("linksymfmt") - } - - if s == nil { - return "" - } - - if s.Name == "_" { - return "_" - } - buf := fmtBufferPool.Get().(*bytes.Buffer) - buf.Reset() - defer fmtBufferPool.Put(buf) - - flag, mode = flag.update(mode) - symfmt(buf, s, flag, mode) - return types.InternString(buf.Bytes()) -} - -func sconv2(b *bytes.Buffer, s *types.Sym, flag FmtFlag, mode fmtMode) { - if flag&FmtLong != 0 { - panic("linksymfmt") - } - if s == nil { - b.WriteString("") - return - } - if s.Name == "_" { - b.WriteString("_") - return - } - - flag, mode = flag.update(mode) - symfmt(b, s, flag, mode) -} - -func fldconv(b *bytes.Buffer, f *types.Field, flag FmtFlag, mode fmtMode, visited map[*types.Type]int, funarg types.Funarg) { - if f == nil { - b.WriteString("") - return - } - flag, mode = flag.update(mode) - if mode == FTypeIdName { - flag |= FmtUnsigned - } - - var name string - if flag&FmtShort == 0 { - s := f.Sym - - // Take the name from the original. - if mode == FErr { - s = origSym(s) - } - - if s != nil && f.Embedded == 0 { - if funarg != types.FunargNone { - name = asNode(f.Nname).modeString(mode) - } else if flag&FmtLong != 0 { - name = mode.Sprintf("%0S", s) - if !types.IsExported(name) && flag&FmtUnsigned == 0 { - name = smodeString(s, mode) // qualify non-exported names (used on structs, not on funarg) - } - } else { - name = smodeString(s, mode) - } - } - } - - if name != "" { - b.WriteString(name) - b.WriteString(" ") - } - - if f.IsDDD() { - var et *types.Type - if f.Type != nil { - et = f.Type.Elem() - } - b.WriteString("...") - tconv2(b, et, 0, mode, visited) - } else { - tconv2(b, f.Type, 0, mode, visited) - } - - if flag&FmtShort == 0 && funarg == types.FunargNone && f.Note != "" { - b.WriteString(" ") - b.WriteString(strconv.Quote(f.Note)) - } -} - -// "%L" print definition, not name -// "%S" omit 'func' and receiver from function types, short type names -func typeFormat(t *types.Type, s fmt.State, verb rune, mode fmtMode) { - switch verb { - case 'v', 'S', 'L': - fmt.Fprint(s, tconv(t, fmtFlag(s, verb), mode)) - default: - fmt.Fprintf(s, "%%!%c(*Type=%p)", verb, t) - } -} - -func (n *Node) String() string { return fmt.Sprint(n) } -func (n *Node) modeString(mode fmtMode) string { return mode.Sprint(n) } - -// "%L" suffix with "(type %T)" where possible -// "%+S" in debug mode, don't recurse, no multiline output -func (n *Node) nconv(s fmt.State, flag FmtFlag, mode fmtMode) { - if n == nil { - fmt.Fprint(s, "") - return - } - - flag, mode = flag.update(mode) - - switch mode { - case FErr: - n.nodefmt(s, flag, mode) - - case FDbg: - dumpdepth++ - n.nodedump(s, flag, mode) - dumpdepth-- - - default: - Fatalf("unhandled %%N mode: %d", mode) - } -} - -func (l Nodes) format(s fmt.State, verb rune, mode fmtMode) { - switch verb { - case 'v': - l.hconv(s, fmtFlag(s, verb), mode) - - default: - fmt.Fprintf(s, "%%!%c(Nodes)", verb) - } -} - -func (n Nodes) String() string { - return fmt.Sprint(n) -} - -// Flags: all those of %N plus '.': separate with comma's instead of semicolons. -func (l Nodes) hconv(s fmt.State, flag FmtFlag, mode fmtMode) { - if l.Len() == 0 && mode == FDbg { - fmt.Fprint(s, "") - return - } - - flag, mode = flag.update(mode) - sep := "; " - if mode == FDbg { - sep = "\n" - } else if flag&FmtComma != 0 { - sep = ", " - } - - for i, n := range l.Slice() { - fmt.Fprint(s, n.modeString(mode)) - if i+1 < l.Len() { - fmt.Fprint(s, sep) - } - } -} - -func dumplist(s string, l Nodes) { - fmt.Printf("%s%+v\n", s, l) -} - -func fdumplist(w io.Writer, s string, l Nodes) { - fmt.Fprintf(w, "%s%+v\n", s, l) -} - -func Dump(s string, n *Node) { - fmt.Printf("%s [%p]%+v\n", s, n, n) -} - -// TODO(gri) make variable local somehow -var dumpdepth int - -// indent prints indentation to s. -func indent(s fmt.State) { - fmt.Fprint(s, "\n") - for i := 0; i < dumpdepth; i++ { - fmt.Fprint(s, ". ") - } -} - -func ellipsisIf(b bool) string { - if b { - return "..." - } - return "" -} diff --git a/src/cmd/compile/internal/gc/gen.go b/src/cmd/compile/internal/gc/gen.go deleted file mode 100644 index 929653ebbd092fa3d86f5fc8e8a2862a51e6e447..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/gen.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/obj" - "cmd/internal/src" - "strconv" -) - -// sysfunc looks up Go function name in package runtime. This function -// must follow the internal calling convention. -func sysfunc(name string) *obj.LSym { - s := Runtimepkg.Lookup(name) - s.SetFunc(true) - return s.Linksym() -} - -// sysvar looks up a variable (or assembly function) name in package -// runtime. If this is a function, it may have a special calling -// convention. -func sysvar(name string) *obj.LSym { - return Runtimepkg.Lookup(name).Linksym() -} - -// isParamStackCopy reports whether this is the on-stack copy of a -// function parameter that moved to the heap. -func (n *Node) isParamStackCopy() bool { - return n.Op == ONAME && (n.Class() == PPARAM || n.Class() == PPARAMOUT) && n.Name.Param.Heapaddr != nil -} - -// isParamHeapCopy reports whether this is the on-heap copy of -// a function parameter that moved to the heap. -func (n *Node) isParamHeapCopy() bool { - return n.Op == ONAME && n.Class() == PAUTOHEAP && n.Name.Param.Stackcopy != nil -} - -// autotmpname returns the name for an autotmp variable numbered n. -func autotmpname(n int) string { - // Give each tmp a different name so that they can be registerized. - // Add a preceding . to avoid clashing with legal names. - const prefix = ".autotmp_" - // Start with a buffer big enough to hold a large n. - b := []byte(prefix + " ")[:len(prefix)] - b = strconv.AppendInt(b, int64(n), 10) - return types.InternString(b) -} - -// make a new Node off the books -func tempAt(pos src.XPos, curfn *Node, t *types.Type) *Node { - if curfn == nil { - Fatalf("no curfn for tempAt") - } - if curfn.Func.Closure != nil && curfn.Op == OCLOSURE { - Dump("tempAt", curfn) - Fatalf("adding tempAt to wrong closure function") - } - if t == nil { - Fatalf("tempAt called with nil type") - } - - s := &types.Sym{ - Name: autotmpname(len(curfn.Func.Dcl)), - Pkg: localpkg, - } - n := newnamel(pos, s) - s.Def = asTypesNode(n) - n.Type = t - n.SetClass(PAUTO) - n.Esc = EscNever - n.Name.Curfn = curfn - n.Name.SetUsed(true) - n.Name.SetAutoTemp(true) - curfn.Func.Dcl = append(curfn.Func.Dcl, n) - - dowidth(t) - - return n.Orig -} - -func temp(t *types.Type) *Node { - return tempAt(lineno, Curfn, t) -} diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go deleted file mode 100644 index 274930bd156f89e709e480361c24409eded4ad88..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/go.go +++ /dev/null @@ -1,349 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/ssa" - "cmd/compile/internal/types" - "cmd/internal/obj" - "cmd/internal/src" - "sync" -) - -const ( - BADWIDTH = types.BADWIDTH -) - -var ( - // maximum size variable which we will allocate on the stack. - // This limit is for explicit variable declarations like "var x T" or "x := ...". - // Note: the flag smallframes can update this value. - maxStackVarSize = int64(10 * 1024 * 1024) - - // maximum size of implicit variables that we will allocate on the stack. - // p := new(T) allocating T on the stack - // p := &T{} allocating T on the stack - // s := make([]T, n) allocating [n]T on the stack - // s := []byte("...") allocating [n]byte on the stack - // Note: the flag smallframes can update this value. - maxImplicitStackVarSize = int64(64 * 1024) - - // smallArrayBytes is the maximum size of an array which is considered small. - // Small arrays will be initialized directly with a sequence of constant stores. - // Large arrays will be initialized by copying from a static temp. - // 256 bytes was chosen to minimize generated code + statictmp size. - smallArrayBytes = int64(256) -) - -// isRuntimePkg reports whether p is package runtime. -func isRuntimePkg(p *types.Pkg) bool { - if compiling_runtime && p == localpkg { - return true - } - return p.Path == "runtime" -} - -// isReflectPkg reports whether p is package reflect. -func isReflectPkg(p *types.Pkg) bool { - if p == localpkg { - return myimportpath == "reflect" - } - return p.Path == "reflect" -} - -// The Class of a variable/function describes the "storage class" -// of a variable or function. During parsing, storage classes are -// called declaration contexts. -type Class uint8 - -//go:generate stringer -type=Class -const ( - Pxxx Class = iota // no class; used during ssa conversion to indicate pseudo-variables - PEXTERN // global variables - PAUTO // local variables - PAUTOHEAP // local variables or parameters moved to heap - PPARAM // input arguments - PPARAMOUT // output results - PFUNC // global functions - - // Careful: Class is stored in three bits in Node.flags. - _ = uint((1 << 3) - iota) // static assert for iota <= (1 << 3) -) - -// Slices in the runtime are represented by three components: -// -// type slice struct { -// ptr unsafe.Pointer -// len int -// cap int -// } -// -// Strings in the runtime are represented by two components: -// -// type string struct { -// ptr unsafe.Pointer -// len int -// } -// -// These variables are the offsets of fields and sizes of these structs. -var ( - slicePtrOffset int64 - sliceLenOffset int64 - sliceCapOffset int64 - - sizeofSlice int64 - sizeofString int64 -) - -var pragcgobuf [][]string - -var outfile string -var linkobj string - -// nerrors is the number of compiler errors reported -// since the last call to saveerrors. -var nerrors int - -// nsavederrors is the total number of compiler errors -// reported before the last call to saveerrors. -var nsavederrors int - -var nsyntaxerrors int - -var decldepth int32 - -var nolocalimports bool - -// gc debug flags -type DebugFlags struct { - P, B, C, E, - K, L, N, S, - W, e, h, j, - l, m, r, w int -} - -var Debug DebugFlags - -var debugstr string - -var Debug_checknil int -var Debug_typeassert int - -var localpkg *types.Pkg // package being compiled - -var inimport bool // set during import - -var itabpkg *types.Pkg // fake pkg for itab entries - -var itablinkpkg *types.Pkg // fake package for runtime itab entries - -var Runtimepkg *types.Pkg // fake package runtime - -var racepkg *types.Pkg // package runtime/race - -var msanpkg *types.Pkg // package runtime/msan - -var unsafepkg *types.Pkg // package unsafe - -var trackpkg *types.Pkg // fake package for field tracking - -var mappkg *types.Pkg // fake package for map zero value - -var gopkg *types.Pkg // pseudo-package for method symbols on anonymous receiver types - -var zerosize int64 - -var myimportpath string - -var localimport string - -var asmhdr string - -var simtype [NTYPE]types.EType - -var ( - isInt [NTYPE]bool - isFloat [NTYPE]bool - isComplex [NTYPE]bool - issimple [NTYPE]bool -) - -var ( - okforeq [NTYPE]bool - okforadd [NTYPE]bool - okforand [NTYPE]bool - okfornone [NTYPE]bool - okforcmp [NTYPE]bool - okforbool [NTYPE]bool - okforcap [NTYPE]bool - okforlen [NTYPE]bool - okforarith [NTYPE]bool - okforconst [NTYPE]bool -) - -var ( - okfor [OEND][]bool - iscmp [OEND]bool -) - -var minintval [NTYPE]*Mpint - -var maxintval [NTYPE]*Mpint - -var minfltval [NTYPE]*Mpflt - -var maxfltval [NTYPE]*Mpflt - -var xtop []*Node - -var exportlist []*Node - -var importlist []*Node // imported functions and methods with inlinable bodies - -var ( - funcsymsmu sync.Mutex // protects funcsyms and associated package lookups (see func funcsym) - funcsyms []*types.Sym -) - -var dclcontext Class // PEXTERN/PAUTO - -var Curfn *Node - -var Widthptr int - -var Widthreg int - -var nblank *Node - -var typecheckok bool - -var compiling_runtime bool - -// Compiling the standard library -var compiling_std bool - -var use_writebarrier bool - -var pure_go bool - -var flag_installsuffix string - -var flag_race bool - -var flag_msan bool - -var flagDWARF bool - -// Whether we are adding any sort of code instrumentation, such as -// when the race detector is enabled. -var instrumenting bool - -// Whether we are tracking lexical scopes for DWARF. -var trackScopes bool - -// Controls generation of DWARF inlined instance records. Zero -// disables, 1 emits inlined routines but suppresses var info, -// and 2 emits inlined routines with tracking of formals/locals. -var genDwarfInline int - -var debuglive int - -var Ctxt *obj.Link - -var writearchive bool - -var nodfp *Node - -var disable_checknil int - -var autogeneratedPos src.XPos - -// interface to back end - -type Arch struct { - LinkArch *obj.LinkArch - - REGSP int - MAXWIDTH int64 - SoftFloat bool - - PadFrame func(int64) int64 - - // ZeroRange zeroes a range of memory on stack. It is only inserted - // at function entry, and it is ok to clobber registers. - ZeroRange func(*Progs, *obj.Prog, int64, int64, *uint32) *obj.Prog - - Ginsnop func(*Progs) *obj.Prog - Ginsnopdefer func(*Progs) *obj.Prog // special ginsnop for deferreturn - - // SSAMarkMoves marks any MOVXconst ops that need to avoid clobbering flags. - SSAMarkMoves func(*SSAGenState, *ssa.Block) - - // SSAGenValue emits Prog(s) for the Value. - SSAGenValue func(*SSAGenState, *ssa.Value) - - // SSAGenBlock emits end-of-block Progs. SSAGenValue should be called - // for all values in the block before SSAGenBlock. - SSAGenBlock func(s *SSAGenState, b, next *ssa.Block) -} - -var thearch Arch - -var ( - staticuint64s, - zerobase *Node - - assertE2I, - assertE2I2, - assertI2I, - assertI2I2, - deferproc, - deferprocStack, - Deferreturn, - Duffcopy, - Duffzero, - gcWriteBarrier, - goschedguarded, - growslice, - msanread, - msanwrite, - msanmove, - newobject, - newproc, - panicdivide, - panicshift, - panicdottypeE, - panicdottypeI, - panicnildottype, - panicoverflow, - raceread, - racereadrange, - racewrite, - racewriterange, - x86HasPOPCNT, - x86HasSSE41, - x86HasFMA, - armHasVFPv4, - arm64HasATOMICS, - typedmemclr, - typedmemmove, - Udiv, - writeBarrier, - zerobaseSym *obj.LSym - - BoundsCheckFunc [ssa.BoundsKindCount]*obj.LSym - ExtendCheckFunc [ssa.BoundsKindCount]*obj.LSym - - // Wasm - WasmMove, - WasmZero, - WasmDiv, - WasmTruncS, - WasmTruncU, - SigPanic *obj.LSym -) - -// GCWriteBarrierReg maps from registers to gcWriteBarrier implementation LSyms. -var GCWriteBarrierReg map[int16]*obj.LSym diff --git a/src/cmd/compile/internal/gc/gsubr.go b/src/cmd/compile/internal/gc/gsubr.go deleted file mode 100644 index d599a383e7b884da0e0bdb2d22732a4d29ea87bc..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/gsubr.go +++ /dev/null @@ -1,333 +0,0 @@ -// Derived from Inferno utils/6c/txt.c -// https://bitbucket.org/inferno-os/inferno-os/src/master/utils/6c/txt.c -// -// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved. -// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net) -// Portions Copyright © 1997-1999 Vita Nuova Limited -// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com) -// Portions Copyright © 2004,2006 Bruce Ellis -// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net) -// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others -// Portions Copyright © 2009 The Go Authors. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package gc - -import ( - "cmd/compile/internal/ssa" - "cmd/internal/obj" - "cmd/internal/objabi" - "cmd/internal/src" -) - -var sharedProgArray = new([10000]obj.Prog) // *T instead of T to work around issue 19839 - -// Progs accumulates Progs for a function and converts them into machine code. -type Progs struct { - Text *obj.Prog // ATEXT Prog for this function - next *obj.Prog // next Prog - pc int64 // virtual PC; count of Progs - pos src.XPos // position to use for new Progs - curfn *Node // fn these Progs are for - progcache []obj.Prog // local progcache - cacheidx int // first free element of progcache - - nextLive LivenessIndex // liveness index for the next Prog - prevLive LivenessIndex // last emitted liveness index -} - -// newProgs returns a new Progs for fn. -// worker indicates which of the backend workers will use the Progs. -func newProgs(fn *Node, worker int) *Progs { - pp := new(Progs) - if Ctxt.CanReuseProgs() { - sz := len(sharedProgArray) / nBackendWorkers - pp.progcache = sharedProgArray[sz*worker : sz*(worker+1)] - } - pp.curfn = fn - - // prime the pump - pp.next = pp.NewProg() - pp.clearp(pp.next) - - pp.pos = fn.Pos - pp.settext(fn) - // PCDATA tables implicitly start with index -1. - pp.prevLive = LivenessIndex{-1, false} - pp.nextLive = pp.prevLive - return pp -} - -func (pp *Progs) NewProg() *obj.Prog { - var p *obj.Prog - if pp.cacheidx < len(pp.progcache) { - p = &pp.progcache[pp.cacheidx] - pp.cacheidx++ - } else { - p = new(obj.Prog) - } - p.Ctxt = Ctxt - return p -} - -// Flush converts from pp to machine code. -func (pp *Progs) Flush() { - plist := &obj.Plist{Firstpc: pp.Text, Curfn: pp.curfn} - obj.Flushplist(Ctxt, plist, pp.NewProg, myimportpath) -} - -// Free clears pp and any associated resources. -func (pp *Progs) Free() { - if Ctxt.CanReuseProgs() { - // Clear progs to enable GC and avoid abuse. - s := pp.progcache[:pp.cacheidx] - for i := range s { - s[i] = obj.Prog{} - } - } - // Clear pp to avoid abuse. - *pp = Progs{} -} - -// Prog adds a Prog with instruction As to pp. -func (pp *Progs) Prog(as obj.As) *obj.Prog { - if pp.nextLive.StackMapValid() && pp.nextLive.stackMapIndex != pp.prevLive.stackMapIndex { - // Emit stack map index change. - idx := pp.nextLive.stackMapIndex - pp.prevLive.stackMapIndex = idx - p := pp.Prog(obj.APCDATA) - Addrconst(&p.From, objabi.PCDATA_StackMapIndex) - Addrconst(&p.To, int64(idx)) - } - if pp.nextLive.isUnsafePoint != pp.prevLive.isUnsafePoint { - // Emit unsafe-point marker. - pp.prevLive.isUnsafePoint = pp.nextLive.isUnsafePoint - p := pp.Prog(obj.APCDATA) - Addrconst(&p.From, objabi.PCDATA_UnsafePoint) - if pp.nextLive.isUnsafePoint { - Addrconst(&p.To, objabi.PCDATA_UnsafePointUnsafe) - } else { - Addrconst(&p.To, objabi.PCDATA_UnsafePointSafe) - } - } - - p := pp.next - pp.next = pp.NewProg() - pp.clearp(pp.next) - p.Link = pp.next - - if !pp.pos.IsKnown() && Debug.K != 0 { - Warn("prog: unknown position (line 0)") - } - - p.As = as - p.Pos = pp.pos - if pp.pos.IsStmt() == src.PosIsStmt { - // Clear IsStmt for later Progs at this pos provided that as can be marked as a stmt - if ssa.LosesStmtMark(as) { - return p - } - pp.pos = pp.pos.WithNotStmt() - } - return p -} - -func (pp *Progs) clearp(p *obj.Prog) { - obj.Nopout(p) - p.As = obj.AEND - p.Pc = pp.pc - pp.pc++ -} - -func (pp *Progs) Appendpp(p *obj.Prog, as obj.As, ftype obj.AddrType, freg int16, foffset int64, ttype obj.AddrType, treg int16, toffset int64) *obj.Prog { - q := pp.NewProg() - pp.clearp(q) - q.As = as - q.Pos = p.Pos - q.From.Type = ftype - q.From.Reg = freg - q.From.Offset = foffset - q.To.Type = ttype - q.To.Reg = treg - q.To.Offset = toffset - q.Link = p.Link - p.Link = q - return q -} - -func (pp *Progs) settext(fn *Node) { - if pp.Text != nil { - Fatalf("Progs.settext called twice") - } - ptxt := pp.Prog(obj.ATEXT) - pp.Text = ptxt - - fn.Func.lsym.Func().Text = ptxt - ptxt.From.Type = obj.TYPE_MEM - ptxt.From.Name = obj.NAME_EXTERN - ptxt.From.Sym = fn.Func.lsym -} - -// initLSym defines f's obj.LSym and initializes it based on the -// properties of f. This includes setting the symbol flags and ABI and -// creating and initializing related DWARF symbols. -// -// initLSym must be called exactly once per function and must be -// called for both functions with bodies and functions without bodies. -func (f *Func) initLSym(hasBody bool) { - if f.lsym != nil { - Fatalf("Func.initLSym called twice") - } - - if nam := f.Nname; !nam.isBlank() { - f.lsym = nam.Sym.Linksym() - if f.Pragma&Systemstack != 0 { - f.lsym.Set(obj.AttrCFunc, true) - } - - var aliasABI obj.ABI - needABIAlias := false - defABI, hasDefABI := symabiDefs[f.lsym.Name] - if hasDefABI && defABI == obj.ABI0 { - // Symbol is defined as ABI0. Create an - // Internal -> ABI0 wrapper. - f.lsym.SetABI(obj.ABI0) - needABIAlias, aliasABI = true, obj.ABIInternal - } else { - // No ABI override. Check that the symbol is - // using the expected ABI. - want := obj.ABIInternal - if f.lsym.ABI() != want { - Fatalf("function symbol %s has the wrong ABI %v, expected %v", f.lsym.Name, f.lsym.ABI(), want) - } - } - - isLinknameExported := nam.Sym.Linkname != "" && (hasBody || hasDefABI) - if abi, ok := symabiRefs[f.lsym.Name]; (ok && abi == obj.ABI0) || isLinknameExported { - // Either 1) this symbol is definitely - // referenced as ABI0 from this package; or 2) - // this symbol is defined in this package but - // given a linkname, indicating that it may be - // referenced from another package. Create an - // ABI0 -> Internal wrapper so it can be - // called as ABI0. In case 2, it's important - // that we know it's defined in this package - // since other packages may "pull" symbols - // using linkname and we don't want to create - // duplicate ABI wrappers. - if f.lsym.ABI() != obj.ABI0 { - needABIAlias, aliasABI = true, obj.ABI0 - } - } - - if needABIAlias { - // These LSyms have the same name as the - // native function, so we create them directly - // rather than looking them up. The uniqueness - // of f.lsym ensures uniqueness of asym. - asym := &obj.LSym{ - Name: f.lsym.Name, - Type: objabi.SABIALIAS, - R: []obj.Reloc{{Sym: f.lsym}}, // 0 size, so "informational" - } - asym.SetABI(aliasABI) - asym.Set(obj.AttrDuplicateOK, true) - Ctxt.ABIAliases = append(Ctxt.ABIAliases, asym) - } - } - - if !hasBody { - // For body-less functions, we only create the LSym. - return - } - - var flag int - if f.Dupok() { - flag |= obj.DUPOK - } - if f.Wrapper() { - flag |= obj.WRAPPER - } - if f.Needctxt() { - flag |= obj.NEEDCTXT - } - if f.Pragma&Nosplit != 0 { - flag |= obj.NOSPLIT - } - if f.ReflectMethod() { - flag |= obj.REFLECTMETHOD - } - - // Clumsy but important. - // See test/recover.go for test cases and src/reflect/value.go - // for the actual functions being considered. - if myimportpath == "reflect" { - switch f.Nname.Sym.Name { - case "callReflect", "callMethod": - flag |= obj.WRAPPER - } - } - - Ctxt.InitTextSym(f.lsym, flag) -} - -func ggloblnod(nam *Node) { - s := nam.Sym.Linksym() - s.Gotype = ngotype(nam).Linksym() - flags := 0 - if nam.Name.Readonly() { - flags = obj.RODATA - } - if nam.Type != nil && !nam.Type.HasPointers() { - flags |= obj.NOPTR - } - Ctxt.Globl(s, nam.Type.Width, flags) - if nam.Name.LibfuzzerExtraCounter() { - s.Type = objabi.SLIBFUZZER_EXTRA_COUNTER - } - if nam.Sym.Linkname != "" { - // Make sure linkname'd symbol is non-package. When a symbol is - // both imported and linkname'd, s.Pkg may not set to "_" in - // types.Sym.Linksym because LSym already exists. Set it here. - s.Pkg = "_" - } -} - -func ggloblsym(s *obj.LSym, width int32, flags int16) { - if flags&obj.LOCAL != 0 { - s.Set(obj.AttrLocal, true) - flags &^= obj.LOCAL - } - Ctxt.Globl(s, int64(width), int(flags)) -} - -func Addrconst(a *obj.Addr, v int64) { - a.Sym = nil - a.Type = obj.TYPE_CONST - a.Offset = v -} - -func Patch(p *obj.Prog, to *obj.Prog) { - if p.To.Type != obj.TYPE_BRANCH { - Fatalf("patch: not a branch") - } - p.To.SetTarget(to) - p.To.Offset = to.Pc -} diff --git a/src/cmd/compile/internal/gc/iexport.go b/src/cmd/compile/internal/gc/iexport.go deleted file mode 100644 index 1f53d8ca7dc195bc26d5d68a5e7bf10d729b5f1d..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/iexport.go +++ /dev/null @@ -1,1515 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Indexed package export. -// -// The indexed export data format is an evolution of the previous -// binary export data format. Its chief contribution is introducing an -// index table, which allows efficient random access of individual -// declarations and inline function bodies. In turn, this allows -// avoiding unnecessary work for compilation units that import large -// packages. -// -// -// The top-level data format is structured as: -// -// Header struct { -// Tag byte // 'i' -// Version uvarint -// StringSize uvarint -// DataSize uvarint -// } -// -// Strings [StringSize]byte -// Data [DataSize]byte -// -// MainIndex []struct{ -// PkgPath stringOff -// PkgName stringOff -// PkgHeight uvarint -// -// Decls []struct{ -// Name stringOff -// Offset declOff -// } -// } -// -// Fingerprint [8]byte -// -// uvarint means a uint64 written out using uvarint encoding. -// -// []T means a uvarint followed by that many T objects. In other -// words: -// -// Len uvarint -// Elems [Len]T -// -// stringOff means a uvarint that indicates an offset within the -// Strings section. At that offset is another uvarint, followed by -// that many bytes, which form the string value. -// -// declOff means a uvarint that indicates an offset within the Data -// section where the associated declaration can be found. -// -// -// There are five kinds of declarations, distinguished by their first -// byte: -// -// type Var struct { -// Tag byte // 'V' -// Pos Pos -// Type typeOff -// } -// -// type Func struct { -// Tag byte // 'F' -// Pos Pos -// Signature Signature -// } -// -// type Const struct { -// Tag byte // 'C' -// Pos Pos -// Value Value -// } -// -// type Type struct { -// Tag byte // 'T' -// Pos Pos -// Underlying typeOff -// -// Methods []struct{ // omitted if Underlying is an interface type -// Pos Pos -// Name stringOff -// Recv Param -// Signature Signature -// } -// } -// -// type Alias struct { -// Tag byte // 'A' -// Pos Pos -// Type typeOff -// } -// -// -// typeOff means a uvarint that either indicates a predeclared type, -// or an offset into the Data section. If the uvarint is less than -// predeclReserved, then it indicates the index into the predeclared -// types list (see predeclared in bexport.go for order). Otherwise, -// subtracting predeclReserved yields the offset of a type descriptor. -// -// Value means a type and type-specific value. See -// (*exportWriter).value for details. -// -// -// There are nine kinds of type descriptors, distinguished by an itag: -// -// type DefinedType struct { -// Tag itag // definedType -// Name stringOff -// PkgPath stringOff -// } -// -// type PointerType struct { -// Tag itag // pointerType -// Elem typeOff -// } -// -// type SliceType struct { -// Tag itag // sliceType -// Elem typeOff -// } -// -// type ArrayType struct { -// Tag itag // arrayType -// Len uint64 -// Elem typeOff -// } -// -// type ChanType struct { -// Tag itag // chanType -// Dir uint64 // 1 RecvOnly; 2 SendOnly; 3 SendRecv -// Elem typeOff -// } -// -// type MapType struct { -// Tag itag // mapType -// Key typeOff -// Elem typeOff -// } -// -// type FuncType struct { -// Tag itag // signatureType -// PkgPath stringOff -// Signature Signature -// } -// -// type StructType struct { -// Tag itag // structType -// PkgPath stringOff -// Fields []struct { -// Pos Pos -// Name stringOff -// Type typeOff -// Embedded bool -// Note stringOff -// } -// } -// -// type InterfaceType struct { -// Tag itag // interfaceType -// PkgPath stringOff -// Embeddeds []struct { -// Pos Pos -// Type typeOff -// } -// Methods []struct { -// Pos Pos -// Name stringOff -// Signature Signature -// } -// } -// -// -// type Signature struct { -// Params []Param -// Results []Param -// Variadic bool // omitted if Results is empty -// } -// -// type Param struct { -// Pos Pos -// Name stringOff -// Type typOff -// } -// -// -// Pos encodes a file:line:column triple, incorporating a simple delta -// encoding scheme within a data object. See exportWriter.pos for -// details. -// -// -// Compiler-specific details. -// -// cmd/compile writes out a second index for inline bodies and also -// appends additional compiler-specific details after declarations. -// Third-party tools are not expected to depend on these details and -// they're expected to change much more rapidly, so they're omitted -// here. See exportWriter's varExt/funcExt/etc methods for details. - -package gc - -import ( - "bufio" - "bytes" - "cmd/compile/internal/types" - "cmd/internal/goobj" - "cmd/internal/src" - "crypto/md5" - "encoding/binary" - "fmt" - "io" - "math/big" - "sort" - "strings" -) - -// Current indexed export format version. Increase with each format change. -// 1: added column details to Pos -// 0: Go1.11 encoding -const iexportVersion = 1 - -// predeclReserved is the number of type offsets reserved for types -// implicitly declared in the universe block. -const predeclReserved = 32 - -// An itag distinguishes the kind of type that was written into the -// indexed export format. -type itag uint64 - -const ( - // Types - definedType itag = iota - pointerType - sliceType - arrayType - chanType - mapType - signatureType - structType - interfaceType -) - -func iexport(out *bufio.Writer) { - // Mark inline bodies that are reachable through exported types. - // (Phase 0 of bexport.go.) - { - // TODO(mdempsky): Separate from bexport logic. - p := &exporter{marked: make(map[*types.Type]bool)} - for _, n := range exportlist { - sym := n.Sym - p.markType(asNode(sym.Def).Type) - } - } - - p := iexporter{ - allPkgs: map[*types.Pkg]bool{}, - stringIndex: map[string]uint64{}, - declIndex: map[*Node]uint64{}, - inlineIndex: map[*Node]uint64{}, - typIndex: map[*types.Type]uint64{}, - } - - for i, pt := range predeclared() { - p.typIndex[pt] = uint64(i) - } - if len(p.typIndex) > predeclReserved { - Fatalf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved) - } - - // Initialize work queue with exported declarations. - for _, n := range exportlist { - p.pushDecl(n) - } - - // Loop until no more work. We use a queue because while - // writing out inline bodies, we may discover additional - // declarations that are needed. - for !p.declTodo.empty() { - p.doDecl(p.declTodo.popLeft()) - } - - // Append indices to data0 section. - dataLen := uint64(p.data0.Len()) - w := p.newWriter() - w.writeIndex(p.declIndex, true) - w.writeIndex(p.inlineIndex, false) - w.flush() - - // Assemble header. - var hdr intWriter - hdr.WriteByte('i') - hdr.uint64(iexportVersion) - hdr.uint64(uint64(p.strings.Len())) - hdr.uint64(dataLen) - - // Flush output. - h := md5.New() - wr := io.MultiWriter(out, h) - io.Copy(wr, &hdr) - io.Copy(wr, &p.strings) - io.Copy(wr, &p.data0) - - // Add fingerprint (used by linker object file). - // Attach this to the end, so tools (e.g. gcimporter) don't care. - copy(Ctxt.Fingerprint[:], h.Sum(nil)[:]) - out.Write(Ctxt.Fingerprint[:]) -} - -// writeIndex writes out an object index. mainIndex indicates whether -// we're writing out the main index, which is also read by -// non-compiler tools and includes a complete package description -// (i.e., name and height). -func (w *exportWriter) writeIndex(index map[*Node]uint64, mainIndex bool) { - // Build a map from packages to objects from that package. - pkgObjs := map[*types.Pkg][]*Node{} - - // For the main index, make sure to include every package that - // we reference, even if we're not exporting (or reexporting) - // any symbols from it. - if mainIndex { - pkgObjs[localpkg] = nil - for pkg := range w.p.allPkgs { - pkgObjs[pkg] = nil - } - } - - for n := range index { - pkgObjs[n.Sym.Pkg] = append(pkgObjs[n.Sym.Pkg], n) - } - - var pkgs []*types.Pkg - for pkg, objs := range pkgObjs { - pkgs = append(pkgs, pkg) - - sort.Slice(objs, func(i, j int) bool { - return objs[i].Sym.Name < objs[j].Sym.Name - }) - } - - sort.Slice(pkgs, func(i, j int) bool { - return pkgs[i].Path < pkgs[j].Path - }) - - w.uint64(uint64(len(pkgs))) - for _, pkg := range pkgs { - w.string(pkg.Path) - if mainIndex { - w.string(pkg.Name) - w.uint64(uint64(pkg.Height)) - } - - objs := pkgObjs[pkg] - w.uint64(uint64(len(objs))) - for _, n := range objs { - w.string(n.Sym.Name) - w.uint64(index[n]) - } - } -} - -type iexporter struct { - // allPkgs tracks all packages that have been referenced by - // the export data, so we can ensure to include them in the - // main index. - allPkgs map[*types.Pkg]bool - - declTodo nodeQueue - - strings intWriter - stringIndex map[string]uint64 - - data0 intWriter - declIndex map[*Node]uint64 - inlineIndex map[*Node]uint64 - typIndex map[*types.Type]uint64 -} - -// stringOff returns the offset of s within the string section. -// If not already present, it's added to the end. -func (p *iexporter) stringOff(s string) uint64 { - off, ok := p.stringIndex[s] - if !ok { - off = uint64(p.strings.Len()) - p.stringIndex[s] = off - - p.strings.uint64(uint64(len(s))) - p.strings.WriteString(s) - } - return off -} - -// pushDecl adds n to the declaration work queue, if not already present. -func (p *iexporter) pushDecl(n *Node) { - if n.Sym == nil || asNode(n.Sym.Def) != n && n.Op != OTYPE { - Fatalf("weird Sym: %v, %v", n, n.Sym) - } - - // Don't export predeclared declarations. - if n.Sym.Pkg == builtinpkg || n.Sym.Pkg == unsafepkg { - return - } - - if _, ok := p.declIndex[n]; ok { - return - } - - p.declIndex[n] = ^uint64(0) // mark n present in work queue - p.declTodo.pushRight(n) -} - -// exportWriter handles writing out individual data section chunks. -type exportWriter struct { - p *iexporter - - data intWriter - currPkg *types.Pkg - prevFile string - prevLine int64 - prevColumn int64 -} - -func (p *iexporter) doDecl(n *Node) { - w := p.newWriter() - w.setPkg(n.Sym.Pkg, false) - - switch n.Op { - case ONAME: - switch n.Class() { - case PEXTERN: - // Variable. - w.tag('V') - w.pos(n.Pos) - w.typ(n.Type) - w.varExt(n) - - case PFUNC: - if n.IsMethod() { - Fatalf("unexpected method: %v", n) - } - - // Function. - w.tag('F') - w.pos(n.Pos) - w.signature(n.Type) - w.funcExt(n) - - default: - Fatalf("unexpected class: %v, %v", n, n.Class()) - } - - case OLITERAL: - // Constant. - n = typecheck(n, ctxExpr) - w.tag('C') - w.pos(n.Pos) - w.value(n.Type, n.Val()) - - case OTYPE: - if IsAlias(n.Sym) { - // Alias. - w.tag('A') - w.pos(n.Pos) - w.typ(n.Type) - break - } - - // Defined type. - w.tag('T') - w.pos(n.Pos) - - underlying := n.Type.Orig - if underlying == types.Errortype.Orig { - // For "type T error", use error as the - // underlying type instead of error's own - // underlying anonymous interface. This - // ensures consistency with how importers may - // declare error (e.g., go/types uses nil Pkg - // for predeclared objects). - underlying = types.Errortype - } - w.typ(underlying) - - t := n.Type - if t.IsInterface() { - w.typeExt(t) - break - } - - ms := t.Methods() - w.uint64(uint64(ms.Len())) - for _, m := range ms.Slice() { - w.pos(m.Pos) - w.selector(m.Sym) - w.param(m.Type.Recv()) - w.signature(m.Type) - } - - w.typeExt(t) - for _, m := range ms.Slice() { - w.methExt(m) - } - - default: - Fatalf("unexpected node: %v", n) - } - - p.declIndex[n] = w.flush() -} - -func (w *exportWriter) tag(tag byte) { - w.data.WriteByte(tag) -} - -func (p *iexporter) doInline(f *Node) { - w := p.newWriter() - w.setPkg(fnpkg(f), false) - - w.stmtList(asNodes(f.Func.Inl.Body)) - - p.inlineIndex[f] = w.flush() -} - -func (w *exportWriter) pos(pos src.XPos) { - p := Ctxt.PosTable.Pos(pos) - file := p.Base().AbsFilename() - line := int64(p.RelLine()) - column := int64(p.RelCol()) - - // Encode position relative to the last position: column - // delta, then line delta, then file name. We reserve the - // bottom bit of the column and line deltas to encode whether - // the remaining fields are present. - // - // Note: Because data objects may be read out of order (or not - // at all), we can only apply delta encoding within a single - // object. This is handled implicitly by tracking prevFile, - // prevLine, and prevColumn as fields of exportWriter. - - deltaColumn := (column - w.prevColumn) << 1 - deltaLine := (line - w.prevLine) << 1 - - if file != w.prevFile { - deltaLine |= 1 - } - if deltaLine != 0 { - deltaColumn |= 1 - } - - w.int64(deltaColumn) - if deltaColumn&1 != 0 { - w.int64(deltaLine) - if deltaLine&1 != 0 { - w.string(file) - } - } - - w.prevFile = file - w.prevLine = line - w.prevColumn = column -} - -func (w *exportWriter) pkg(pkg *types.Pkg) { - // Ensure any referenced packages are declared in the main index. - w.p.allPkgs[pkg] = true - - w.string(pkg.Path) -} - -func (w *exportWriter) qualifiedIdent(n *Node) { - // Ensure any referenced declarations are written out too. - w.p.pushDecl(n) - - s := n.Sym - w.string(s.Name) - w.pkg(s.Pkg) -} - -func (w *exportWriter) selector(s *types.Sym) { - if w.currPkg == nil { - Fatalf("missing currPkg") - } - - // Method selectors are rewritten into method symbols (of the - // form T.M) during typechecking, but we want to write out - // just the bare method name. - name := s.Name - if i := strings.LastIndex(name, "."); i >= 0 { - name = name[i+1:] - } else { - pkg := w.currPkg - if types.IsExported(name) { - pkg = localpkg - } - if s.Pkg != pkg { - Fatalf("package mismatch in selector: %v in package %q, but want %q", s, s.Pkg.Path, pkg.Path) - } - } - - w.string(name) -} - -func (w *exportWriter) typ(t *types.Type) { - w.data.uint64(w.p.typOff(t)) -} - -func (p *iexporter) newWriter() *exportWriter { - return &exportWriter{p: p} -} - -func (w *exportWriter) flush() uint64 { - off := uint64(w.p.data0.Len()) - io.Copy(&w.p.data0, &w.data) - return off -} - -func (p *iexporter) typOff(t *types.Type) uint64 { - off, ok := p.typIndex[t] - if !ok { - w := p.newWriter() - w.doTyp(t) - off = predeclReserved + w.flush() - p.typIndex[t] = off - } - return off -} - -func (w *exportWriter) startType(k itag) { - w.data.uint64(uint64(k)) -} - -func (w *exportWriter) doTyp(t *types.Type) { - if t.Sym != nil { - if t.Sym.Pkg == builtinpkg || t.Sym.Pkg == unsafepkg { - Fatalf("builtin type missing from typIndex: %v", t) - } - - w.startType(definedType) - w.qualifiedIdent(typenod(t)) - return - } - - switch t.Etype { - case TPTR: - w.startType(pointerType) - w.typ(t.Elem()) - - case TSLICE: - w.startType(sliceType) - w.typ(t.Elem()) - - case TARRAY: - w.startType(arrayType) - w.uint64(uint64(t.NumElem())) - w.typ(t.Elem()) - - case TCHAN: - w.startType(chanType) - w.uint64(uint64(t.ChanDir())) - w.typ(t.Elem()) - - case TMAP: - w.startType(mapType) - w.typ(t.Key()) - w.typ(t.Elem()) - - case TFUNC: - w.startType(signatureType) - w.setPkg(t.Pkg(), true) - w.signature(t) - - case TSTRUCT: - w.startType(structType) - w.setPkg(t.Pkg(), true) - - w.uint64(uint64(t.NumFields())) - for _, f := range t.FieldSlice() { - w.pos(f.Pos) - w.selector(f.Sym) - w.typ(f.Type) - w.bool(f.Embedded != 0) - w.string(f.Note) - } - - case TINTER: - var embeddeds, methods []*types.Field - for _, m := range t.Methods().Slice() { - if m.Sym != nil { - methods = append(methods, m) - } else { - embeddeds = append(embeddeds, m) - } - } - - w.startType(interfaceType) - w.setPkg(t.Pkg(), true) - - w.uint64(uint64(len(embeddeds))) - for _, f := range embeddeds { - w.pos(f.Pos) - w.typ(f.Type) - } - - w.uint64(uint64(len(methods))) - for _, f := range methods { - w.pos(f.Pos) - w.selector(f.Sym) - w.signature(f.Type) - } - - default: - Fatalf("unexpected type: %v", t) - } -} - -func (w *exportWriter) setPkg(pkg *types.Pkg, write bool) { - if pkg == nil { - // TODO(mdempsky): Proactively set Pkg for types and - // remove this fallback logic. - pkg = localpkg - } - - if write { - w.pkg(pkg) - } - - w.currPkg = pkg -} - -func (w *exportWriter) signature(t *types.Type) { - w.paramList(t.Params().FieldSlice()) - w.paramList(t.Results().FieldSlice()) - if n := t.Params().NumFields(); n > 0 { - w.bool(t.Params().Field(n - 1).IsDDD()) - } -} - -func (w *exportWriter) paramList(fs []*types.Field) { - w.uint64(uint64(len(fs))) - for _, f := range fs { - w.param(f) - } -} - -func (w *exportWriter) param(f *types.Field) { - w.pos(f.Pos) - w.localIdent(origSym(f.Sym), 0) - w.typ(f.Type) -} - -func constTypeOf(typ *types.Type) Ctype { - switch typ { - case types.UntypedInt, types.UntypedRune: - return CTINT - case types.UntypedFloat: - return CTFLT - case types.UntypedComplex: - return CTCPLX - } - - switch typ.Etype { - case TCHAN, TFUNC, TMAP, TNIL, TINTER, TPTR, TSLICE, TUNSAFEPTR: - return CTNIL - case TBOOL: - return CTBOOL - case TSTRING: - return CTSTR - case TINT, TINT8, TINT16, TINT32, TINT64, - TUINT, TUINT8, TUINT16, TUINT32, TUINT64, TUINTPTR: - return CTINT - case TFLOAT32, TFLOAT64: - return CTFLT - case TCOMPLEX64, TCOMPLEX128: - return CTCPLX - } - - Fatalf("unexpected constant type: %v", typ) - return 0 -} - -func (w *exportWriter) value(typ *types.Type, v Val) { - if vt := idealType(v.Ctype()); typ.IsUntyped() && typ != vt { - Fatalf("exporter: untyped type mismatch, have: %v, want: %v", typ, vt) - } - w.typ(typ) - - // Each type has only one admissible constant representation, - // so we could type switch directly on v.U here. However, - // switching on the type increases symmetry with import logic - // and provides a useful consistency check. - - switch constTypeOf(typ) { - case CTNIL: - // Only one value; nothing to encode. - _ = v.U.(*NilVal) - case CTBOOL: - w.bool(v.U.(bool)) - case CTSTR: - w.string(v.U.(string)) - case CTINT: - w.mpint(&v.U.(*Mpint).Val, typ) - case CTFLT: - w.mpfloat(&v.U.(*Mpflt).Val, typ) - case CTCPLX: - x := v.U.(*Mpcplx) - w.mpfloat(&x.Real.Val, typ) - w.mpfloat(&x.Imag.Val, typ) - } -} - -func intSize(typ *types.Type) (signed bool, maxBytes uint) { - if typ.IsUntyped() { - return true, Mpprec / 8 - } - - switch typ.Etype { - case TFLOAT32, TCOMPLEX64: - return true, 3 - case TFLOAT64, TCOMPLEX128: - return true, 7 - } - - signed = typ.IsSigned() - maxBytes = uint(typ.Size()) - - // The go/types API doesn't expose sizes to importers, so they - // don't know how big these types are. - switch typ.Etype { - case TINT, TUINT, TUINTPTR: - maxBytes = 8 - } - - return -} - -// mpint exports a multi-precision integer. -// -// For unsigned types, small values are written out as a single -// byte. Larger values are written out as a length-prefixed big-endian -// byte string, where the length prefix is encoded as its complement. -// For example, bytes 0, 1, and 2 directly represent the integer -// values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-, -// 2-, and 3-byte big-endian string follow. -// -// Encoding for signed types use the same general approach as for -// unsigned types, except small values use zig-zag encoding and the -// bottom bit of length prefix byte for large values is reserved as a -// sign bit. -// -// The exact boundary between small and large encodings varies -// according to the maximum number of bytes needed to encode a value -// of type typ. As a special case, 8-bit types are always encoded as a -// single byte. -// -// TODO(mdempsky): Is this level of complexity really worthwhile? -func (w *exportWriter) mpint(x *big.Int, typ *types.Type) { - signed, maxBytes := intSize(typ) - - negative := x.Sign() < 0 - if !signed && negative { - Fatalf("negative unsigned integer; type %v, value %v", typ, x) - } - - b := x.Bytes() - if len(b) > 0 && b[0] == 0 { - Fatalf("leading zeros") - } - if uint(len(b)) > maxBytes { - Fatalf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x) - } - - maxSmall := 256 - maxBytes - if signed { - maxSmall = 256 - 2*maxBytes - } - if maxBytes == 1 { - maxSmall = 256 - } - - // Check if x can use small value encoding. - if len(b) <= 1 { - var ux uint - if len(b) == 1 { - ux = uint(b[0]) - } - if signed { - ux <<= 1 - if negative { - ux-- - } - } - if ux < maxSmall { - w.data.WriteByte(byte(ux)) - return - } - } - - n := 256 - uint(len(b)) - if signed { - n = 256 - 2*uint(len(b)) - if negative { - n |= 1 - } - } - if n < maxSmall || n >= 256 { - Fatalf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n) - } - - w.data.WriteByte(byte(n)) - w.data.Write(b) -} - -// mpfloat exports a multi-precision floating point number. -// -// The number's value is decomposed into mantissa × 2**exponent, where -// mantissa is an integer. The value is written out as mantissa (as a -// multi-precision integer) and then the exponent, except exponent is -// omitted if mantissa is zero. -func (w *exportWriter) mpfloat(f *big.Float, typ *types.Type) { - if f.IsInf() { - Fatalf("infinite constant") - } - - // Break into f = mant × 2**exp, with 0.5 <= mant < 1. - var mant big.Float - exp := int64(f.MantExp(&mant)) - - // Scale so that mant is an integer. - prec := mant.MinPrec() - mant.SetMantExp(&mant, int(prec)) - exp -= int64(prec) - - manti, acc := mant.Int(nil) - if acc != big.Exact { - Fatalf("mantissa scaling failed for %f (%s)", f, acc) - } - w.mpint(manti, typ) - if manti.Sign() != 0 { - w.int64(exp) - } -} - -func (w *exportWriter) bool(b bool) bool { - var x uint64 - if b { - x = 1 - } - w.uint64(x) - return b -} - -func (w *exportWriter) int64(x int64) { w.data.int64(x) } -func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) } -func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) } - -// Compiler-specific extensions. - -func (w *exportWriter) varExt(n *Node) { - w.linkname(n.Sym) - w.symIdx(n.Sym) -} - -func (w *exportWriter) funcExt(n *Node) { - w.linkname(n.Sym) - w.symIdx(n.Sym) - - // Escape analysis. - for _, fs := range &types.RecvsParams { - for _, f := range fs(n.Type).FieldSlice() { - w.string(f.Note) - } - } - - // Inline body. - if n.Func.Inl != nil { - w.uint64(1 + uint64(n.Func.Inl.Cost)) - if n.Func.ExportInline() { - w.p.doInline(n) - } - - // Endlineno for inlined function. - if n.Name.Defn != nil { - w.pos(n.Name.Defn.Func.Endlineno) - } else { - // When the exported node was defined externally, - // e.g. io exports atomic.(*Value).Load or bytes exports errors.New. - // Keep it as we don't distinguish this case in iimport.go. - w.pos(n.Func.Endlineno) - } - } else { - w.uint64(0) - } -} - -func (w *exportWriter) methExt(m *types.Field) { - w.bool(m.Nointerface()) - w.funcExt(asNode(m.Type.Nname())) -} - -func (w *exportWriter) linkname(s *types.Sym) { - w.string(s.Linkname) -} - -func (w *exportWriter) symIdx(s *types.Sym) { - lsym := s.Linksym() - if lsym.PkgIdx > goobj.PkgIdxSelf || (lsym.PkgIdx == goobj.PkgIdxInvalid && !lsym.Indexed()) || s.Linkname != "" { - // Don't export index for non-package symbols, linkname'd symbols, - // and symbols without an index. They can only be referenced by - // name. - w.int64(-1) - } else { - // For a defined symbol, export its index. - // For re-exporting an imported symbol, pass its index through. - w.int64(int64(lsym.SymIdx)) - } -} - -func (w *exportWriter) typeExt(t *types.Type) { - // Export whether this type is marked notinheap. - w.bool(t.NotInHeap()) - // For type T, export the index of type descriptor symbols of T and *T. - if i, ok := typeSymIdx[t]; ok { - w.int64(i[0]) - w.int64(i[1]) - return - } - w.symIdx(typesym(t)) - w.symIdx(typesym(t.PtrTo())) -} - -// Inline bodies. - -func (w *exportWriter) stmtList(list Nodes) { - for _, n := range list.Slice() { - w.node(n) - } - w.op(OEND) -} - -func (w *exportWriter) node(n *Node) { - if opprec[n.Op] < 0 { - w.stmt(n) - } else { - w.expr(n) - } -} - -// Caution: stmt will emit more than one node for statement nodes n that have a non-empty -// n.Ninit and where n cannot have a natural init section (such as in "if", "for", etc.). -func (w *exportWriter) stmt(n *Node) { - if n.Ninit.Len() > 0 && !stmtwithinit(n.Op) { - // can't use stmtList here since we don't want the final OEND - for _, n := range n.Ninit.Slice() { - w.stmt(n) - } - } - - switch op := n.Op; op { - case ODCL: - w.op(ODCL) - w.pos(n.Left.Pos) - w.localName(n.Left) - w.typ(n.Left.Type) - - // case ODCLFIELD: - // unimplemented - handled by default case - - case OAS: - // Don't export "v = " initializing statements, hope they're always - // preceded by the DCL which will be re-parsed and typecheck to reproduce - // the "v = " again. - if n.Right != nil { - w.op(OAS) - w.pos(n.Pos) - w.expr(n.Left) - w.expr(n.Right) - } - - case OASOP: - w.op(OASOP) - w.pos(n.Pos) - w.op(n.SubOp()) - w.expr(n.Left) - if w.bool(!n.Implicit()) { - w.expr(n.Right) - } - - case OAS2: - w.op(OAS2) - w.pos(n.Pos) - w.exprList(n.List) - w.exprList(n.Rlist) - - case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: - w.op(OAS2) - w.pos(n.Pos) - w.exprList(n.List) - w.exprList(asNodes([]*Node{n.Right})) - - case ORETURN: - w.op(ORETURN) - w.pos(n.Pos) - w.exprList(n.List) - - // case ORETJMP: - // unreachable - generated by compiler for trampolin routines - - case OGO, ODEFER: - w.op(op) - w.pos(n.Pos) - w.expr(n.Left) - - case OIF: - w.op(OIF) - w.pos(n.Pos) - w.stmtList(n.Ninit) - w.expr(n.Left) - w.stmtList(n.Nbody) - w.stmtList(n.Rlist) - - case OFOR: - w.op(OFOR) - w.pos(n.Pos) - w.stmtList(n.Ninit) - w.exprsOrNil(n.Left, n.Right) - w.stmtList(n.Nbody) - - case ORANGE: - w.op(ORANGE) - w.pos(n.Pos) - w.stmtList(n.List) - w.expr(n.Right) - w.stmtList(n.Nbody) - - case OSELECT, OSWITCH: - w.op(op) - w.pos(n.Pos) - w.stmtList(n.Ninit) - w.exprsOrNil(n.Left, nil) - w.caseList(n) - - // case OCASE: - // handled by caseList - - case OFALL: - w.op(OFALL) - w.pos(n.Pos) - - case OBREAK, OCONTINUE: - w.op(op) - w.pos(n.Pos) - w.exprsOrNil(n.Left, nil) - - case OEMPTY: - // nothing to emit - - case OGOTO, OLABEL: - w.op(op) - w.pos(n.Pos) - w.string(n.Sym.Name) - - default: - Fatalf("exporter: CANNOT EXPORT: %v\nPlease notify gri@\n", n.Op) - } -} - -func (w *exportWriter) caseList(sw *Node) { - namedTypeSwitch := sw.Op == OSWITCH && sw.Left != nil && sw.Left.Op == OTYPESW && sw.Left.Left != nil - - cases := sw.List.Slice() - w.uint64(uint64(len(cases))) - for _, cas := range cases { - if cas.Op != OCASE { - Fatalf("expected OCASE, got %v", cas) - } - w.pos(cas.Pos) - w.stmtList(cas.List) - if namedTypeSwitch { - w.localName(cas.Rlist.First()) - } - w.stmtList(cas.Nbody) - } -} - -func (w *exportWriter) exprList(list Nodes) { - for _, n := range list.Slice() { - w.expr(n) - } - w.op(OEND) -} - -func (w *exportWriter) expr(n *Node) { - // from nodefmt (fmt.go) - // - // nodefmt reverts nodes back to their original - we don't need to do - // it because we are not bound to produce valid Go syntax when exporting - // - // if (fmtmode != FExp || n.Op != OLITERAL) && n.Orig != nil { - // n = n.Orig - // } - - // from exprfmt (fmt.go) - for n.Op == OPAREN || n.Implicit() && (n.Op == ODEREF || n.Op == OADDR || n.Op == ODOT || n.Op == ODOTPTR) { - n = n.Left - } - - switch op := n.Op; op { - // expressions - // (somewhat closely following the structure of exprfmt in fmt.go) - case OLITERAL: - if n.Val().Ctype() == CTNIL && n.Orig != nil && n.Orig != n { - w.expr(n.Orig) - break - } - w.op(OLITERAL) - w.pos(n.Pos) - w.value(n.Type, n.Val()) - - case ONAME: - // Special case: explicit name of func (*T) method(...) is turned into pkg.(*T).method, - // but for export, this should be rendered as (*pkg.T).meth. - // These nodes have the special property that they are names with a left OTYPE and a right ONAME. - if n.isMethodExpression() { - w.op(OXDOT) - w.pos(n.Pos) - w.expr(n.Left) // n.Left.Op == OTYPE - w.selector(n.Right.Sym) - break - } - - // Package scope name. - if (n.Class() == PEXTERN || n.Class() == PFUNC) && !n.isBlank() { - w.op(ONONAME) - w.qualifiedIdent(n) - break - } - - // Function scope name. - w.op(ONAME) - w.localName(n) - - // case OPACK, ONONAME: - // should have been resolved by typechecking - handled by default case - - case OTYPE: - w.op(OTYPE) - w.typ(n.Type) - - case OTYPESW: - w.op(OTYPESW) - w.pos(n.Pos) - var s *types.Sym - if n.Left != nil { - if n.Left.Op != ONONAME { - Fatalf("expected ONONAME, got %v", n.Left) - } - s = n.Left.Sym - } - w.localIdent(s, 0) // declared pseudo-variable, if any - w.exprsOrNil(n.Right, nil) - - // case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC: - // should have been resolved by typechecking - handled by default case - - // case OCLOSURE: - // unimplemented - handled by default case - - // case OCOMPLIT: - // should have been resolved by typechecking - handled by default case - - case OPTRLIT: - w.op(OADDR) - w.pos(n.Pos) - w.expr(n.Left) - - case OSTRUCTLIT: - w.op(OSTRUCTLIT) - w.pos(n.Pos) - w.typ(n.Type) - w.elemList(n.List) // special handling of field names - - case OARRAYLIT, OSLICELIT, OMAPLIT: - w.op(OCOMPLIT) - w.pos(n.Pos) - w.typ(n.Type) - w.exprList(n.List) - - case OKEY: - w.op(OKEY) - w.pos(n.Pos) - w.exprsOrNil(n.Left, n.Right) - - // case OSTRUCTKEY: - // unreachable - handled in case OSTRUCTLIT by elemList - - case OCALLPART: - // An OCALLPART is an OXDOT before type checking. - w.op(OXDOT) - w.pos(n.Pos) - w.expr(n.Left) - // Right node should be ONAME - w.selector(n.Right.Sym) - - case OXDOT, ODOT, ODOTPTR, ODOTINTER, ODOTMETH: - w.op(OXDOT) - w.pos(n.Pos) - w.expr(n.Left) - w.selector(n.Sym) - - case ODOTTYPE, ODOTTYPE2: - w.op(ODOTTYPE) - w.pos(n.Pos) - w.expr(n.Left) - w.typ(n.Type) - - case OINDEX, OINDEXMAP: - w.op(OINDEX) - w.pos(n.Pos) - w.expr(n.Left) - w.expr(n.Right) - - case OSLICE, OSLICESTR, OSLICEARR: - w.op(OSLICE) - w.pos(n.Pos) - w.expr(n.Left) - low, high, _ := n.SliceBounds() - w.exprsOrNil(low, high) - - case OSLICE3, OSLICE3ARR: - w.op(OSLICE3) - w.pos(n.Pos) - w.expr(n.Left) - low, high, max := n.SliceBounds() - w.exprsOrNil(low, high) - w.expr(max) - - case OCOPY, OCOMPLEX: - // treated like other builtin calls (see e.g., OREAL) - w.op(op) - w.pos(n.Pos) - w.expr(n.Left) - w.expr(n.Right) - w.op(OEND) - - case OCONV, OCONVIFACE, OCONVNOP, OBYTES2STR, ORUNES2STR, OSTR2BYTES, OSTR2RUNES, ORUNESTR: - w.op(OCONV) - w.pos(n.Pos) - w.expr(n.Left) - w.typ(n.Type) - - case OREAL, OIMAG, OAPPEND, OCAP, OCLOSE, ODELETE, OLEN, OMAKE, ONEW, OPANIC, ORECOVER, OPRINT, OPRINTN: - w.op(op) - w.pos(n.Pos) - if n.Left != nil { - w.expr(n.Left) - w.op(OEND) - } else { - w.exprList(n.List) // emits terminating OEND - } - // only append() calls may contain '...' arguments - if op == OAPPEND { - w.bool(n.IsDDD()) - } else if n.IsDDD() { - Fatalf("exporter: unexpected '...' with %v call", op) - } - - case OCALL, OCALLFUNC, OCALLMETH, OCALLINTER, OGETG: - w.op(OCALL) - w.pos(n.Pos) - w.stmtList(n.Ninit) - w.expr(n.Left) - w.exprList(n.List) - w.bool(n.IsDDD()) - - case OMAKEMAP, OMAKECHAN, OMAKESLICE: - w.op(op) // must keep separate from OMAKE for importer - w.pos(n.Pos) - w.typ(n.Type) - switch { - default: - // empty list - w.op(OEND) - case n.List.Len() != 0: // pre-typecheck - w.exprList(n.List) // emits terminating OEND - case n.Right != nil: - w.expr(n.Left) - w.expr(n.Right) - w.op(OEND) - case n.Left != nil && (n.Op == OMAKESLICE || !n.Left.Type.IsUntyped()): - w.expr(n.Left) - w.op(OEND) - } - - // unary expressions - case OPLUS, ONEG, OADDR, OBITNOT, ODEREF, ONOT, ORECV: - w.op(op) - w.pos(n.Pos) - w.expr(n.Left) - - // binary expressions - case OADD, OAND, OANDAND, OANDNOT, ODIV, OEQ, OGE, OGT, OLE, OLT, - OLSH, OMOD, OMUL, ONE, OOR, OOROR, ORSH, OSEND, OSUB, OXOR: - w.op(op) - w.pos(n.Pos) - w.expr(n.Left) - w.expr(n.Right) - - case OADDSTR: - w.op(OADDSTR) - w.pos(n.Pos) - w.exprList(n.List) - - case ODCLCONST: - // if exporting, DCLCONST should just be removed as its usage - // has already been replaced with literals - - default: - Fatalf("cannot export %v (%d) node\n"+ - "\t==> please file an issue and assign to gri@", n.Op, int(n.Op)) - } -} - -func (w *exportWriter) op(op Op) { - w.uint64(uint64(op)) -} - -func (w *exportWriter) exprsOrNil(a, b *Node) { - ab := 0 - if a != nil { - ab |= 1 - } - if b != nil { - ab |= 2 - } - w.uint64(uint64(ab)) - if ab&1 != 0 { - w.expr(a) - } - if ab&2 != 0 { - w.node(b) - } -} - -func (w *exportWriter) elemList(list Nodes) { - w.uint64(uint64(list.Len())) - for _, n := range list.Slice() { - w.selector(n.Sym) - w.expr(n.Left) - } -} - -func (w *exportWriter) localName(n *Node) { - // Escape analysis happens after inline bodies are saved, but - // we're using the same ONAME nodes, so we might still see - // PAUTOHEAP here. - // - // Check for Stackcopy to identify PAUTOHEAP that came from - // PPARAM/PPARAMOUT, because we only want to include vargen in - // non-param names. - var v int32 - if n.Class() == PAUTO || (n.Class() == PAUTOHEAP && n.Name.Param.Stackcopy == nil) { - v = n.Name.Vargen - } - - w.localIdent(n.Sym, v) -} - -func (w *exportWriter) localIdent(s *types.Sym, v int32) { - // Anonymous parameters. - if s == nil { - w.string("") - return - } - - name := s.Name - if name == "_" { - w.string("_") - return - } - - // TODO(mdempsky): Fix autotmp hack. - if i := strings.LastIndex(name, "."); i >= 0 && !strings.HasPrefix(name, ".autotmp_") { - Fatalf("unexpected dot in identifier: %v", name) - } - - if v > 0 { - if strings.Contains(name, "·") { - Fatalf("exporter: unexpected · in symbol name") - } - name = fmt.Sprintf("%s·%d", name, v) - } - - if !types.IsExported(name) && s.Pkg != w.currPkg { - Fatalf("weird package in name: %v => %v, not %q", s, name, w.currPkg.Path) - } - - w.string(name) -} - -type intWriter struct { - bytes.Buffer -} - -func (w *intWriter) int64(x int64) { - var buf [binary.MaxVarintLen64]byte - n := binary.PutVarint(buf[:], x) - w.Write(buf[:n]) -} - -func (w *intWriter) uint64(x uint64) { - var buf [binary.MaxVarintLen64]byte - n := binary.PutUvarint(buf[:], x) - w.Write(buf[:n]) -} diff --git a/src/cmd/compile/internal/gc/iimport.go b/src/cmd/compile/internal/gc/iimport.go deleted file mode 100644 index c0114d0e53b166be1daf6cc9554006ad744b1ff2..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/iimport.go +++ /dev/null @@ -1,1117 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Indexed package import. -// See iexport.go for the export data format. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/bio" - "cmd/internal/goobj" - "cmd/internal/obj" - "cmd/internal/src" - "encoding/binary" - "fmt" - "io" - "math/big" - "os" - "strings" -) - -// An iimporterAndOffset identifies an importer and an offset within -// its data section. -type iimporterAndOffset struct { - p *iimporter - off uint64 -} - -var ( - // declImporter maps from imported identifiers to an importer - // and offset where that identifier's declaration can be read. - declImporter = map[*types.Sym]iimporterAndOffset{} - - // inlineImporter is like declImporter, but for inline bodies - // for function and method symbols. - inlineImporter = map[*types.Sym]iimporterAndOffset{} -) - -func expandDecl(n *Node) { - if n.Op != ONONAME { - return - } - - r := importReaderFor(n, declImporter) - if r == nil { - // Can happen if user tries to reference an undeclared name. - return - } - - r.doDecl(n) -} - -func expandInline(fn *Node) { - if fn.Func.Inl.Body != nil { - return - } - - r := importReaderFor(fn, inlineImporter) - if r == nil { - Fatalf("missing import reader for %v", fn) - } - - r.doInline(fn) -} - -func importReaderFor(n *Node, importers map[*types.Sym]iimporterAndOffset) *importReader { - x, ok := importers[n.Sym] - if !ok { - return nil - } - - return x.p.newReader(x.off, n.Sym.Pkg) -} - -type intReader struct { - *bio.Reader - pkg *types.Pkg -} - -func (r *intReader) int64() int64 { - i, err := binary.ReadVarint(r.Reader) - if err != nil { - yyerror("import %q: read error: %v", r.pkg.Path, err) - errorexit() - } - return i -} - -func (r *intReader) uint64() uint64 { - i, err := binary.ReadUvarint(r.Reader) - if err != nil { - yyerror("import %q: read error: %v", r.pkg.Path, err) - errorexit() - } - return i -} - -func iimport(pkg *types.Pkg, in *bio.Reader) (fingerprint goobj.FingerprintType) { - ir := &intReader{in, pkg} - - version := ir.uint64() - if version != iexportVersion { - yyerror("import %q: unknown export format version %d", pkg.Path, version) - errorexit() - } - - sLen := ir.uint64() - dLen := ir.uint64() - - // Map string (and data) section into memory as a single large - // string. This reduces heap fragmentation and allows - // returning individual substrings very efficiently. - data, err := mapFile(in.File(), in.Offset(), int64(sLen+dLen)) - if err != nil { - yyerror("import %q: mapping input: %v", pkg.Path, err) - errorexit() - } - stringData := data[:sLen] - declData := data[sLen:] - - in.MustSeek(int64(sLen+dLen), os.SEEK_CUR) - - p := &iimporter{ - ipkg: pkg, - - pkgCache: map[uint64]*types.Pkg{}, - posBaseCache: map[uint64]*src.PosBase{}, - typCache: map[uint64]*types.Type{}, - - stringData: stringData, - declData: declData, - } - - for i, pt := range predeclared() { - p.typCache[uint64(i)] = pt - } - - // Declaration index. - for nPkgs := ir.uint64(); nPkgs > 0; nPkgs-- { - pkg := p.pkgAt(ir.uint64()) - pkgName := p.stringAt(ir.uint64()) - pkgHeight := int(ir.uint64()) - if pkg.Name == "" { - pkg.Name = pkgName - pkg.Height = pkgHeight - numImport[pkgName]++ - - // TODO(mdempsky): This belongs somewhere else. - pkg.Lookup("_").Def = asTypesNode(nblank) - } else { - if pkg.Name != pkgName { - Fatalf("conflicting package names %v and %v for path %q", pkg.Name, pkgName, pkg.Path) - } - if pkg.Height != pkgHeight { - Fatalf("conflicting package heights %v and %v for path %q", pkg.Height, pkgHeight, pkg.Path) - } - } - - for nSyms := ir.uint64(); nSyms > 0; nSyms-- { - s := pkg.Lookup(p.stringAt(ir.uint64())) - off := ir.uint64() - - if _, ok := declImporter[s]; ok { - continue - } - declImporter[s] = iimporterAndOffset{p, off} - - // Create stub declaration. If used, this will - // be overwritten by expandDecl. - if s.Def != nil { - Fatalf("unexpected definition for %v: %v", s, asNode(s.Def)) - } - s.Def = asTypesNode(npos(src.NoXPos, dclname(s))) - } - } - - // Inline body index. - for nPkgs := ir.uint64(); nPkgs > 0; nPkgs-- { - pkg := p.pkgAt(ir.uint64()) - - for nSyms := ir.uint64(); nSyms > 0; nSyms-- { - s := pkg.Lookup(p.stringAt(ir.uint64())) - off := ir.uint64() - - if _, ok := inlineImporter[s]; ok { - continue - } - inlineImporter[s] = iimporterAndOffset{p, off} - } - } - - // Fingerprint. - _, err = io.ReadFull(in, fingerprint[:]) - if err != nil { - yyerror("import %s: error reading fingerprint", pkg.Path) - errorexit() - } - return fingerprint -} - -type iimporter struct { - ipkg *types.Pkg - - pkgCache map[uint64]*types.Pkg - posBaseCache map[uint64]*src.PosBase - typCache map[uint64]*types.Type - - stringData string - declData string -} - -func (p *iimporter) stringAt(off uint64) string { - var x [binary.MaxVarintLen64]byte - n := copy(x[:], p.stringData[off:]) - - slen, n := binary.Uvarint(x[:n]) - if n <= 0 { - Fatalf("varint failed") - } - spos := off + uint64(n) - return p.stringData[spos : spos+slen] -} - -func (p *iimporter) posBaseAt(off uint64) *src.PosBase { - if posBase, ok := p.posBaseCache[off]; ok { - return posBase - } - - file := p.stringAt(off) - posBase := src.NewFileBase(file, file) - p.posBaseCache[off] = posBase - return posBase -} - -func (p *iimporter) pkgAt(off uint64) *types.Pkg { - if pkg, ok := p.pkgCache[off]; ok { - return pkg - } - - pkg := p.ipkg - if pkgPath := p.stringAt(off); pkgPath != "" { - pkg = types.NewPkg(pkgPath, "") - } - p.pkgCache[off] = pkg - return pkg -} - -// An importReader keeps state for reading an individual imported -// object (declaration or inline body). -type importReader struct { - strings.Reader - p *iimporter - - currPkg *types.Pkg - prevBase *src.PosBase - prevLine int64 - prevColumn int64 -} - -func (p *iimporter) newReader(off uint64, pkg *types.Pkg) *importReader { - r := &importReader{ - p: p, - currPkg: pkg, - } - // (*strings.Reader).Reset wasn't added until Go 1.7, and we - // need to build with Go 1.4. - r.Reader = *strings.NewReader(p.declData[off:]) - return r -} - -func (r *importReader) string() string { return r.p.stringAt(r.uint64()) } -func (r *importReader) posBase() *src.PosBase { return r.p.posBaseAt(r.uint64()) } -func (r *importReader) pkg() *types.Pkg { return r.p.pkgAt(r.uint64()) } - -func (r *importReader) setPkg() { - r.currPkg = r.pkg() -} - -func (r *importReader) doDecl(n *Node) { - if n.Op != ONONAME { - Fatalf("doDecl: unexpected Op for %v: %v", n.Sym, n.Op) - } - - tag := r.byte() - pos := r.pos() - - switch tag { - case 'A': - typ := r.typ() - - importalias(r.p.ipkg, pos, n.Sym, typ) - - case 'C': - typ, val := r.value() - - importconst(r.p.ipkg, pos, n.Sym, typ, val) - - case 'F': - typ := r.signature(nil) - - importfunc(r.p.ipkg, pos, n.Sym, typ) - r.funcExt(n) - - case 'T': - // Types can be recursive. We need to setup a stub - // declaration before recursing. - t := importtype(r.p.ipkg, pos, n.Sym) - - // We also need to defer width calculations until - // after the underlying type has been assigned. - defercheckwidth() - underlying := r.typ() - setUnderlying(t, underlying) - resumecheckwidth() - - if underlying.IsInterface() { - r.typeExt(t) - break - } - - ms := make([]*types.Field, r.uint64()) - for i := range ms { - mpos := r.pos() - msym := r.ident() - recv := r.param() - mtyp := r.signature(recv) - - f := types.NewField() - f.Pos = mpos - f.Sym = msym - f.Type = mtyp - ms[i] = f - - m := newfuncnamel(mpos, methodSym(recv.Type, msym)) - m.Type = mtyp - m.SetClass(PFUNC) - // methodSym already marked m.Sym as a function. - - // (comment from parser.go) - // inl.C's inlnode in on a dotmeth node expects to find the inlineable body as - // (dotmeth's type).Nname.Inl, and dotmeth's type has been pulled - // out by typecheck's lookdot as this $$.ttype. So by providing - // this back link here we avoid special casing there. - mtyp.SetNname(asTypesNode(m)) - } - t.Methods().Set(ms) - - r.typeExt(t) - for _, m := range ms { - r.methExt(m) - } - - case 'V': - typ := r.typ() - - importvar(r.p.ipkg, pos, n.Sym, typ) - r.varExt(n) - - default: - Fatalf("unexpected tag: %v", tag) - } -} - -func (p *importReader) value() (typ *types.Type, v Val) { - typ = p.typ() - - switch constTypeOf(typ) { - case CTNIL: - v.U = &NilVal{} - case CTBOOL: - v.U = p.bool() - case CTSTR: - v.U = p.string() - case CTINT: - x := new(Mpint) - x.Rune = typ == types.UntypedRune - p.mpint(&x.Val, typ) - v.U = x - case CTFLT: - x := newMpflt() - p.float(x, typ) - v.U = x - case CTCPLX: - x := newMpcmplx() - p.float(&x.Real, typ) - p.float(&x.Imag, typ) - v.U = x - } - return -} - -func (p *importReader) mpint(x *big.Int, typ *types.Type) { - signed, maxBytes := intSize(typ) - - maxSmall := 256 - maxBytes - if signed { - maxSmall = 256 - 2*maxBytes - } - if maxBytes == 1 { - maxSmall = 256 - } - - n, _ := p.ReadByte() - if uint(n) < maxSmall { - v := int64(n) - if signed { - v >>= 1 - if n&1 != 0 { - v = ^v - } - } - x.SetInt64(v) - return - } - - v := -n - if signed { - v = -(n &^ 1) >> 1 - } - if v < 1 || uint(v) > maxBytes { - Fatalf("weird decoding: %v, %v => %v", n, signed, v) - } - b := make([]byte, v) - p.Read(b) - x.SetBytes(b) - if signed && n&1 != 0 { - x.Neg(x) - } -} - -func (p *importReader) float(x *Mpflt, typ *types.Type) { - var mant big.Int - p.mpint(&mant, typ) - m := x.Val.SetInt(&mant) - if m.Sign() == 0 { - return - } - m.SetMantExp(m, int(p.int64())) -} - -func (r *importReader) ident() *types.Sym { - name := r.string() - if name == "" { - return nil - } - pkg := r.currPkg - if types.IsExported(name) { - pkg = localpkg - } - return pkg.Lookup(name) -} - -func (r *importReader) qualifiedIdent() *types.Sym { - name := r.string() - pkg := r.pkg() - return pkg.Lookup(name) -} - -func (r *importReader) pos() src.XPos { - delta := r.int64() - r.prevColumn += delta >> 1 - if delta&1 != 0 { - delta = r.int64() - r.prevLine += delta >> 1 - if delta&1 != 0 { - r.prevBase = r.posBase() - } - } - - if (r.prevBase == nil || r.prevBase.AbsFilename() == "") && r.prevLine == 0 && r.prevColumn == 0 { - // TODO(mdempsky): Remove once we reliably write - // position information for all nodes. - return src.NoXPos - } - - if r.prevBase == nil { - Fatalf("missing posbase") - } - pos := src.MakePos(r.prevBase, uint(r.prevLine), uint(r.prevColumn)) - return Ctxt.PosTable.XPos(pos) -} - -func (r *importReader) typ() *types.Type { - return r.p.typAt(r.uint64()) -} - -func (p *iimporter) typAt(off uint64) *types.Type { - t, ok := p.typCache[off] - if !ok { - if off < predeclReserved { - Fatalf("predeclared type missing from cache: %d", off) - } - t = p.newReader(off-predeclReserved, nil).typ1() - p.typCache[off] = t - } - return t -} - -func (r *importReader) typ1() *types.Type { - switch k := r.kind(); k { - default: - Fatalf("unexpected kind tag in %q: %v", r.p.ipkg.Path, k) - return nil - - case definedType: - // We might be called from within doInline, in which - // case Sym.Def can point to declared parameters - // instead of the top-level types. Also, we don't - // support inlining functions with local defined - // types. Therefore, this must be a package-scope - // type. - n := asNode(r.qualifiedIdent().PkgDef()) - if n.Op == ONONAME { - expandDecl(n) - } - if n.Op != OTYPE { - Fatalf("expected OTYPE, got %v: %v, %v", n.Op, n.Sym, n) - } - return n.Type - case pointerType: - return types.NewPtr(r.typ()) - case sliceType: - return types.NewSlice(r.typ()) - case arrayType: - n := r.uint64() - return types.NewArray(r.typ(), int64(n)) - case chanType: - dir := types.ChanDir(r.uint64()) - return types.NewChan(r.typ(), dir) - case mapType: - return types.NewMap(r.typ(), r.typ()) - - case signatureType: - r.setPkg() - return r.signature(nil) - - case structType: - r.setPkg() - - fs := make([]*types.Field, r.uint64()) - for i := range fs { - pos := r.pos() - sym := r.ident() - typ := r.typ() - emb := r.bool() - note := r.string() - - f := types.NewField() - f.Pos = pos - f.Sym = sym - f.Type = typ - if emb { - f.Embedded = 1 - } - f.Note = note - fs[i] = f - } - - t := types.New(TSTRUCT) - t.SetPkg(r.currPkg) - t.SetFields(fs) - return t - - case interfaceType: - r.setPkg() - - embeddeds := make([]*types.Field, r.uint64()) - for i := range embeddeds { - pos := r.pos() - typ := r.typ() - - f := types.NewField() - f.Pos = pos - f.Type = typ - embeddeds[i] = f - } - - methods := make([]*types.Field, r.uint64()) - for i := range methods { - pos := r.pos() - sym := r.ident() - typ := r.signature(fakeRecvField()) - - f := types.NewField() - f.Pos = pos - f.Sym = sym - f.Type = typ - methods[i] = f - } - - t := types.New(TINTER) - t.SetPkg(r.currPkg) - t.SetInterface(append(embeddeds, methods...)) - - // Ensure we expand the interface in the frontend (#25055). - checkwidth(t) - return t - } -} - -func (r *importReader) kind() itag { - return itag(r.uint64()) -} - -func (r *importReader) signature(recv *types.Field) *types.Type { - params := r.paramList() - results := r.paramList() - if n := len(params); n > 0 { - params[n-1].SetIsDDD(r.bool()) - } - t := functypefield(recv, params, results) - t.SetPkg(r.currPkg) - return t -} - -func (r *importReader) paramList() []*types.Field { - fs := make([]*types.Field, r.uint64()) - for i := range fs { - fs[i] = r.param() - } - return fs -} - -func (r *importReader) param() *types.Field { - f := types.NewField() - f.Pos = r.pos() - f.Sym = r.ident() - f.Type = r.typ() - return f -} - -func (r *importReader) bool() bool { - return r.uint64() != 0 -} - -func (r *importReader) int64() int64 { - n, err := binary.ReadVarint(r) - if err != nil { - Fatalf("readVarint: %v", err) - } - return n -} - -func (r *importReader) uint64() uint64 { - n, err := binary.ReadUvarint(r) - if err != nil { - Fatalf("readVarint: %v", err) - } - return n -} - -func (r *importReader) byte() byte { - x, err := r.ReadByte() - if err != nil { - Fatalf("declReader.ReadByte: %v", err) - } - return x -} - -// Compiler-specific extensions. - -func (r *importReader) varExt(n *Node) { - r.linkname(n.Sym) - r.symIdx(n.Sym) -} - -func (r *importReader) funcExt(n *Node) { - r.linkname(n.Sym) - r.symIdx(n.Sym) - - // Escape analysis. - for _, fs := range &types.RecvsParams { - for _, f := range fs(n.Type).FieldSlice() { - f.Note = r.string() - } - } - - // Inline body. - if u := r.uint64(); u > 0 { - n.Func.Inl = &Inline{ - Cost: int32(u - 1), - } - n.Func.Endlineno = r.pos() - } -} - -func (r *importReader) methExt(m *types.Field) { - if r.bool() { - m.SetNointerface(true) - } - r.funcExt(asNode(m.Type.Nname())) -} - -func (r *importReader) linkname(s *types.Sym) { - s.Linkname = r.string() -} - -func (r *importReader) symIdx(s *types.Sym) { - lsym := s.Linksym() - idx := int32(r.int64()) - if idx != -1 { - if s.Linkname != "" { - Fatalf("bad index for linknamed symbol: %v %d\n", lsym, idx) - } - lsym.SymIdx = idx - lsym.Set(obj.AttrIndexed, true) - } -} - -func (r *importReader) typeExt(t *types.Type) { - t.SetNotInHeap(r.bool()) - i, pi := r.int64(), r.int64() - if i != -1 && pi != -1 { - typeSymIdx[t] = [2]int64{i, pi} - } -} - -// Map imported type T to the index of type descriptor symbols of T and *T, -// so we can use index to reference the symbol. -var typeSymIdx = make(map[*types.Type][2]int64) - -func (r *importReader) doInline(n *Node) { - if len(n.Func.Inl.Body) != 0 { - Fatalf("%v already has inline body", n) - } - - funchdr(n) - body := r.stmtList() - funcbody() - if body == nil { - // - // Make sure empty body is not interpreted as - // no inlineable body (see also parser.fnbody) - // (not doing so can cause significant performance - // degradation due to unnecessary calls to empty - // functions). - body = []*Node{} - } - n.Func.Inl.Body = body - - importlist = append(importlist, n) - - if Debug.E > 0 && Debug.m > 2 { - if Debug.m > 3 { - fmt.Printf("inl body for %v %#v: %+v\n", n, n.Type, asNodes(n.Func.Inl.Body)) - } else { - fmt.Printf("inl body for %v %#v: %v\n", n, n.Type, asNodes(n.Func.Inl.Body)) - } - } -} - -// ---------------------------------------------------------------------------- -// Inlined function bodies - -// Approach: Read nodes and use them to create/declare the same data structures -// as done originally by the (hidden) parser by closely following the parser's -// original code. In other words, "parsing" the import data (which happens to -// be encoded in binary rather textual form) is the best way at the moment to -// re-establish the syntax tree's invariants. At some future point we might be -// able to avoid this round-about way and create the rewritten nodes directly, -// possibly avoiding a lot of duplicate work (name resolution, type checking). -// -// Refined nodes (e.g., ODOTPTR as a refinement of OXDOT) are exported as their -// unrefined nodes (since this is what the importer uses). The respective case -// entries are unreachable in the importer. - -func (r *importReader) stmtList() []*Node { - var list []*Node - for { - n := r.node() - if n == nil { - break - } - // OBLOCK nodes may be created when importing ODCL nodes - unpack them - if n.Op == OBLOCK { - list = append(list, n.List.Slice()...) - } else { - list = append(list, n) - } - - } - return list -} - -func (r *importReader) caseList(sw *Node) []*Node { - namedTypeSwitch := sw.Op == OSWITCH && sw.Left != nil && sw.Left.Op == OTYPESW && sw.Left.Left != nil - - cases := make([]*Node, r.uint64()) - for i := range cases { - cas := nodl(r.pos(), OCASE, nil, nil) - cas.List.Set(r.stmtList()) - if namedTypeSwitch { - // Note: per-case variables will have distinct, dotted - // names after import. That's okay: swt.go only needs - // Sym for diagnostics anyway. - caseVar := newnamel(cas.Pos, r.ident()) - declare(caseVar, dclcontext) - cas.Rlist.Set1(caseVar) - caseVar.Name.Defn = sw.Left - } - cas.Nbody.Set(r.stmtList()) - cases[i] = cas - } - return cases -} - -func (r *importReader) exprList() []*Node { - var list []*Node - for { - n := r.expr() - if n == nil { - break - } - list = append(list, n) - } - return list -} - -func (r *importReader) expr() *Node { - n := r.node() - if n != nil && n.Op == OBLOCK { - Fatalf("unexpected block node: %v", n) - } - return n -} - -// TODO(gri) split into expr and stmt -func (r *importReader) node() *Node { - switch op := r.op(); op { - // expressions - // case OPAREN: - // unreachable - unpacked by exporter - - case OLITERAL: - pos := r.pos() - typ, val := r.value() - - n := npos(pos, nodlit(val)) - n.Type = typ - return n - - case ONONAME: - return mkname(r.qualifiedIdent()) - - case ONAME: - return mkname(r.ident()) - - // case OPACK, ONONAME: - // unreachable - should have been resolved by typechecking - - case OTYPE: - return typenod(r.typ()) - - case OTYPESW: - n := nodl(r.pos(), OTYPESW, nil, nil) - if s := r.ident(); s != nil { - n.Left = npos(n.Pos, newnoname(s)) - } - n.Right, _ = r.exprsOrNil() - return n - - // case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC: - // unreachable - should have been resolved by typechecking - - // case OCLOSURE: - // unimplemented - - // case OPTRLIT: - // unreachable - mapped to case OADDR below by exporter - - case OSTRUCTLIT: - // TODO(mdempsky): Export position information for OSTRUCTKEY nodes. - savedlineno := lineno - lineno = r.pos() - n := nodl(lineno, OCOMPLIT, nil, typenod(r.typ())) - n.List.Set(r.elemList()) // special handling of field names - lineno = savedlineno - return n - - // case OARRAYLIT, OSLICELIT, OMAPLIT: - // unreachable - mapped to case OCOMPLIT below by exporter - - case OCOMPLIT: - n := nodl(r.pos(), OCOMPLIT, nil, typenod(r.typ())) - n.List.Set(r.exprList()) - return n - - case OKEY: - pos := r.pos() - left, right := r.exprsOrNil() - return nodl(pos, OKEY, left, right) - - // case OSTRUCTKEY: - // unreachable - handled in case OSTRUCTLIT by elemList - - // case OCALLPART: - // unreachable - mapped to case OXDOT below by exporter - - // case OXDOT, ODOT, ODOTPTR, ODOTINTER, ODOTMETH: - // unreachable - mapped to case OXDOT below by exporter - - case OXDOT: - // see parser.new_dotname - return npos(r.pos(), nodSym(OXDOT, r.expr(), r.ident())) - - // case ODOTTYPE, ODOTTYPE2: - // unreachable - mapped to case ODOTTYPE below by exporter - - case ODOTTYPE: - n := nodl(r.pos(), ODOTTYPE, r.expr(), nil) - n.Type = r.typ() - return n - - // case OINDEX, OINDEXMAP, OSLICE, OSLICESTR, OSLICEARR, OSLICE3, OSLICE3ARR: - // unreachable - mapped to cases below by exporter - - case OINDEX: - return nodl(r.pos(), op, r.expr(), r.expr()) - - case OSLICE, OSLICE3: - n := nodl(r.pos(), op, r.expr(), nil) - low, high := r.exprsOrNil() - var max *Node - if n.Op.IsSlice3() { - max = r.expr() - } - n.SetSliceBounds(low, high, max) - return n - - // case OCONV, OCONVIFACE, OCONVNOP, OBYTES2STR, ORUNES2STR, OSTR2BYTES, OSTR2RUNES, ORUNESTR: - // unreachable - mapped to OCONV case below by exporter - - case OCONV: - n := nodl(r.pos(), OCONV, r.expr(), nil) - n.Type = r.typ() - return n - - case OCOPY, OCOMPLEX, OREAL, OIMAG, OAPPEND, OCAP, OCLOSE, ODELETE, OLEN, OMAKE, ONEW, OPANIC, ORECOVER, OPRINT, OPRINTN: - n := npos(r.pos(), builtinCall(op)) - n.List.Set(r.exprList()) - if op == OAPPEND { - n.SetIsDDD(r.bool()) - } - return n - - // case OCALL, OCALLFUNC, OCALLMETH, OCALLINTER, OGETG: - // unreachable - mapped to OCALL case below by exporter - - case OCALL: - n := nodl(r.pos(), OCALL, nil, nil) - n.Ninit.Set(r.stmtList()) - n.Left = r.expr() - n.List.Set(r.exprList()) - n.SetIsDDD(r.bool()) - return n - - case OMAKEMAP, OMAKECHAN, OMAKESLICE: - n := npos(r.pos(), builtinCall(OMAKE)) - n.List.Append(typenod(r.typ())) - n.List.Append(r.exprList()...) - return n - - // unary expressions - case OPLUS, ONEG, OADDR, OBITNOT, ODEREF, ONOT, ORECV: - return nodl(r.pos(), op, r.expr(), nil) - - // binary expressions - case OADD, OAND, OANDAND, OANDNOT, ODIV, OEQ, OGE, OGT, OLE, OLT, - OLSH, OMOD, OMUL, ONE, OOR, OOROR, ORSH, OSEND, OSUB, OXOR: - return nodl(r.pos(), op, r.expr(), r.expr()) - - case OADDSTR: - pos := r.pos() - list := r.exprList() - x := npos(pos, list[0]) - for _, y := range list[1:] { - x = nodl(pos, OADD, x, y) - } - return x - - // -------------------------------------------------------------------- - // statements - case ODCL: - pos := r.pos() - lhs := npos(pos, dclname(r.ident())) - typ := typenod(r.typ()) - return npos(pos, liststmt(variter([]*Node{lhs}, typ, nil))) // TODO(gri) avoid list creation - - // case ODCLFIELD: - // unimplemented - - // case OAS, OASWB: - // unreachable - mapped to OAS case below by exporter - - case OAS: - return nodl(r.pos(), OAS, r.expr(), r.expr()) - - case OASOP: - n := nodl(r.pos(), OASOP, nil, nil) - n.SetSubOp(r.op()) - n.Left = r.expr() - if !r.bool() { - n.Right = nodintconst(1) - n.SetImplicit(true) - } else { - n.Right = r.expr() - } - return n - - // case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: - // unreachable - mapped to OAS2 case below by exporter - - case OAS2: - n := nodl(r.pos(), OAS2, nil, nil) - n.List.Set(r.exprList()) - n.Rlist.Set(r.exprList()) - return n - - case ORETURN: - n := nodl(r.pos(), ORETURN, nil, nil) - n.List.Set(r.exprList()) - return n - - // case ORETJMP: - // unreachable - generated by compiler for trampolin routines (not exported) - - case OGO, ODEFER: - return nodl(r.pos(), op, r.expr(), nil) - - case OIF: - n := nodl(r.pos(), OIF, nil, nil) - n.Ninit.Set(r.stmtList()) - n.Left = r.expr() - n.Nbody.Set(r.stmtList()) - n.Rlist.Set(r.stmtList()) - return n - - case OFOR: - n := nodl(r.pos(), OFOR, nil, nil) - n.Ninit.Set(r.stmtList()) - n.Left, n.Right = r.exprsOrNil() - n.Nbody.Set(r.stmtList()) - return n - - case ORANGE: - n := nodl(r.pos(), ORANGE, nil, nil) - n.List.Set(r.stmtList()) - n.Right = r.expr() - n.Nbody.Set(r.stmtList()) - return n - - case OSELECT, OSWITCH: - n := nodl(r.pos(), op, nil, nil) - n.Ninit.Set(r.stmtList()) - n.Left, _ = r.exprsOrNil() - n.List.Set(r.caseList(n)) - return n - - // case OCASE: - // handled by caseList - - case OFALL: - n := nodl(r.pos(), OFALL, nil, nil) - return n - - case OBREAK, OCONTINUE: - pos := r.pos() - left, _ := r.exprsOrNil() - if left != nil { - left = newname(left.Sym) - } - return nodl(pos, op, left, nil) - - // case OEMPTY: - // unreachable - not emitted by exporter - - case OGOTO, OLABEL: - n := nodl(r.pos(), op, nil, nil) - n.Sym = lookup(r.string()) - return n - - case OEND: - return nil - - default: - Fatalf("cannot import %v (%d) node\n"+ - "\t==> please file an issue and assign to gri@", op, int(op)) - panic("unreachable") // satisfy compiler - } -} - -func (r *importReader) op() Op { - return Op(r.uint64()) -} - -func (r *importReader) elemList() []*Node { - c := r.uint64() - list := make([]*Node, c) - for i := range list { - s := r.ident() - list[i] = nodSym(OSTRUCTKEY, r.expr(), s) - } - return list -} - -func (r *importReader) exprsOrNil() (a, b *Node) { - ab := r.uint64() - if ab&1 != 0 { - a = r.expr() - } - if ab&2 != 0 { - b = r.node() - } - return -} diff --git a/src/cmd/compile/internal/gc/init.go b/src/cmd/compile/internal/gc/init.go deleted file mode 100644 index ec9cc4bddc54bcc320401f79bf09cec694ad0075..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/init.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/obj" -) - -// A function named init is a special case. -// It is called by the initialization before main is run. -// To make it unique within a package and also uncallable, -// the name, normally "pkg.init", is altered to "pkg.init.0". -var renameinitgen int - -// Dummy function for autotmps generated during typechecking. -var dummyInitFn = nod(ODCLFUNC, nil, nil) - -func renameinit() *types.Sym { - s := lookupN("init.", renameinitgen) - renameinitgen++ - return s -} - -// fninit makes an initialization record for the package. -// See runtime/proc.go:initTask for its layout. -// The 3 tasks for initialization are: -// 1) Initialize all of the packages the current package depends on. -// 2) Initialize all the variables that have initializers. -// 3) Run any init functions. -func fninit(n []*Node) { - nf := initOrder(n) - - var deps []*obj.LSym // initTask records for packages the current package depends on - var fns []*obj.LSym // functions to call for package initialization - - // Find imported packages with init tasks. - for _, s := range types.InitSyms { - deps = append(deps, s.Linksym()) - } - - // Make a function that contains all the initialization statements. - if len(nf) > 0 { - lineno = nf[0].Pos // prolog/epilog gets line number of first init stmt - initializers := lookup("init") - fn := dclfunc(initializers, nod(OTFUNC, nil, nil)) - for _, dcl := range dummyInitFn.Func.Dcl { - dcl.Name.Curfn = fn - } - fn.Func.Dcl = append(fn.Func.Dcl, dummyInitFn.Func.Dcl...) - dummyInitFn.Func.Dcl = nil - - fn.Nbody.Set(nf) - funcbody() - - fn = typecheck(fn, ctxStmt) - Curfn = fn - typecheckslice(nf, ctxStmt) - Curfn = nil - xtop = append(xtop, fn) - fns = append(fns, initializers.Linksym()) - } - if dummyInitFn.Func.Dcl != nil { - // We only generate temps using dummyInitFn if there - // are package-scope initialization statements, so - // something's weird if we get here. - Fatalf("dummyInitFn still has declarations") - } - dummyInitFn = nil - - // Record user init functions. - for i := 0; i < renameinitgen; i++ { - s := lookupN("init.", i) - fn := asNode(s.Def).Name.Defn - // Skip init functions with empty bodies. - if fn.Nbody.Len() == 1 && fn.Nbody.First().Op == OEMPTY { - continue - } - fns = append(fns, s.Linksym()) - } - - if len(deps) == 0 && len(fns) == 0 && localpkg.Name != "main" && localpkg.Name != "runtime" { - return // nothing to initialize - } - - // Make an .inittask structure. - sym := lookup(".inittask") - nn := newname(sym) - nn.Type = types.Types[TUINT8] // dummy type - nn.SetClass(PEXTERN) - sym.Def = asTypesNode(nn) - exportsym(nn) - lsym := sym.Linksym() - ot := 0 - ot = duintptr(lsym, ot, 0) // state: not initialized yet - ot = duintptr(lsym, ot, uint64(len(deps))) - ot = duintptr(lsym, ot, uint64(len(fns))) - for _, d := range deps { - ot = dsymptr(lsym, ot, d, 0) - } - for _, f := range fns { - ot = dsymptr(lsym, ot, f, 0) - } - // An initTask has pointers, but none into the Go heap. - // It's not quite read only, the state field must be modifiable. - ggloblsym(lsym, int32(ot), obj.NOPTR) -} diff --git a/src/cmd/compile/internal/gc/inl.go b/src/cmd/compile/internal/gc/inl.go deleted file mode 100644 index a8cc01082a44b5fa678c54576aa8434048e0ae33..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/inl.go +++ /dev/null @@ -1,1507 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -// -// The inlining facility makes 2 passes: first caninl determines which -// functions are suitable for inlining, and for those that are it -// saves a copy of the body. Then inlcalls walks each function body to -// expand calls to inlinable functions. -// -// The Debug.l flag controls the aggressiveness. Note that main() swaps level 0 and 1, -// making 1 the default and -l disable. Additional levels (beyond -l) may be buggy and -// are not supported. -// 0: disabled -// 1: 80-nodes leaf functions, oneliners, panic, lazy typechecking (default) -// 2: (unassigned) -// 3: (unassigned) -// 4: allow non-leaf functions -// -// At some point this may get another default and become switch-offable with -N. -// -// The -d typcheckinl flag enables early typechecking of all imported bodies, -// which is useful to flush out bugs. -// -// The Debug.m flag enables diagnostic output. a single -m is useful for verifying -// which calls get inlined or not, more is for debugging, and may go away at any point. - -package gc - -import ( - "cmd/compile/internal/logopt" - "cmd/compile/internal/types" - "cmd/internal/obj" - "cmd/internal/src" - "fmt" - "strings" -) - -// Inlining budget parameters, gathered in one place -const ( - inlineMaxBudget = 80 - inlineExtraAppendCost = 0 - // default is to inline if there's at most one call. -l=4 overrides this by using 1 instead. - inlineExtraCallCost = 57 // 57 was benchmarked to provided most benefit with no bad surprises; see https://github.com/golang/go/issues/19348#issuecomment-439370742 - inlineExtraPanicCost = 1 // do not penalize inlining panics. - inlineExtraThrowCost = inlineMaxBudget // with current (2018-05/1.11) code, inlining runtime.throw does not help. - - inlineBigFunctionNodes = 5000 // Functions with this many nodes are considered "big". - inlineBigFunctionMaxCost = 20 // Max cost of inlinee when inlining into a "big" function. -) - -// Get the function's package. For ordinary functions it's on the ->sym, but for imported methods -// the ->sym can be re-used in the local package, so peel it off the receiver's type. -func fnpkg(fn *Node) *types.Pkg { - if fn.IsMethod() { - // method - rcvr := fn.Type.Recv().Type - - if rcvr.IsPtr() { - rcvr = rcvr.Elem() - } - if rcvr.Sym == nil { - Fatalf("receiver with no sym: [%v] %L (%v)", fn.Sym, fn, rcvr) - } - return rcvr.Sym.Pkg - } - - // non-method - return fn.Sym.Pkg -} - -// Lazy typechecking of imported bodies. For local functions, caninl will set ->typecheck -// because they're a copy of an already checked body. -func typecheckinl(fn *Node) { - lno := setlineno(fn) - - expandInline(fn) - - // typecheckinl is only for imported functions; - // their bodies may refer to unsafe as long as the package - // was marked safe during import (which was checked then). - // the ->inl of a local function has been typechecked before caninl copied it. - pkg := fnpkg(fn) - - if pkg == localpkg || pkg == nil { - return // typecheckinl on local function - } - - if Debug.m > 2 || Debug_export != 0 { - fmt.Printf("typecheck import [%v] %L { %#v }\n", fn.Sym, fn, asNodes(fn.Func.Inl.Body)) - } - - savefn := Curfn - Curfn = fn - typecheckslice(fn.Func.Inl.Body, ctxStmt) - Curfn = savefn - - // During expandInline (which imports fn.Func.Inl.Body), - // declarations are added to fn.Func.Dcl by funcHdr(). Move them - // to fn.Func.Inl.Dcl for consistency with how local functions - // behave. (Append because typecheckinl may be called multiple - // times.) - fn.Func.Inl.Dcl = append(fn.Func.Inl.Dcl, fn.Func.Dcl...) - fn.Func.Dcl = nil - - lineno = lno -} - -// Caninl determines whether fn is inlineable. -// If so, caninl saves fn->nbody in fn->inl and substitutes it with a copy. -// fn and ->nbody will already have been typechecked. -func caninl(fn *Node) { - if fn.Op != ODCLFUNC { - Fatalf("caninl %v", fn) - } - if fn.Func.Nname == nil { - Fatalf("caninl no nname %+v", fn) - } - - var reason string // reason, if any, that the function was not inlined - if Debug.m > 1 || logopt.Enabled() { - defer func() { - if reason != "" { - if Debug.m > 1 { - fmt.Printf("%v: cannot inline %v: %s\n", fn.Line(), fn.Func.Nname, reason) - } - if logopt.Enabled() { - logopt.LogOpt(fn.Pos, "cannotInlineFunction", "inline", fn.funcname(), reason) - } - } - }() - } - - // If marked "go:noinline", don't inline - if fn.Func.Pragma&Noinline != 0 { - reason = "marked go:noinline" - return - } - - // If marked "go:norace" and -race compilation, don't inline. - if flag_race && fn.Func.Pragma&Norace != 0 { - reason = "marked go:norace with -race compilation" - return - } - - // If marked "go:nocheckptr" and -d checkptr compilation, don't inline. - if Debug_checkptr != 0 && fn.Func.Pragma&NoCheckPtr != 0 { - reason = "marked go:nocheckptr" - return - } - - // If marked "go:cgo_unsafe_args", don't inline, since the - // function makes assumptions about its argument frame layout. - if fn.Func.Pragma&CgoUnsafeArgs != 0 { - reason = "marked go:cgo_unsafe_args" - return - } - - // If marked as "go:uintptrescapes", don't inline, since the - // escape information is lost during inlining. - if fn.Func.Pragma&UintptrEscapes != 0 { - reason = "marked as having an escaping uintptr argument" - return - } - - // The nowritebarrierrec checker currently works at function - // granularity, so inlining yeswritebarrierrec functions can - // confuse it (#22342). As a workaround, disallow inlining - // them for now. - if fn.Func.Pragma&Yeswritebarrierrec != 0 { - reason = "marked go:yeswritebarrierrec" - return - } - - // If fn has no body (is defined outside of Go), cannot inline it. - if fn.Nbody.Len() == 0 { - reason = "no function body" - return - } - - if fn.Typecheck() == 0 { - Fatalf("caninl on non-typechecked function %v", fn) - } - - n := fn.Func.Nname - if n.Func.InlinabilityChecked() { - return - } - defer n.Func.SetInlinabilityChecked(true) - - cc := int32(inlineExtraCallCost) - if Debug.l == 4 { - cc = 1 // this appears to yield better performance than 0. - } - - // At this point in the game the function we're looking at may - // have "stale" autos, vars that still appear in the Dcl list, but - // which no longer have any uses in the function body (due to - // elimination by deadcode). We'd like to exclude these dead vars - // when creating the "Inline.Dcl" field below; to accomplish this, - // the hairyVisitor below builds up a map of used/referenced - // locals, and we use this map to produce a pruned Inline.Dcl - // list. See issue 25249 for more context. - - visitor := hairyVisitor{ - budget: inlineMaxBudget, - extraCallCost: cc, - usedLocals: make(map[*Node]bool), - } - if visitor.visitList(fn.Nbody) { - reason = visitor.reason - return - } - if visitor.budget < 0 { - reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-visitor.budget, inlineMaxBudget) - return - } - - n.Func.Inl = &Inline{ - Cost: inlineMaxBudget - visitor.budget, - Dcl: inlcopylist(pruneUnusedAutos(n.Name.Defn.Func.Dcl, &visitor)), - Body: inlcopylist(fn.Nbody.Slice()), - } - - // hack, TODO, check for better way to link method nodes back to the thing with the ->inl - // this is so export can find the body of a method - fn.Type.FuncType().Nname = asTypesNode(n) - - if Debug.m > 1 { - fmt.Printf("%v: can inline %#v with cost %d as: %#v { %#v }\n", fn.Line(), n, inlineMaxBudget-visitor.budget, fn.Type, asNodes(n.Func.Inl.Body)) - } else if Debug.m != 0 { - fmt.Printf("%v: can inline %v\n", fn.Line(), n) - } - if logopt.Enabled() { - logopt.LogOpt(fn.Pos, "canInlineFunction", "inline", fn.funcname(), fmt.Sprintf("cost: %d", inlineMaxBudget-visitor.budget)) - } -} - -// inlFlood marks n's inline body for export and recursively ensures -// all called functions are marked too. -func inlFlood(n *Node) { - if n == nil { - return - } - if n.Op != ONAME || n.Class() != PFUNC { - Fatalf("inlFlood: unexpected %v, %v, %v", n, n.Op, n.Class()) - } - if n.Func == nil { - Fatalf("inlFlood: missing Func on %v", n) - } - if n.Func.Inl == nil { - return - } - - if n.Func.ExportInline() { - return - } - n.Func.SetExportInline(true) - - typecheckinl(n) - - // Recursively identify all referenced functions for - // reexport. We want to include even non-called functions, - // because after inlining they might be callable. - inspectList(asNodes(n.Func.Inl.Body), func(n *Node) bool { - switch n.Op { - case ONAME: - switch n.Class() { - case PFUNC: - if n.isMethodExpression() { - inlFlood(asNode(n.Type.Nname())) - } else { - inlFlood(n) - exportsym(n) - } - case PEXTERN: - exportsym(n) - } - - case ODOTMETH: - fn := asNode(n.Type.Nname()) - inlFlood(fn) - - case OCALLPART: - // Okay, because we don't yet inline indirect - // calls to method values. - case OCLOSURE: - // If the closure is inlinable, we'll need to - // flood it too. But today we don't support - // inlining functions that contain closures. - // - // When we do, we'll probably want: - // inlFlood(n.Func.Closure.Func.Nname) - Fatalf("unexpected closure in inlinable function") - } - return true - }) -} - -// hairyVisitor visits a function body to determine its inlining -// hairiness and whether or not it can be inlined. -type hairyVisitor struct { - budget int32 - reason string - extraCallCost int32 - usedLocals map[*Node]bool -} - -// Look for anything we want to punt on. -func (v *hairyVisitor) visitList(ll Nodes) bool { - for _, n := range ll.Slice() { - if v.visit(n) { - return true - } - } - return false -} - -func (v *hairyVisitor) visit(n *Node) bool { - if n == nil { - return false - } - - switch n.Op { - // Call is okay if inlinable and we have the budget for the body. - case OCALLFUNC: - // Functions that call runtime.getcaller{pc,sp} can not be inlined - // because getcaller{pc,sp} expect a pointer to the caller's first argument. - // - // runtime.throw is a "cheap call" like panic in normal code. - if n.Left.Op == ONAME && n.Left.Class() == PFUNC && isRuntimePkg(n.Left.Sym.Pkg) { - fn := n.Left.Sym.Name - if fn == "getcallerpc" || fn == "getcallersp" { - v.reason = "call to " + fn - return true - } - if fn == "throw" { - v.budget -= inlineExtraThrowCost - break - } - } - - if isIntrinsicCall(n) { - // Treat like any other node. - break - } - - if fn := inlCallee(n.Left); fn != nil && fn.Func.Inl != nil { - v.budget -= fn.Func.Inl.Cost - break - } - - // Call cost for non-leaf inlining. - v.budget -= v.extraCallCost - - // Call is okay if inlinable and we have the budget for the body. - case OCALLMETH: - t := n.Left.Type - if t == nil { - Fatalf("no function type for [%p] %+v\n", n.Left, n.Left) - } - if t.Nname() == nil { - Fatalf("no function definition for [%p] %+v\n", t, t) - } - if isRuntimePkg(n.Left.Sym.Pkg) { - fn := n.Left.Sym.Name - if fn == "heapBits.nextArena" { - // Special case: explicitly allow - // mid-stack inlining of - // runtime.heapBits.next even though - // it calls slow-path - // runtime.heapBits.nextArena. - break - } - } - if inlfn := asNode(t.FuncType().Nname).Func; inlfn.Inl != nil { - v.budget -= inlfn.Inl.Cost - break - } - // Call cost for non-leaf inlining. - v.budget -= v.extraCallCost - - // Things that are too hairy, irrespective of the budget - case OCALL, OCALLINTER: - // Call cost for non-leaf inlining. - v.budget -= v.extraCallCost - - case OPANIC: - v.budget -= inlineExtraPanicCost - - case ORECOVER: - // recover matches the argument frame pointer to find - // the right panic value, so it needs an argument frame. - v.reason = "call to recover" - return true - - case OCLOSURE, - ORANGE, - OSELECT, - OGO, - ODEFER, - ODCLTYPE, // can't print yet - ORETJMP: - v.reason = "unhandled op " + n.Op.String() - return true - - case OAPPEND: - v.budget -= inlineExtraAppendCost - - case ODCLCONST, OEMPTY, OFALL: - // These nodes don't produce code; omit from inlining budget. - return false - - case OLABEL: - // TODO(mdempsky): Add support for inlining labeled control statements. - if n.labeledControl() != nil { - v.reason = "labeled control" - return true - } - - case OBREAK, OCONTINUE: - if n.Sym != nil { - // Should have short-circuited due to labeledControl above. - Fatalf("unexpected labeled break/continue: %v", n) - } - - case OIF: - if Isconst(n.Left, CTBOOL) { - // This if and the condition cost nothing. - return v.visitList(n.Ninit) || v.visitList(n.Nbody) || - v.visitList(n.Rlist) - } - - case ONAME: - if n.Class() == PAUTO { - v.usedLocals[n] = true - } - - } - - v.budget-- - - // When debugging, don't stop early, to get full cost of inlining this function - if v.budget < 0 && Debug.m < 2 && !logopt.Enabled() { - return true - } - - return v.visit(n.Left) || v.visit(n.Right) || - v.visitList(n.List) || v.visitList(n.Rlist) || - v.visitList(n.Ninit) || v.visitList(n.Nbody) -} - -// inlcopylist (together with inlcopy) recursively copies a list of nodes, except -// that it keeps the same ONAME, OTYPE, and OLITERAL nodes. It is used for copying -// the body and dcls of an inlineable function. -func inlcopylist(ll []*Node) []*Node { - s := make([]*Node, 0, len(ll)) - for _, n := range ll { - s = append(s, inlcopy(n)) - } - return s -} - -func inlcopy(n *Node) *Node { - if n == nil { - return nil - } - - switch n.Op { - case ONAME, OTYPE, OLITERAL: - return n - } - - m := n.copy() - if n.Op != OCALLPART && m.Func != nil { - Fatalf("unexpected Func: %v", m) - } - m.Left = inlcopy(n.Left) - m.Right = inlcopy(n.Right) - m.List.Set(inlcopylist(n.List.Slice())) - m.Rlist.Set(inlcopylist(n.Rlist.Slice())) - m.Ninit.Set(inlcopylist(n.Ninit.Slice())) - m.Nbody.Set(inlcopylist(n.Nbody.Slice())) - - return m -} - -func countNodes(n *Node) int { - if n == nil { - return 0 - } - cnt := 1 - cnt += countNodes(n.Left) - cnt += countNodes(n.Right) - for _, n1 := range n.Ninit.Slice() { - cnt += countNodes(n1) - } - for _, n1 := range n.Nbody.Slice() { - cnt += countNodes(n1) - } - for _, n1 := range n.List.Slice() { - cnt += countNodes(n1) - } - for _, n1 := range n.Rlist.Slice() { - cnt += countNodes(n1) - } - return cnt -} - -// Inlcalls/nodelist/node walks fn's statements and expressions and substitutes any -// calls made to inlineable functions. This is the external entry point. -func inlcalls(fn *Node) { - savefn := Curfn - Curfn = fn - maxCost := int32(inlineMaxBudget) - if countNodes(fn) >= inlineBigFunctionNodes { - maxCost = inlineBigFunctionMaxCost - } - // Map to keep track of functions that have been inlined at a particular - // call site, in order to stop inlining when we reach the beginning of a - // recursion cycle again. We don't inline immediately recursive functions, - // but allow inlining if there is a recursion cycle of many functions. - // Most likely, the inlining will stop before we even hit the beginning of - // the cycle again, but the map catches the unusual case. - inlMap := make(map[*Node]bool) - fn = inlnode(fn, maxCost, inlMap) - if fn != Curfn { - Fatalf("inlnode replaced curfn") - } - Curfn = savefn -} - -// Turn an OINLCALL into a statement. -func inlconv2stmt(n *Node) { - n.Op = OBLOCK - - // n->ninit stays - n.List.Set(n.Nbody.Slice()) - - n.Nbody.Set(nil) - n.Rlist.Set(nil) -} - -// Turn an OINLCALL into a single valued expression. -// The result of inlconv2expr MUST be assigned back to n, e.g. -// n.Left = inlconv2expr(n.Left) -func inlconv2expr(n *Node) *Node { - r := n.Rlist.First() - return addinit(r, append(n.Ninit.Slice(), n.Nbody.Slice()...)) -} - -// Turn the rlist (with the return values) of the OINLCALL in -// n into an expression list lumping the ninit and body -// containing the inlined statements on the first list element so -// order will be preserved Used in return, oas2func and call -// statements. -func inlconv2list(n *Node) []*Node { - if n.Op != OINLCALL || n.Rlist.Len() == 0 { - Fatalf("inlconv2list %+v\n", n) - } - - s := n.Rlist.Slice() - s[0] = addinit(s[0], append(n.Ninit.Slice(), n.Nbody.Slice()...)) - return s -} - -func inlnodelist(l Nodes, maxCost int32, inlMap map[*Node]bool) { - s := l.Slice() - for i := range s { - s[i] = inlnode(s[i], maxCost, inlMap) - } -} - -// inlnode recurses over the tree to find inlineable calls, which will -// be turned into OINLCALLs by mkinlcall. When the recursion comes -// back up will examine left, right, list, rlist, ninit, ntest, nincr, -// nbody and nelse and use one of the 4 inlconv/glue functions above -// to turn the OINLCALL into an expression, a statement, or patch it -// in to this nodes list or rlist as appropriate. -// NOTE it makes no sense to pass the glue functions down the -// recursion to the level where the OINLCALL gets created because they -// have to edit /this/ n, so you'd have to push that one down as well, -// but then you may as well do it here. so this is cleaner and -// shorter and less complicated. -// The result of inlnode MUST be assigned back to n, e.g. -// n.Left = inlnode(n.Left) -func inlnode(n *Node, maxCost int32, inlMap map[*Node]bool) *Node { - if n == nil { - return n - } - - switch n.Op { - case ODEFER, OGO: - switch n.Left.Op { - case OCALLFUNC, OCALLMETH: - n.Left.SetNoInline(true) - } - - // TODO do them here (or earlier), - // so escape analysis can avoid more heapmoves. - case OCLOSURE: - return n - case OCALLMETH: - // Prevent inlining some reflect.Value methods when using checkptr, - // even when package reflect was compiled without it (#35073). - if s := n.Left.Sym; Debug_checkptr != 0 && isReflectPkg(s.Pkg) && (s.Name == "Value.UnsafeAddr" || s.Name == "Value.Pointer") { - return n - } - } - - lno := setlineno(n) - - inlnodelist(n.Ninit, maxCost, inlMap) - for _, n1 := range n.Ninit.Slice() { - if n1.Op == OINLCALL { - inlconv2stmt(n1) - } - } - - n.Left = inlnode(n.Left, maxCost, inlMap) - if n.Left != nil && n.Left.Op == OINLCALL { - n.Left = inlconv2expr(n.Left) - } - - n.Right = inlnode(n.Right, maxCost, inlMap) - if n.Right != nil && n.Right.Op == OINLCALL { - if n.Op == OFOR || n.Op == OFORUNTIL { - inlconv2stmt(n.Right) - } else if n.Op == OAS2FUNC { - n.Rlist.Set(inlconv2list(n.Right)) - n.Right = nil - n.Op = OAS2 - n.SetTypecheck(0) - n = typecheck(n, ctxStmt) - } else { - n.Right = inlconv2expr(n.Right) - } - } - - inlnodelist(n.List, maxCost, inlMap) - if n.Op == OBLOCK { - for _, n2 := range n.List.Slice() { - if n2.Op == OINLCALL { - inlconv2stmt(n2) - } - } - } else { - s := n.List.Slice() - for i1, n1 := range s { - if n1 != nil && n1.Op == OINLCALL { - s[i1] = inlconv2expr(s[i1]) - } - } - } - - inlnodelist(n.Rlist, maxCost, inlMap) - s := n.Rlist.Slice() - for i1, n1 := range s { - if n1.Op == OINLCALL { - if n.Op == OIF { - inlconv2stmt(n1) - } else { - s[i1] = inlconv2expr(s[i1]) - } - } - } - - inlnodelist(n.Nbody, maxCost, inlMap) - for _, n := range n.Nbody.Slice() { - if n.Op == OINLCALL { - inlconv2stmt(n) - } - } - - // with all the branches out of the way, it is now time to - // transmogrify this node itself unless inhibited by the - // switch at the top of this function. - switch n.Op { - case OCALLFUNC, OCALLMETH: - if n.NoInline() { - return n - } - } - - switch n.Op { - case OCALLFUNC: - if Debug.m > 3 { - fmt.Printf("%v:call to func %+v\n", n.Line(), n.Left) - } - if isIntrinsicCall(n) { - break - } - if fn := inlCallee(n.Left); fn != nil && fn.Func.Inl != nil { - n = mkinlcall(n, fn, maxCost, inlMap) - } - - case OCALLMETH: - if Debug.m > 3 { - fmt.Printf("%v:call to meth %L\n", n.Line(), n.Left.Right) - } - - // typecheck should have resolved ODOTMETH->type, whose nname points to the actual function. - if n.Left.Type == nil { - Fatalf("no function type for [%p] %+v\n", n.Left, n.Left) - } - - if n.Left.Type.Nname() == nil { - Fatalf("no function definition for [%p] %+v\n", n.Left.Type, n.Left.Type) - } - - n = mkinlcall(n, asNode(n.Left.Type.FuncType().Nname), maxCost, inlMap) - } - - lineno = lno - return n -} - -// inlCallee takes a function-typed expression and returns the underlying function ONAME -// that it refers to if statically known. Otherwise, it returns nil. -func inlCallee(fn *Node) *Node { - fn = staticValue(fn) - switch { - case fn.Op == ONAME && fn.Class() == PFUNC: - if fn.isMethodExpression() { - n := asNode(fn.Type.Nname()) - // Check that receiver type matches fn.Left. - // TODO(mdempsky): Handle implicit dereference - // of pointer receiver argument? - if n == nil || !types.Identical(n.Type.Recv().Type, fn.Left.Type) { - return nil - } - return n - } - return fn - case fn.Op == OCLOSURE: - c := fn.Func.Closure - caninl(c) - return c.Func.Nname - } - return nil -} - -func staticValue(n *Node) *Node { - for { - if n.Op == OCONVNOP { - n = n.Left - continue - } - - n1 := staticValue1(n) - if n1 == nil { - return n - } - n = n1 - } -} - -// staticValue1 implements a simple SSA-like optimization. If n is a local variable -// that is initialized and never reassigned, staticValue1 returns the initializer -// expression. Otherwise, it returns nil. -func staticValue1(n *Node) *Node { - if n.Op != ONAME || n.Class() != PAUTO || n.Name.Addrtaken() { - return nil - } - - defn := n.Name.Defn - if defn == nil { - return nil - } - - var rhs *Node -FindRHS: - switch defn.Op { - case OAS: - rhs = defn.Right - case OAS2: - for i, lhs := range defn.List.Slice() { - if lhs == n { - rhs = defn.Rlist.Index(i) - break FindRHS - } - } - Fatalf("%v missing from LHS of %v", n, defn) - default: - return nil - } - if rhs == nil { - Fatalf("RHS is nil: %v", defn) - } - - unsafe, _ := reassigned(n) - if unsafe { - return nil - } - - return rhs -} - -// reassigned takes an ONAME node, walks the function in which it is defined, and returns a boolean -// indicating whether the name has any assignments other than its declaration. -// The second return value is the first such assignment encountered in the walk, if any. It is mostly -// useful for -m output documenting the reason for inhibited optimizations. -// NB: global variables are always considered to be re-assigned. -// TODO: handle initial declaration not including an assignment and followed by a single assignment? -func reassigned(n *Node) (bool, *Node) { - if n.Op != ONAME { - Fatalf("reassigned %v", n) - } - // no way to reliably check for no-reassignment of globals, assume it can be - if n.Name.Curfn == nil { - return true, nil - } - f := n.Name.Curfn - // There just might be a good reason for this although this can be pretty surprising: - // local variables inside a closure have Curfn pointing to the OCLOSURE node instead - // of the corresponding ODCLFUNC. - // We need to walk the function body to check for reassignments so we follow the - // linkage to the ODCLFUNC node as that is where body is held. - if f.Op == OCLOSURE { - f = f.Func.Closure - } - v := reassignVisitor{name: n} - a := v.visitList(f.Nbody) - return a != nil, a -} - -type reassignVisitor struct { - name *Node -} - -func (v *reassignVisitor) visit(n *Node) *Node { - if n == nil { - return nil - } - switch n.Op { - case OAS, OSELRECV: - if n.Left == v.name && n != v.name.Name.Defn { - return n - } - case OAS2, OAS2FUNC, OAS2MAPR, OAS2DOTTYPE, OAS2RECV: - for _, p := range n.List.Slice() { - if p == v.name && n != v.name.Name.Defn { - return n - } - } - case OSELRECV2: - if (n.Left == v.name || n.List.First() == v.name) && n != v.name.Name.Defn { - return n - } - } - if a := v.visit(n.Left); a != nil { - return a - } - if a := v.visit(n.Right); a != nil { - return a - } - if a := v.visitList(n.List); a != nil { - return a - } - if a := v.visitList(n.Rlist); a != nil { - return a - } - if a := v.visitList(n.Ninit); a != nil { - return a - } - if a := v.visitList(n.Nbody); a != nil { - return a - } - return nil -} - -func (v *reassignVisitor) visitList(l Nodes) *Node { - for _, n := range l.Slice() { - if a := v.visit(n); a != nil { - return a - } - } - return nil -} - -func inlParam(t *types.Field, as *Node, inlvars map[*Node]*Node) *Node { - n := asNode(t.Nname) - if n == nil || n.isBlank() { - return nblank - } - - inlvar := inlvars[n] - if inlvar == nil { - Fatalf("missing inlvar for %v", n) - } - as.Ninit.Append(nod(ODCL, inlvar, nil)) - inlvar.Name.Defn = as - return inlvar -} - -var inlgen int - -// If n is a call node (OCALLFUNC or OCALLMETH), and fn is an ONAME node for a -// function with an inlinable body, return an OINLCALL node that can replace n. -// The returned node's Ninit has the parameter assignments, the Nbody is the -// inlined function body, and (List, Rlist) contain the (input, output) -// parameters. -// The result of mkinlcall MUST be assigned back to n, e.g. -// n.Left = mkinlcall(n.Left, fn, isddd) -func mkinlcall(n, fn *Node, maxCost int32, inlMap map[*Node]bool) *Node { - if fn.Func.Inl == nil { - if logopt.Enabled() { - logopt.LogOpt(n.Pos, "cannotInlineCall", "inline", Curfn.funcname(), - fmt.Sprintf("%s cannot be inlined", fn.pkgFuncName())) - } - return n - } - if fn.Func.Inl.Cost > maxCost { - // The inlined function body is too big. Typically we use this check to restrict - // inlining into very big functions. See issue 26546 and 17566. - if logopt.Enabled() { - logopt.LogOpt(n.Pos, "cannotInlineCall", "inline", Curfn.funcname(), - fmt.Sprintf("cost %d of %s exceeds max large caller cost %d", fn.Func.Inl.Cost, fn.pkgFuncName(), maxCost)) - } - return n - } - - if fn == Curfn || fn.Name.Defn == Curfn { - // Can't recursively inline a function into itself. - if logopt.Enabled() { - logopt.LogOpt(n.Pos, "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", Curfn.funcname())) - } - return n - } - - if instrumenting && isRuntimePkg(fn.Sym.Pkg) { - // Runtime package must not be instrumented. - // Instrument skips runtime package. However, some runtime code can be - // inlined into other packages and instrumented there. To avoid this, - // we disable inlining of runtime functions when instrumenting. - // The example that we observed is inlining of LockOSThread, - // which lead to false race reports on m contents. - return n - } - - if inlMap[fn] { - if Debug.m > 1 { - fmt.Printf("%v: cannot inline %v into %v: repeated recursive cycle\n", n.Line(), fn, Curfn.funcname()) - } - return n - } - inlMap[fn] = true - defer func() { - inlMap[fn] = false - }() - if Debug_typecheckinl == 0 { - typecheckinl(fn) - } - - // We have a function node, and it has an inlineable body. - if Debug.m > 1 { - fmt.Printf("%v: inlining call to %v %#v { %#v }\n", n.Line(), fn.Sym, fn.Type, asNodes(fn.Func.Inl.Body)) - } else if Debug.m != 0 { - fmt.Printf("%v: inlining call to %v\n", n.Line(), fn) - } - if Debug.m > 2 { - fmt.Printf("%v: Before inlining: %+v\n", n.Line(), n) - } - - if ssaDump != "" && ssaDump == Curfn.funcname() { - ssaDumpInlined = append(ssaDumpInlined, fn) - } - - ninit := n.Ninit - - // For normal function calls, the function callee expression - // may contain side effects (e.g., added by addinit during - // inlconv2expr or inlconv2list). Make sure to preserve these, - // if necessary (#42703). - if n.Op == OCALLFUNC { - callee := n.Left - for callee.Op == OCONVNOP { - ninit.AppendNodes(&callee.Ninit) - callee = callee.Left - } - if callee.Op != ONAME && callee.Op != OCLOSURE { - Fatalf("unexpected callee expression: %v", callee) - } - } - - // Make temp names to use instead of the originals. - inlvars := make(map[*Node]*Node) - - // record formals/locals for later post-processing - var inlfvars []*Node - - // Handle captured variables when inlining closures. - if fn.Name.Defn != nil { - if c := fn.Name.Defn.Func.Closure; c != nil { - for _, v := range c.Func.Closure.Func.Cvars.Slice() { - if v.Op == OXXX { - continue - } - - o := v.Name.Param.Outer - // make sure the outer param matches the inlining location - // NB: if we enabled inlining of functions containing OCLOSURE or refined - // the reassigned check via some sort of copy propagation this would most - // likely need to be changed to a loop to walk up to the correct Param - if o == nil || (o.Name.Curfn != Curfn && o.Name.Curfn.Func.Closure != Curfn) { - Fatalf("%v: unresolvable capture %v %v\n", n.Line(), fn, v) - } - - if v.Name.Byval() { - iv := typecheck(inlvar(v), ctxExpr) - ninit.Append(nod(ODCL, iv, nil)) - ninit.Append(typecheck(nod(OAS, iv, o), ctxStmt)) - inlvars[v] = iv - } else { - addr := newname(lookup("&" + v.Sym.Name)) - addr.Type = types.NewPtr(v.Type) - ia := typecheck(inlvar(addr), ctxExpr) - ninit.Append(nod(ODCL, ia, nil)) - ninit.Append(typecheck(nod(OAS, ia, nod(OADDR, o, nil)), ctxStmt)) - inlvars[addr] = ia - - // When capturing by reference, all occurrence of the captured var - // must be substituted with dereference of the temporary address - inlvars[v] = typecheck(nod(ODEREF, ia, nil), ctxExpr) - } - } - } - } - - for _, ln := range fn.Func.Inl.Dcl { - if ln.Op != ONAME { - continue - } - if ln.Class() == PPARAMOUT { // return values handled below. - continue - } - if ln.isParamStackCopy() { // ignore the on-stack copy of a parameter that moved to the heap - // TODO(mdempsky): Remove once I'm confident - // this never actually happens. We currently - // perform inlining before escape analysis, so - // nothing should have moved to the heap yet. - Fatalf("impossible: %v", ln) - } - inlf := typecheck(inlvar(ln), ctxExpr) - inlvars[ln] = inlf - if genDwarfInline > 0 { - if ln.Class() == PPARAM { - inlf.Name.SetInlFormal(true) - } else { - inlf.Name.SetInlLocal(true) - } - inlf.Pos = ln.Pos - inlfvars = append(inlfvars, inlf) - } - } - - // We can delay declaring+initializing result parameters if: - // (1) there's exactly one "return" statement in the inlined function; - // (2) it's not an empty return statement (#44355); and - // (3) the result parameters aren't named. - delayretvars := true - - nreturns := 0 - inspectList(asNodes(fn.Func.Inl.Body), func(n *Node) bool { - if n != nil && n.Op == ORETURN { - nreturns++ - if n.List.Len() == 0 { - delayretvars = false // empty return statement (case 2) - } - } - return true - }) - - if nreturns != 1 { - delayretvars = false // not exactly one return statement (case 1) - } - - // temporaries for return values. - var retvars []*Node - for i, t := range fn.Type.Results().Fields().Slice() { - var m *Node - if n := asNode(t.Nname); n != nil && !n.isBlank() && !strings.HasPrefix(n.Sym.Name, "~r") { - m = inlvar(n) - m = typecheck(m, ctxExpr) - inlvars[n] = m - delayretvars = false // found a named result parameter (case 3) - } else { - // anonymous return values, synthesize names for use in assignment that replaces return - m = retvar(t, i) - } - - if genDwarfInline > 0 { - // Don't update the src.Pos on a return variable if it - // was manufactured by the inliner (e.g. "~R2"); such vars - // were not part of the original callee. - if !strings.HasPrefix(m.Sym.Name, "~R") { - m.Name.SetInlFormal(true) - m.Pos = t.Pos - inlfvars = append(inlfvars, m) - } - } - - retvars = append(retvars, m) - } - - // Assign arguments to the parameters' temp names. - as := nod(OAS2, nil, nil) - as.SetColas(true) - if n.Op == OCALLMETH { - if n.Left.Left == nil { - Fatalf("method call without receiver: %+v", n) - } - as.Rlist.Append(n.Left.Left) - } - as.Rlist.Append(n.List.Slice()...) - - // For non-dotted calls to variadic functions, we assign the - // variadic parameter's temp name separately. - var vas *Node - - if recv := fn.Type.Recv(); recv != nil { - as.List.Append(inlParam(recv, as, inlvars)) - } - for _, param := range fn.Type.Params().Fields().Slice() { - // For ordinary parameters or variadic parameters in - // dotted calls, just add the variable to the - // assignment list, and we're done. - if !param.IsDDD() || n.IsDDD() { - as.List.Append(inlParam(param, as, inlvars)) - continue - } - - // Otherwise, we need to collect the remaining values - // to pass as a slice. - - x := as.List.Len() - for as.List.Len() < as.Rlist.Len() { - as.List.Append(argvar(param.Type, as.List.Len())) - } - varargs := as.List.Slice()[x:] - - vas = nod(OAS, nil, nil) - vas.Left = inlParam(param, vas, inlvars) - if len(varargs) == 0 { - vas.Right = nodnil() - vas.Right.Type = param.Type - } else { - vas.Right = nod(OCOMPLIT, nil, typenod(param.Type)) - vas.Right.List.Set(varargs) - } - } - - if as.Rlist.Len() != 0 { - as = typecheck(as, ctxStmt) - ninit.Append(as) - } - - if vas != nil { - vas = typecheck(vas, ctxStmt) - ninit.Append(vas) - } - - if !delayretvars { - // Zero the return parameters. - for _, n := range retvars { - ninit.Append(nod(ODCL, n, nil)) - ras := nod(OAS, n, nil) - ras = typecheck(ras, ctxStmt) - ninit.Append(ras) - } - } - - retlabel := autolabel(".i") - - inlgen++ - - parent := -1 - if b := Ctxt.PosTable.Pos(n.Pos).Base(); b != nil { - parent = b.InliningIndex() - } - newIndex := Ctxt.InlTree.Add(parent, n.Pos, fn.Sym.Linksym()) - - // Add an inline mark just before the inlined body. - // This mark is inline in the code so that it's a reasonable spot - // to put a breakpoint. Not sure if that's really necessary or not - // (in which case it could go at the end of the function instead). - // Note issue 28603. - inlMark := nod(OINLMARK, nil, nil) - inlMark.Pos = n.Pos.WithIsStmt() - inlMark.Xoffset = int64(newIndex) - ninit.Append(inlMark) - - if genDwarfInline > 0 { - if !fn.Sym.Linksym().WasInlined() { - Ctxt.DwFixups.SetPrecursorFunc(fn.Sym.Linksym(), fn) - fn.Sym.Linksym().Set(obj.AttrWasInlined, true) - } - } - - subst := inlsubst{ - retlabel: retlabel, - retvars: retvars, - delayretvars: delayretvars, - inlvars: inlvars, - bases: make(map[*src.PosBase]*src.PosBase), - newInlIndex: newIndex, - } - - body := subst.list(asNodes(fn.Func.Inl.Body)) - - lab := nodSym(OLABEL, nil, retlabel) - body = append(body, lab) - - typecheckslice(body, ctxStmt) - - if genDwarfInline > 0 { - for _, v := range inlfvars { - v.Pos = subst.updatedPos(v.Pos) - } - } - - //dumplist("ninit post", ninit); - - call := nod(OINLCALL, nil, nil) - call.Ninit.Set(ninit.Slice()) - call.Nbody.Set(body) - call.Rlist.Set(retvars) - call.Type = n.Type - call.SetTypecheck(1) - - // transitive inlining - // might be nice to do this before exporting the body, - // but can't emit the body with inlining expanded. - // instead we emit the things that the body needs - // and each use must redo the inlining. - // luckily these are small. - inlnodelist(call.Nbody, maxCost, inlMap) - for _, n := range call.Nbody.Slice() { - if n.Op == OINLCALL { - inlconv2stmt(n) - } - } - - if Debug.m > 2 { - fmt.Printf("%v: After inlining %+v\n\n", call.Line(), call) - } - - return call -} - -// Every time we expand a function we generate a new set of tmpnames, -// PAUTO's in the calling functions, and link them off of the -// PPARAM's, PAUTOS and PPARAMOUTs of the called function. -func inlvar(var_ *Node) *Node { - if Debug.m > 3 { - fmt.Printf("inlvar %+v\n", var_) - } - - n := newname(var_.Sym) - n.Type = var_.Type - n.SetClass(PAUTO) - n.Name.SetUsed(true) - n.Name.Curfn = Curfn // the calling function, not the called one - n.Name.SetAddrtaken(var_.Name.Addrtaken()) - - Curfn.Func.Dcl = append(Curfn.Func.Dcl, n) - return n -} - -// Synthesize a variable to store the inlined function's results in. -func retvar(t *types.Field, i int) *Node { - n := newname(lookupN("~R", i)) - n.Type = t.Type - n.SetClass(PAUTO) - n.Name.SetUsed(true) - n.Name.Curfn = Curfn // the calling function, not the called one - Curfn.Func.Dcl = append(Curfn.Func.Dcl, n) - return n -} - -// Synthesize a variable to store the inlined function's arguments -// when they come from a multiple return call. -func argvar(t *types.Type, i int) *Node { - n := newname(lookupN("~arg", i)) - n.Type = t.Elem() - n.SetClass(PAUTO) - n.Name.SetUsed(true) - n.Name.Curfn = Curfn // the calling function, not the called one - Curfn.Func.Dcl = append(Curfn.Func.Dcl, n) - return n -} - -// The inlsubst type implements the actual inlining of a single -// function call. -type inlsubst struct { - // Target of the goto substituted in place of a return. - retlabel *types.Sym - - // Temporary result variables. - retvars []*Node - - // Whether result variables should be initialized at the - // "return" statement. - delayretvars bool - - inlvars map[*Node]*Node - - // bases maps from original PosBase to PosBase with an extra - // inlined call frame. - bases map[*src.PosBase]*src.PosBase - - // newInlIndex is the index of the inlined call frame to - // insert for inlined nodes. - newInlIndex int -} - -// list inlines a list of nodes. -func (subst *inlsubst) list(ll Nodes) []*Node { - s := make([]*Node, 0, ll.Len()) - for _, n := range ll.Slice() { - s = append(s, subst.node(n)) - } - return s -} - -// node recursively copies a node from the saved pristine body of the -// inlined function, substituting references to input/output -// parameters with ones to the tmpnames, and substituting returns with -// assignments to the output. -func (subst *inlsubst) node(n *Node) *Node { - if n == nil { - return nil - } - - switch n.Op { - case ONAME: - if inlvar := subst.inlvars[n]; inlvar != nil { // These will be set during inlnode - if Debug.m > 2 { - fmt.Printf("substituting name %+v -> %+v\n", n, inlvar) - } - return inlvar - } - - if Debug.m > 2 { - fmt.Printf("not substituting name %+v\n", n) - } - return n - - case OLITERAL, OTYPE: - // If n is a named constant or type, we can continue - // using it in the inline copy. Otherwise, make a copy - // so we can update the line number. - if n.Sym != nil { - return n - } - - // Since we don't handle bodies with closures, this return is guaranteed to belong to the current inlined function. - - // dump("Return before substitution", n); - case ORETURN: - m := nodSym(OGOTO, nil, subst.retlabel) - m.Ninit.Set(subst.list(n.Ninit)) - - if len(subst.retvars) != 0 && n.List.Len() != 0 { - as := nod(OAS2, nil, nil) - - // Make a shallow copy of retvars. - // Otherwise OINLCALL.Rlist will be the same list, - // and later walk and typecheck may clobber it. - for _, n := range subst.retvars { - as.List.Append(n) - } - as.Rlist.Set(subst.list(n.List)) - - if subst.delayretvars { - for _, n := range as.List.Slice() { - as.Ninit.Append(nod(ODCL, n, nil)) - n.Name.Defn = as - } - } - - as = typecheck(as, ctxStmt) - m.Ninit.Append(as) - } - - typecheckslice(m.Ninit.Slice(), ctxStmt) - m = typecheck(m, ctxStmt) - - // dump("Return after substitution", m); - return m - - case OGOTO, OLABEL: - m := n.copy() - m.Pos = subst.updatedPos(m.Pos) - m.Ninit.Set(nil) - p := fmt.Sprintf("%s·%d", n.Sym.Name, inlgen) - m.Sym = lookup(p) - - return m - } - - m := n.copy() - m.Pos = subst.updatedPos(m.Pos) - m.Ninit.Set(nil) - - if n.Op == OCLOSURE { - Fatalf("cannot inline function containing closure: %+v", n) - } - - m.Left = subst.node(n.Left) - m.Right = subst.node(n.Right) - m.List.Set(subst.list(n.List)) - m.Rlist.Set(subst.list(n.Rlist)) - m.Ninit.Set(append(m.Ninit.Slice(), subst.list(n.Ninit)...)) - m.Nbody.Set(subst.list(n.Nbody)) - - return m -} - -func (subst *inlsubst) updatedPos(xpos src.XPos) src.XPos { - pos := Ctxt.PosTable.Pos(xpos) - oldbase := pos.Base() // can be nil - newbase := subst.bases[oldbase] - if newbase == nil { - newbase = src.NewInliningBase(oldbase, subst.newInlIndex) - subst.bases[oldbase] = newbase - } - pos.SetBase(newbase) - return Ctxt.PosTable.XPos(pos) -} - -func pruneUnusedAutos(ll []*Node, vis *hairyVisitor) []*Node { - s := make([]*Node, 0, len(ll)) - for _, n := range ll { - if n.Class() == PAUTO { - if _, found := vis.usedLocals[n]; !found { - continue - } - } - s = append(s, n) - } - return s -} - -// devirtualize replaces interface method calls within fn with direct -// concrete-type method calls where applicable. -func devirtualize(fn *Node) { - Curfn = fn - inspectList(fn.Nbody, func(n *Node) bool { - if n.Op == OCALLINTER { - devirtualizeCall(n) - } - return true - }) -} - -func devirtualizeCall(call *Node) { - recv := staticValue(call.Left.Left) - if recv.Op != OCONVIFACE { - return - } - - typ := recv.Left.Type - if typ.IsInterface() { - return - } - - x := nodl(call.Left.Pos, ODOTTYPE, call.Left.Left, nil) - x.Type = typ - x = nodlSym(call.Left.Pos, OXDOT, x, call.Left.Sym) - x = typecheck(x, ctxExpr|ctxCallee) - switch x.Op { - case ODOTMETH: - if Debug.m != 0 { - Warnl(call.Pos, "devirtualizing %v to %v", call.Left, typ) - } - call.Op = OCALLMETH - call.Left = x - case ODOTINTER: - // Promoted method from embedded interface-typed field (#42279). - if Debug.m != 0 { - Warnl(call.Pos, "partially devirtualizing %v to %v", call.Left, typ) - } - call.Op = OCALLINTER - call.Left = x - default: - // TODO(mdempsky): Turn back into Fatalf after more testing. - if Debug.m != 0 { - Warnl(call.Pos, "failed to devirtualize %v (%v)", x, x.Op) - } - return - } - - // Duplicated logic from typecheck for function call return - // value types. - // - // Receiver parameter size may have changed; need to update - // call.Type to get correct stack offsets for result - // parameters. - checkwidth(x.Type) - switch ft := x.Type; ft.NumResults() { - case 0: - case 1: - call.Type = ft.Results().Field(0).Type - default: - call.Type = ft.Results() - } -} diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index a6963a3d66e62c36ad5f8d46a89cbac3ca53e73c..ce50cbb4c2e691ed3054d20894fc1bc722623eda 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -2,716 +2,246 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:generate go run mkbuiltin.go - package gc import ( "bufio" "bytes" + "cmd/compile/internal/base" + "cmd/compile/internal/deadcode" + "cmd/compile/internal/devirtualize" + "cmd/compile/internal/dwarfgen" + "cmd/compile/internal/escape" + "cmd/compile/internal/inline" + "cmd/compile/internal/ir" "cmd/compile/internal/logopt" + "cmd/compile/internal/noder" + "cmd/compile/internal/pkginit" + "cmd/compile/internal/reflectdata" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/typecheck" "cmd/compile/internal/types" - "cmd/internal/bio" "cmd/internal/dwarf" - "cmd/internal/goobj" "cmd/internal/obj" "cmd/internal/objabi" "cmd/internal/src" - "cmd/internal/sys" "flag" "fmt" - "internal/goversion" - "io" - "io/ioutil" + "internal/buildcfg" "log" "os" - "path" - "regexp" "runtime" - "sort" - "strconv" - "strings" -) - -var ( - buildid string - spectre string - spectreIndex bool ) -var ( - Debug_append int - Debug_checkptr int - Debug_closure int - Debug_compilelater int - debug_dclstack int - Debug_dumpptrs int - Debug_libfuzzer int - Debug_panic int - Debug_slice int - Debug_vlog bool - Debug_wb int - Debug_pctab string - Debug_locationlist int - Debug_typecheckinl int - Debug_gendwarfinl int - Debug_softfloat int - Debug_defer int -) - -// Debug arguments. -// These can be specified with the -d flag, as in "-d nil" -// to set the debug_checknil variable. -// Multiple options can be comma-separated. -// Each option accepts an optional argument, as in "gcprog=2" -var debugtab = []struct { - name string - help string - val interface{} // must be *int or *string -}{ - {"append", "print information about append compilation", &Debug_append}, - {"checkptr", "instrument unsafe pointer conversions", &Debug_checkptr}, - {"closure", "print information about closure compilation", &Debug_closure}, - {"compilelater", "compile functions as late as possible", &Debug_compilelater}, - {"disablenil", "disable nil checks", &disable_checknil}, - {"dclstack", "run internal dclstack check", &debug_dclstack}, - {"dumpptrs", "show Node pointer values in Dump/dumplist output", &Debug_dumpptrs}, - {"gcprog", "print dump of GC programs", &Debug_gcprog}, - {"libfuzzer", "coverage instrumentation for libfuzzer", &Debug_libfuzzer}, - {"nil", "print information about nil checks", &Debug_checknil}, - {"panic", "do not hide any compiler panic", &Debug_panic}, - {"slice", "print information about slice compilation", &Debug_slice}, - {"typeassert", "print information about type assertion inlining", &Debug_typeassert}, - {"wb", "print information about write barriers", &Debug_wb}, - {"export", "print export data", &Debug_export}, - {"pctab", "print named pc-value table", &Debug_pctab}, - {"locationlists", "print information about DWARF location list creation", &Debug_locationlist}, - {"typecheckinl", "eager typechecking of inline function bodies", &Debug_typecheckinl}, - {"dwarfinl", "print information about DWARF inlined function creation", &Debug_gendwarfinl}, - {"softfloat", "force compiler to emit soft-float code", &Debug_softfloat}, - {"defer", "print information about defer compilation", &Debug_defer}, - {"fieldtrack", "enable fieldtracking", &objabi.Fieldtrack_enabled}, -} - -const debugHelpHeader = `usage: -d arg[,arg]* and arg is [=] - - is one of: - -` - -const debugHelpFooter = ` - is key-specific. - -Key "checkptr" supports values: - "0": instrumentation disabled - "1": conversions involving unsafe.Pointer are instrumented - "2": conversions to unsafe.Pointer force heap allocation - -Key "pctab" supports values: - "pctospadj", "pctofile", "pctoline", "pctoinline", "pctopcdata" -` - -func usage() { - fmt.Fprintf(os.Stderr, "usage: compile [options] file.go...\n") - objabi.Flagprint(os.Stderr) - Exit(2) -} - func hidePanic() { - if Debug_panic == 0 && nsavederrors+nerrors > 0 { + if base.Debug.Panic == 0 && base.Errors() > 0 { // If we've already complained about things // in the program, don't bother complaining // about a panic too; let the user clean up // the code and try again. if err := recover(); err != nil { - errorexit() + if err == "-h" { + panic(err) + } + base.ErrorExit() } } } -// supportsDynlink reports whether or not the code generator for the given -// architecture supports the -shared and -dynlink flags. -func supportsDynlink(arch *sys.Arch) bool { - return arch.InFamily(sys.AMD64, sys.ARM, sys.ARM64, sys.I386, sys.PPC64, sys.RISCV64, sys.S390X) -} - -// timing data for compiler phases -var timings Timings -var benchfile string - -var nowritebarrierrecCheck *nowritebarrierrecChecker - // Main parses flags and Go source files specified in the command-line // arguments, type-checks the parsed Go package, compiles functions to machine // code, and finally writes the compiled package definition to disk. -func Main(archInit func(*Arch)) { - timings.Start("fe", "init") +func Main(archInit func(*ssagen.ArchInfo)) { + base.Timer.Start("fe", "init") defer hidePanic() - archInit(&thearch) + archInit(&ssagen.Arch) - Ctxt = obj.Linknew(thearch.LinkArch) - Ctxt.DiagFunc = yyerror - Ctxt.DiagFlush = flusherrors - Ctxt.Bso = bufio.NewWriter(os.Stdout) + base.Ctxt = obj.Linknew(ssagen.Arch.LinkArch) + base.Ctxt.DiagFunc = base.Errorf + base.Ctxt.DiagFlush = base.FlushErrors + base.Ctxt.Bso = bufio.NewWriter(os.Stdout) // UseBASEntries is preferred because it shaves about 2% off build time, but LLDB, dsymutil, and dwarfdump // on Darwin don't support it properly, especially since macOS 10.14 (Mojave). This is exposed as a flag // to allow testing with LLVM tools on Linux, and to help with reporting this bug to the LLVM project. // See bugs 31188 and 21945 (CLs 170638, 98075, 72371). - Ctxt.UseBASEntries = Ctxt.Headtype != objabi.Hdarwin + base.Ctxt.UseBASEntries = base.Ctxt.Headtype != objabi.Hdarwin - localpkg = types.NewPkg("", "") - localpkg.Prefix = "\"\"" + types.LocalPkg = types.NewPkg("", "") + types.LocalPkg.Prefix = "\"\"" // We won't know localpkg's height until after import // processing. In the mean time, set to MaxPkgHeight to ensure // height comparisons at least work until then. - localpkg.Height = types.MaxPkgHeight + types.LocalPkg.Height = types.MaxPkgHeight // pseudo-package, for scoping - builtinpkg = types.NewPkg("go.builtin", "") // TODO(gri) name this package go.builtin? - builtinpkg.Prefix = "go.builtin" // not go%2ebuiltin + types.BuiltinPkg = types.NewPkg("go.builtin", "") // TODO(gri) name this package go.builtin? + types.BuiltinPkg.Prefix = "go.builtin" // not go%2ebuiltin // pseudo-package, accessed by import "unsafe" - unsafepkg = types.NewPkg("unsafe", "unsafe") + ir.Pkgs.Unsafe = types.NewPkg("unsafe", "unsafe") // Pseudo-package that contains the compiler's builtin // declarations for package runtime. These are declared in a // separate package to avoid conflicts with package runtime's // actual declarations, which may differ intentionally but // insignificantly. - Runtimepkg = types.NewPkg("go.runtime", "runtime") - Runtimepkg.Prefix = "runtime" + ir.Pkgs.Runtime = types.NewPkg("go.runtime", "runtime") + ir.Pkgs.Runtime.Prefix = "runtime" // pseudo-packages used in symbol tables - itabpkg = types.NewPkg("go.itab", "go.itab") - itabpkg.Prefix = "go.itab" // not go%2eitab - - itablinkpkg = types.NewPkg("go.itablink", "go.itablink") - itablinkpkg.Prefix = "go.itablink" // not go%2eitablink - - trackpkg = types.NewPkg("go.track", "go.track") - trackpkg.Prefix = "go.track" // not go%2etrack - - // pseudo-package used for map zero values - mappkg = types.NewPkg("go.map", "go.map") - mappkg.Prefix = "go.map" + ir.Pkgs.Itab = types.NewPkg("go.itab", "go.itab") + ir.Pkgs.Itab.Prefix = "go.itab" // not go%2eitab // pseudo-package used for methods with anonymous receivers - gopkg = types.NewPkg("go", "") + ir.Pkgs.Go = types.NewPkg("go", "") - Wasm := objabi.GOARCH == "wasm" - - // Whether the limit for stack-allocated objects is much smaller than normal. - // This can be helpful for diagnosing certain causes of GC latency. See #27732. - smallFrames := false - jsonLogOpt := "" - - flag.BoolVar(&compiling_runtime, "+", false, "compiling runtime") - flag.BoolVar(&compiling_std, "std", false, "compiling standard library") - flag.StringVar(&localimport, "D", "", "set relative `path` for local imports") - - objabi.Flagcount("%", "debug non-static initializers", &Debug.P) - objabi.Flagcount("B", "disable bounds checking", &Debug.B) - objabi.Flagcount("C", "disable printing of columns in error messages", &Debug.C) - objabi.Flagcount("E", "debug symbol export", &Debug.E) - objabi.Flagcount("K", "debug missing line numbers", &Debug.K) - objabi.Flagcount("L", "show full file names in error messages", &Debug.L) - objabi.Flagcount("N", "disable optimizations", &Debug.N) - objabi.Flagcount("S", "print assembly listing", &Debug.S) - objabi.Flagcount("W", "debug parse tree after type checking", &Debug.W) - objabi.Flagcount("e", "no limit on number of errors reported", &Debug.e) - objabi.Flagcount("h", "halt on error", &Debug.h) - objabi.Flagcount("j", "debug runtime-initialized variables", &Debug.j) - objabi.Flagcount("l", "disable inlining", &Debug.l) - objabi.Flagcount("m", "print optimization decisions", &Debug.m) - objabi.Flagcount("r", "debug generated wrappers", &Debug.r) - objabi.Flagcount("w", "debug type checking", &Debug.w) - - objabi.Flagfn1("I", "add `directory` to import search path", addidir) - objabi.AddVersionFlag() // -V - flag.StringVar(&asmhdr, "asmhdr", "", "write assembly header to `file`") - flag.StringVar(&buildid, "buildid", "", "record `id` as the build id in the export metadata") - flag.IntVar(&nBackendWorkers, "c", 1, "concurrency during compilation, 1 means no concurrency") - flag.BoolVar(&pure_go, "complete", false, "compiling complete package (no C or assembly)") - flag.StringVar(&debugstr, "d", "", "print debug information about items in `list`; try -d help") - flag.BoolVar(&flagDWARF, "dwarf", !Wasm, "generate DWARF symbols") - flag.BoolVar(&Ctxt.Flag_locationlists, "dwarflocationlists", true, "add location lists to DWARF in optimized mode") - flag.IntVar(&genDwarfInline, "gendwarfinl", 2, "generate DWARF inline info records") - objabi.Flagfn1("embedcfg", "read go:embed configuration from `file`", readEmbedCfg) - objabi.Flagfn1("importmap", "add `definition` of the form source=actual to import map", addImportMap) - objabi.Flagfn1("importcfg", "read import configuration from `file`", readImportCfg) - flag.StringVar(&flag_installsuffix, "installsuffix", "", "set pkg directory `suffix`") - flag.StringVar(&flag_lang, "lang", "", "release to compile for") - flag.StringVar(&linkobj, "linkobj", "", "write linker-specific object to `file`") - objabi.Flagcount("live", "debug liveness analysis", &debuglive) - if sys.MSanSupported(objabi.GOOS, objabi.GOARCH) { - flag.BoolVar(&flag_msan, "msan", false, "build code compatible with C/C++ memory sanitizer") - } - flag.BoolVar(&nolocalimports, "nolocalimports", false, "reject local (relative) imports") - flag.StringVar(&outfile, "o", "", "write output to `file`") - flag.StringVar(&myimportpath, "p", "", "set expected package import `path`") - flag.BoolVar(&writearchive, "pack", false, "write to file.a instead of file.o") - if sys.RaceDetectorSupported(objabi.GOOS, objabi.GOARCH) { - flag.BoolVar(&flag_race, "race", false, "enable race detector") - } - flag.StringVar(&spectre, "spectre", spectre, "enable spectre mitigations in `list` (all, index, ret)") - if enableTrace { - flag.BoolVar(&trace, "t", false, "trace type-checking") - } - flag.StringVar(&pathPrefix, "trimpath", "", "remove `prefix` from recorded source file paths") - flag.BoolVar(&Debug_vlog, "v", false, "increase debug verbosity") - flag.BoolVar(&use_writebarrier, "wb", true, "enable write barrier") - var flag_shared bool - var flag_dynlink bool - if supportsDynlink(thearch.LinkArch.Arch) { - flag.BoolVar(&flag_shared, "shared", false, "generate code that can be linked into a shared library") - flag.BoolVar(&flag_dynlink, "dynlink", false, "support references to Go symbols defined in other shared libraries") - flag.BoolVar(&Ctxt.Flag_linkshared, "linkshared", false, "generate code that will be linked against Go shared libraries") - } - flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to `file`") - flag.StringVar(&memprofile, "memprofile", "", "write memory profile to `file`") - flag.Int64Var(&memprofilerate, "memprofilerate", 0, "set runtime.MemProfileRate to `rate`") - var goversion string - flag.StringVar(&goversion, "goversion", "", "required version of the runtime") - var symabisPath string - flag.StringVar(&symabisPath, "symabis", "", "read symbol ABIs from `file`") - flag.StringVar(&traceprofile, "traceprofile", "", "write an execution trace to `file`") - flag.StringVar(&blockprofile, "blockprofile", "", "write block profile to `file`") - flag.StringVar(&mutexprofile, "mutexprofile", "", "write mutex profile to `file`") - flag.StringVar(&benchfile, "bench", "", "append benchmark times to `file`") - flag.BoolVar(&smallFrames, "smallframes", false, "reduce the size limit for stack allocated objects") - flag.BoolVar(&Ctxt.UseBASEntries, "dwarfbasentries", Ctxt.UseBASEntries, "use base address selection entries in DWARF") - flag.StringVar(&jsonLogOpt, "json", "", "version,destination for JSON compiler/optimizer logging") - - objabi.Flagparse(usage) - - Ctxt.Pkgpath = myimportpath - - for _, f := range strings.Split(spectre, ",") { - f = strings.TrimSpace(f) - switch f { - default: - log.Fatalf("unknown setting -spectre=%s", f) - case "": - // nothing - case "all": - spectreIndex = true - Ctxt.Retpoline = true - case "index": - spectreIndex = true - case "ret": - Ctxt.Retpoline = true - } - } - - if spectreIndex { - switch objabi.GOARCH { - case "amd64": - // ok - default: - log.Fatalf("GOARCH=%s does not support -spectre=index", objabi.GOARCH) - } - } + base.DebugSSA = ssa.PhaseOption + base.ParseFlags() // Record flags that affect the build result. (And don't // record flags that don't, since that would cause spurious // changes in the binary.) - recordFlags("B", "N", "l", "msan", "race", "shared", "dynlink", "dwarflocationlists", "dwarfbasentries", "smallframes", "spectre") - - if smallFrames { - maxStackVarSize = 128 * 1024 - maxImplicitStackVarSize = 16 * 1024 - } - - Ctxt.Flag_shared = flag_dynlink || flag_shared - Ctxt.Flag_dynlink = flag_dynlink - Ctxt.Flag_optimize = Debug.N == 0 - - Ctxt.Debugasm = Debug.S - Ctxt.Debugvlog = Debug_vlog - if flagDWARF { - Ctxt.DebugInfo = debuginfo - Ctxt.GenAbstractFunc = genAbstractFunc - Ctxt.DwFixups = obj.NewDwarfFixupTable(Ctxt) - } else { - // turn off inline generation if no dwarf at all - genDwarfInline = 0 - Ctxt.Flag_locationlists = false - } - - if flag.NArg() < 1 && debugstr != "help" && debugstr != "ssa/help" { - usage() - } + dwarfgen.RecordFlags("B", "N", "l", "msan", "race", "shared", "dynlink", "dwarf", "dwarflocationlists", "dwarfbasentries", "smallframes", "spectre") - if goversion != "" && goversion != runtime.Version() { - fmt.Printf("compile: version %q does not match go tool version %q\n", runtime.Version(), goversion) - Exit(2) + if !base.EnableTrace && base.Flag.LowerT { + log.Fatalf("compiler not built with support for -t") } - checkLang() - - if symabisPath != "" { - readSymABIs(symabisPath, myimportpath) + // Enable inlining (after RecordFlags, to avoid recording the rewritten -l). For now: + // default: inlining on. (Flag.LowerL == 1) + // -l: inlining off (Flag.LowerL == 0) + // -l=2, -l=3: inlining on again, with extra debugging (Flag.LowerL > 1) + if base.Flag.LowerL <= 1 { + base.Flag.LowerL = 1 - base.Flag.LowerL } - thearch.LinkArch.Init(Ctxt) - - if outfile == "" { - p := flag.Arg(0) - if i := strings.LastIndex(p, "/"); i >= 0 { - p = p[i+1:] - } - if runtime.GOOS == "windows" { - if i := strings.LastIndex(p, `\`); i >= 0 { - p = p[i+1:] - } - } - if i := strings.LastIndex(p, "."); i >= 0 { - p = p[:i] - } - suffix := ".o" - if writearchive { - suffix = ".a" - } - outfile = p + suffix + if base.Flag.SmallFrames { + ir.MaxStackVarSize = 128 * 1024 + ir.MaxImplicitStackVarSize = 16 * 1024 } - startProfile() - - if flag_race && flag_msan { - log.Fatal("cannot use both -race and -msan") - } - if flag_race || flag_msan { - // -race and -msan imply -d=checkptr for now. - Debug_checkptr = 1 - } - if ispkgin(omit_pkgs) { - flag_race = false - flag_msan = false - } - if flag_race { - racepkg = types.NewPkg("runtime/race", "") - } - if flag_msan { - msanpkg = types.NewPkg("runtime/msan", "") - } - if flag_race || flag_msan { - instrumenting = true - } - - if compiling_runtime && Debug.N != 0 { - log.Fatal("cannot disable optimizations while compiling runtime") - } - if nBackendWorkers < 1 { - log.Fatalf("-c must be at least 1, got %d", nBackendWorkers) - } - if nBackendWorkers > 1 && !concurrentBackendAllowed() { - log.Fatalf("cannot use concurrent backend compilation with provided flags; invoked as %v", os.Args) - } - if Ctxt.Flag_locationlists && len(Ctxt.Arch.DWARFRegisters) == 0 { - log.Fatalf("location lists requested but register mapping not available on %v", Ctxt.Arch.Name) + if base.Flag.Dwarf { + base.Ctxt.DebugInfo = dwarfgen.Info + base.Ctxt.GenAbstractFunc = dwarfgen.AbstractFunc + base.Ctxt.DwFixups = obj.NewDwarfFixupTable(base.Ctxt) + } else { + // turn off inline generation if no dwarf at all + base.Flag.GenDwarfInl = 0 + base.Ctxt.Flag_locationlists = false } - - // parse -d argument - if debugstr != "" { - Split: - for _, name := range strings.Split(debugstr, ",") { - if name == "" { - continue - } - // display help about the -d option itself and quit - if name == "help" { - fmt.Print(debugHelpHeader) - maxLen := len("ssa/help") - for _, t := range debugtab { - if len(t.name) > maxLen { - maxLen = len(t.name) - } - } - for _, t := range debugtab { - fmt.Printf("\t%-*s\t%s\n", maxLen, t.name, t.help) - } - // ssa options have their own help - fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging") - fmt.Print(debugHelpFooter) - os.Exit(0) - } - val, valstring, haveInt := 1, "", true - if i := strings.IndexAny(name, "=:"); i >= 0 { - var err error - name, valstring = name[:i], name[i+1:] - val, err = strconv.Atoi(valstring) - if err != nil { - val, haveInt = 1, false - } - } - for _, t := range debugtab { - if t.name != name { - continue - } - switch vp := t.val.(type) { - case nil: - // Ignore - case *string: - *vp = valstring - case *int: - if !haveInt { - log.Fatalf("invalid debug value %v", name) - } - *vp = val - default: - panic("bad debugtab type") - } - continue Split - } - // special case for ssa for now - if strings.HasPrefix(name, "ssa/") { - // expect form ssa/phase/flag - // e.g. -d=ssa/generic_cse/time - // _ in phase name also matches space - phase := name[4:] - flag := "debug" // default flag is debug - if i := strings.Index(phase, "/"); i >= 0 { - flag = phase[i+1:] - phase = phase[:i] - } - err := ssa.PhaseOption(phase, flag, val, valstring) - if err != "" { - log.Fatalf(err) - } - continue Split - } - log.Fatalf("unknown debug key -d %s\n", name) - } + if base.Ctxt.Flag_locationlists && len(base.Ctxt.Arch.DWARFRegisters) == 0 { + log.Fatalf("location lists requested but register mapping not available on %v", base.Ctxt.Arch.Name) } - if compiling_runtime { - // Runtime can't use -d=checkptr, at least not yet. - Debug_checkptr = 0 + types.ParseLangFlag() - // Fuzzing the runtime isn't interesting either. - Debug_libfuzzer = 0 + symABIs := ssagen.NewSymABIs(base.Ctxt.Pkgpath) + if base.Flag.SymABIs != "" { + symABIs.ReadSymABIs(base.Flag.SymABIs) } - // set via a -d flag - Ctxt.Debugpcln = Debug_pctab - if flagDWARF { - dwarf.EnableLogging(Debug_gendwarfinl != 0) + if base.Compiling(base.NoInstrumentPkgs) { + base.Flag.Race = false + base.Flag.MSan = false } - if Debug_softfloat != 0 { - thearch.SoftFloat = true - } - - // enable inlining. for now: - // default: inlining on. (Debug.l == 1) - // -l: inlining off (Debug.l == 0) - // -l=2, -l=3: inlining on again, with extra debugging (Debug.l > 1) - if Debug.l <= 1 { - Debug.l = 1 - Debug.l + ssagen.Arch.LinkArch.Init(base.Ctxt) + startProfile() + if base.Flag.Race || base.Flag.MSan { + base.Flag.Cfg.Instrumenting = true } - - if jsonLogOpt != "" { // parse version,destination from json logging optimization. - logopt.LogJsonOption(jsonLogOpt) + if base.Flag.Dwarf { + dwarf.EnableLogging(base.Debug.DwarfInl != 0) } - - ssaDump = os.Getenv("GOSSAFUNC") - ssaDir = os.Getenv("GOSSADIR") - if ssaDump != "" { - if strings.HasSuffix(ssaDump, "+") { - ssaDump = ssaDump[:len(ssaDump)-1] - ssaDumpStdout = true - } - spl := strings.Split(ssaDump, ":") - if len(spl) > 1 { - ssaDump = spl[0] - ssaDumpCFG = spl[1] + if base.Debug.SoftFloat != 0 { + if buildcfg.Experiment.RegabiArgs { + log.Fatalf("softfloat mode with GOEXPERIMENT=regabiargs not implemented ") } + ssagen.Arch.SoftFloat = true } - trackScopes = flagDWARF - - Widthptr = thearch.LinkArch.PtrSize - Widthreg = thearch.LinkArch.RegSize - - // initialize types package - // (we need to do this to break dependencies that otherwise - // would lead to import cycles) - types.Widthptr = Widthptr - types.Dowidth = dowidth - types.Fatalf = Fatalf - types.Sconv = func(s *types.Sym, flag, mode int) string { - return sconv(s, FmtFlag(flag), fmtMode(mode)) - } - types.Tconv = func(t *types.Type, flag, mode int) string { - return tconv(t, FmtFlag(flag), fmtMode(mode)) + if base.Flag.JSON != "" { // parse version,destination from json logging optimization. + logopt.LogJsonOption(base.Flag.JSON) } - types.FormatSym = func(sym *types.Sym, s fmt.State, verb rune, mode int) { - symFormat(sym, s, verb, fmtMode(mode)) - } - types.FormatType = func(t *types.Type, s fmt.State, verb rune, mode int) { - typeFormat(t, s, verb, fmtMode(mode)) - } - types.TypeLinkSym = func(t *types.Type) *obj.LSym { - return typenamesym(t).Linksym() - } - types.FmtLeft = int(FmtLeft) - types.FmtUnsigned = int(FmtUnsigned) - types.FErr = int(FErr) - types.Ctxt = Ctxt - - initUniverse() - - dclcontext = PEXTERN - nerrors = 0 - autogeneratedPos = makePos(src.NewFileBase("", ""), 1, 0) + ir.EscFmt = escape.Fmt + ir.IsIntrinsicCall = ssagen.IsIntrinsicCall + inline.SSADumpInline = ssagen.DumpInline + ssagen.InitEnv() + ssagen.InitTables() - timings.Start("fe", "loadsys") - loadsys() + types.PtrSize = ssagen.Arch.LinkArch.PtrSize + types.RegSize = ssagen.Arch.LinkArch.RegSize + types.MaxWidth = ssagen.Arch.MAXWIDTH - timings.Start("fe", "parse") - lines := parseFiles(flag.Args()) - timings.Stop() - timings.AddEvent(int64(lines), "lines") + typecheck.Target = new(ir.Package) - finishUniverse() + typecheck.NeedITab = func(t, iface *types.Type) { reflectdata.ITabAddr(t, iface) } + typecheck.NeedRuntimeType = reflectdata.NeedRuntimeType // TODO(rsc): TypeSym for lock? - recordPackageName() + base.AutogeneratedPos = makePos(src.NewFileBase("", ""), 1, 0) - typecheckok = true + typecheck.InitUniverse() - // Process top-level declarations in phases. + // Parse and typecheck input. + noder.LoadPackage(flag.Args()) - // Phase 1: const, type, and names and types of funcs. - // This will gather all the information about types - // and methods but doesn't depend on any of it. - // - // We also defer type alias declarations until phase 2 - // to avoid cycles like #18640. - // TODO(gri) Remove this again once we have a fix for #25838. + dwarfgen.RecordPackageName() - // Don't use range--typecheck can add closures to xtop. - timings.Start("fe", "typecheck", "top1") - for i := 0; i < len(xtop); i++ { - n := xtop[i] - if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias()) { - xtop[i] = typecheck(n, ctxStmt) - } - } - - // Phase 2: Variable assignments. - // To check interface assignments, depends on phase 1. - - // Don't use range--typecheck can add closures to xtop. - timings.Start("fe", "typecheck", "top2") - for i := 0; i < len(xtop); i++ { - n := xtop[i] - if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias() { - xtop[i] = typecheck(n, ctxStmt) - } + // Build init task. + if initTask := pkginit.Task(); initTask != nil { + typecheck.Export(initTask) } - // Phase 3: Type check function bodies. - // Don't use range--typecheck can add closures to xtop. - timings.Start("fe", "typecheck", "func") - var fcount int64 - for i := 0; i < len(xtop); i++ { - n := xtop[i] - if n.Op == ODCLFUNC { - Curfn = n - decldepth = 1 - saveerrors() - typecheckslice(Curfn.Nbody.Slice(), ctxStmt) - checkreturn(Curfn) - if nerrors != 0 { - Curfn.Nbody.Set(nil) // type errors; do not compile - } - // Now that we've checked whether n terminates, - // we can eliminate some obviously dead code. - deadcode(Curfn) - fcount++ + // Eliminate some obviously dead code. + // Must happen after typechecking. + for _, n := range typecheck.Target.Decls { + if n.Op() == ir.ODCLFUNC { + deadcode.Func(n.(*ir.Func)) } } - // With all types checked, it's now safe to verify map keys. One single - // check past phase 9 isn't sufficient, as we may exit with other errors - // before then, thus skipping map key errors. - checkMapKeys() - timings.AddEvent(fcount, "funcs") - - if nsavederrors+nerrors != 0 { - errorexit() - } - fninit(xtop) - - // Phase 4: Decide how to capture closed variables. - // This needs to run before escape analysis, - // because variables captured by value do not escape. - timings.Start("fe", "capturevars") - for _, n := range xtop { - if n.Op == ODCLFUNC && n.Func.Closure != nil { - Curfn = n - capturevars(n) - } + // Compute Addrtaken for names. + // We need to wait until typechecking is done so that when we see &x[i] + // we know that x has its address taken if x is an array, but not if x is a slice. + // We compute Addrtaken in bulk here. + // After this phase, we maintain Addrtaken incrementally. + if typecheck.DirtyAddrtaken { + typecheck.ComputeAddrtaken(typecheck.Target.Decls) + typecheck.DirtyAddrtaken = false } - capturevarscomplete = true - - Curfn = nil + typecheck.IncrementalAddrtaken = true - if nsavederrors+nerrors != 0 { - errorexit() - } - - // Phase 5: Inlining - timings.Start("fe", "inlining") - if Debug_typecheckinl != 0 { + if base.Debug.TypecheckInl != 0 { // Typecheck imported function bodies if Debug.l > 1, // otherwise lazily when used or re-exported. - for _, n := range importlist { - if n.Func.Inl != nil { - saveerrors() - typecheckinl(n) - } - } - - if nsavederrors+nerrors != 0 { - errorexit() - } + typecheck.AllImportedBodies() } - if Debug.l != 0 { - // Find functions that can be inlined and clone them before walk expands them. - visitBottomUp(xtop, func(list []*Node, recursive bool) { - numfns := numNonClosures(list) - for _, n := range list { - if !recursive || numfns > 1 { - // We allow inlining if there is no - // recursion, or the recursion cycle is - // across more than one function. - caninl(n) - } else { - if Debug.m > 1 { - fmt.Printf("%v: cannot inline %v: recursive\n", n.Line(), n.Func.Nname) - } - } - inlcalls(n) - } - }) + // Inlining + base.Timer.Start("fe", "inlining") + if base.Flag.LowerL != 0 { + inline.InlinePackage() } - for _, n := range xtop { - if n.Op == ODCLFUNC { - devirtualize(n) + // Devirtualize. + for _, n := range typecheck.Target.Decls { + if n.Op() == ir.ODCLFUNC { + devirtualize.Func(n.(*ir.Func)) } } - Curfn = nil + ir.CurFunc = nil + + // Generate ABI wrappers. Must happen before escape analysis + // and doesn't benefit from dead-coding or inlining. + symABIs.GenABIWrappers() - // Phase 6: Escape analysis. + // Escape analysis. // Required for moving heap allocations onto stack, // which in turn is required by the closure implementation, // which stores the addresses of stack variables into the closure. @@ -719,140 +249,86 @@ func Main(archInit func(*Arch)) { // or else the stack copier will not update it. // Large values are also moved off stack in escape analysis; // because large values may contain pointers, it must happen early. - timings.Start("fe", "escapes") - escapes(xtop) + base.Timer.Start("fe", "escapes") + escape.Funcs(typecheck.Target.Decls) // Collect information for go:nowritebarrierrec - // checking. This must happen before transformclosure. + // checking. This must happen before transforming closures during Walk // We'll do the final check after write barriers are // inserted. - if compiling_runtime { - nowritebarrierrecCheck = newNowritebarrierrecChecker() - } - - // Phase 7: Transform closure bodies to properly reference captured variables. - // This needs to happen before walk, because closures must be transformed - // before walk reaches a call of a closure. - timings.Start("fe", "xclosures") - for _, n := range xtop { - if n.Op == ODCLFUNC && n.Func.Closure != nil { - Curfn = n - transformclosure(n) - } + if base.Flag.CompilingRuntime { + ssagen.EnableNoWriteBarrierRecCheck() } // Prepare for SSA compilation. - // This must be before peekitabs, because peekitabs + // This must be before CompileITabs, because CompileITabs // can trigger function compilation. - initssaconfig() + typecheck.InitRuntime() + ssagen.InitConfig() // Just before compilation, compile itabs found on // the right side of OCONVIFACE so that methods // can be de-virtualized during compilation. - Curfn = nil - peekitabs() - - // Phase 8: Compile top level functions. - // Don't use range--walk can add functions to xtop. - timings.Start("be", "compilefuncs") - fcount = 0 - for i := 0; i < len(xtop); i++ { - n := xtop[i] - if n.Op == ODCLFUNC { - funccompile(n) + ir.CurFunc = nil + reflectdata.CompileITabs() + + // Compile top level functions. + // Don't use range--walk can add functions to Target.Decls. + base.Timer.Start("be", "compilefuncs") + fcount := int64(0) + for i := 0; i < len(typecheck.Target.Decls); i++ { + if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok { + enqueueFunc(fn) fcount++ } } - timings.AddEvent(fcount, "funcs") + base.Timer.AddEvent(fcount, "funcs") compileFunctions() - if nowritebarrierrecCheck != nil { - // Write barriers are now known. Check the - // call graph. - nowritebarrierrecCheck.check() - nowritebarrierrecCheck = nil + if base.Flag.CompilingRuntime { + // Write barriers are now known. Check the call graph. + ssagen.NoWriteBarrierRecCheck() } // Finalize DWARF inline routine DIEs, then explicitly turn off // DWARF inlining gen so as to avoid problems with generated // method wrappers. - if Ctxt.DwFixups != nil { - Ctxt.DwFixups.Finalize(myimportpath, Debug_gendwarfinl != 0) - Ctxt.DwFixups = nil - genDwarfInline = 0 - } - - // Phase 9: Check external declarations. - timings.Start("be", "externaldcls") - for i, n := range externdcl { - if n.Op == ONAME { - externdcl[i] = typecheck(externdcl[i], ctxExpr) - } - } - // Check the map keys again, since we typechecked the external - // declarations. - checkMapKeys() - - if nerrors+nsavederrors != 0 { - errorexit() + if base.Ctxt.DwFixups != nil { + base.Ctxt.DwFixups.Finalize(base.Ctxt.Pkgpath, base.Debug.DwarfInl != 0) + base.Ctxt.DwFixups = nil + base.Flag.GenDwarfInl = 0 } // Write object data to disk. - timings.Start("be", "dumpobj") + base.Timer.Start("be", "dumpobj") dumpdata() - Ctxt.NumberSyms() + base.Ctxt.NumberSyms() dumpobj() - if asmhdr != "" { + if base.Flag.AsmHdr != "" { dumpasmhdr() } - // Check whether any of the functions we have compiled have gigantic stack frames. - sort.Slice(largeStackFrames, func(i, j int) bool { - return largeStackFrames[i].pos.Before(largeStackFrames[j].pos) - }) - for _, large := range largeStackFrames { - if large.callee != 0 { - yyerrorl(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args + %d MB callee", large.locals>>20, large.args>>20, large.callee>>20) - } else { - yyerrorl(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args", large.locals>>20, large.args>>20) - } - } + ssagen.CheckLargeStacks() + typecheck.CheckFuncStack() - if len(funcStack) != 0 { - Fatalf("funcStack is non-empty: %v", len(funcStack)) - } if len(compilequeue) != 0 { - Fatalf("%d uncompiled functions", len(compilequeue)) + base.Fatalf("%d uncompiled functions", len(compilequeue)) } - logopt.FlushLoggedOpts(Ctxt, myimportpath) - - if nerrors+nsavederrors != 0 { - errorexit() - } + logopt.FlushLoggedOpts(base.Ctxt, base.Ctxt.Pkgpath) + base.ExitIfErrors() - flusherrors() - timings.Stop() + base.FlushErrors() + base.Timer.Stop() - if benchfile != "" { - if err := writebench(benchfile); err != nil { + if base.Flag.Bench != "" { + if err := writebench(base.Flag.Bench); err != nil { log.Fatalf("cannot write benchmark data: %v", err) } } } -// numNonClosures returns the number of functions in list which are not closures. -func numNonClosures(list []*Node) int { - count := 0 - for _, n := range list { - if n.Func.Closure == nil { - count++ - } - } - return count -} - func writebench(filename string) error { f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { @@ -860,10 +336,10 @@ func writebench(filename string) error { } var buf bytes.Buffer - fmt.Fprintln(&buf, "commit:", objabi.Version) + fmt.Fprintln(&buf, "commit:", buildcfg.Version) fmt.Fprintln(&buf, "goos:", runtime.GOOS) fmt.Fprintln(&buf, "goarch:", runtime.GOARCH) - timings.Write(&buf, "BenchmarkCompile:"+myimportpath+":") + base.Timer.Write(&buf, "BenchmarkCompile:"+base.Ctxt.Pkgpath+":") n, err := f.Write(buf.Bytes()) if err != nil { @@ -876,735 +352,6 @@ func writebench(filename string) error { return f.Close() } -var ( - importMap = map[string]string{} - packageFile map[string]string // nil means not in use -) - -func addImportMap(s string) { - if strings.Count(s, "=") != 1 { - log.Fatal("-importmap argument must be of the form source=actual") - } - i := strings.Index(s, "=") - source, actual := s[:i], s[i+1:] - if source == "" || actual == "" { - log.Fatal("-importmap argument must be of the form source=actual; source and actual must be non-empty") - } - importMap[source] = actual -} - -func readImportCfg(file string) { - packageFile = map[string]string{} - data, err := ioutil.ReadFile(file) - if err != nil { - log.Fatalf("-importcfg: %v", err) - } - - for lineNum, line := range strings.Split(string(data), "\n") { - lineNum++ // 1-based - line = strings.TrimSpace(line) - if line == "" || strings.HasPrefix(line, "#") { - continue - } - - var verb, args string - if i := strings.Index(line, " "); i < 0 { - verb = line - } else { - verb, args = line[:i], strings.TrimSpace(line[i+1:]) - } - var before, after string - if i := strings.Index(args, "="); i >= 0 { - before, after = args[:i], args[i+1:] - } - switch verb { - default: - log.Fatalf("%s:%d: unknown directive %q", file, lineNum, verb) - case "importmap": - if before == "" || after == "" { - log.Fatalf(`%s:%d: invalid importmap: syntax is "importmap old=new"`, file, lineNum) - } - importMap[before] = after - case "packagefile": - if before == "" || after == "" { - log.Fatalf(`%s:%d: invalid packagefile: syntax is "packagefile path=filename"`, file, lineNum) - } - packageFile[before] = after - } - } -} - -// symabiDefs and symabiRefs record the defined and referenced ABIs of -// symbols required by non-Go code. These are keyed by link symbol -// name, where the local package prefix is always `"".` -var symabiDefs, symabiRefs map[string]obj.ABI - -// readSymABIs reads a symabis file that specifies definitions and -// references of text symbols by ABI. -// -// The symabis format is a set of lines, where each line is a sequence -// of whitespace-separated fields. The first field is a verb and is -// either "def" for defining a symbol ABI or "ref" for referencing a -// symbol using an ABI. For both "def" and "ref", the second field is -// the symbol name and the third field is the ABI name, as one of the -// named cmd/internal/obj.ABI constants. -func readSymABIs(file, myimportpath string) { - data, err := ioutil.ReadFile(file) - if err != nil { - log.Fatalf("-symabis: %v", err) - } - - symabiDefs = make(map[string]obj.ABI) - symabiRefs = make(map[string]obj.ABI) - - localPrefix := "" - if myimportpath != "" { - // Symbols in this package may be written either as - // "".X or with the package's import path already in - // the symbol. - localPrefix = objabi.PathToPrefix(myimportpath) + "." - } - - for lineNum, line := range strings.Split(string(data), "\n") { - lineNum++ // 1-based - line = strings.TrimSpace(line) - if line == "" || strings.HasPrefix(line, "#") { - continue - } - - parts := strings.Fields(line) - switch parts[0] { - case "def", "ref": - // Parse line. - if len(parts) != 3 { - log.Fatalf(`%s:%d: invalid symabi: syntax is "%s sym abi"`, file, lineNum, parts[0]) - } - sym, abistr := parts[1], parts[2] - abi, valid := obj.ParseABI(abistr) - if !valid { - log.Fatalf(`%s:%d: invalid symabi: unknown abi "%s"`, file, lineNum, abistr) - } - - // If the symbol is already prefixed with - // myimportpath, rewrite it to start with "" - // so it matches the compiler's internal - // symbol names. - if localPrefix != "" && strings.HasPrefix(sym, localPrefix) { - sym = `"".` + sym[len(localPrefix):] - } - - // Record for later. - if parts[0] == "def" { - symabiDefs[sym] = abi - } else { - symabiRefs[sym] = abi - } - default: - log.Fatalf(`%s:%d: invalid symabi type "%s"`, file, lineNum, parts[0]) - } - } -} - -func saveerrors() { - nsavederrors += nerrors - nerrors = 0 -} - -func arsize(b *bufio.Reader, name string) int { - var buf [ArhdrSize]byte - if _, err := io.ReadFull(b, buf[:]); err != nil { - return -1 - } - aname := strings.Trim(string(buf[0:16]), " ") - if !strings.HasPrefix(aname, name) { - return -1 - } - asize := strings.Trim(string(buf[48:58]), " ") - i, _ := strconv.Atoi(asize) - return i -} - -var idirs []string - -func addidir(dir string) { - if dir != "" { - idirs = append(idirs, dir) - } -} - -func isDriveLetter(b byte) bool { - return 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' -} - -// is this path a local name? begins with ./ or ../ or / -func islocalname(name string) bool { - return strings.HasPrefix(name, "/") || - runtime.GOOS == "windows" && len(name) >= 3 && isDriveLetter(name[0]) && name[1] == ':' && name[2] == '/' || - strings.HasPrefix(name, "./") || name == "." || - strings.HasPrefix(name, "../") || name == ".." -} - -func findpkg(name string) (file string, ok bool) { - if islocalname(name) { - if nolocalimports { - return "", false - } - - if packageFile != nil { - file, ok = packageFile[name] - return file, ok - } - - // try .a before .6. important for building libraries: - // if there is an array.6 in the array.a library, - // want to find all of array.a, not just array.6. - file = fmt.Sprintf("%s.a", name) - if _, err := os.Stat(file); err == nil { - return file, true - } - file = fmt.Sprintf("%s.o", name) - if _, err := os.Stat(file); err == nil { - return file, true - } - return "", false - } - - // local imports should be canonicalized already. - // don't want to see "encoding/../encoding/base64" - // as different from "encoding/base64". - if q := path.Clean(name); q != name { - yyerror("non-canonical import path %q (should be %q)", name, q) - return "", false - } - - if packageFile != nil { - file, ok = packageFile[name] - return file, ok - } - - for _, dir := range idirs { - file = fmt.Sprintf("%s/%s.a", dir, name) - if _, err := os.Stat(file); err == nil { - return file, true - } - file = fmt.Sprintf("%s/%s.o", dir, name) - if _, err := os.Stat(file); err == nil { - return file, true - } - } - - if objabi.GOROOT != "" { - suffix := "" - suffixsep := "" - if flag_installsuffix != "" { - suffixsep = "_" - suffix = flag_installsuffix - } else if flag_race { - suffixsep = "_" - suffix = "race" - } else if flag_msan { - suffixsep = "_" - suffix = "msan" - } - - file = fmt.Sprintf("%s/pkg/%s_%s%s%s/%s.a", objabi.GOROOT, objabi.GOOS, objabi.GOARCH, suffixsep, suffix, name) - if _, err := os.Stat(file); err == nil { - return file, true - } - file = fmt.Sprintf("%s/pkg/%s_%s%s%s/%s.o", objabi.GOROOT, objabi.GOOS, objabi.GOARCH, suffixsep, suffix, name) - if _, err := os.Stat(file); err == nil { - return file, true - } - } - - return "", false -} - -// loadsys loads the definitions for the low-level runtime functions, -// so that the compiler can generate calls to them, -// but does not make them visible to user code. -func loadsys() { - types.Block = 1 - - inimport = true - typecheckok = true - - typs := runtimeTypes() - for _, d := range &runtimeDecls { - sym := Runtimepkg.Lookup(d.name) - typ := typs[d.typ] - switch d.tag { - case funcTag: - importfunc(Runtimepkg, src.NoXPos, sym, typ) - case varTag: - importvar(Runtimepkg, src.NoXPos, sym, typ) - default: - Fatalf("unhandled declaration tag %v", d.tag) - } - } - - typecheckok = false - inimport = false -} - -// myheight tracks the local package's height based on packages -// imported so far. -var myheight int - -func importfile(f *Val) *types.Pkg { - path_, ok := f.U.(string) - if !ok { - yyerror("import path must be a string") - return nil - } - - if len(path_) == 0 { - yyerror("import path is empty") - return nil - } - - if isbadimport(path_, false) { - return nil - } - - // The package name main is no longer reserved, - // but we reserve the import path "main" to identify - // the main package, just as we reserve the import - // path "math" to identify the standard math package. - if path_ == "main" { - yyerror("cannot import \"main\"") - errorexit() - } - - if myimportpath != "" && path_ == myimportpath { - yyerror("import %q while compiling that package (import cycle)", path_) - errorexit() - } - - if mapped, ok := importMap[path_]; ok { - path_ = mapped - } - - if path_ == "unsafe" { - return unsafepkg - } - - if islocalname(path_) { - if path_[0] == '/' { - yyerror("import path cannot be absolute path") - return nil - } - - prefix := Ctxt.Pathname - if localimport != "" { - prefix = localimport - } - path_ = path.Join(prefix, path_) - - if isbadimport(path_, true) { - return nil - } - } - - file, found := findpkg(path_) - if !found { - yyerror("can't find import: %q", path_) - errorexit() - } - - importpkg := types.NewPkg(path_, "") - if importpkg.Imported { - return importpkg - } - - importpkg.Imported = true - - imp, err := bio.Open(file) - if err != nil { - yyerror("can't open import: %q: %v", path_, err) - errorexit() - } - defer imp.Close() - - // check object header - p, err := imp.ReadString('\n') - if err != nil { - yyerror("import %s: reading input: %v", file, err) - errorexit() - } - - if p == "!\n" { // package archive - // package export block should be first - sz := arsize(imp.Reader, "__.PKGDEF") - if sz <= 0 { - yyerror("import %s: not a package file", file) - errorexit() - } - p, err = imp.ReadString('\n') - if err != nil { - yyerror("import %s: reading input: %v", file, err) - errorexit() - } - } - - if !strings.HasPrefix(p, "go object ") { - yyerror("import %s: not a go object file: %s", file, p) - errorexit() - } - q := fmt.Sprintf("%s %s %s %s\n", objabi.GOOS, objabi.GOARCH, objabi.Version, objabi.Expstring()) - if p[10:] != q { - yyerror("import %s: object is [%s] expected [%s]", file, p[10:], q) - errorexit() - } - - // process header lines - for { - p, err = imp.ReadString('\n') - if err != nil { - yyerror("import %s: reading input: %v", file, err) - errorexit() - } - if p == "\n" { - break // header ends with blank line - } - } - - // In the importfile, if we find: - // $$\n (textual format): not supported anymore - // $$B\n (binary format) : import directly, then feed the lexer a dummy statement - - // look for $$ - var c byte - for { - c, err = imp.ReadByte() - if err != nil { - break - } - if c == '$' { - c, err = imp.ReadByte() - if c == '$' || err != nil { - break - } - } - } - - // get character after $$ - if err == nil { - c, _ = imp.ReadByte() - } - - var fingerprint goobj.FingerprintType - switch c { - case '\n': - yyerror("cannot import %s: old export format no longer supported (recompile library)", path_) - return nil - - case 'B': - if Debug_export != 0 { - fmt.Printf("importing %s (%s)\n", path_, file) - } - imp.ReadByte() // skip \n after $$B - - c, err = imp.ReadByte() - if err != nil { - yyerror("import %s: reading input: %v", file, err) - errorexit() - } - - // Indexed format is distinguished by an 'i' byte, - // whereas previous export formats started with 'c', 'd', or 'v'. - if c != 'i' { - yyerror("import %s: unexpected package format byte: %v", file, c) - errorexit() - } - fingerprint = iimport(importpkg, imp) - - default: - yyerror("no import in %q", path_) - errorexit() - } - - // assume files move (get installed) so don't record the full path - if packageFile != nil { - // If using a packageFile map, assume path_ can be recorded directly. - Ctxt.AddImport(path_, fingerprint) - } else { - // For file "/Users/foo/go/pkg/darwin_amd64/math.a" record "math.a". - Ctxt.AddImport(file[len(file)-len(path_)-len(".a"):], fingerprint) - } - - if importpkg.Height >= myheight { - myheight = importpkg.Height + 1 - } - - return importpkg -} - -func pkgnotused(lineno src.XPos, path string, name string) { - // If the package was imported with a name other than the final - // import path element, show it explicitly in the error message. - // Note that this handles both renamed imports and imports of - // packages containing unconventional package declarations. - // Note that this uses / always, even on Windows, because Go import - // paths always use forward slashes. - elem := path - if i := strings.LastIndex(elem, "/"); i >= 0 { - elem = elem[i+1:] - } - if name == "" || elem == name { - yyerrorl(lineno, "imported and not used: %q", path) - } else { - yyerrorl(lineno, "imported and not used: %q as %s", path, name) - } -} - -func mkpackage(pkgname string) { - if localpkg.Name == "" { - if pkgname == "_" { - yyerror("invalid package name _") - } - localpkg.Name = pkgname - } else { - if pkgname != localpkg.Name { - yyerror("package %s; expected %s", pkgname, localpkg.Name) - } - } -} - -func clearImports() { - type importedPkg struct { - pos src.XPos - path string - name string - } - var unused []importedPkg - - for _, s := range localpkg.Syms { - n := asNode(s.Def) - if n == nil { - continue - } - if n.Op == OPACK { - // throw away top-level package name left over - // from previous file. - // leave s->block set to cause redeclaration - // errors if a conflicting top-level name is - // introduced by a different file. - if !n.Name.Used() && nsyntaxerrors == 0 { - unused = append(unused, importedPkg{n.Pos, n.Name.Pkg.Path, s.Name}) - } - s.Def = nil - continue - } - if IsAlias(s) { - // throw away top-level name left over - // from previous import . "x" - if n.Name != nil && n.Name.Pack != nil && !n.Name.Pack.Name.Used() && nsyntaxerrors == 0 { - unused = append(unused, importedPkg{n.Name.Pack.Pos, n.Name.Pack.Name.Pkg.Path, ""}) - n.Name.Pack.Name.SetUsed(true) - } - s.Def = nil - continue - } - } - - sort.Slice(unused, func(i, j int) bool { return unused[i].pos.Before(unused[j].pos) }) - for _, pkg := range unused { - pkgnotused(pkg.pos, pkg.path, pkg.name) - } -} - -func IsAlias(sym *types.Sym) bool { - return sym.Def != nil && asNode(sym.Def).Sym != sym -} - -// By default, assume any debug flags are incompatible with concurrent -// compilation. A few are safe and potentially in common use for -// normal compiles, though; return true for those. -func concurrentFlagOk() bool { - // Report whether any debug flag that would prevent concurrent - // compilation is set, by zeroing out the allowed ones and then - // checking if the resulting struct is zero. - d := Debug - d.B = 0 // disable bounds checking - d.C = 0 // disable printing of columns in error messages - d.e = 0 // no limit on errors; errors all come from non-concurrent code - d.N = 0 // disable optimizations - d.l = 0 // disable inlining - d.w = 0 // all printing happens before compilation - d.W = 0 // all printing happens before compilation - d.S = 0 // printing disassembly happens at the end (but see concurrentBackendAllowed below) - - return d == DebugFlags{} -} - -func concurrentBackendAllowed() bool { - if !concurrentFlagOk() { - return false - } - - // Debug.S by itself is ok, because all printing occurs - // while writing the object file, and that is non-concurrent. - // Adding Debug_vlog, however, causes Debug.S to also print - // while flushing the plist, which happens concurrently. - if Debug_vlog || debugstr != "" || debuglive > 0 { - return false - } - // TODO: Test and delete this condition. - if objabi.Fieldtrack_enabled != 0 { - return false - } - // TODO: fix races and enable the following flags - if Ctxt.Flag_shared || Ctxt.Flag_dynlink || flag_race { - return false - } - return true -} - -// recordFlags records the specified command-line flags to be placed -// in the DWARF info. -func recordFlags(flags ...string) { - if myimportpath == "" { - // We can't record the flags if we don't know what the - // package name is. - return - } - - type BoolFlag interface { - IsBoolFlag() bool - } - type CountFlag interface { - IsCountFlag() bool - } - var cmd bytes.Buffer - for _, name := range flags { - f := flag.Lookup(name) - if f == nil { - continue - } - getter := f.Value.(flag.Getter) - if getter.String() == f.DefValue { - // Flag has default value, so omit it. - continue - } - if bf, ok := f.Value.(BoolFlag); ok && bf.IsBoolFlag() { - val, ok := getter.Get().(bool) - if ok && val { - fmt.Fprintf(&cmd, " -%s", f.Name) - continue - } - } - if cf, ok := f.Value.(CountFlag); ok && cf.IsCountFlag() { - val, ok := getter.Get().(int) - if ok && val == 1 { - fmt.Fprintf(&cmd, " -%s", f.Name) - continue - } - } - fmt.Fprintf(&cmd, " -%s=%v", f.Name, getter.Get()) - } - - if cmd.Len() == 0 { - return - } - s := Ctxt.Lookup(dwarf.CUInfoPrefix + "producer." + myimportpath) - s.Type = objabi.SDWARFCUINFO - // Sometimes (for example when building tests) we can link - // together two package main archives. So allow dups. - s.Set(obj.AttrDuplicateOK, true) - Ctxt.Data = append(Ctxt.Data, s) - s.P = cmd.Bytes()[1:] -} - -// recordPackageName records the name of the package being -// compiled, so that the linker can save it in the compile unit's DIE. -func recordPackageName() { - s := Ctxt.Lookup(dwarf.CUInfoPrefix + "packagename." + myimportpath) - s.Type = objabi.SDWARFCUINFO - // Sometimes (for example when building tests) we can link - // together two package main archives. So allow dups. - s.Set(obj.AttrDuplicateOK, true) - Ctxt.Data = append(Ctxt.Data, s) - s.P = []byte(localpkg.Name) -} - -// flag_lang is the language version we are compiling for, set by the -lang flag. -var flag_lang string - -// currentLang returns the current language version. -func currentLang() string { - return fmt.Sprintf("go1.%d", goversion.Version) -} - -// goVersionRE is a regular expression that matches the valid -// arguments to the -lang flag. -var goVersionRE = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`) - -// A lang is a language version broken into major and minor numbers. -type lang struct { - major, minor int -} - -// langWant is the desired language version set by the -lang flag. -// If the -lang flag is not set, this is the zero value, meaning that -// any language version is supported. -var langWant lang - -// langSupported reports whether language version major.minor is -// supported in a particular package. -func langSupported(major, minor int, pkg *types.Pkg) bool { - if pkg == nil { - // TODO(mdempsky): Set Pkg for local types earlier. - pkg = localpkg - } - if pkg != localpkg { - // Assume imported packages passed type-checking. - return true - } - - if langWant.major == 0 && langWant.minor == 0 { - return true - } - return langWant.major > major || (langWant.major == major && langWant.minor >= minor) -} - -// checkLang verifies that the -lang flag holds a valid value, and -// exits if not. It initializes data used by langSupported. -func checkLang() { - if flag_lang == "" { - return - } - - var err error - langWant, err = parseLang(flag_lang) - if err != nil { - log.Fatalf("invalid value %q for -lang: %v", flag_lang, err) - } - - if def := currentLang(); flag_lang != def { - defVers, err := parseLang(def) - if err != nil { - log.Fatalf("internal error parsing default lang %q: %v", def, err) - } - if langWant.major > defVers.major || (langWant.major == defVers.major && langWant.minor > defVers.minor) { - log.Fatalf("invalid value %q for -lang: max known version is %q", flag_lang, def) - } - } -} - -// parseLang parses a -lang option into a langVer. -func parseLang(s string) (lang, error) { - matches := goVersionRE.FindStringSubmatch(s) - if matches == nil { - return lang{}, fmt.Errorf(`should be something like "go1.12"`) - } - major, err := strconv.Atoi(matches[1]) - if err != nil { - return lang{}, err - } - minor, err := strconv.Atoi(matches[2]) - if err != nil { - return lang{}, err - } - return lang{major: major, minor: minor}, nil +func makePos(b *src.PosBase, line, col uint) src.XPos { + return base.Ctxt.PosTable.XPos(src.MakePos(b, line, col)) } diff --git a/src/cmd/compile/internal/gc/mpfloat.go b/src/cmd/compile/internal/gc/mpfloat.go deleted file mode 100644 index 401aef319de7ccc58a2a5d8b3188dade255233d5..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/mpfloat.go +++ /dev/null @@ -1,357 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "fmt" - "math" - "math/big" -) - -// implements float arithmetic - -const ( - // Maximum size in bits for Mpints before signalling - // overflow and also mantissa precision for Mpflts. - Mpprec = 512 - // Turn on for constant arithmetic debugging output. - Mpdebug = false -) - -// Mpflt represents a floating-point constant. -type Mpflt struct { - Val big.Float -} - -// Mpcplx represents a complex constant. -type Mpcplx struct { - Real Mpflt - Imag Mpflt -} - -// Use newMpflt (not new(Mpflt)!) to get the correct default precision. -func newMpflt() *Mpflt { - var a Mpflt - a.Val.SetPrec(Mpprec) - return &a -} - -// Use newMpcmplx (not new(Mpcplx)!) to get the correct default precision. -func newMpcmplx() *Mpcplx { - var a Mpcplx - a.Real = *newMpflt() - a.Imag = *newMpflt() - return &a -} - -func (a *Mpflt) SetInt(b *Mpint) { - if b.checkOverflow(0) { - // sign doesn't really matter but copy anyway - a.Val.SetInf(b.Val.Sign() < 0) - return - } - a.Val.SetInt(&b.Val) -} - -func (a *Mpflt) Set(b *Mpflt) { - a.Val.Set(&b.Val) -} - -func (a *Mpflt) Add(b *Mpflt) { - if Mpdebug { - fmt.Printf("\n%v + %v", a, b) - } - - a.Val.Add(&a.Val, &b.Val) - - if Mpdebug { - fmt.Printf(" = %v\n\n", a) - } -} - -func (a *Mpflt) AddFloat64(c float64) { - var b Mpflt - - b.SetFloat64(c) - a.Add(&b) -} - -func (a *Mpflt) Sub(b *Mpflt) { - if Mpdebug { - fmt.Printf("\n%v - %v", a, b) - } - - a.Val.Sub(&a.Val, &b.Val) - - if Mpdebug { - fmt.Printf(" = %v\n\n", a) - } -} - -func (a *Mpflt) Mul(b *Mpflt) { - if Mpdebug { - fmt.Printf("%v\n * %v\n", a, b) - } - - a.Val.Mul(&a.Val, &b.Val) - - if Mpdebug { - fmt.Printf(" = %v\n\n", a) - } -} - -func (a *Mpflt) MulFloat64(c float64) { - var b Mpflt - - b.SetFloat64(c) - a.Mul(&b) -} - -func (a *Mpflt) Quo(b *Mpflt) { - if Mpdebug { - fmt.Printf("%v\n / %v\n", a, b) - } - - a.Val.Quo(&a.Val, &b.Val) - - if Mpdebug { - fmt.Printf(" = %v\n\n", a) - } -} - -func (a *Mpflt) Cmp(b *Mpflt) int { - return a.Val.Cmp(&b.Val) -} - -func (a *Mpflt) CmpFloat64(c float64) int { - if c == 0 { - return a.Val.Sign() // common case shortcut - } - return a.Val.Cmp(big.NewFloat(c)) -} - -func (a *Mpflt) Float64() float64 { - x, _ := a.Val.Float64() - - // check for overflow - if math.IsInf(x, 0) && nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpflt Float64") - } - - return x + 0 // avoid -0 (should not be needed, but be conservative) -} - -func (a *Mpflt) Float32() float64 { - x32, _ := a.Val.Float32() - x := float64(x32) - - // check for overflow - if math.IsInf(x, 0) && nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpflt Float32") - } - - return x + 0 // avoid -0 (should not be needed, but be conservative) -} - -func (a *Mpflt) SetFloat64(c float64) { - if Mpdebug { - fmt.Printf("\nconst %g", c) - } - - // convert -0 to 0 - if c == 0 { - c = 0 - } - a.Val.SetFloat64(c) - - if Mpdebug { - fmt.Printf(" = %v\n", a) - } -} - -func (a *Mpflt) Neg() { - // avoid -0 - if a.Val.Sign() != 0 { - a.Val.Neg(&a.Val) - } -} - -func (a *Mpflt) SetString(as string) { - f, _, err := a.Val.Parse(as, 0) - if err != nil { - yyerror("malformed constant: %s (%v)", as, err) - a.Val.SetFloat64(0) - return - } - - if f.IsInf() { - yyerror("constant too large: %s", as) - a.Val.SetFloat64(0) - return - } - - // -0 becomes 0 - if f.Sign() == 0 && f.Signbit() { - a.Val.SetFloat64(0) - } -} - -func (f *Mpflt) String() string { - return f.Val.Text('b', 0) -} - -func (fvp *Mpflt) GoString() string { - // determine sign - sign := "" - f := &fvp.Val - if f.Sign() < 0 { - sign = "-" - f = new(big.Float).Abs(f) - } - - // Don't try to convert infinities (will not terminate). - if f.IsInf() { - return sign + "Inf" - } - - // Use exact fmt formatting if in float64 range (common case): - // proceed if f doesn't underflow to 0 or overflow to inf. - if x, _ := f.Float64(); f.Sign() == 0 == (x == 0) && !math.IsInf(x, 0) { - return fmt.Sprintf("%s%.6g", sign, x) - } - - // Out of float64 range. Do approximate manual to decimal - // conversion to avoid precise but possibly slow Float - // formatting. - // f = mant * 2**exp - var mant big.Float - exp := f.MantExp(&mant) // 0.5 <= mant < 1.0 - - // approximate float64 mantissa m and decimal exponent d - // f ~ m * 10**d - m, _ := mant.Float64() // 0.5 <= m < 1.0 - d := float64(exp) * (math.Ln2 / math.Ln10) // log_10(2) - - // adjust m for truncated (integer) decimal exponent e - e := int64(d) - m *= math.Pow(10, d-float64(e)) - - // ensure 1 <= m < 10 - switch { - case m < 1-0.5e-6: - // The %.6g format below rounds m to 5 digits after the - // decimal point. Make sure that m*10 < 10 even after - // rounding up: m*10 + 0.5e-5 < 10 => m < 1 - 0.5e6. - m *= 10 - e-- - case m >= 10: - m /= 10 - e++ - } - - return fmt.Sprintf("%s%.6ge%+d", sign, m, e) -} - -// complex multiply v *= rv -// (a, b) * (c, d) = (a*c - b*d, b*c + a*d) -func (v *Mpcplx) Mul(rv *Mpcplx) { - var ac, ad, bc, bd Mpflt - - ac.Set(&v.Real) - ac.Mul(&rv.Real) // ac - - bd.Set(&v.Imag) - bd.Mul(&rv.Imag) // bd - - bc.Set(&v.Imag) - bc.Mul(&rv.Real) // bc - - ad.Set(&v.Real) - ad.Mul(&rv.Imag) // ad - - v.Real.Set(&ac) - v.Real.Sub(&bd) // ac-bd - - v.Imag.Set(&bc) - v.Imag.Add(&ad) // bc+ad -} - -// complex divide v /= rv -// (a, b) / (c, d) = ((a*c + b*d), (b*c - a*d))/(c*c + d*d) -func (v *Mpcplx) Div(rv *Mpcplx) bool { - if rv.Real.CmpFloat64(0) == 0 && rv.Imag.CmpFloat64(0) == 0 { - return false - } - - var ac, ad, bc, bd, cc_plus_dd Mpflt - - cc_plus_dd.Set(&rv.Real) - cc_plus_dd.Mul(&rv.Real) // cc - - ac.Set(&rv.Imag) - ac.Mul(&rv.Imag) // dd - cc_plus_dd.Add(&ac) // cc+dd - - // We already checked that c and d are not both zero, but we can't - // assume that c²+d² != 0 follows, because for tiny values of c - // and/or d c²+d² can underflow to zero. Check that c²+d² is - // nonzero, return if it's not. - if cc_plus_dd.CmpFloat64(0) == 0 { - return false - } - - ac.Set(&v.Real) - ac.Mul(&rv.Real) // ac - - bd.Set(&v.Imag) - bd.Mul(&rv.Imag) // bd - - bc.Set(&v.Imag) - bc.Mul(&rv.Real) // bc - - ad.Set(&v.Real) - ad.Mul(&rv.Imag) // ad - - v.Real.Set(&ac) - v.Real.Add(&bd) // ac+bd - v.Real.Quo(&cc_plus_dd) // (ac+bd)/(cc+dd) - - v.Imag.Set(&bc) - v.Imag.Sub(&ad) // bc-ad - v.Imag.Quo(&cc_plus_dd) // (bc+ad)/(cc+dd) - - return true -} - -func (v *Mpcplx) String() string { - return fmt.Sprintf("(%s+%si)", v.Real.String(), v.Imag.String()) -} - -func (v *Mpcplx) GoString() string { - var re string - sre := v.Real.CmpFloat64(0) - if sre != 0 { - re = v.Real.GoString() - } - - var im string - sim := v.Imag.CmpFloat64(0) - if sim != 0 { - im = v.Imag.GoString() - } - - switch { - case sre == 0 && sim == 0: - return "0" - case sre == 0: - return im + "i" - case sim == 0: - return re - case sim < 0: - return fmt.Sprintf("(%s%si)", re, im) - default: - return fmt.Sprintf("(%s+%si)", re, im) - } -} diff --git a/src/cmd/compile/internal/gc/mpint.go b/src/cmd/compile/internal/gc/mpint.go deleted file mode 100644 index 340350bca7b2bf7db60039cb15e969015468c46d..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/mpint.go +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "fmt" - "math/big" -) - -// implements integer arithmetic - -// Mpint represents an integer constant. -type Mpint struct { - Val big.Int - Ovf bool // set if Val overflowed compiler limit (sticky) - Rune bool // set if syntax indicates default type rune -} - -func (a *Mpint) SetOverflow() { - a.Val.SetUint64(1) // avoid spurious div-zero errors - a.Ovf = true -} - -func (a *Mpint) checkOverflow(extra int) bool { - // We don't need to be precise here, any reasonable upper limit would do. - // For now, use existing limit so we pass all the tests unchanged. - if a.Val.BitLen()+extra > Mpprec { - a.SetOverflow() - } - return a.Ovf -} - -func (a *Mpint) Set(b *Mpint) { - a.Val.Set(&b.Val) -} - -func (a *Mpint) SetFloat(b *Mpflt) bool { - // avoid converting huge floating-point numbers to integers - // (2*Mpprec is large enough to permit all tests to pass) - if b.Val.MantExp(nil) > 2*Mpprec { - a.SetOverflow() - return false - } - - if _, acc := b.Val.Int(&a.Val); acc == big.Exact { - return true - } - - const delta = 16 // a reasonably small number of bits > 0 - var t big.Float - t.SetPrec(Mpprec - delta) - - // try rounding down a little - t.SetMode(big.ToZero) - t.Set(&b.Val) - if _, acc := t.Int(&a.Val); acc == big.Exact { - return true - } - - // try rounding up a little - t.SetMode(big.AwayFromZero) - t.Set(&b.Val) - if _, acc := t.Int(&a.Val); acc == big.Exact { - return true - } - - a.Ovf = false - return false -} - -func (a *Mpint) Add(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint Add") - } - a.SetOverflow() - return - } - - a.Val.Add(&a.Val, &b.Val) - - if a.checkOverflow(0) { - yyerror("constant addition overflow") - } -} - -func (a *Mpint) Sub(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint Sub") - } - a.SetOverflow() - return - } - - a.Val.Sub(&a.Val, &b.Val) - - if a.checkOverflow(0) { - yyerror("constant subtraction overflow") - } -} - -func (a *Mpint) Mul(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint Mul") - } - a.SetOverflow() - return - } - - a.Val.Mul(&a.Val, &b.Val) - - if a.checkOverflow(0) { - yyerror("constant multiplication overflow") - } -} - -func (a *Mpint) Quo(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint Quo") - } - a.SetOverflow() - return - } - - a.Val.Quo(&a.Val, &b.Val) - - if a.checkOverflow(0) { - // can only happen for div-0 which should be checked elsewhere - yyerror("constant division overflow") - } -} - -func (a *Mpint) Rem(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint Rem") - } - a.SetOverflow() - return - } - - a.Val.Rem(&a.Val, &b.Val) - - if a.checkOverflow(0) { - // should never happen - yyerror("constant modulo overflow") - } -} - -func (a *Mpint) Or(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint Or") - } - a.SetOverflow() - return - } - - a.Val.Or(&a.Val, &b.Val) -} - -func (a *Mpint) And(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint And") - } - a.SetOverflow() - return - } - - a.Val.And(&a.Val, &b.Val) -} - -func (a *Mpint) AndNot(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint AndNot") - } - a.SetOverflow() - return - } - - a.Val.AndNot(&a.Val, &b.Val) -} - -func (a *Mpint) Xor(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint Xor") - } - a.SetOverflow() - return - } - - a.Val.Xor(&a.Val, &b.Val) -} - -func (a *Mpint) Lsh(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint Lsh") - } - a.SetOverflow() - return - } - - s := b.Int64() - if s < 0 || s >= Mpprec { - msg := "shift count too large" - if s < 0 { - msg = "invalid negative shift count" - } - yyerror("%s: %d", msg, s) - a.SetInt64(0) - return - } - - if a.checkOverflow(int(s)) { - yyerror("constant shift overflow") - return - } - a.Val.Lsh(&a.Val, uint(s)) -} - -func (a *Mpint) Rsh(b *Mpint) { - if a.Ovf || b.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("ovf in Mpint Rsh") - } - a.SetOverflow() - return - } - - s := b.Int64() - if s < 0 { - yyerror("invalid negative shift count: %d", s) - if a.Val.Sign() < 0 { - a.SetInt64(-1) - } else { - a.SetInt64(0) - } - return - } - - a.Val.Rsh(&a.Val, uint(s)) -} - -func (a *Mpint) Cmp(b *Mpint) int { - return a.Val.Cmp(&b.Val) -} - -func (a *Mpint) CmpInt64(c int64) int { - if c == 0 { - return a.Val.Sign() // common case shortcut - } - return a.Val.Cmp(big.NewInt(c)) -} - -func (a *Mpint) Neg() { - a.Val.Neg(&a.Val) -} - -func (a *Mpint) Int64() int64 { - if a.Ovf { - if nsavederrors+nerrors == 0 { - Fatalf("constant overflow") - } - return 0 - } - - return a.Val.Int64() -} - -func (a *Mpint) SetInt64(c int64) { - a.Val.SetInt64(c) -} - -func (a *Mpint) SetString(as string) { - _, ok := a.Val.SetString(as, 0) - if !ok { - // The lexer checks for correct syntax of the literal - // and reports detailed errors. Thus SetString should - // never fail (in theory it might run out of memory, - // but that wouldn't be reported as an error here). - Fatalf("malformed integer constant: %s", as) - return - } - if a.checkOverflow(0) { - yyerror("constant too large: %s", as) - } -} - -func (a *Mpint) GoString() string { - return a.Val.String() -} - -func (a *Mpint) String() string { - return fmt.Sprintf("%#x", &a.Val) -} diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go deleted file mode 100644 index 7494c3ef6b1b7b2a81017f0db08eaae630f8bb2a..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/noder.go +++ /dev/null @@ -1,1756 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strconv" - "strings" - "unicode" - "unicode/utf8" - - "cmd/compile/internal/syntax" - "cmd/compile/internal/types" - "cmd/internal/obj" - "cmd/internal/objabi" - "cmd/internal/src" -) - -// parseFiles concurrently parses files into *syntax.File structures. -// Each declaration in every *syntax.File is converted to a syntax tree -// and its root represented by *Node is appended to xtop. -// Returns the total count of parsed lines. -func parseFiles(filenames []string) uint { - noders := make([]*noder, 0, len(filenames)) - // Limit the number of simultaneously open files. - sem := make(chan struct{}, runtime.GOMAXPROCS(0)+10) - - for _, filename := range filenames { - p := &noder{ - basemap: make(map[*syntax.PosBase]*src.PosBase), - err: make(chan syntax.Error), - } - noders = append(noders, p) - - go func(filename string) { - sem <- struct{}{} - defer func() { <-sem }() - defer close(p.err) - base := syntax.NewFileBase(filename) - - f, err := os.Open(filename) - if err != nil { - p.error(syntax.Error{Msg: err.Error()}) - return - } - defer f.Close() - - p.file, _ = syntax.Parse(base, f, p.error, p.pragma, syntax.CheckBranches) // errors are tracked via p.error - }(filename) - } - - var lines uint - for _, p := range noders { - for e := range p.err { - p.yyerrorpos(e.Pos, "%s", e.Msg) - } - - p.node() - lines += p.file.Lines - p.file = nil // release memory - - if nsyntaxerrors != 0 { - errorexit() - } - // Always run testdclstack here, even when debug_dclstack is not set, as a sanity measure. - testdclstack() - } - - localpkg.Height = myheight - - return lines -} - -// makeSrcPosBase translates from a *syntax.PosBase to a *src.PosBase. -func (p *noder) makeSrcPosBase(b0 *syntax.PosBase) *src.PosBase { - // fast path: most likely PosBase hasn't changed - if p.basecache.last == b0 { - return p.basecache.base - } - - b1, ok := p.basemap[b0] - if !ok { - fn := b0.Filename() - if b0.IsFileBase() { - b1 = src.NewFileBase(fn, absFilename(fn)) - } else { - // line directive base - p0 := b0.Pos() - p0b := p0.Base() - if p0b == b0 { - panic("infinite recursion in makeSrcPosBase") - } - p1 := src.MakePos(p.makeSrcPosBase(p0b), p0.Line(), p0.Col()) - b1 = src.NewLinePragmaBase(p1, fn, fileh(fn), b0.Line(), b0.Col()) - } - p.basemap[b0] = b1 - } - - // update cache - p.basecache.last = b0 - p.basecache.base = b1 - - return b1 -} - -func (p *noder) makeXPos(pos syntax.Pos) (_ src.XPos) { - return Ctxt.PosTable.XPos(src.MakePos(p.makeSrcPosBase(pos.Base()), pos.Line(), pos.Col())) -} - -func (p *noder) yyerrorpos(pos syntax.Pos, format string, args ...interface{}) { - yyerrorl(p.makeXPos(pos), format, args...) -} - -var pathPrefix string - -// TODO(gri) Can we eliminate fileh in favor of absFilename? -func fileh(name string) string { - return objabi.AbsFile("", name, pathPrefix) -} - -func absFilename(name string) string { - return objabi.AbsFile(Ctxt.Pathname, name, pathPrefix) -} - -// noder transforms package syntax's AST into a Node tree. -type noder struct { - basemap map[*syntax.PosBase]*src.PosBase - basecache struct { - last *syntax.PosBase - base *src.PosBase - } - - file *syntax.File - linknames []linkname - pragcgobuf [][]string - err chan syntax.Error - scope ScopeID - importedUnsafe bool - importedEmbed bool - - // scopeVars is a stack tracking the number of variables declared in the - // current function at the moment each open scope was opened. - scopeVars []int - - lastCloseScopePos syntax.Pos -} - -func (p *noder) funcBody(fn *Node, block *syntax.BlockStmt) { - oldScope := p.scope - p.scope = 0 - funchdr(fn) - - if block != nil { - body := p.stmts(block.List) - if body == nil { - body = []*Node{nod(OEMPTY, nil, nil)} - } - fn.Nbody.Set(body) - - lineno = p.makeXPos(block.Rbrace) - fn.Func.Endlineno = lineno - } - - funcbody() - p.scope = oldScope -} - -func (p *noder) openScope(pos syntax.Pos) { - types.Markdcl() - - if trackScopes { - Curfn.Func.Parents = append(Curfn.Func.Parents, p.scope) - p.scopeVars = append(p.scopeVars, len(Curfn.Func.Dcl)) - p.scope = ScopeID(len(Curfn.Func.Parents)) - - p.markScope(pos) - } -} - -func (p *noder) closeScope(pos syntax.Pos) { - p.lastCloseScopePos = pos - types.Popdcl() - - if trackScopes { - scopeVars := p.scopeVars[len(p.scopeVars)-1] - p.scopeVars = p.scopeVars[:len(p.scopeVars)-1] - if scopeVars == len(Curfn.Func.Dcl) { - // no variables were declared in this scope, so we can retract it. - - if int(p.scope) != len(Curfn.Func.Parents) { - Fatalf("scope tracking inconsistency, no variables declared but scopes were not retracted") - } - - p.scope = Curfn.Func.Parents[p.scope-1] - Curfn.Func.Parents = Curfn.Func.Parents[:len(Curfn.Func.Parents)-1] - - nmarks := len(Curfn.Func.Marks) - Curfn.Func.Marks[nmarks-1].Scope = p.scope - prevScope := ScopeID(0) - if nmarks >= 2 { - prevScope = Curfn.Func.Marks[nmarks-2].Scope - } - if Curfn.Func.Marks[nmarks-1].Scope == prevScope { - Curfn.Func.Marks = Curfn.Func.Marks[:nmarks-1] - } - return - } - - p.scope = Curfn.Func.Parents[p.scope-1] - - p.markScope(pos) - } -} - -func (p *noder) markScope(pos syntax.Pos) { - xpos := p.makeXPos(pos) - if i := len(Curfn.Func.Marks); i > 0 && Curfn.Func.Marks[i-1].Pos == xpos { - Curfn.Func.Marks[i-1].Scope = p.scope - } else { - Curfn.Func.Marks = append(Curfn.Func.Marks, Mark{xpos, p.scope}) - } -} - -// closeAnotherScope is like closeScope, but it reuses the same mark -// position as the last closeScope call. This is useful for "for" and -// "if" statements, as their implicit blocks always end at the same -// position as an explicit block. -func (p *noder) closeAnotherScope() { - p.closeScope(p.lastCloseScopePos) -} - -// linkname records a //go:linkname directive. -type linkname struct { - pos syntax.Pos - local string - remote string -} - -func (p *noder) node() { - types.Block = 1 - p.importedUnsafe = false - p.importedEmbed = false - - p.setlineno(p.file.PkgName) - mkpackage(p.file.PkgName.Value) - - if pragma, ok := p.file.Pragma.(*Pragma); ok { - pragma.Flag &^= GoBuildPragma - p.checkUnused(pragma) - } - - xtop = append(xtop, p.decls(p.file.DeclList)...) - - for _, n := range p.linknames { - if !p.importedUnsafe { - p.yyerrorpos(n.pos, "//go:linkname only allowed in Go files that import \"unsafe\"") - continue - } - s := lookup(n.local) - if n.remote != "" { - s.Linkname = n.remote - } else { - // Use the default object symbol name if the - // user didn't provide one. - if myimportpath == "" { - p.yyerrorpos(n.pos, "//go:linkname requires linkname argument or -p compiler flag") - } else { - s.Linkname = objabi.PathToPrefix(myimportpath) + "." + n.local - } - } - } - - // The linker expects an ABI0 wrapper for all cgo-exported - // functions. - for _, prag := range p.pragcgobuf { - switch prag[0] { - case "cgo_export_static", "cgo_export_dynamic": - if symabiRefs == nil { - symabiRefs = make(map[string]obj.ABI) - } - symabiRefs[prag[1]] = obj.ABI0 - } - } - - pragcgobuf = append(pragcgobuf, p.pragcgobuf...) - lineno = src.NoXPos - clearImports() -} - -func (p *noder) decls(decls []syntax.Decl) (l []*Node) { - var cs constState - - for _, decl := range decls { - p.setlineno(decl) - switch decl := decl.(type) { - case *syntax.ImportDecl: - p.importDecl(decl) - - case *syntax.VarDecl: - l = append(l, p.varDecl(decl)...) - - case *syntax.ConstDecl: - l = append(l, p.constDecl(decl, &cs)...) - - case *syntax.TypeDecl: - l = append(l, p.typeDecl(decl)) - - case *syntax.FuncDecl: - l = append(l, p.funcDecl(decl)) - - default: - panic("unhandled Decl") - } - } - - return -} - -func (p *noder) importDecl(imp *syntax.ImportDecl) { - if imp.Path.Bad { - return // avoid follow-on errors if there was a syntax error - } - - if pragma, ok := imp.Pragma.(*Pragma); ok { - p.checkUnused(pragma) - } - - val := p.basicLit(imp.Path) - ipkg := importfile(&val) - if ipkg == nil { - if nerrors == 0 { - Fatalf("phase error in import") - } - return - } - - if ipkg == unsafepkg { - p.importedUnsafe = true - } - if ipkg.Path == "embed" { - p.importedEmbed = true - } - - ipkg.Direct = true - - var my *types.Sym - if imp.LocalPkgName != nil { - my = p.name(imp.LocalPkgName) - } else { - my = lookup(ipkg.Name) - } - - pack := p.nod(imp, OPACK, nil, nil) - pack.Sym = my - pack.Name.Pkg = ipkg - - switch my.Name { - case ".": - importdot(ipkg, pack) - return - case "init": - yyerrorl(pack.Pos, "cannot import package as init - init must be a func") - return - case "_": - return - } - if my.Def != nil { - redeclare(pack.Pos, my, "as imported package name") - } - my.Def = asTypesNode(pack) - my.Lastlineno = pack.Pos - my.Block = 1 // at top level -} - -func (p *noder) varDecl(decl *syntax.VarDecl) []*Node { - names := p.declNames(decl.NameList) - typ := p.typeExprOrNil(decl.Type) - - var exprs []*Node - if decl.Values != nil { - exprs = p.exprList(decl.Values) - } - - if pragma, ok := decl.Pragma.(*Pragma); ok { - if len(pragma.Embeds) > 0 { - if !p.importedEmbed { - // This check can't be done when building the list pragma.Embeds - // because that list is created before the noder starts walking over the file, - // so at that point it hasn't seen the imports. - // We're left to check now, just before applying the //go:embed lines. - for _, e := range pragma.Embeds { - p.yyerrorpos(e.Pos, "//go:embed only allowed in Go files that import \"embed\"") - } - } else { - varEmbed(p, names, typ, exprs, pragma.Embeds) - } - pragma.Embeds = nil - } - p.checkUnused(pragma) - } - - p.setlineno(decl) - return variter(names, typ, exprs) -} - -// constState tracks state between constant specifiers within a -// declaration group. This state is kept separate from noder so nested -// constant declarations are handled correctly (e.g., issue 15550). -type constState struct { - group *syntax.Group - typ *Node - values []*Node - iota int64 -} - -func (p *noder) constDecl(decl *syntax.ConstDecl, cs *constState) []*Node { - if decl.Group == nil || decl.Group != cs.group { - *cs = constState{ - group: decl.Group, - } - } - - if pragma, ok := decl.Pragma.(*Pragma); ok { - p.checkUnused(pragma) - } - - names := p.declNames(decl.NameList) - typ := p.typeExprOrNil(decl.Type) - - var values []*Node - if decl.Values != nil { - values = p.exprList(decl.Values) - cs.typ, cs.values = typ, values - } else { - if typ != nil { - yyerror("const declaration cannot have type without expression") - } - typ, values = cs.typ, cs.values - } - - nn := make([]*Node, 0, len(names)) - for i, n := range names { - if i >= len(values) { - yyerror("missing value in const declaration") - break - } - v := values[i] - if decl.Values == nil { - v = treecopy(v, n.Pos) - } - - n.Op = OLITERAL - declare(n, dclcontext) - - n.Name.Param.Ntype = typ - n.Name.Defn = v - n.SetIota(cs.iota) - - nn = append(nn, p.nod(decl, ODCLCONST, n, nil)) - } - - if len(values) > len(names) { - yyerror("extra expression in const declaration") - } - - cs.iota++ - - return nn -} - -func (p *noder) typeDecl(decl *syntax.TypeDecl) *Node { - n := p.declName(decl.Name) - n.Op = OTYPE - declare(n, dclcontext) - - // decl.Type may be nil but in that case we got a syntax error during parsing - typ := p.typeExprOrNil(decl.Type) - - param := n.Name.Param - param.Ntype = typ - param.SetAlias(decl.Alias) - if pragma, ok := decl.Pragma.(*Pragma); ok { - if !decl.Alias { - param.SetPragma(pragma.Flag & TypePragmas) - pragma.Flag &^= TypePragmas - } - p.checkUnused(pragma) - } - - nod := p.nod(decl, ODCLTYPE, n, nil) - if param.Alias() && !langSupported(1, 9, localpkg) { - yyerrorl(nod.Pos, "type aliases only supported as of -lang=go1.9") - } - return nod -} - -func (p *noder) declNames(names []*syntax.Name) []*Node { - nodes := make([]*Node, 0, len(names)) - for _, name := range names { - nodes = append(nodes, p.declName(name)) - } - return nodes -} - -func (p *noder) declName(name *syntax.Name) *Node { - n := dclname(p.name(name)) - n.Pos = p.pos(name) - return n -} - -func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node { - name := p.name(fun.Name) - t := p.signature(fun.Recv, fun.Type) - f := p.nod(fun, ODCLFUNC, nil, nil) - - if fun.Recv == nil { - if name.Name == "init" { - name = renameinit() - if t.List.Len() > 0 || t.Rlist.Len() > 0 { - yyerrorl(f.Pos, "func init must have no arguments and no return values") - } - } - - if localpkg.Name == "main" && name.Name == "main" { - if t.List.Len() > 0 || t.Rlist.Len() > 0 { - yyerrorl(f.Pos, "func main must have no arguments and no return values") - } - } - } else { - f.Func.Shortname = name - name = nblank.Sym // filled in by typecheckfunc - } - - f.Func.Nname = newfuncnamel(p.pos(fun.Name), name) - f.Func.Nname.Name.Defn = f - f.Func.Nname.Name.Param.Ntype = t - - if pragma, ok := fun.Pragma.(*Pragma); ok { - f.Func.Pragma = pragma.Flag & FuncPragmas - if pragma.Flag&Systemstack != 0 && pragma.Flag&Nosplit != 0 { - yyerrorl(f.Pos, "go:nosplit and go:systemstack cannot be combined") - } - pragma.Flag &^= FuncPragmas - p.checkUnused(pragma) - } - - if fun.Recv == nil { - declare(f.Func.Nname, PFUNC) - } - - p.funcBody(f, fun.Body) - - if fun.Body != nil { - if f.Func.Pragma&Noescape != 0 { - yyerrorl(f.Pos, "can only use //go:noescape with external func implementations") - } - } else { - if pure_go || strings.HasPrefix(f.funcname(), "init.") { - // Linknamed functions are allowed to have no body. Hopefully - // the linkname target has a body. See issue 23311. - isLinknamed := false - for _, n := range p.linknames { - if f.funcname() == n.local { - isLinknamed = true - break - } - } - if !isLinknamed { - yyerrorl(f.Pos, "missing function body") - } - } - } - - return f -} - -func (p *noder) signature(recv *syntax.Field, typ *syntax.FuncType) *Node { - n := p.nod(typ, OTFUNC, nil, nil) - if recv != nil { - n.Left = p.param(recv, false, false) - } - n.List.Set(p.params(typ.ParamList, true)) - n.Rlist.Set(p.params(typ.ResultList, false)) - return n -} - -func (p *noder) params(params []*syntax.Field, dddOk bool) []*Node { - nodes := make([]*Node, 0, len(params)) - for i, param := range params { - p.setlineno(param) - nodes = append(nodes, p.param(param, dddOk, i+1 == len(params))) - } - return nodes -} - -func (p *noder) param(param *syntax.Field, dddOk, final bool) *Node { - var name *types.Sym - if param.Name != nil { - name = p.name(param.Name) - } - - typ := p.typeExpr(param.Type) - n := p.nodSym(param, ODCLFIELD, typ, name) - - // rewrite ...T parameter - if typ.Op == ODDD { - if !dddOk { - // We mark these as syntax errors to get automatic elimination - // of multiple such errors per line (see yyerrorl in subr.go). - yyerror("syntax error: cannot use ... in receiver or result parameter list") - } else if !final { - if param.Name == nil { - yyerror("syntax error: cannot use ... with non-final parameter") - } else { - p.yyerrorpos(param.Name.Pos(), "syntax error: cannot use ... with non-final parameter %s", param.Name.Value) - } - } - typ.Op = OTARRAY - typ.Right = typ.Left - typ.Left = nil - n.SetIsDDD(true) - if n.Left != nil { - n.Left.SetIsDDD(true) - } - } - - return n -} - -func (p *noder) exprList(expr syntax.Expr) []*Node { - if list, ok := expr.(*syntax.ListExpr); ok { - return p.exprs(list.ElemList) - } - return []*Node{p.expr(expr)} -} - -func (p *noder) exprs(exprs []syntax.Expr) []*Node { - nodes := make([]*Node, 0, len(exprs)) - for _, expr := range exprs { - nodes = append(nodes, p.expr(expr)) - } - return nodes -} - -func (p *noder) expr(expr syntax.Expr) *Node { - p.setlineno(expr) - switch expr := expr.(type) { - case nil, *syntax.BadExpr: - return nil - case *syntax.Name: - return p.mkname(expr) - case *syntax.BasicLit: - n := nodlit(p.basicLit(expr)) - n.SetDiag(expr.Bad) // avoid follow-on errors if there was a syntax error - return n - case *syntax.CompositeLit: - n := p.nod(expr, OCOMPLIT, nil, nil) - if expr.Type != nil { - n.Right = p.expr(expr.Type) - } - l := p.exprs(expr.ElemList) - for i, e := range l { - l[i] = p.wrapname(expr.ElemList[i], e) - } - n.List.Set(l) - lineno = p.makeXPos(expr.Rbrace) - return n - case *syntax.KeyValueExpr: - // use position of expr.Key rather than of expr (which has position of ':') - return p.nod(expr.Key, OKEY, p.expr(expr.Key), p.wrapname(expr.Value, p.expr(expr.Value))) - case *syntax.FuncLit: - return p.funcLit(expr) - case *syntax.ParenExpr: - return p.nod(expr, OPAREN, p.expr(expr.X), nil) - case *syntax.SelectorExpr: - // parser.new_dotname - obj := p.expr(expr.X) - if obj.Op == OPACK { - obj.Name.SetUsed(true) - return importName(obj.Name.Pkg.Lookup(expr.Sel.Value)) - } - n := nodSym(OXDOT, obj, p.name(expr.Sel)) - n.Pos = p.pos(expr) // lineno may have been changed by p.expr(expr.X) - return n - case *syntax.IndexExpr: - return p.nod(expr, OINDEX, p.expr(expr.X), p.expr(expr.Index)) - case *syntax.SliceExpr: - op := OSLICE - if expr.Full { - op = OSLICE3 - } - n := p.nod(expr, op, p.expr(expr.X), nil) - var index [3]*Node - for i, x := range &expr.Index { - if x != nil { - index[i] = p.expr(x) - } - } - n.SetSliceBounds(index[0], index[1], index[2]) - return n - case *syntax.AssertExpr: - return p.nod(expr, ODOTTYPE, p.expr(expr.X), p.typeExpr(expr.Type)) - case *syntax.Operation: - if expr.Op == syntax.Add && expr.Y != nil { - return p.sum(expr) - } - x := p.expr(expr.X) - if expr.Y == nil { - return p.nod(expr, p.unOp(expr.Op), x, nil) - } - return p.nod(expr, p.binOp(expr.Op), x, p.expr(expr.Y)) - case *syntax.CallExpr: - n := p.nod(expr, OCALL, p.expr(expr.Fun), nil) - n.List.Set(p.exprs(expr.ArgList)) - n.SetIsDDD(expr.HasDots) - return n - - case *syntax.ArrayType: - var len *Node - if expr.Len != nil { - len = p.expr(expr.Len) - } else { - len = p.nod(expr, ODDD, nil, nil) - } - return p.nod(expr, OTARRAY, len, p.typeExpr(expr.Elem)) - case *syntax.SliceType: - return p.nod(expr, OTARRAY, nil, p.typeExpr(expr.Elem)) - case *syntax.DotsType: - return p.nod(expr, ODDD, p.typeExpr(expr.Elem), nil) - case *syntax.StructType: - return p.structType(expr) - case *syntax.InterfaceType: - return p.interfaceType(expr) - case *syntax.FuncType: - return p.signature(nil, expr) - case *syntax.MapType: - return p.nod(expr, OTMAP, p.typeExpr(expr.Key), p.typeExpr(expr.Value)) - case *syntax.ChanType: - n := p.nod(expr, OTCHAN, p.typeExpr(expr.Elem), nil) - n.SetTChanDir(p.chanDir(expr.Dir)) - return n - - case *syntax.TypeSwitchGuard: - n := p.nod(expr, OTYPESW, nil, p.expr(expr.X)) - if expr.Lhs != nil { - n.Left = p.declName(expr.Lhs) - if n.Left.isBlank() { - yyerror("invalid variable name %v in type switch", n.Left) - } - } - return n - } - panic("unhandled Expr") -} - -// sum efficiently handles very large summation expressions (such as -// in issue #16394). In particular, it avoids left recursion and -// collapses string literals. -func (p *noder) sum(x syntax.Expr) *Node { - // While we need to handle long sums with asymptotic - // efficiency, the vast majority of sums are very small: ~95% - // have only 2 or 3 operands, and ~99% of string literals are - // never concatenated. - - adds := make([]*syntax.Operation, 0, 2) - for { - add, ok := x.(*syntax.Operation) - if !ok || add.Op != syntax.Add || add.Y == nil { - break - } - adds = append(adds, add) - x = add.X - } - - // nstr is the current rightmost string literal in the - // summation (if any), and chunks holds its accumulated - // substrings. - // - // Consider the expression x + "a" + "b" + "c" + y. When we - // reach the string literal "a", we assign nstr to point to - // its corresponding Node and initialize chunks to {"a"}. - // Visiting the subsequent string literals "b" and "c", we - // simply append their values to chunks. Finally, when we - // reach the non-constant operand y, we'll join chunks to form - // "abc" and reassign the "a" string literal's value. - // - // N.B., we need to be careful about named string constants - // (indicated by Sym != nil) because 1) we can't modify their - // value, as doing so would affect other uses of the string - // constant, and 2) they may have types, which we need to - // handle correctly. For now, we avoid these problems by - // treating named string constants the same as non-constant - // operands. - var nstr *Node - chunks := make([]string, 0, 1) - - n := p.expr(x) - if Isconst(n, CTSTR) && n.Sym == nil { - nstr = n - chunks = append(chunks, nstr.StringVal()) - } - - for i := len(adds) - 1; i >= 0; i-- { - add := adds[i] - - r := p.expr(add.Y) - if Isconst(r, CTSTR) && r.Sym == nil { - if nstr != nil { - // Collapse r into nstr instead of adding to n. - chunks = append(chunks, r.StringVal()) - continue - } - - nstr = r - chunks = append(chunks, nstr.StringVal()) - } else { - if len(chunks) > 1 { - nstr.SetVal(Val{U: strings.Join(chunks, "")}) - } - nstr = nil - chunks = chunks[:0] - } - n = p.nod(add, OADD, n, r) - } - if len(chunks) > 1 { - nstr.SetVal(Val{U: strings.Join(chunks, "")}) - } - - return n -} - -func (p *noder) typeExpr(typ syntax.Expr) *Node { - // TODO(mdempsky): Be stricter? typecheck should handle errors anyway. - return p.expr(typ) -} - -func (p *noder) typeExprOrNil(typ syntax.Expr) *Node { - if typ != nil { - return p.expr(typ) - } - return nil -} - -func (p *noder) chanDir(dir syntax.ChanDir) types.ChanDir { - switch dir { - case 0: - return types.Cboth - case syntax.SendOnly: - return types.Csend - case syntax.RecvOnly: - return types.Crecv - } - panic("unhandled ChanDir") -} - -func (p *noder) structType(expr *syntax.StructType) *Node { - l := make([]*Node, 0, len(expr.FieldList)) - for i, field := range expr.FieldList { - p.setlineno(field) - var n *Node - if field.Name == nil { - n = p.embedded(field.Type) - } else { - n = p.nodSym(field, ODCLFIELD, p.typeExpr(field.Type), p.name(field.Name)) - } - if i < len(expr.TagList) && expr.TagList[i] != nil { - n.SetVal(p.basicLit(expr.TagList[i])) - } - l = append(l, n) - } - - p.setlineno(expr) - n := p.nod(expr, OTSTRUCT, nil, nil) - n.List.Set(l) - return n -} - -func (p *noder) interfaceType(expr *syntax.InterfaceType) *Node { - l := make([]*Node, 0, len(expr.MethodList)) - for _, method := range expr.MethodList { - p.setlineno(method) - var n *Node - if method.Name == nil { - n = p.nodSym(method, ODCLFIELD, importName(p.packname(method.Type)), nil) - } else { - mname := p.name(method.Name) - sig := p.typeExpr(method.Type) - sig.Left = fakeRecv() - n = p.nodSym(method, ODCLFIELD, sig, mname) - ifacedcl(n) - } - l = append(l, n) - } - - n := p.nod(expr, OTINTER, nil, nil) - n.List.Set(l) - return n -} - -func (p *noder) packname(expr syntax.Expr) *types.Sym { - switch expr := expr.(type) { - case *syntax.Name: - name := p.name(expr) - if n := oldname(name); n.Name != nil && n.Name.Pack != nil { - n.Name.Pack.Name.SetUsed(true) - } - return name - case *syntax.SelectorExpr: - name := p.name(expr.X.(*syntax.Name)) - def := asNode(name.Def) - if def == nil { - yyerror("undefined: %v", name) - return name - } - var pkg *types.Pkg - if def.Op != OPACK { - yyerror("%v is not a package", name) - pkg = localpkg - } else { - def.Name.SetUsed(true) - pkg = def.Name.Pkg - } - return pkg.Lookup(expr.Sel.Value) - } - panic(fmt.Sprintf("unexpected packname: %#v", expr)) -} - -func (p *noder) embedded(typ syntax.Expr) *Node { - op, isStar := typ.(*syntax.Operation) - if isStar { - if op.Op != syntax.Mul || op.Y != nil { - panic("unexpected Operation") - } - typ = op.X - } - - sym := p.packname(typ) - n := p.nodSym(typ, ODCLFIELD, importName(sym), lookup(sym.Name)) - n.SetEmbedded(true) - - if isStar { - n.Left = p.nod(op, ODEREF, n.Left, nil) - } - return n -} - -func (p *noder) stmts(stmts []syntax.Stmt) []*Node { - return p.stmtsFall(stmts, false) -} - -func (p *noder) stmtsFall(stmts []syntax.Stmt, fallOK bool) []*Node { - var nodes []*Node - for i, stmt := range stmts { - s := p.stmtFall(stmt, fallOK && i+1 == len(stmts)) - if s == nil { - } else if s.Op == OBLOCK && s.Ninit.Len() == 0 { - nodes = append(nodes, s.List.Slice()...) - } else { - nodes = append(nodes, s) - } - } - return nodes -} - -func (p *noder) stmt(stmt syntax.Stmt) *Node { - return p.stmtFall(stmt, false) -} - -func (p *noder) stmtFall(stmt syntax.Stmt, fallOK bool) *Node { - p.setlineno(stmt) - switch stmt := stmt.(type) { - case *syntax.EmptyStmt: - return nil - case *syntax.LabeledStmt: - return p.labeledStmt(stmt, fallOK) - case *syntax.BlockStmt: - l := p.blockStmt(stmt) - if len(l) == 0 { - // TODO(mdempsky): Line number? - return nod(OEMPTY, nil, nil) - } - return liststmt(l) - case *syntax.ExprStmt: - return p.wrapname(stmt, p.expr(stmt.X)) - case *syntax.SendStmt: - return p.nod(stmt, OSEND, p.expr(stmt.Chan), p.expr(stmt.Value)) - case *syntax.DeclStmt: - return liststmt(p.decls(stmt.DeclList)) - case *syntax.AssignStmt: - if stmt.Op != 0 && stmt.Op != syntax.Def { - n := p.nod(stmt, OASOP, p.expr(stmt.Lhs), p.expr(stmt.Rhs)) - n.SetImplicit(stmt.Rhs == syntax.ImplicitOne) - n.SetSubOp(p.binOp(stmt.Op)) - return n - } - - n := p.nod(stmt, OAS, nil, nil) // assume common case - - rhs := p.exprList(stmt.Rhs) - lhs := p.assignList(stmt.Lhs, n, stmt.Op == syntax.Def) - - if len(lhs) == 1 && len(rhs) == 1 { - // common case - n.Left = lhs[0] - n.Right = rhs[0] - } else { - n.Op = OAS2 - n.List.Set(lhs) - n.Rlist.Set(rhs) - } - return n - - case *syntax.BranchStmt: - var op Op - switch stmt.Tok { - case syntax.Break: - op = OBREAK - case syntax.Continue: - op = OCONTINUE - case syntax.Fallthrough: - if !fallOK { - yyerror("fallthrough statement out of place") - } - op = OFALL - case syntax.Goto: - op = OGOTO - default: - panic("unhandled BranchStmt") - } - n := p.nod(stmt, op, nil, nil) - if stmt.Label != nil { - n.Sym = p.name(stmt.Label) - } - return n - case *syntax.CallStmt: - var op Op - switch stmt.Tok { - case syntax.Defer: - op = ODEFER - case syntax.Go: - op = OGO - default: - panic("unhandled CallStmt") - } - return p.nod(stmt, op, p.expr(stmt.Call), nil) - case *syntax.ReturnStmt: - var results []*Node - if stmt.Results != nil { - results = p.exprList(stmt.Results) - } - n := p.nod(stmt, ORETURN, nil, nil) - n.List.Set(results) - if n.List.Len() == 0 && Curfn != nil { - for _, ln := range Curfn.Func.Dcl { - if ln.Class() == PPARAM { - continue - } - if ln.Class() != PPARAMOUT { - break - } - if asNode(ln.Sym.Def) != ln { - yyerror("%s is shadowed during return", ln.Sym.Name) - } - } - } - return n - case *syntax.IfStmt: - return p.ifStmt(stmt) - case *syntax.ForStmt: - return p.forStmt(stmt) - case *syntax.SwitchStmt: - return p.switchStmt(stmt) - case *syntax.SelectStmt: - return p.selectStmt(stmt) - } - panic("unhandled Stmt") -} - -func (p *noder) assignList(expr syntax.Expr, defn *Node, colas bool) []*Node { - if !colas { - return p.exprList(expr) - } - - defn.SetColas(true) - - var exprs []syntax.Expr - if list, ok := expr.(*syntax.ListExpr); ok { - exprs = list.ElemList - } else { - exprs = []syntax.Expr{expr} - } - - res := make([]*Node, len(exprs)) - seen := make(map[*types.Sym]bool, len(exprs)) - - newOrErr := false - for i, expr := range exprs { - p.setlineno(expr) - res[i] = nblank - - name, ok := expr.(*syntax.Name) - if !ok { - p.yyerrorpos(expr.Pos(), "non-name %v on left side of :=", p.expr(expr)) - newOrErr = true - continue - } - - sym := p.name(name) - if sym.IsBlank() { - continue - } - - if seen[sym] { - p.yyerrorpos(expr.Pos(), "%v repeated on left side of :=", sym) - newOrErr = true - continue - } - seen[sym] = true - - if sym.Block == types.Block { - res[i] = oldname(sym) - continue - } - - newOrErr = true - n := newname(sym) - declare(n, dclcontext) - n.Name.Defn = defn - defn.Ninit.Append(nod(ODCL, n, nil)) - res[i] = n - } - - if !newOrErr { - yyerrorl(defn.Pos, "no new variables on left side of :=") - } - return res -} - -func (p *noder) blockStmt(stmt *syntax.BlockStmt) []*Node { - p.openScope(stmt.Pos()) - nodes := p.stmts(stmt.List) - p.closeScope(stmt.Rbrace) - return nodes -} - -func (p *noder) ifStmt(stmt *syntax.IfStmt) *Node { - p.openScope(stmt.Pos()) - n := p.nod(stmt, OIF, nil, nil) - if stmt.Init != nil { - n.Ninit.Set1(p.stmt(stmt.Init)) - } - if stmt.Cond != nil { - n.Left = p.expr(stmt.Cond) - } - n.Nbody.Set(p.blockStmt(stmt.Then)) - if stmt.Else != nil { - e := p.stmt(stmt.Else) - if e.Op == OBLOCK && e.Ninit.Len() == 0 { - n.Rlist.Set(e.List.Slice()) - } else { - n.Rlist.Set1(e) - } - } - p.closeAnotherScope() - return n -} - -func (p *noder) forStmt(stmt *syntax.ForStmt) *Node { - p.openScope(stmt.Pos()) - var n *Node - if r, ok := stmt.Init.(*syntax.RangeClause); ok { - if stmt.Cond != nil || stmt.Post != nil { - panic("unexpected RangeClause") - } - - n = p.nod(r, ORANGE, nil, p.expr(r.X)) - if r.Lhs != nil { - n.List.Set(p.assignList(r.Lhs, n, r.Def)) - } - } else { - n = p.nod(stmt, OFOR, nil, nil) - if stmt.Init != nil { - n.Ninit.Set1(p.stmt(stmt.Init)) - } - if stmt.Cond != nil { - n.Left = p.expr(stmt.Cond) - } - if stmt.Post != nil { - n.Right = p.stmt(stmt.Post) - } - } - n.Nbody.Set(p.blockStmt(stmt.Body)) - p.closeAnotherScope() - return n -} - -func (p *noder) switchStmt(stmt *syntax.SwitchStmt) *Node { - p.openScope(stmt.Pos()) - n := p.nod(stmt, OSWITCH, nil, nil) - if stmt.Init != nil { - n.Ninit.Set1(p.stmt(stmt.Init)) - } - if stmt.Tag != nil { - n.Left = p.expr(stmt.Tag) - } - - tswitch := n.Left - if tswitch != nil && tswitch.Op != OTYPESW { - tswitch = nil - } - n.List.Set(p.caseClauses(stmt.Body, tswitch, stmt.Rbrace)) - - p.closeScope(stmt.Rbrace) - return n -} - -func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *Node, rbrace syntax.Pos) []*Node { - nodes := make([]*Node, 0, len(clauses)) - for i, clause := range clauses { - p.setlineno(clause) - if i > 0 { - p.closeScope(clause.Pos()) - } - p.openScope(clause.Pos()) - - n := p.nod(clause, OCASE, nil, nil) - if clause.Cases != nil { - n.List.Set(p.exprList(clause.Cases)) - } - if tswitch != nil && tswitch.Left != nil { - nn := newname(tswitch.Left.Sym) - declare(nn, dclcontext) - n.Rlist.Set1(nn) - // keep track of the instances for reporting unused - nn.Name.Defn = tswitch - } - - // Trim trailing empty statements. We omit them from - // the Node AST anyway, and it's easier to identify - // out-of-place fallthrough statements without them. - body := clause.Body - for len(body) > 0 { - if _, ok := body[len(body)-1].(*syntax.EmptyStmt); !ok { - break - } - body = body[:len(body)-1] - } - - n.Nbody.Set(p.stmtsFall(body, true)) - if l := n.Nbody.Len(); l > 0 && n.Nbody.Index(l-1).Op == OFALL { - if tswitch != nil { - yyerror("cannot fallthrough in type switch") - } - if i+1 == len(clauses) { - yyerror("cannot fallthrough final case in switch") - } - } - - nodes = append(nodes, n) - } - if len(clauses) > 0 { - p.closeScope(rbrace) - } - return nodes -} - -func (p *noder) selectStmt(stmt *syntax.SelectStmt) *Node { - n := p.nod(stmt, OSELECT, nil, nil) - n.List.Set(p.commClauses(stmt.Body, stmt.Rbrace)) - return n -} - -func (p *noder) commClauses(clauses []*syntax.CommClause, rbrace syntax.Pos) []*Node { - nodes := make([]*Node, 0, len(clauses)) - for i, clause := range clauses { - p.setlineno(clause) - if i > 0 { - p.closeScope(clause.Pos()) - } - p.openScope(clause.Pos()) - - n := p.nod(clause, OCASE, nil, nil) - if clause.Comm != nil { - n.List.Set1(p.stmt(clause.Comm)) - } - n.Nbody.Set(p.stmts(clause.Body)) - nodes = append(nodes, n) - } - if len(clauses) > 0 { - p.closeScope(rbrace) - } - return nodes -} - -func (p *noder) labeledStmt(label *syntax.LabeledStmt, fallOK bool) *Node { - lhs := p.nodSym(label, OLABEL, nil, p.name(label.Label)) - - var ls *Node - if label.Stmt != nil { // TODO(mdempsky): Should always be present. - ls = p.stmtFall(label.Stmt, fallOK) - } - - lhs.Name.Defn = ls - l := []*Node{lhs} - if ls != nil { - if ls.Op == OBLOCK && ls.Ninit.Len() == 0 { - l = append(l, ls.List.Slice()...) - } else { - l = append(l, ls) - } - } - return liststmt(l) -} - -var unOps = [...]Op{ - syntax.Recv: ORECV, - syntax.Mul: ODEREF, - syntax.And: OADDR, - - syntax.Not: ONOT, - syntax.Xor: OBITNOT, - syntax.Add: OPLUS, - syntax.Sub: ONEG, -} - -func (p *noder) unOp(op syntax.Operator) Op { - if uint64(op) >= uint64(len(unOps)) || unOps[op] == 0 { - panic("invalid Operator") - } - return unOps[op] -} - -var binOps = [...]Op{ - syntax.OrOr: OOROR, - syntax.AndAnd: OANDAND, - - syntax.Eql: OEQ, - syntax.Neq: ONE, - syntax.Lss: OLT, - syntax.Leq: OLE, - syntax.Gtr: OGT, - syntax.Geq: OGE, - - syntax.Add: OADD, - syntax.Sub: OSUB, - syntax.Or: OOR, - syntax.Xor: OXOR, - - syntax.Mul: OMUL, - syntax.Div: ODIV, - syntax.Rem: OMOD, - syntax.And: OAND, - syntax.AndNot: OANDNOT, - syntax.Shl: OLSH, - syntax.Shr: ORSH, -} - -func (p *noder) binOp(op syntax.Operator) Op { - if uint64(op) >= uint64(len(binOps)) || binOps[op] == 0 { - panic("invalid Operator") - } - return binOps[op] -} - -// checkLangCompat reports an error if the representation of a numeric -// literal is not compatible with the current language version. -func checkLangCompat(lit *syntax.BasicLit) { - s := lit.Value - if len(s) <= 2 || langSupported(1, 13, localpkg) { - return - } - // len(s) > 2 - if strings.Contains(s, "_") { - yyerrorv("go1.13", "underscores in numeric literals") - return - } - if s[0] != '0' { - return - } - base := s[1] - if base == 'b' || base == 'B' { - yyerrorv("go1.13", "binary literals") - return - } - if base == 'o' || base == 'O' { - yyerrorv("go1.13", "0o/0O-style octal literals") - return - } - if lit.Kind != syntax.IntLit && (base == 'x' || base == 'X') { - yyerrorv("go1.13", "hexadecimal floating-point literals") - } -} - -func (p *noder) basicLit(lit *syntax.BasicLit) Val { - // We don't use the errors of the conversion routines to determine - // if a literal string is valid because the conversion routines may - // accept a wider syntax than the language permits. Rely on lit.Bad - // instead. - switch s := lit.Value; lit.Kind { - case syntax.IntLit: - checkLangCompat(lit) - x := new(Mpint) - if !lit.Bad { - x.SetString(s) - } - return Val{U: x} - - case syntax.FloatLit: - checkLangCompat(lit) - x := newMpflt() - if !lit.Bad { - x.SetString(s) - } - return Val{U: x} - - case syntax.ImagLit: - checkLangCompat(lit) - x := newMpcmplx() - if !lit.Bad { - x.Imag.SetString(strings.TrimSuffix(s, "i")) - } - return Val{U: x} - - case syntax.RuneLit: - x := new(Mpint) - x.Rune = true - if !lit.Bad { - u, _ := strconv.Unquote(s) - var r rune - if len(u) == 1 { - r = rune(u[0]) - } else { - r, _ = utf8.DecodeRuneInString(u) - } - x.SetInt64(int64(r)) - } - return Val{U: x} - - case syntax.StringLit: - var x string - if !lit.Bad { - if len(s) > 0 && s[0] == '`' { - // strip carriage returns from raw string - s = strings.Replace(s, "\r", "", -1) - } - x, _ = strconv.Unquote(s) - } - return Val{U: x} - - default: - panic("unhandled BasicLit kind") - } -} - -func (p *noder) name(name *syntax.Name) *types.Sym { - return lookup(name.Value) -} - -func (p *noder) mkname(name *syntax.Name) *Node { - // TODO(mdempsky): Set line number? - return mkname(p.name(name)) -} - -func (p *noder) wrapname(n syntax.Node, x *Node) *Node { - // These nodes do not carry line numbers. - // Introduce a wrapper node to give them the correct line. - switch x.Op { - case OTYPE, OLITERAL: - if x.Sym == nil { - break - } - fallthrough - case ONAME, ONONAME, OPACK: - x = p.nod(n, OPAREN, x, nil) - x.SetImplicit(true) - } - return x -} - -func (p *noder) nod(orig syntax.Node, op Op, left, right *Node) *Node { - return nodl(p.pos(orig), op, left, right) -} - -func (p *noder) nodSym(orig syntax.Node, op Op, left *Node, sym *types.Sym) *Node { - n := nodSym(op, left, sym) - n.Pos = p.pos(orig) - return n -} - -func (p *noder) pos(n syntax.Node) src.XPos { - // TODO(gri): orig.Pos() should always be known - fix package syntax - xpos := lineno - if pos := n.Pos(); pos.IsKnown() { - xpos = p.makeXPos(pos) - } - return xpos -} - -func (p *noder) setlineno(n syntax.Node) { - if n != nil { - lineno = p.pos(n) - } -} - -// error is called concurrently if files are parsed concurrently. -func (p *noder) error(err error) { - p.err <- err.(syntax.Error) -} - -// pragmas that are allowed in the std lib, but don't have -// a syntax.Pragma value (see lex.go) associated with them. -var allowedStdPragmas = map[string]bool{ - "go:cgo_export_static": true, - "go:cgo_export_dynamic": true, - "go:cgo_import_static": true, - "go:cgo_import_dynamic": true, - "go:cgo_ldflag": true, - "go:cgo_dynamic_linker": true, - "go:embed": true, - "go:generate": true, -} - -// *Pragma is the value stored in a syntax.Pragma during parsing. -type Pragma struct { - Flag PragmaFlag // collected bits - Pos []PragmaPos // position of each individual flag - Embeds []PragmaEmbed -} - -type PragmaPos struct { - Flag PragmaFlag - Pos syntax.Pos -} - -type PragmaEmbed struct { - Pos syntax.Pos - Patterns []string -} - -func (p *noder) checkUnused(pragma *Pragma) { - for _, pos := range pragma.Pos { - if pos.Flag&pragma.Flag != 0 { - p.yyerrorpos(pos.Pos, "misplaced compiler directive") - } - } - if len(pragma.Embeds) > 0 { - for _, e := range pragma.Embeds { - p.yyerrorpos(e.Pos, "misplaced go:embed directive") - } - } -} - -func (p *noder) checkUnusedDuringParse(pragma *Pragma) { - for _, pos := range pragma.Pos { - if pos.Flag&pragma.Flag != 0 { - p.error(syntax.Error{Pos: pos.Pos, Msg: "misplaced compiler directive"}) - } - } - if len(pragma.Embeds) > 0 { - for _, e := range pragma.Embeds { - p.error(syntax.Error{Pos: e.Pos, Msg: "misplaced go:embed directive"}) - } - } -} - -// pragma is called concurrently if files are parsed concurrently. -func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.Pragma) syntax.Pragma { - pragma, _ := old.(*Pragma) - if pragma == nil { - pragma = new(Pragma) - } - - if text == "" { - // unused pragma; only called with old != nil. - p.checkUnusedDuringParse(pragma) - return nil - } - - if strings.HasPrefix(text, "line ") { - // line directives are handled by syntax package - panic("unreachable") - } - - if !blankLine { - // directive must be on line by itself - p.error(syntax.Error{Pos: pos, Msg: "misplaced compiler directive"}) - return pragma - } - - switch { - case strings.HasPrefix(text, "go:linkname "): - f := strings.Fields(text) - if !(2 <= len(f) && len(f) <= 3) { - p.error(syntax.Error{Pos: pos, Msg: "usage: //go:linkname localname [linkname]"}) - break - } - // The second argument is optional. If omitted, we use - // the default object symbol name for this and - // linkname only serves to mark this symbol as - // something that may be referenced via the object - // symbol name from another package. - var target string - if len(f) == 3 { - target = f[2] - } - p.linknames = append(p.linknames, linkname{pos, f[1], target}) - - case text == "go:embed", strings.HasPrefix(text, "go:embed "): - args, err := parseGoEmbed(text[len("go:embed"):]) - if err != nil { - p.error(syntax.Error{Pos: pos, Msg: err.Error()}) - } - if len(args) == 0 { - p.error(syntax.Error{Pos: pos, Msg: "usage: //go:embed pattern..."}) - break - } - pragma.Embeds = append(pragma.Embeds, PragmaEmbed{pos, args}) - - case strings.HasPrefix(text, "go:cgo_import_dynamic "): - // This is permitted for general use because Solaris - // code relies on it in golang.org/x/sys/unix and others. - fields := pragmaFields(text) - if len(fields) >= 4 { - lib := strings.Trim(fields[3], `"`) - if lib != "" && !safeArg(lib) && !isCgoGeneratedFile(pos) { - p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("invalid library name %q in cgo_import_dynamic directive", lib)}) - } - p.pragcgo(pos, text) - pragma.Flag |= pragmaFlag("go:cgo_import_dynamic") - break - } - fallthrough - case strings.HasPrefix(text, "go:cgo_"): - // For security, we disallow //go:cgo_* directives other - // than cgo_import_dynamic outside cgo-generated files. - // Exception: they are allowed in the standard library, for runtime and syscall. - if !isCgoGeneratedFile(pos) && !compiling_std { - p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in cgo-generated code", text)}) - } - p.pragcgo(pos, text) - fallthrough // because of //go:cgo_unsafe_args - default: - verb := text - if i := strings.Index(text, " "); i >= 0 { - verb = verb[:i] - } - flag := pragmaFlag(verb) - const runtimePragmas = Systemstack | Nowritebarrier | Nowritebarrierrec | Yeswritebarrierrec - if !compiling_runtime && flag&runtimePragmas != 0 { - p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in runtime", verb)}) - } - if flag == 0 && !allowedStdPragmas[verb] && compiling_std { - p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s is not allowed in the standard library", verb)}) - } - pragma.Flag |= flag - pragma.Pos = append(pragma.Pos, PragmaPos{flag, pos}) - } - - return pragma -} - -// isCgoGeneratedFile reports whether pos is in a file -// generated by cgo, which is to say a file with name -// beginning with "_cgo_". Such files are allowed to -// contain cgo directives, and for security reasons -// (primarily misuse of linker flags), other files are not. -// See golang.org/issue/23672. -func isCgoGeneratedFile(pos syntax.Pos) bool { - return strings.HasPrefix(filepath.Base(filepath.Clean(fileh(pos.Base().Filename()))), "_cgo_") -} - -// safeArg reports whether arg is a "safe" command-line argument, -// meaning that when it appears in a command-line, it probably -// doesn't have some special meaning other than its own name. -// This is copied from SafeArg in cmd/go/internal/load/pkg.go. -func safeArg(name string) bool { - if name == "" { - return false - } - c := name[0] - return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf -} - -func mkname(sym *types.Sym) *Node { - n := oldname(sym) - if n.Name != nil && n.Name.Pack != nil { - n.Name.Pack.Name.SetUsed(true) - } - return n -} - -// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns. -// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings. -// go/build/read.go also processes these strings and contains similar logic. -func parseGoEmbed(args string) ([]string, error) { - var list []string - for args = strings.TrimSpace(args); args != ""; args = strings.TrimSpace(args) { - var path string - Switch: - switch args[0] { - default: - i := len(args) - for j, c := range args { - if unicode.IsSpace(c) { - i = j - break - } - } - path = args[:i] - args = args[i:] - - case '`': - i := strings.Index(args[1:], "`") - if i < 0 { - return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) - } - path = args[1 : 1+i] - args = args[1+i+1:] - - case '"': - i := 1 - for ; i < len(args); i++ { - if args[i] == '\\' { - i++ - continue - } - if args[i] == '"' { - q, err := strconv.Unquote(args[:i+1]) - if err != nil { - return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1]) - } - path = q - args = args[i+1:] - break Switch - } - } - if i >= len(args) { - return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) - } - } - - if args != "" { - r, _ := utf8.DecodeRuneInString(args) - if !unicode.IsSpace(r) { - return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) - } - } - list = append(list, path) - } - return list, nil -} diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index 32aa7c5bb1ae0958739d95e42815c221fbf8586c..474d718525f5b80a3adc9c47c3aad8e879830e48 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -5,28 +5,21 @@ package gc import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/staticdata" + "cmd/compile/internal/typecheck" "cmd/compile/internal/types" + "cmd/internal/archive" "cmd/internal/bio" "cmd/internal/obj" "cmd/internal/objabi" - "cmd/internal/src" - "crypto/sha256" "encoding/json" "fmt" - "io" - "io/ioutil" - "os" - "sort" - "strconv" ) -// architecture-independent object file output -const ArhdrSize = 60 - -func formathdr(arhdr []byte, name string, size int64) { - copy(arhdr[:], fmt.Sprintf("%-16s%-12d%-6d%-6d%-8o%-10d`\n", name, 0, 0, 0, 0644, size)) -} - // These modes say which kind of object file to generate. // The default use of the toolchain is to set both bits, // generating a combined compiler+linker object, one that @@ -46,20 +39,20 @@ const ( ) func dumpobj() { - if linkobj == "" { - dumpobj1(outfile, modeCompilerObj|modeLinkerObj) + if base.Flag.LinkObj == "" { + dumpobj1(base.Flag.LowerO, modeCompilerObj|modeLinkerObj) return } - dumpobj1(outfile, modeCompilerObj) - dumpobj1(linkobj, modeLinkerObj) + dumpobj1(base.Flag.LowerO, modeCompilerObj) + dumpobj1(base.Flag.LinkObj, modeLinkerObj) } func dumpobj1(outfile string, mode int) { bout, err := bio.Create(outfile) if err != nil { - flusherrors() + base.FlushErrors() fmt.Printf("can't create %s: %v\n", outfile, err) - errorexit() + base.ErrorExit() } defer bout.Close() bout.WriteString("!\n") @@ -77,18 +70,18 @@ func dumpobj1(outfile string, mode int) { } func printObjHeader(bout *bio.Writer) { - fmt.Fprintf(bout, "go object %s %s %s %s\n", objabi.GOOS, objabi.GOARCH, objabi.Version, objabi.Expstring()) - if buildid != "" { - fmt.Fprintf(bout, "build id %q\n", buildid) + bout.WriteString(objabi.HeaderString()) + if base.Flag.BuildID != "" { + fmt.Fprintf(bout, "build id %q\n", base.Flag.BuildID) } - if localpkg.Name == "main" { + if types.LocalPkg.Name == "main" { fmt.Fprintf(bout, "main\n") } fmt.Fprintf(bout, "\n") // header ends with blank line } func startArchiveEntry(bout *bio.Writer) int64 { - var arhdr [ArhdrSize]byte + var arhdr [archive.HeaderSize]byte bout.Write(arhdr[:]) return bout.Offset() } @@ -99,10 +92,10 @@ func finishArchiveEntry(bout *bio.Writer, start int64, name string) { if size&1 != 0 { bout.WriteByte(0) } - bout.MustSeek(start-ArhdrSize, 0) + bout.MustSeek(start-archive.HeaderSize, 0) - var arhdr [ArhdrSize]byte - formathdr(arhdr[:], name, size) + var arhdr [archive.HeaderSize]byte + archive.FormatHeader(arhdr[:], name, size) bout.Write(arhdr[:]) bout.Flush() bout.MustSeek(start+size+(size&1), 0) @@ -114,22 +107,21 @@ func dumpCompilerObj(bout *bio.Writer) { } func dumpdata() { - externs := len(externdcl) - xtops := len(xtop) - - dumpglobls() - addptabs() - exportlistLen := len(exportlist) - addsignats(externdcl) - dumpsignats() - dumptabs() - ptabsLen := len(ptabs) - itabsLen := len(itabs) - dumpimportstrings() - dumpbasictypes() + numExterns := len(typecheck.Target.Externs) + numDecls := len(typecheck.Target.Decls) + + dumpglobls(typecheck.Target.Externs) + reflectdata.CollectPTabs() + numExports := len(typecheck.Target.Exports) + addsignats(typecheck.Target.Externs) + reflectdata.WriteRuntimeTypes() + reflectdata.WriteTabs() + numPTabs, numITabs := reflectdata.CountTabs() + reflectdata.WriteImportStrings() + reflectdata.WriteBasicTypes() dumpembeds() - // Calls to dumpsignats can generate functions, + // Calls to WriteRuntimeTypes can generate functions, // like method wrappers and hash and equality routines. // Compile any generated functions, process any new resulting types, repeat. // This can't loop forever, because there is no way to generate an infinite @@ -137,169 +129,110 @@ func dumpdata() { // In the typical case, we loop 0 or 1 times. // It was not until issue 24761 that we found any code that required a loop at all. for { - for i := xtops; i < len(xtop); i++ { - n := xtop[i] - if n.Op == ODCLFUNC { - funccompile(n) + for i := numDecls; i < len(typecheck.Target.Decls); i++ { + if n, ok := typecheck.Target.Decls[i].(*ir.Func); ok { + enqueueFunc(n) } } - xtops = len(xtop) + numDecls = len(typecheck.Target.Decls) compileFunctions() - dumpsignats() - if xtops == len(xtop) { + reflectdata.WriteRuntimeTypes() + if numDecls == len(typecheck.Target.Decls) { break } } // Dump extra globals. - tmp := externdcl + dumpglobls(typecheck.Target.Externs[numExterns:]) - if externdcl != nil { - externdcl = externdcl[externs:] - } - dumpglobls() - externdcl = tmp - - if zerosize > 0 { - zero := mappkg.Lookup("zero") - ggloblsym(zero.Linksym(), int32(zerosize), obj.DUPOK|obj.RODATA) + if reflectdata.ZeroSize > 0 { + zero := base.PkgLinksym("go.map", "zero", obj.ABI0) + objw.Global(zero, int32(reflectdata.ZeroSize), obj.DUPOK|obj.RODATA) + zero.Set(obj.AttrStatic, true) } + staticdata.WriteFuncSyms() addGCLocals() - if exportlistLen != len(exportlist) { - Fatalf("exportlist changed after compile functions loop") + if numExports != len(typecheck.Target.Exports) { + base.Fatalf("Target.Exports changed after compile functions loop") } - if ptabsLen != len(ptabs) { - Fatalf("ptabs changed after compile functions loop") + newNumPTabs, newNumITabs := reflectdata.CountTabs() + if newNumPTabs != numPTabs { + base.Fatalf("ptabs changed after compile functions loop") } - if itabsLen != len(itabs) { - Fatalf("itabs changed after compile functions loop") + if newNumITabs != numITabs { + base.Fatalf("itabs changed after compile functions loop") } } func dumpLinkerObj(bout *bio.Writer) { printObjHeader(bout) - if len(pragcgobuf) != 0 { + if len(typecheck.Target.CgoPragmas) != 0 { // write empty export section; must be before cgo section fmt.Fprintf(bout, "\n$$\n\n$$\n\n") fmt.Fprintf(bout, "\n$$ // cgo\n") - if err := json.NewEncoder(bout).Encode(pragcgobuf); err != nil { - Fatalf("serializing pragcgobuf: %v", err) + if err := json.NewEncoder(bout).Encode(typecheck.Target.CgoPragmas); err != nil { + base.Fatalf("serializing pragcgobuf: %v", err) } fmt.Fprintf(bout, "\n$$\n\n") } fmt.Fprintf(bout, "\n!\n") - obj.WriteObjFile(Ctxt, bout) -} - -func addptabs() { - if !Ctxt.Flag_dynlink || localpkg.Name != "main" { - return - } - for _, exportn := range exportlist { - s := exportn.Sym - n := asNode(s.Def) - if n == nil { - continue - } - if n.Op != ONAME { - continue - } - if !types.IsExported(s.Name) { - continue - } - if s.Pkg.Name != "main" { - continue - } - if n.Type.Etype == TFUNC && n.Class() == PFUNC { - // function - ptabs = append(ptabs, ptabEntry{s: s, t: asNode(s.Def).Type}) - } else { - // variable - ptabs = append(ptabs, ptabEntry{s: s, t: types.NewPtr(asNode(s.Def).Type)}) - } - } + obj.WriteObjFile(base.Ctxt, bout) } -func dumpGlobal(n *Node) { - if n.Type == nil { - Fatalf("external %v nil type\n", n) +func dumpGlobal(n *ir.Name) { + if n.Type() == nil { + base.Fatalf("external %v nil type\n", n) } - if n.Class() == PFUNC { + if n.Class == ir.PFUNC { return } - if n.Sym.Pkg != localpkg { + if n.Sym().Pkg != types.LocalPkg { return } - dowidth(n.Type) + types.CalcSize(n.Type()) ggloblnod(n) + base.Ctxt.DwarfGlobal(base.Ctxt.Pkgpath, types.TypeSymName(n.Type()), n.Linksym()) } -func dumpGlobalConst(n *Node) { +func dumpGlobalConst(n ir.Node) { // only export typed constants - t := n.Type + t := n.Type() if t == nil { return } - if n.Sym.Pkg != localpkg { + if n.Sym().Pkg != types.LocalPkg { return } // only export integer constants for now - switch t.Etype { - case TINT8: - case TINT16: - case TINT32: - case TINT64: - case TINT: - case TUINT8: - case TUINT16: - case TUINT32: - case TUINT64: - case TUINT: - case TUINTPTR: - // ok - case TIDEAL: - if !Isconst(n, CTINT) { - return - } - x := n.Val().U.(*Mpint) - if x.Cmp(minintval[TINT]) < 0 || x.Cmp(maxintval[TINT]) > 0 { + if !t.IsInteger() { + return + } + v := n.Val() + if t.IsUntyped() { + // Export untyped integers as int (if they fit). + t = types.Types[types.TINT] + if ir.ConstOverflow(v, t) { return } - // Ideal integers we export as int (if they fit). - t = types.Types[TINT] - default: - return } - Ctxt.DwarfIntConst(myimportpath, n.Sym.Name, typesymname(t), n.Int64Val()) + base.Ctxt.DwarfIntConst(base.Ctxt.Pkgpath, n.Sym().Name, types.TypeSymName(t), ir.IntVal(t, v)) } -func dumpglobls() { +func dumpglobls(externs []ir.Node) { // add globals - for _, n := range externdcl { - switch n.Op { - case ONAME: - dumpGlobal(n) - case OLITERAL: + for _, n := range externs { + switch n.Op() { + case ir.ONAME: + dumpGlobal(n.(*ir.Name)) + case ir.OLITERAL: dumpGlobalConst(n) } } - - sort.Slice(funcsyms, func(i, j int) bool { - return funcsyms[i].LinksymName() < funcsyms[j].LinksymName() - }) - for _, s := range funcsyms { - sf := s.Pkg.Lookup(funcsymname(s)).Linksym() - dsymptr(sf, 0, s.Linksym(), 0) - ggloblsym(sf, int32(Widthptr), obj.DUPOK|obj.RODATA) - } - - // Do not reprocess funcsyms on next dumpglobls call. - funcsyms = nil } // addGCLocals adds gcargs, gclocals, gcregs, and stack object symbols to Ctxt.Data. @@ -307,332 +240,65 @@ func dumpglobls() { // This is done during the sequential phase after compilation, since // global symbols can't be declared during parallel compilation. func addGCLocals() { - for _, s := range Ctxt.Text { + for _, s := range base.Ctxt.Text { fn := s.Func() if fn == nil { continue } for _, gcsym := range []*obj.LSym{fn.GCArgs, fn.GCLocals} { if gcsym != nil && !gcsym.OnList() { - ggloblsym(gcsym, int32(len(gcsym.P)), obj.RODATA|obj.DUPOK) + objw.Global(gcsym, int32(len(gcsym.P)), obj.RODATA|obj.DUPOK) } } if x := fn.StackObjects; x != nil { attr := int16(obj.RODATA) - ggloblsym(x, int32(len(x.P)), attr) + objw.Global(x, int32(len(x.P)), attr) x.Set(obj.AttrStatic, true) } if x := fn.OpenCodedDeferInfo; x != nil { - ggloblsym(x, int32(len(x.P)), obj.RODATA|obj.DUPOK) + objw.Global(x, int32(len(x.P)), obj.RODATA|obj.DUPOK) } - } -} - -func duintxx(s *obj.LSym, off int, v uint64, wid int) int { - if off&(wid-1) != 0 { - Fatalf("duintxxLSym: misaligned: v=%d wid=%d off=%d", v, wid, off) - } - s.WriteInt(Ctxt, int64(off), wid, int64(v)) - return off + wid -} - -func duint8(s *obj.LSym, off int, v uint8) int { - return duintxx(s, off, uint64(v), 1) -} - -func duint16(s *obj.LSym, off int, v uint16) int { - return duintxx(s, off, uint64(v), 2) -} - -func duint32(s *obj.LSym, off int, v uint32) int { - return duintxx(s, off, uint64(v), 4) -} - -func duintptr(s *obj.LSym, off int, v uint64) int { - return duintxx(s, off, v, Widthptr) -} - -func dbvec(s *obj.LSym, off int, bv bvec) int { - // Runtime reads the bitmaps as byte arrays. Oblige. - for j := 0; int32(j) < bv.n; j += 8 { - word := bv.b[j/32] - off = duint8(s, off, uint8(word>>(uint(j)%32))) - } - return off -} - -const ( - stringSymPrefix = "go.string." - stringSymPattern = ".gostring.%d.%x" -) - -// stringsym returns a symbol containing the string s. -// The symbol contains the string data, not a string header. -func stringsym(pos src.XPos, s string) (data *obj.LSym) { - var symname string - if len(s) > 100 { - // Huge strings are hashed to avoid long names in object files. - // Indulge in some paranoia by writing the length of s, too, - // as protection against length extension attacks. - // Same pattern is known to fileStringSym below. - h := sha256.New() - io.WriteString(h, s) - symname = fmt.Sprintf(stringSymPattern, len(s), h.Sum(nil)) - } else { - // Small strings get named directly by their contents. - symname = strconv.Quote(s) - } - - symdata := Ctxt.Lookup(stringSymPrefix + symname) - if !symdata.OnList() { - off := dstringdata(symdata, 0, s, pos, "string") - ggloblsym(symdata, int32(off), obj.DUPOK|obj.RODATA|obj.LOCAL) - symdata.Set(obj.AttrContentAddressable, true) - } - - return symdata -} - -// fileStringSym returns a symbol for the contents and the size of file. -// If readonly is true, the symbol shares storage with any literal string -// or other file with the same content and is placed in a read-only section. -// If readonly is false, the symbol is a read-write copy separate from any other, -// for use as the backing store of a []byte. -// The content hash of file is copied into hash. (If hash is nil, nothing is copied.) -// The returned symbol contains the data itself, not a string header. -func fileStringSym(pos src.XPos, file string, readonly bool, hash []byte) (*obj.LSym, int64, error) { - f, err := os.Open(file) - if err != nil { - return nil, 0, err - } - defer f.Close() - info, err := f.Stat() - if err != nil { - return nil, 0, err - } - if !info.Mode().IsRegular() { - return nil, 0, fmt.Errorf("not a regular file") - } - size := info.Size() - if size <= 1*1024 { - data, err := ioutil.ReadAll(f) - if err != nil { - return nil, 0, err - } - if int64(len(data)) != size { - return nil, 0, fmt.Errorf("file changed between reads") - } - var sym *obj.LSym - if readonly { - sym = stringsym(pos, string(data)) - } else { - sym = slicedata(pos, string(data)).Sym.Linksym() - } - if len(hash) > 0 { - sum := sha256.Sum256(data) - copy(hash, sum[:]) - } - return sym, size, nil - } - if size > 2e9 { - // ggloblsym takes an int32, - // and probably the rest of the toolchain - // can't handle such big symbols either. - // See golang.org/issue/9862. - return nil, 0, fmt.Errorf("file too large") - } - - // File is too big to read and keep in memory. - // Compute hash if needed for read-only content hashing or if the caller wants it. - var sum []byte - if readonly || len(hash) > 0 { - h := sha256.New() - n, err := io.Copy(h, f) - if err != nil { - return nil, 0, err - } - if n != size { - return nil, 0, fmt.Errorf("file changed between reads") - } - sum = h.Sum(nil) - copy(hash, sum) - } - - var symdata *obj.LSym - if readonly { - symname := fmt.Sprintf(stringSymPattern, size, sum) - symdata = Ctxt.Lookup(stringSymPrefix + symname) - if !symdata.OnList() { - info := symdata.NewFileInfo() - info.Name = file - info.Size = size - ggloblsym(symdata, int32(size), obj.DUPOK|obj.RODATA|obj.LOCAL) - // Note: AttrContentAddressable cannot be set here, - // because the content-addressable-handling code - // does not know about file symbols. + if x := fn.ArgInfo; x != nil { + objw.Global(x, int32(len(x.P)), obj.RODATA|obj.DUPOK) + x.Set(obj.AttrStatic, true) + x.Set(obj.AttrContentAddressable, true) } - } else { - // Emit a zero-length data symbol - // and then fix up length and content to use file. - symdata = slicedata(pos, "").Sym.Linksym() - symdata.Size = size - symdata.Type = objabi.SNOPTRDATA - info := symdata.NewFileInfo() - info.Name = file - info.Size = size } - - return symdata, size, nil } -var slicedataGen int - -func slicedata(pos src.XPos, s string) *Node { - slicedataGen++ - symname := fmt.Sprintf(".gobytes.%d", slicedataGen) - sym := localpkg.Lookup(symname) - symnode := newname(sym) - sym.Def = asTypesNode(symnode) - - lsym := sym.Linksym() - off := dstringdata(lsym, 0, s, pos, "slice") - ggloblsym(lsym, int32(off), obj.NOPTR|obj.LOCAL) - - return symnode -} - -func slicebytes(nam *Node, s string) { - if nam.Op != ONAME { - Fatalf("slicebytes %v", nam) +func ggloblnod(nam *ir.Name) { + s := nam.Linksym() + s.Gotype = reflectdata.TypeLinksym(nam.Type()) + flags := 0 + if nam.Readonly() { + flags = obj.RODATA } - slicesym(nam, slicedata(nam.Pos, s), int64(len(s))) -} - -func dstringdata(s *obj.LSym, off int, t string, pos src.XPos, what string) int { - // Objects that are too large will cause the data section to overflow right away, - // causing a cryptic error message by the linker. Check for oversize objects here - // and provide a useful error message instead. - if int64(len(t)) > 2e9 { - yyerrorl(pos, "%v with length %v is too big", what, len(t)) - return 0 + if nam.Type() != nil && !nam.Type().HasPointers() { + flags |= obj.NOPTR } - - s.WriteString(Ctxt, int64(off), len(t), t) - return off + len(t) -} - -func dsymptr(s *obj.LSym, off int, x *obj.LSym, xoff int) int { - off = int(Rnd(int64(off), int64(Widthptr))) - s.WriteAddr(Ctxt, int64(off), Widthptr, x, int64(xoff)) - off += Widthptr - return off -} - -func dsymptrOff(s *obj.LSym, off int, x *obj.LSym) int { - s.WriteOff(Ctxt, int64(off), x, 0) - off += 4 - return off -} - -func dsymptrWeakOff(s *obj.LSym, off int, x *obj.LSym) int { - s.WriteWeakOff(Ctxt, int64(off), x, 0) - off += 4 - return off -} - -// slicesym writes a static slice symbol {&arr, lencap, lencap} to n. -// arr must be an ONAME. slicesym does not modify n. -func slicesym(n, arr *Node, lencap int64) { - s := n.Sym.Linksym() - base := n.Xoffset - if arr.Op != ONAME { - Fatalf("slicesym non-name arr %v", arr) - } - s.WriteAddr(Ctxt, base, Widthptr, arr.Sym.Linksym(), arr.Xoffset) - s.WriteInt(Ctxt, base+sliceLenOffset, Widthptr, lencap) - s.WriteInt(Ctxt, base+sliceCapOffset, Widthptr, lencap) -} - -// addrsym writes the static address of a to n. a must be an ONAME. -// Neither n nor a is modified. -func addrsym(n, a *Node) { - if n.Op != ONAME { - Fatalf("addrsym n op %v", n.Op) + base.Ctxt.Globl(s, nam.Type().Width, flags) + if nam.LibfuzzerExtraCounter() { + s.Type = objabi.SLIBFUZZER_EXTRA_COUNTER } - if n.Sym == nil { - Fatalf("addrsym nil n sym") + if nam.Sym().Linkname != "" { + // Make sure linkname'd symbol is non-package. When a symbol is + // both imported and linkname'd, s.Pkg may not set to "_" in + // types.Sym.Linksym because LSym already exists. Set it here. + s.Pkg = "_" } - if a.Op != ONAME { - Fatalf("addrsym a op %v", a.Op) - } - s := n.Sym.Linksym() - s.WriteAddr(Ctxt, n.Xoffset, Widthptr, a.Sym.Linksym(), a.Xoffset) } -// pfuncsym writes the static address of f to n. f must be a global function. -// Neither n nor f is modified. -func pfuncsym(n, f *Node) { - if n.Op != ONAME { - Fatalf("pfuncsym n op %v", n.Op) - } - if n.Sym == nil { - Fatalf("pfuncsym nil n sym") - } - if f.Class() != PFUNC { - Fatalf("pfuncsym class not PFUNC %d", f.Class()) +func dumpembeds() { + for _, v := range typecheck.Target.Embeds { + staticdata.WriteEmbed(v) } - s := n.Sym.Linksym() - s.WriteAddr(Ctxt, n.Xoffset, Widthptr, funcsym(f.Sym).Linksym(), f.Xoffset) } -// litsym writes the static literal c to n. -// Neither n nor c is modified. -func litsym(n, c *Node, wid int) { - if n.Op != ONAME { - Fatalf("litsym n op %v", n.Op) - } - if c.Op != OLITERAL { - Fatalf("litsym c op %v", c.Op) - } - if n.Sym == nil { - Fatalf("litsym nil n sym") - } - s := n.Sym.Linksym() - switch u := c.Val().U.(type) { - case bool: - i := int64(obj.Bool2int(u)) - s.WriteInt(Ctxt, n.Xoffset, wid, i) - - case *Mpint: - s.WriteInt(Ctxt, n.Xoffset, wid, u.Int64()) - - case *Mpflt: - f := u.Float64() - switch n.Type.Etype { - case TFLOAT32: - s.WriteFloat32(Ctxt, n.Xoffset, float32(f)) - case TFLOAT64: - s.WriteFloat64(Ctxt, n.Xoffset, f) - } - - case *Mpcplx: - r := u.Real.Float64() - i := u.Imag.Float64() - switch n.Type.Etype { - case TCOMPLEX64: - s.WriteFloat32(Ctxt, n.Xoffset, float32(r)) - s.WriteFloat32(Ctxt, n.Xoffset+4, float32(i)) - case TCOMPLEX128: - s.WriteFloat64(Ctxt, n.Xoffset, r) - s.WriteFloat64(Ctxt, n.Xoffset+8, i) +func addsignats(dcls []ir.Node) { + // copy types from dcl list to signatset + for _, n := range dcls { + if n.Op() == ir.OTYPE { + reflectdata.NeedRuntimeType(n.Type()) } - - case string: - symdata := stringsym(n.Pos, u) - s.WriteAddr(Ctxt, n.Xoffset, Widthptr, symdata, 0) - s.WriteInt(Ctxt, n.Xoffset+int64(Widthptr), Widthptr, int64(len(u))) - - default: - Fatalf("litsym unhandled OLITERAL %v", c) } } diff --git a/src/cmd/compile/internal/gc/op_string.go b/src/cmd/compile/internal/gc/op_string.go deleted file mode 100644 index 41d588309c01eedea9e2f744ffdd5b96f8990683..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/op_string.go +++ /dev/null @@ -1,175 +0,0 @@ -// Code generated by "stringer -type=Op -trimprefix=O"; DO NOT EDIT. - -package gc - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[OXXX-0] - _ = x[ONAME-1] - _ = x[ONONAME-2] - _ = x[OTYPE-3] - _ = x[OPACK-4] - _ = x[OLITERAL-5] - _ = x[OADD-6] - _ = x[OSUB-7] - _ = x[OOR-8] - _ = x[OXOR-9] - _ = x[OADDSTR-10] - _ = x[OADDR-11] - _ = x[OANDAND-12] - _ = x[OAPPEND-13] - _ = x[OBYTES2STR-14] - _ = x[OBYTES2STRTMP-15] - _ = x[ORUNES2STR-16] - _ = x[OSTR2BYTES-17] - _ = x[OSTR2BYTESTMP-18] - _ = x[OSTR2RUNES-19] - _ = x[OAS-20] - _ = x[OAS2-21] - _ = x[OAS2DOTTYPE-22] - _ = x[OAS2FUNC-23] - _ = x[OAS2MAPR-24] - _ = x[OAS2RECV-25] - _ = x[OASOP-26] - _ = x[OCALL-27] - _ = x[OCALLFUNC-28] - _ = x[OCALLMETH-29] - _ = x[OCALLINTER-30] - _ = x[OCALLPART-31] - _ = x[OCAP-32] - _ = x[OCLOSE-33] - _ = x[OCLOSURE-34] - _ = x[OCOMPLIT-35] - _ = x[OMAPLIT-36] - _ = x[OSTRUCTLIT-37] - _ = x[OARRAYLIT-38] - _ = x[OSLICELIT-39] - _ = x[OPTRLIT-40] - _ = x[OCONV-41] - _ = x[OCONVIFACE-42] - _ = x[OCONVNOP-43] - _ = x[OCOPY-44] - _ = x[ODCL-45] - _ = x[ODCLFUNC-46] - _ = x[ODCLFIELD-47] - _ = x[ODCLCONST-48] - _ = x[ODCLTYPE-49] - _ = x[ODELETE-50] - _ = x[ODOT-51] - _ = x[ODOTPTR-52] - _ = x[ODOTMETH-53] - _ = x[ODOTINTER-54] - _ = x[OXDOT-55] - _ = x[ODOTTYPE-56] - _ = x[ODOTTYPE2-57] - _ = x[OEQ-58] - _ = x[ONE-59] - _ = x[OLT-60] - _ = x[OLE-61] - _ = x[OGE-62] - _ = x[OGT-63] - _ = x[ODEREF-64] - _ = x[OINDEX-65] - _ = x[OINDEXMAP-66] - _ = x[OKEY-67] - _ = x[OSTRUCTKEY-68] - _ = x[OLEN-69] - _ = x[OMAKE-70] - _ = x[OMAKECHAN-71] - _ = x[OMAKEMAP-72] - _ = x[OMAKESLICE-73] - _ = x[OMAKESLICECOPY-74] - _ = x[OMUL-75] - _ = x[ODIV-76] - _ = x[OMOD-77] - _ = x[OLSH-78] - _ = x[ORSH-79] - _ = x[OAND-80] - _ = x[OANDNOT-81] - _ = x[ONEW-82] - _ = x[ONEWOBJ-83] - _ = x[ONOT-84] - _ = x[OBITNOT-85] - _ = x[OPLUS-86] - _ = x[ONEG-87] - _ = x[OOROR-88] - _ = x[OPANIC-89] - _ = x[OPRINT-90] - _ = x[OPRINTN-91] - _ = x[OPAREN-92] - _ = x[OSEND-93] - _ = x[OSLICE-94] - _ = x[OSLICEARR-95] - _ = x[OSLICESTR-96] - _ = x[OSLICE3-97] - _ = x[OSLICE3ARR-98] - _ = x[OSLICEHEADER-99] - _ = x[ORECOVER-100] - _ = x[ORECV-101] - _ = x[ORUNESTR-102] - _ = x[OSELRECV-103] - _ = x[OSELRECV2-104] - _ = x[OIOTA-105] - _ = x[OREAL-106] - _ = x[OIMAG-107] - _ = x[OCOMPLEX-108] - _ = x[OALIGNOF-109] - _ = x[OOFFSETOF-110] - _ = x[OSIZEOF-111] - _ = x[OBLOCK-112] - _ = x[OBREAK-113] - _ = x[OCASE-114] - _ = x[OCONTINUE-115] - _ = x[ODEFER-116] - _ = x[OEMPTY-117] - _ = x[OFALL-118] - _ = x[OFOR-119] - _ = x[OFORUNTIL-120] - _ = x[OGOTO-121] - _ = x[OIF-122] - _ = x[OLABEL-123] - _ = x[OGO-124] - _ = x[ORANGE-125] - _ = x[ORETURN-126] - _ = x[OSELECT-127] - _ = x[OSWITCH-128] - _ = x[OTYPESW-129] - _ = x[OTCHAN-130] - _ = x[OTMAP-131] - _ = x[OTSTRUCT-132] - _ = x[OTINTER-133] - _ = x[OTFUNC-134] - _ = x[OTARRAY-135] - _ = x[ODDD-136] - _ = x[OINLCALL-137] - _ = x[OEFACE-138] - _ = x[OITAB-139] - _ = x[OIDATA-140] - _ = x[OSPTR-141] - _ = x[OCLOSUREVAR-142] - _ = x[OCFUNC-143] - _ = x[OCHECKNIL-144] - _ = x[OVARDEF-145] - _ = x[OVARKILL-146] - _ = x[OVARLIVE-147] - _ = x[ORESULT-148] - _ = x[OINLMARK-149] - _ = x[ORETJMP-150] - _ = x[OGETG-151] - _ = x[OEND-152] -} - -const _Op_name = "XXXNAMENONAMETYPEPACKLITERALADDSUBORXORADDSTRADDRANDANDAPPENDBYTES2STRBYTES2STRTMPRUNES2STRSTR2BYTESSTR2BYTESTMPSTR2RUNESASAS2AS2DOTTYPEAS2FUNCAS2MAPRAS2RECVASOPCALLCALLFUNCCALLMETHCALLINTERCALLPARTCAPCLOSECLOSURECOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVNOPCOPYDCLDCLFUNCDCLFIELDDCLCONSTDCLTYPEDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTDEREFINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMAKESLICECOPYMULDIVMODLSHRSHANDANDNOTNEWNEWOBJNOTBITNOTPLUSNEGORORPANICPRINTPRINTNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRSLICEHEADERRECOVERRECVRUNESTRSELRECVSELRECV2IOTAREALIMAGCOMPLEXALIGNOFOFFSETOFSIZEOFBLOCKBREAKCASECONTINUEDEFEREMPTYFALLFORFORUNTILGOTOIFLABELGORANGERETURNSELECTSWITCHTYPESWTCHANTMAPTSTRUCTTINTERTFUNCTARRAYDDDINLCALLEFACEITABIDATASPTRCLOSUREVARCFUNCCHECKNILVARDEFVARKILLVARLIVERESULTINLMARKRETJMPGETGEND" - -var _Op_index = [...]uint16{0, 3, 7, 13, 17, 21, 28, 31, 34, 36, 39, 45, 49, 55, 61, 70, 82, 91, 100, 112, 121, 123, 126, 136, 143, 150, 157, 161, 165, 173, 181, 190, 198, 201, 206, 213, 220, 226, 235, 243, 251, 257, 261, 270, 277, 281, 284, 291, 299, 307, 314, 320, 323, 329, 336, 344, 348, 355, 363, 365, 367, 369, 371, 373, 375, 380, 385, 393, 396, 405, 408, 412, 420, 427, 436, 449, 452, 455, 458, 461, 464, 467, 473, 476, 482, 485, 491, 495, 498, 502, 507, 512, 518, 523, 527, 532, 540, 548, 554, 563, 574, 581, 585, 592, 599, 607, 611, 615, 619, 626, 633, 641, 647, 652, 657, 661, 669, 674, 679, 683, 686, 694, 698, 700, 705, 707, 712, 718, 724, 730, 736, 741, 745, 752, 758, 763, 769, 772, 779, 784, 788, 793, 797, 807, 812, 820, 826, 833, 840, 846, 853, 859, 863, 866} - -func (i Op) String() string { - if i >= Op(len(_Op_index)-1) { - return "Op(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _Op_name[_Op_index[i]:_Op_index[i+1]] -} diff --git a/src/cmd/compile/internal/gc/order.go b/src/cmd/compile/internal/gc/order.go deleted file mode 100644 index 30e1535c09c43a516e00d7ad864ec56f25abf34a..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/order.go +++ /dev/null @@ -1,1441 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/src" - "fmt" -) - -// Rewrite tree to use separate statements to enforce -// order of evaluation. Makes walk easier, because it -// can (after this runs) reorder at will within an expression. -// -// Rewrite m[k] op= r into m[k] = m[k] op r if op is / or %. -// -// Introduce temporaries as needed by runtime routines. -// For example, the map runtime routines take the map key -// by reference, so make sure all map keys are addressable -// by copying them to temporaries as needed. -// The same is true for channel operations. -// -// Arrange that map index expressions only appear in direct -// assignments x = m[k] or m[k] = x, never in larger expressions. -// -// Arrange that receive expressions only appear in direct assignments -// x = <-c or as standalone statements <-c, never in larger expressions. - -// TODO(rsc): The temporary introduction during multiple assignments -// should be moved into this file, so that the temporaries can be cleaned -// and so that conversions implicit in the OAS2FUNC and OAS2RECV -// nodes can be made explicit and then have their temporaries cleaned. - -// TODO(rsc): Goto and multilevel break/continue can jump over -// inserted VARKILL annotations. Work out a way to handle these. -// The current implementation is safe, in that it will execute correctly. -// But it won't reuse temporaries as aggressively as it might, and -// it can result in unnecessary zeroing of those variables in the function -// prologue. - -// Order holds state during the ordering process. -type Order struct { - out []*Node // list of generated statements - temp []*Node // stack of temporary variables - free map[string][]*Node // free list of unused temporaries, by type.LongString(). -} - -// Order rewrites fn.Nbody to apply the ordering constraints -// described in the comment at the top of the file. -func order(fn *Node) { - if Debug.W > 1 { - s := fmt.Sprintf("\nbefore order %v", fn.Func.Nname.Sym) - dumplist(s, fn.Nbody) - } - - orderBlock(&fn.Nbody, map[string][]*Node{}) -} - -// newTemp allocates a new temporary with the given type, -// pushes it onto the temp stack, and returns it. -// If clear is true, newTemp emits code to zero the temporary. -func (o *Order) newTemp(t *types.Type, clear bool) *Node { - var v *Node - // Note: LongString is close to the type equality we want, - // but not exactly. We still need to double-check with types.Identical. - key := t.LongString() - a := o.free[key] - for i, n := range a { - if types.Identical(t, n.Type) { - v = a[i] - a[i] = a[len(a)-1] - a = a[:len(a)-1] - o.free[key] = a - break - } - } - if v == nil { - v = temp(t) - } - if clear { - a := nod(OAS, v, nil) - a = typecheck(a, ctxStmt) - o.out = append(o.out, a) - } - - o.temp = append(o.temp, v) - return v -} - -// copyExpr behaves like newTemp but also emits -// code to initialize the temporary to the value n. -// -// The clear argument is provided for use when the evaluation -// of tmp = n turns into a function call that is passed a pointer -// to the temporary as the output space. If the call blocks before -// tmp has been written, the garbage collector will still treat the -// temporary as live, so we must zero it before entering that call. -// Today, this only happens for channel receive operations. -// (The other candidate would be map access, but map access -// returns a pointer to the result data instead of taking a pointer -// to be filled in.) -func (o *Order) copyExpr(n *Node, t *types.Type, clear bool) *Node { - v := o.newTemp(t, clear) - a := nod(OAS, v, n) - a = typecheck(a, ctxStmt) - o.out = append(o.out, a) - return v -} - -// cheapExpr returns a cheap version of n. -// The definition of cheap is that n is a variable or constant. -// If not, cheapExpr allocates a new tmp, emits tmp = n, -// and then returns tmp. -func (o *Order) cheapExpr(n *Node) *Node { - if n == nil { - return nil - } - - switch n.Op { - case ONAME, OLITERAL: - return n - case OLEN, OCAP: - l := o.cheapExpr(n.Left) - if l == n.Left { - return n - } - a := n.sepcopy() - a.Left = l - return typecheck(a, ctxExpr) - } - - return o.copyExpr(n, n.Type, false) -} - -// safeExpr returns a safe version of n. -// The definition of safe is that n can appear multiple times -// without violating the semantics of the original program, -// and that assigning to the safe version has the same effect -// as assigning to the original n. -// -// The intended use is to apply to x when rewriting x += y into x = x + y. -func (o *Order) safeExpr(n *Node) *Node { - switch n.Op { - case ONAME, OLITERAL: - return n - - case ODOT, OLEN, OCAP: - l := o.safeExpr(n.Left) - if l == n.Left { - return n - } - a := n.sepcopy() - a.Left = l - return typecheck(a, ctxExpr) - - case ODOTPTR, ODEREF: - l := o.cheapExpr(n.Left) - if l == n.Left { - return n - } - a := n.sepcopy() - a.Left = l - return typecheck(a, ctxExpr) - - case OINDEX, OINDEXMAP: - var l *Node - if n.Left.Type.IsArray() { - l = o.safeExpr(n.Left) - } else { - l = o.cheapExpr(n.Left) - } - r := o.cheapExpr(n.Right) - if l == n.Left && r == n.Right { - return n - } - a := n.sepcopy() - a.Left = l - a.Right = r - return typecheck(a, ctxExpr) - - default: - Fatalf("order.safeExpr %v", n.Op) - return nil // not reached - } -} - -// isaddrokay reports whether it is okay to pass n's address to runtime routines. -// Taking the address of a variable makes the liveness and optimization analyses -// lose track of where the variable's lifetime ends. To avoid hurting the analyses -// of ordinary stack variables, those are not 'isaddrokay'. Temporaries are okay, -// because we emit explicit VARKILL instructions marking the end of those -// temporaries' lifetimes. -func isaddrokay(n *Node) bool { - return islvalue(n) && (n.Op != ONAME || n.Class() == PEXTERN || n.IsAutoTmp()) -} - -// addrTemp ensures that n is okay to pass by address to runtime routines. -// If the original argument n is not okay, addrTemp creates a tmp, emits -// tmp = n, and then returns tmp. -// The result of addrTemp MUST be assigned back to n, e.g. -// n.Left = o.addrTemp(n.Left) -func (o *Order) addrTemp(n *Node) *Node { - if consttype(n) != CTxxx { - // TODO: expand this to all static composite literal nodes? - n = defaultlit(n, nil) - dowidth(n.Type) - vstat := readonlystaticname(n.Type) - var s InitSchedule - s.staticassign(vstat, n) - if s.out != nil { - Fatalf("staticassign of const generated code: %+v", n) - } - vstat = typecheck(vstat, ctxExpr) - return vstat - } - if isaddrokay(n) { - return n - } - return o.copyExpr(n, n.Type, false) -} - -// mapKeyTemp prepares n to be a key in a map runtime call and returns n. -// It should only be used for map runtime calls which have *_fast* versions. -func (o *Order) mapKeyTemp(t *types.Type, n *Node) *Node { - // Most map calls need to take the address of the key. - // Exception: map*_fast* calls. See golang.org/issue/19015. - if mapfast(t) == mapslow { - return o.addrTemp(n) - } - return n -} - -// mapKeyReplaceStrConv replaces OBYTES2STR by OBYTES2STRTMP -// in n to avoid string allocations for keys in map lookups. -// Returns a bool that signals if a modification was made. -// -// For: -// x = m[string(k)] -// x = m[T1{... Tn{..., string(k), ...}] -// where k is []byte, T1 to Tn is a nesting of struct and array literals, -// the allocation of backing bytes for the string can be avoided -// by reusing the []byte backing array. These are special cases -// for avoiding allocations when converting byte slices to strings. -// It would be nice to handle these generally, but because -// []byte keys are not allowed in maps, the use of string(k) -// comes up in important cases in practice. See issue 3512. -func mapKeyReplaceStrConv(n *Node) bool { - var replaced bool - switch n.Op { - case OBYTES2STR: - n.Op = OBYTES2STRTMP - replaced = true - case OSTRUCTLIT: - for _, elem := range n.List.Slice() { - if mapKeyReplaceStrConv(elem.Left) { - replaced = true - } - } - case OARRAYLIT: - for _, elem := range n.List.Slice() { - if elem.Op == OKEY { - elem = elem.Right - } - if mapKeyReplaceStrConv(elem) { - replaced = true - } - } - } - return replaced -} - -type ordermarker int - -// markTemp returns the top of the temporary variable stack. -func (o *Order) markTemp() ordermarker { - return ordermarker(len(o.temp)) -} - -// popTemp pops temporaries off the stack until reaching the mark, -// which must have been returned by markTemp. -func (o *Order) popTemp(mark ordermarker) { - for _, n := range o.temp[mark:] { - key := n.Type.LongString() - o.free[key] = append(o.free[key], n) - } - o.temp = o.temp[:mark] -} - -// cleanTempNoPop emits VARKILL instructions to *out -// for each temporary above the mark on the temporary stack. -// It does not pop the temporaries from the stack. -func (o *Order) cleanTempNoPop(mark ordermarker) []*Node { - var out []*Node - for i := len(o.temp) - 1; i >= int(mark); i-- { - n := o.temp[i] - kill := nod(OVARKILL, n, nil) - kill = typecheck(kill, ctxStmt) - out = append(out, kill) - } - return out -} - -// cleanTemp emits VARKILL instructions for each temporary above the -// mark on the temporary stack and removes them from the stack. -func (o *Order) cleanTemp(top ordermarker) { - o.out = append(o.out, o.cleanTempNoPop(top)...) - o.popTemp(top) -} - -// stmtList orders each of the statements in the list. -func (o *Order) stmtList(l Nodes) { - s := l.Slice() - for i := range s { - orderMakeSliceCopy(s[i:]) - o.stmt(s[i]) - } -} - -// orderMakeSliceCopy matches the pattern: -// m = OMAKESLICE([]T, x); OCOPY(m, s) -// and rewrites it to: -// m = OMAKESLICECOPY([]T, x, s); nil -func orderMakeSliceCopy(s []*Node) { - if Debug.N != 0 || instrumenting { - return - } - - if len(s) < 2 { - return - } - - asn := s[0] - copyn := s[1] - - if asn == nil || asn.Op != OAS { - return - } - if asn.Left.Op != ONAME { - return - } - if asn.Left.isBlank() { - return - } - maken := asn.Right - if maken == nil || maken.Op != OMAKESLICE { - return - } - if maken.Esc == EscNone { - return - } - if maken.Left == nil || maken.Right != nil { - return - } - if copyn.Op != OCOPY { - return - } - if copyn.Left.Op != ONAME { - return - } - if asn.Left.Sym != copyn.Left.Sym { - return - } - if copyn.Right.Op != ONAME { - return - } - - if copyn.Left.Sym == copyn.Right.Sym { - return - } - - maken.Op = OMAKESLICECOPY - maken.Right = copyn.Right - // Set bounded when m = OMAKESLICE([]T, len(s)); OCOPY(m, s) - maken.SetBounded(maken.Left.Op == OLEN && samesafeexpr(maken.Left.Left, copyn.Right)) - - maken = typecheck(maken, ctxExpr) - - s[1] = nil // remove separate copy call - - return -} - -// edge inserts coverage instrumentation for libfuzzer. -func (o *Order) edge() { - if Debug_libfuzzer == 0 { - return - } - - // Create a new uint8 counter to be allocated in section - // __libfuzzer_extra_counters. - counter := staticname(types.Types[TUINT8]) - counter.Name.SetLibfuzzerExtraCounter(true) - - // counter += 1 - incr := nod(OASOP, counter, nodintconst(1)) - incr.SetSubOp(OADD) - incr = typecheck(incr, ctxStmt) - - o.out = append(o.out, incr) -} - -// orderBlock orders the block of statements in n into a new slice, -// and then replaces the old slice in n with the new slice. -// free is a map that can be used to obtain temporary variables by type. -func orderBlock(n *Nodes, free map[string][]*Node) { - var order Order - order.free = free - mark := order.markTemp() - order.edge() - order.stmtList(*n) - order.cleanTemp(mark) - n.Set(order.out) -} - -// exprInPlace orders the side effects in *np and -// leaves them as the init list of the final *np. -// The result of exprInPlace MUST be assigned back to n, e.g. -// n.Left = o.exprInPlace(n.Left) -func (o *Order) exprInPlace(n *Node) *Node { - var order Order - order.free = o.free - n = order.expr(n, nil) - n = addinit(n, order.out) - - // insert new temporaries from order - // at head of outer list. - o.temp = append(o.temp, order.temp...) - return n -} - -// orderStmtInPlace orders the side effects of the single statement *np -// and replaces it with the resulting statement list. -// The result of orderStmtInPlace MUST be assigned back to n, e.g. -// n.Left = orderStmtInPlace(n.Left) -// free is a map that can be used to obtain temporary variables by type. -func orderStmtInPlace(n *Node, free map[string][]*Node) *Node { - var order Order - order.free = free - mark := order.markTemp() - order.stmt(n) - order.cleanTemp(mark) - return liststmt(order.out) -} - -// init moves n's init list to o.out. -func (o *Order) init(n *Node) { - if n.mayBeShared() { - // For concurrency safety, don't mutate potentially shared nodes. - // First, ensure that no work is required here. - if n.Ninit.Len() > 0 { - Fatalf("order.init shared node with ninit") - } - return - } - o.stmtList(n.Ninit) - n.Ninit.Set(nil) -} - -// call orders the call expression n. -// n.Op is OCALLMETH/OCALLFUNC/OCALLINTER or a builtin like OCOPY. -func (o *Order) call(n *Node) { - if n.Ninit.Len() > 0 { - // Caller should have already called o.init(n). - Fatalf("%v with unexpected ninit", n.Op) - } - - // Builtin functions. - if n.Op != OCALLFUNC && n.Op != OCALLMETH && n.Op != OCALLINTER { - n.Left = o.expr(n.Left, nil) - n.Right = o.expr(n.Right, nil) - o.exprList(n.List) - return - } - - fixVariadicCall(n) - n.Left = o.expr(n.Left, nil) - o.exprList(n.List) - - if n.Op == OCALLINTER { - return - } - keepAlive := func(arg *Node) { - // If the argument is really a pointer being converted to uintptr, - // arrange for the pointer to be kept alive until the call returns, - // by copying it into a temp and marking that temp - // still alive when we pop the temp stack. - if arg.Op == OCONVNOP && arg.Left.Type.IsUnsafePtr() { - x := o.copyExpr(arg.Left, arg.Left.Type, false) - arg.Left = x - x.Name.SetAddrtaken(true) // ensure SSA keeps the x variable - n.Nbody.Append(typecheck(nod(OVARLIVE, x, nil), ctxStmt)) - } - } - - // Check for "unsafe-uintptr" tag provided by escape analysis. - for i, param := range n.Left.Type.Params().FieldSlice() { - if param.Note == unsafeUintptrTag || param.Note == uintptrEscapesTag { - if arg := n.List.Index(i); arg.Op == OSLICELIT { - for _, elt := range arg.List.Slice() { - keepAlive(elt) - } - } else { - keepAlive(arg) - } - } - } -} - -// mapAssign appends n to o.out, introducing temporaries -// to make sure that all map assignments have the form m[k] = x. -// (Note: expr has already been called on n, so we know k is addressable.) -// -// If n is the multiple assignment form ..., m[k], ... = ..., x, ..., the rewrite is -// t1 = m -// t2 = k -// ...., t3, ... = ..., x, ... -// t1[t2] = t3 -// -// The temporaries t1, t2 are needed in case the ... being assigned -// contain m or k. They are usually unnecessary, but in the unnecessary -// cases they are also typically registerizable, so not much harm done. -// And this only applies to the multiple-assignment form. -// We could do a more precise analysis if needed, like in walk.go. -func (o *Order) mapAssign(n *Node) { - switch n.Op { - default: - Fatalf("order.mapAssign %v", n.Op) - - case OAS, OASOP: - if n.Left.Op == OINDEXMAP { - // Make sure we evaluate the RHS before starting the map insert. - // We need to make sure the RHS won't panic. See issue 22881. - if n.Right.Op == OAPPEND { - s := n.Right.List.Slice()[1:] - for i, n := range s { - s[i] = o.cheapExpr(n) - } - } else { - n.Right = o.cheapExpr(n.Right) - } - } - o.out = append(o.out, n) - - case OAS2, OAS2DOTTYPE, OAS2MAPR, OAS2FUNC: - var post []*Node - for i, m := range n.List.Slice() { - switch { - case m.Op == OINDEXMAP: - if !m.Left.IsAutoTmp() { - m.Left = o.copyExpr(m.Left, m.Left.Type, false) - } - if !m.Right.IsAutoTmp() { - m.Right = o.copyExpr(m.Right, m.Right.Type, false) - } - fallthrough - case instrumenting && n.Op == OAS2FUNC && !m.isBlank(): - t := o.newTemp(m.Type, false) - n.List.SetIndex(i, t) - a := nod(OAS, m, t) - a = typecheck(a, ctxStmt) - post = append(post, a) - } - } - - o.out = append(o.out, n) - o.out = append(o.out, post...) - } -} - -// stmt orders the statement n, appending to o.out. -// Temporaries created during the statement are cleaned -// up using VARKILL instructions as possible. -func (o *Order) stmt(n *Node) { - if n == nil { - return - } - - lno := setlineno(n) - o.init(n) - - switch n.Op { - default: - Fatalf("order.stmt %v", n.Op) - - case OVARKILL, OVARLIVE, OINLMARK: - o.out = append(o.out, n) - - case OAS: - t := o.markTemp() - n.Left = o.expr(n.Left, nil) - n.Right = o.expr(n.Right, n.Left) - o.mapAssign(n) - o.cleanTemp(t) - - case OASOP: - t := o.markTemp() - n.Left = o.expr(n.Left, nil) - n.Right = o.expr(n.Right, nil) - - if instrumenting || n.Left.Op == OINDEXMAP && (n.SubOp() == ODIV || n.SubOp() == OMOD) { - // Rewrite m[k] op= r into m[k] = m[k] op r so - // that we can ensure that if op panics - // because r is zero, the panic happens before - // the map assignment. - - n.Left = o.safeExpr(n.Left) - - l := treecopy(n.Left, src.NoXPos) - if l.Op == OINDEXMAP { - l.SetIndexMapLValue(false) - } - l = o.copyExpr(l, n.Left.Type, false) - n.Right = nod(n.SubOp(), l, n.Right) - n.Right = typecheck(n.Right, ctxExpr) - n.Right = o.expr(n.Right, nil) - - n.Op = OAS - n.ResetAux() - } - - o.mapAssign(n) - o.cleanTemp(t) - - case OAS2: - t := o.markTemp() - o.exprList(n.List) - o.exprList(n.Rlist) - o.mapAssign(n) - o.cleanTemp(t) - - // Special: avoid copy of func call n.Right - case OAS2FUNC: - t := o.markTemp() - o.exprList(n.List) - o.init(n.Right) - o.call(n.Right) - o.as2(n) - o.cleanTemp(t) - - // Special: use temporary variables to hold result, - // so that runtime can take address of temporary. - // No temporary for blank assignment. - // - // OAS2MAPR: make sure key is addressable if needed, - // and make sure OINDEXMAP is not copied out. - case OAS2DOTTYPE, OAS2RECV, OAS2MAPR: - t := o.markTemp() - o.exprList(n.List) - - switch r := n.Right; r.Op { - case ODOTTYPE2, ORECV: - r.Left = o.expr(r.Left, nil) - case OINDEXMAP: - r.Left = o.expr(r.Left, nil) - r.Right = o.expr(r.Right, nil) - // See similar conversion for OINDEXMAP below. - _ = mapKeyReplaceStrConv(r.Right) - r.Right = o.mapKeyTemp(r.Left.Type, r.Right) - default: - Fatalf("order.stmt: %v", r.Op) - } - - o.okAs2(n) - o.cleanTemp(t) - - // Special: does not save n onto out. - case OBLOCK, OEMPTY: - o.stmtList(n.List) - - // Special: n->left is not an expression; save as is. - case OBREAK, - OCONTINUE, - ODCL, - ODCLCONST, - ODCLTYPE, - OFALL, - OGOTO, - OLABEL, - ORETJMP: - o.out = append(o.out, n) - - // Special: handle call arguments. - case OCALLFUNC, OCALLINTER, OCALLMETH: - t := o.markTemp() - o.call(n) - o.out = append(o.out, n) - o.cleanTemp(t) - - case OCLOSE, - OCOPY, - OPRINT, - OPRINTN, - ORECOVER, - ORECV: - t := o.markTemp() - n.Left = o.expr(n.Left, nil) - n.Right = o.expr(n.Right, nil) - o.exprList(n.List) - o.exprList(n.Rlist) - o.out = append(o.out, n) - o.cleanTemp(t) - - // Special: order arguments to inner call but not call itself. - case ODEFER, OGO: - t := o.markTemp() - o.init(n.Left) - o.call(n.Left) - o.out = append(o.out, n) - o.cleanTemp(t) - - case ODELETE: - t := o.markTemp() - n.List.SetFirst(o.expr(n.List.First(), nil)) - n.List.SetSecond(o.expr(n.List.Second(), nil)) - n.List.SetSecond(o.mapKeyTemp(n.List.First().Type, n.List.Second())) - o.out = append(o.out, n) - o.cleanTemp(t) - - // Clean temporaries from condition evaluation at - // beginning of loop body and after for statement. - case OFOR: - t := o.markTemp() - n.Left = o.exprInPlace(n.Left) - n.Nbody.Prepend(o.cleanTempNoPop(t)...) - orderBlock(&n.Nbody, o.free) - n.Right = orderStmtInPlace(n.Right, o.free) - o.out = append(o.out, n) - o.cleanTemp(t) - - // Clean temporaries from condition at - // beginning of both branches. - case OIF: - t := o.markTemp() - n.Left = o.exprInPlace(n.Left) - n.Nbody.Prepend(o.cleanTempNoPop(t)...) - n.Rlist.Prepend(o.cleanTempNoPop(t)...) - o.popTemp(t) - orderBlock(&n.Nbody, o.free) - orderBlock(&n.Rlist, o.free) - o.out = append(o.out, n) - - // Special: argument will be converted to interface using convT2E - // so make sure it is an addressable temporary. - case OPANIC: - t := o.markTemp() - n.Left = o.expr(n.Left, nil) - if !n.Left.Type.IsInterface() { - n.Left = o.addrTemp(n.Left) - } - o.out = append(o.out, n) - o.cleanTemp(t) - - case ORANGE: - // n.Right is the expression being ranged over. - // order it, and then make a copy if we need one. - // We almost always do, to ensure that we don't - // see any value changes made during the loop. - // Usually the copy is cheap (e.g., array pointer, - // chan, slice, string are all tiny). - // The exception is ranging over an array value - // (not a slice, not a pointer to array), - // which must make a copy to avoid seeing updates made during - // the range body. Ranging over an array value is uncommon though. - - // Mark []byte(str) range expression to reuse string backing storage. - // It is safe because the storage cannot be mutated. - if n.Right.Op == OSTR2BYTES { - n.Right.Op = OSTR2BYTESTMP - } - - t := o.markTemp() - n.Right = o.expr(n.Right, nil) - - orderBody := true - switch n.Type.Etype { - default: - Fatalf("order.stmt range %v", n.Type) - - case TARRAY, TSLICE: - if n.List.Len() < 2 || n.List.Second().isBlank() { - // for i := range x will only use x once, to compute len(x). - // No need to copy it. - break - } - fallthrough - - case TCHAN, TSTRING: - // chan, string, slice, array ranges use value multiple times. - // make copy. - r := n.Right - - if r.Type.IsString() && r.Type != types.Types[TSTRING] { - r = nod(OCONV, r, nil) - r.Type = types.Types[TSTRING] - r = typecheck(r, ctxExpr) - } - - n.Right = o.copyExpr(r, r.Type, false) - - case TMAP: - if isMapClear(n) { - // Preserve the body of the map clear pattern so it can - // be detected during walk. The loop body will not be used - // when optimizing away the range loop to a runtime call. - orderBody = false - break - } - - // copy the map value in case it is a map literal. - // TODO(rsc): Make tmp = literal expressions reuse tmp. - // For maps tmp is just one word so it hardly matters. - r := n.Right - n.Right = o.copyExpr(r, r.Type, false) - - // prealloc[n] is the temp for the iterator. - // hiter contains pointers and needs to be zeroed. - prealloc[n] = o.newTemp(hiter(n.Type), true) - } - o.exprListInPlace(n.List) - if orderBody { - orderBlock(&n.Nbody, o.free) - } - o.out = append(o.out, n) - o.cleanTemp(t) - - case ORETURN: - o.exprList(n.List) - o.out = append(o.out, n) - - // Special: clean case temporaries in each block entry. - // Select must enter one of its blocks, so there is no - // need for a cleaning at the end. - // Doubly special: evaluation order for select is stricter - // than ordinary expressions. Even something like p.c - // has to be hoisted into a temporary, so that it cannot be - // reordered after the channel evaluation for a different - // case (if p were nil, then the timing of the fault would - // give this away). - case OSELECT: - t := o.markTemp() - - for _, n2 := range n.List.Slice() { - if n2.Op != OCASE { - Fatalf("order select case %v", n2.Op) - } - r := n2.Left - setlineno(n2) - - // Append any new body prologue to ninit. - // The next loop will insert ninit into nbody. - if n2.Ninit.Len() != 0 { - Fatalf("order select ninit") - } - if r == nil { - continue - } - switch r.Op { - default: - Dump("select case", r) - Fatalf("unknown op in select %v", r.Op) - - // If this is case x := <-ch or case x, y := <-ch, the case has - // the ODCL nodes to declare x and y. We want to delay that - // declaration (and possible allocation) until inside the case body. - // Delete the ODCL nodes here and recreate them inside the body below. - case OSELRECV, OSELRECV2: - if r.Colas() { - i := 0 - if r.Ninit.Len() != 0 && r.Ninit.First().Op == ODCL && r.Ninit.First().Left == r.Left { - i++ - } - if i < r.Ninit.Len() && r.Ninit.Index(i).Op == ODCL && r.List.Len() != 0 && r.Ninit.Index(i).Left == r.List.First() { - i++ - } - if i >= r.Ninit.Len() { - r.Ninit.Set(nil) - } - } - - if r.Ninit.Len() != 0 { - dumplist("ninit", r.Ninit) - Fatalf("ninit on select recv") - } - - // case x = <-c - // case x, ok = <-c - // r->left is x, r->ntest is ok, r->right is ORECV, r->right->left is c. - // r->left == N means 'case <-c'. - // c is always evaluated; x and ok are only evaluated when assigned. - r.Right.Left = o.expr(r.Right.Left, nil) - - if !r.Right.Left.IsAutoTmp() { - r.Right.Left = o.copyExpr(r.Right.Left, r.Right.Left.Type, false) - } - - // Introduce temporary for receive and move actual copy into case body. - // avoids problems with target being addressed, as usual. - // NOTE: If we wanted to be clever, we could arrange for just one - // temporary per distinct type, sharing the temp among all receives - // with that temp. Similarly one ok bool could be shared among all - // the x,ok receives. Not worth doing until there's a clear need. - if r.Left != nil && r.Left.isBlank() { - r.Left = nil - } - if r.Left != nil { - // use channel element type for temporary to avoid conversions, - // such as in case interfacevalue = <-intchan. - // the conversion happens in the OAS instead. - tmp1 := r.Left - - if r.Colas() { - tmp2 := nod(ODCL, tmp1, nil) - tmp2 = typecheck(tmp2, ctxStmt) - n2.Ninit.Append(tmp2) - } - - r.Left = o.newTemp(r.Right.Left.Type.Elem(), r.Right.Left.Type.Elem().HasPointers()) - tmp2 := nod(OAS, tmp1, r.Left) - tmp2 = typecheck(tmp2, ctxStmt) - n2.Ninit.Append(tmp2) - } - - if r.List.Len() != 0 && r.List.First().isBlank() { - r.List.Set(nil) - } - if r.List.Len() != 0 { - tmp1 := r.List.First() - if r.Colas() { - tmp2 := nod(ODCL, tmp1, nil) - tmp2 = typecheck(tmp2, ctxStmt) - n2.Ninit.Append(tmp2) - } - - r.List.Set1(o.newTemp(types.Types[TBOOL], false)) - tmp2 := okas(tmp1, r.List.First()) - tmp2 = typecheck(tmp2, ctxStmt) - n2.Ninit.Append(tmp2) - } - orderBlock(&n2.Ninit, o.free) - - case OSEND: - if r.Ninit.Len() != 0 { - dumplist("ninit", r.Ninit) - Fatalf("ninit on select send") - } - - // case c <- x - // r->left is c, r->right is x, both are always evaluated. - r.Left = o.expr(r.Left, nil) - - if !r.Left.IsAutoTmp() { - r.Left = o.copyExpr(r.Left, r.Left.Type, false) - } - r.Right = o.expr(r.Right, nil) - if !r.Right.IsAutoTmp() { - r.Right = o.copyExpr(r.Right, r.Right.Type, false) - } - } - } - // Now that we have accumulated all the temporaries, clean them. - // Also insert any ninit queued during the previous loop. - // (The temporary cleaning must follow that ninit work.) - for _, n3 := range n.List.Slice() { - orderBlock(&n3.Nbody, o.free) - n3.Nbody.Prepend(o.cleanTempNoPop(t)...) - - // TODO(mdempsky): Is this actually necessary? - // walkselect appears to walk Ninit. - n3.Nbody.Prepend(n3.Ninit.Slice()...) - n3.Ninit.Set(nil) - } - - o.out = append(o.out, n) - o.popTemp(t) - - // Special: value being sent is passed as a pointer; make it addressable. - case OSEND: - t := o.markTemp() - n.Left = o.expr(n.Left, nil) - n.Right = o.expr(n.Right, nil) - if instrumenting { - // Force copying to the stack so that (chan T)(nil) <- x - // is still instrumented as a read of x. - n.Right = o.copyExpr(n.Right, n.Right.Type, false) - } else { - n.Right = o.addrTemp(n.Right) - } - o.out = append(o.out, n) - o.cleanTemp(t) - - // TODO(rsc): Clean temporaries more aggressively. - // Note that because walkswitch will rewrite some of the - // switch into a binary search, this is not as easy as it looks. - // (If we ran that code here we could invoke order.stmt on - // the if-else chain instead.) - // For now just clean all the temporaries at the end. - // In practice that's fine. - case OSWITCH: - if Debug_libfuzzer != 0 && !hasDefaultCase(n) { - // Add empty "default:" case for instrumentation. - n.List.Append(nod(OCASE, nil, nil)) - } - - t := o.markTemp() - n.Left = o.expr(n.Left, nil) - for _, ncas := range n.List.Slice() { - if ncas.Op != OCASE { - Fatalf("order switch case %v", ncas.Op) - } - o.exprListInPlace(ncas.List) - orderBlock(&ncas.Nbody, o.free) - } - - o.out = append(o.out, n) - o.cleanTemp(t) - } - - lineno = lno -} - -func hasDefaultCase(n *Node) bool { - for _, ncas := range n.List.Slice() { - if ncas.Op != OCASE { - Fatalf("expected case, found %v", ncas.Op) - } - if ncas.List.Len() == 0 { - return true - } - } - return false -} - -// exprList orders the expression list l into o. -func (o *Order) exprList(l Nodes) { - s := l.Slice() - for i := range s { - s[i] = o.expr(s[i], nil) - } -} - -// exprListInPlace orders the expression list l but saves -// the side effects on the individual expression ninit lists. -func (o *Order) exprListInPlace(l Nodes) { - s := l.Slice() - for i := range s { - s[i] = o.exprInPlace(s[i]) - } -} - -// prealloc[x] records the allocation to use for x. -var prealloc = map[*Node]*Node{} - -// expr orders a single expression, appending side -// effects to o.out as needed. -// If this is part of an assignment lhs = *np, lhs is given. -// Otherwise lhs == nil. (When lhs != nil it may be possible -// to avoid copying the result of the expression to a temporary.) -// The result of expr MUST be assigned back to n, e.g. -// n.Left = o.expr(n.Left, lhs) -func (o *Order) expr(n, lhs *Node) *Node { - if n == nil { - return n - } - - lno := setlineno(n) - o.init(n) - - switch n.Op { - default: - n.Left = o.expr(n.Left, nil) - n.Right = o.expr(n.Right, nil) - o.exprList(n.List) - o.exprList(n.Rlist) - - // Addition of strings turns into a function call. - // Allocate a temporary to hold the strings. - // Fewer than 5 strings use direct runtime helpers. - case OADDSTR: - o.exprList(n.List) - - if n.List.Len() > 5 { - t := types.NewArray(types.Types[TSTRING], int64(n.List.Len())) - prealloc[n] = o.newTemp(t, false) - } - - // Mark string(byteSlice) arguments to reuse byteSlice backing - // buffer during conversion. String concatenation does not - // memorize the strings for later use, so it is safe. - // However, we can do it only if there is at least one non-empty string literal. - // Otherwise if all other arguments are empty strings, - // concatstrings will return the reference to the temp string - // to the caller. - hasbyte := false - - haslit := false - for _, n1 := range n.List.Slice() { - hasbyte = hasbyte || n1.Op == OBYTES2STR - haslit = haslit || n1.Op == OLITERAL && len(n1.StringVal()) != 0 - } - - if haslit && hasbyte { - for _, n2 := range n.List.Slice() { - if n2.Op == OBYTES2STR { - n2.Op = OBYTES2STRTMP - } - } - } - - case OINDEXMAP: - n.Left = o.expr(n.Left, nil) - n.Right = o.expr(n.Right, nil) - needCopy := false - - if !n.IndexMapLValue() { - // Enforce that any []byte slices we are not copying - // can not be changed before the map index by forcing - // the map index to happen immediately following the - // conversions. See copyExpr a few lines below. - needCopy = mapKeyReplaceStrConv(n.Right) - - if instrumenting { - // Race detector needs the copy so it can - // call treecopy on the result. - needCopy = true - } - } - - // key must be addressable - n.Right = o.mapKeyTemp(n.Left.Type, n.Right) - if needCopy { - n = o.copyExpr(n, n.Type, false) - } - - // concrete type (not interface) argument might need an addressable - // temporary to pass to the runtime conversion routine. - case OCONVIFACE: - n.Left = o.expr(n.Left, nil) - if n.Left.Type.IsInterface() { - break - } - if _, needsaddr := convFuncName(n.Left.Type, n.Type); needsaddr || isStaticCompositeLiteral(n.Left) { - // Need a temp if we need to pass the address to the conversion function. - // We also process static composite literal node here, making a named static global - // whose address we can put directly in an interface (see OCONVIFACE case in walk). - n.Left = o.addrTemp(n.Left) - } - - case OCONVNOP: - if n.Type.IsKind(TUNSAFEPTR) && n.Left.Type.IsKind(TUINTPTR) && (n.Left.Op == OCALLFUNC || n.Left.Op == OCALLINTER || n.Left.Op == OCALLMETH) { - // When reordering unsafe.Pointer(f()) into a separate - // statement, the conversion and function call must stay - // together. See golang.org/issue/15329. - o.init(n.Left) - o.call(n.Left) - if lhs == nil || lhs.Op != ONAME || instrumenting { - n = o.copyExpr(n, n.Type, false) - } - } else { - n.Left = o.expr(n.Left, nil) - } - - case OANDAND, OOROR: - // ... = LHS && RHS - // - // var r bool - // r = LHS - // if r { // or !r, for OROR - // r = RHS - // } - // ... = r - - r := o.newTemp(n.Type, false) - - // Evaluate left-hand side. - lhs := o.expr(n.Left, nil) - o.out = append(o.out, typecheck(nod(OAS, r, lhs), ctxStmt)) - - // Evaluate right-hand side, save generated code. - saveout := o.out - o.out = nil - t := o.markTemp() - o.edge() - rhs := o.expr(n.Right, nil) - o.out = append(o.out, typecheck(nod(OAS, r, rhs), ctxStmt)) - o.cleanTemp(t) - gen := o.out - o.out = saveout - - // If left-hand side doesn't cause a short-circuit, issue right-hand side. - nif := nod(OIF, r, nil) - if n.Op == OANDAND { - nif.Nbody.Set(gen) - } else { - nif.Rlist.Set(gen) - } - o.out = append(o.out, nif) - n = r - - case OCALLFUNC, - OCALLINTER, - OCALLMETH, - OCAP, - OCOMPLEX, - OCOPY, - OIMAG, - OLEN, - OMAKECHAN, - OMAKEMAP, - OMAKESLICE, - OMAKESLICECOPY, - ONEW, - OREAL, - ORECOVER, - OSTR2BYTES, - OSTR2BYTESTMP, - OSTR2RUNES: - - if isRuneCount(n) { - // len([]rune(s)) is rewritten to runtime.countrunes(s) later. - n.Left.Left = o.expr(n.Left.Left, nil) - } else { - o.call(n) - } - - if lhs == nil || lhs.Op != ONAME || instrumenting { - n = o.copyExpr(n, n.Type, false) - } - - case OAPPEND: - // Check for append(x, make([]T, y)...) . - if isAppendOfMake(n) { - n.List.SetFirst(o.expr(n.List.First(), nil)) // order x - n.List.Second().Left = o.expr(n.List.Second().Left, nil) // order y - } else { - o.exprList(n.List) - } - - if lhs == nil || lhs.Op != ONAME && !samesafeexpr(lhs, n.List.First()) { - n = o.copyExpr(n, n.Type, false) - } - - case OSLICE, OSLICEARR, OSLICESTR, OSLICE3, OSLICE3ARR: - n.Left = o.expr(n.Left, nil) - low, high, max := n.SliceBounds() - low = o.expr(low, nil) - low = o.cheapExpr(low) - high = o.expr(high, nil) - high = o.cheapExpr(high) - max = o.expr(max, nil) - max = o.cheapExpr(max) - n.SetSliceBounds(low, high, max) - if lhs == nil || lhs.Op != ONAME && !samesafeexpr(lhs, n.Left) { - n = o.copyExpr(n, n.Type, false) - } - - case OCLOSURE: - if n.Transient() && n.Func.Closure.Func.Cvars.Len() > 0 { - prealloc[n] = o.newTemp(closureType(n), false) - } - - case OSLICELIT, OCALLPART: - n.Left = o.expr(n.Left, nil) - n.Right = o.expr(n.Right, nil) - o.exprList(n.List) - o.exprList(n.Rlist) - if n.Transient() { - var t *types.Type - switch n.Op { - case OSLICELIT: - t = types.NewArray(n.Type.Elem(), n.Right.Int64Val()) - case OCALLPART: - t = partialCallType(n) - } - prealloc[n] = o.newTemp(t, false) - } - - case ODOTTYPE, ODOTTYPE2: - n.Left = o.expr(n.Left, nil) - if !isdirectiface(n.Type) || instrumenting { - n = o.copyExpr(n, n.Type, true) - } - - case ORECV: - n.Left = o.expr(n.Left, nil) - n = o.copyExpr(n, n.Type, true) - - case OEQ, ONE, OLT, OLE, OGT, OGE: - n.Left = o.expr(n.Left, nil) - n.Right = o.expr(n.Right, nil) - - t := n.Left.Type - switch { - case t.IsString(): - // Mark string(byteSlice) arguments to reuse byteSlice backing - // buffer during conversion. String comparison does not - // memorize the strings for later use, so it is safe. - if n.Left.Op == OBYTES2STR { - n.Left.Op = OBYTES2STRTMP - } - if n.Right.Op == OBYTES2STR { - n.Right.Op = OBYTES2STRTMP - } - - case t.IsStruct() || t.IsArray(): - // for complex comparisons, we need both args to be - // addressable so we can pass them to the runtime. - n.Left = o.addrTemp(n.Left) - n.Right = o.addrTemp(n.Right) - } - case OMAPLIT: - // Order map by converting: - // map[int]int{ - // a(): b(), - // c(): d(), - // e(): f(), - // } - // to - // m := map[int]int{} - // m[a()] = b() - // m[c()] = d() - // m[e()] = f() - // Then order the result. - // Without this special case, order would otherwise compute all - // the keys and values before storing any of them to the map. - // See issue 26552. - entries := n.List.Slice() - statics := entries[:0] - var dynamics []*Node - for _, r := range entries { - if r.Op != OKEY { - Fatalf("OMAPLIT entry not OKEY: %v\n", r) - } - - if !isStaticCompositeLiteral(r.Left) || !isStaticCompositeLiteral(r.Right) { - dynamics = append(dynamics, r) - continue - } - - // Recursively ordering some static entries can change them to dynamic; - // e.g., OCONVIFACE nodes. See #31777. - r = o.expr(r, nil) - if !isStaticCompositeLiteral(r.Left) || !isStaticCompositeLiteral(r.Right) { - dynamics = append(dynamics, r) - continue - } - - statics = append(statics, r) - } - n.List.Set(statics) - - if len(dynamics) == 0 { - break - } - - // Emit the creation of the map (with all its static entries). - m := o.newTemp(n.Type, false) - as := nod(OAS, m, n) - typecheck(as, ctxStmt) - o.stmt(as) - n = m - - // Emit eval+insert of dynamic entries, one at a time. - for _, r := range dynamics { - as := nod(OAS, nod(OINDEX, n, r.Left), r.Right) - typecheck(as, ctxStmt) // Note: this converts the OINDEX to an OINDEXMAP - o.stmt(as) - } - } - - lineno = lno - return n -} - -// okas creates and returns an assignment of val to ok, -// including an explicit conversion if necessary. -func okas(ok, val *Node) *Node { - if !ok.isBlank() { - val = conv(val, ok.Type) - } - return nod(OAS, ok, val) -} - -// as2 orders OAS2XXXX nodes. It creates temporaries to ensure left-to-right assignment. -// The caller should order the right-hand side of the assignment before calling order.as2. -// It rewrites, -// a, b, a = ... -// as -// tmp1, tmp2, tmp3 = ... -// a, b, a = tmp1, tmp2, tmp3 -// This is necessary to ensure left to right assignment order. -func (o *Order) as2(n *Node) { - tmplist := []*Node{} - left := []*Node{} - for ni, l := range n.List.Slice() { - if !l.isBlank() { - tmp := o.newTemp(l.Type, l.Type.HasPointers()) - n.List.SetIndex(ni, tmp) - tmplist = append(tmplist, tmp) - left = append(left, l) - } - } - - o.out = append(o.out, n) - - as := nod(OAS2, nil, nil) - as.List.Set(left) - as.Rlist.Set(tmplist) - as = typecheck(as, ctxStmt) - o.stmt(as) -} - -// okAs2 orders OAS2XXX with ok. -// Just like as2, this also adds temporaries to ensure left-to-right assignment. -func (o *Order) okAs2(n *Node) { - var tmp1, tmp2 *Node - if !n.List.First().isBlank() { - typ := n.Right.Type - tmp1 = o.newTemp(typ, typ.HasPointers()) - } - - if !n.List.Second().isBlank() { - tmp2 = o.newTemp(types.Types[TBOOL], false) - } - - o.out = append(o.out, n) - - if tmp1 != nil { - r := nod(OAS, n.List.First(), tmp1) - r = typecheck(r, ctxStmt) - o.mapAssign(r) - n.List.SetFirst(tmp1) - } - if tmp2 != nil { - r := okas(n.List.Second(), tmp2) - r = typecheck(r, ctxStmt) - o.mapAssign(r) - n.List.SetSecond(tmp2) - } -} diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go deleted file mode 100644 index 353f4b08c9dcb3b3ec91b8f0788e316c0be9874f..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/pgen.go +++ /dev/null @@ -1,798 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/ssa" - "cmd/compile/internal/types" - "cmd/internal/dwarf" - "cmd/internal/obj" - "cmd/internal/objabi" - "cmd/internal/src" - "cmd/internal/sys" - "internal/race" - "math/rand" - "sort" - "sync" - "time" -) - -// "Portable" code generation. - -var ( - nBackendWorkers int // number of concurrent backend workers, set by a compiler flag - compilequeue []*Node // functions waiting to be compiled -) - -func emitptrargsmap(fn *Node) { - if fn.funcname() == "_" || fn.Func.Nname.Sym.Linkname != "" { - return - } - lsym := Ctxt.Lookup(fn.Func.lsym.Name + ".args_stackmap") - - nptr := int(fn.Type.ArgWidth() / int64(Widthptr)) - bv := bvalloc(int32(nptr) * 2) - nbitmap := 1 - if fn.Type.NumResults() > 0 { - nbitmap = 2 - } - off := duint32(lsym, 0, uint32(nbitmap)) - off = duint32(lsym, off, uint32(bv.n)) - - if fn.IsMethod() { - onebitwalktype1(fn.Type.Recvs(), 0, bv) - } - if fn.Type.NumParams() > 0 { - onebitwalktype1(fn.Type.Params(), 0, bv) - } - off = dbvec(lsym, off, bv) - - if fn.Type.NumResults() > 0 { - onebitwalktype1(fn.Type.Results(), 0, bv) - off = dbvec(lsym, off, bv) - } - - ggloblsym(lsym, int32(off), obj.RODATA|obj.LOCAL) -} - -// cmpstackvarlt reports whether the stack variable a sorts before b. -// -// Sort the list of stack variables. Autos after anything else, -// within autos, unused after used, within used, things with -// pointers first, zeroed things first, and then decreasing size. -// Because autos are laid out in decreasing addresses -// on the stack, pointers first, zeroed things first and decreasing size -// really means, in memory, things with pointers needing zeroing at -// the top of the stack and increasing in size. -// Non-autos sort on offset. -func cmpstackvarlt(a, b *Node) bool { - if (a.Class() == PAUTO) != (b.Class() == PAUTO) { - return b.Class() == PAUTO - } - - if a.Class() != PAUTO { - return a.Xoffset < b.Xoffset - } - - if a.Name.Used() != b.Name.Used() { - return a.Name.Used() - } - - ap := a.Type.HasPointers() - bp := b.Type.HasPointers() - if ap != bp { - return ap - } - - ap = a.Name.Needzero() - bp = b.Name.Needzero() - if ap != bp { - return ap - } - - if a.Type.Width != b.Type.Width { - return a.Type.Width > b.Type.Width - } - - return a.Sym.Name < b.Sym.Name -} - -// byStackvar implements sort.Interface for []*Node using cmpstackvarlt. -type byStackVar []*Node - -func (s byStackVar) Len() int { return len(s) } -func (s byStackVar) Less(i, j int) bool { return cmpstackvarlt(s[i], s[j]) } -func (s byStackVar) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func (s *ssafn) AllocFrame(f *ssa.Func) { - s.stksize = 0 - s.stkptrsize = 0 - fn := s.curfn.Func - - // Mark the PAUTO's unused. - for _, ln := range fn.Dcl { - if ln.Class() == PAUTO { - ln.Name.SetUsed(false) - } - } - - for _, l := range f.RegAlloc { - if ls, ok := l.(ssa.LocalSlot); ok { - ls.N.(*Node).Name.SetUsed(true) - } - } - - scratchUsed := false - for _, b := range f.Blocks { - for _, v := range b.Values { - if n, ok := v.Aux.(*Node); ok { - switch n.Class() { - case PPARAM, PPARAMOUT: - // Don't modify nodfp; it is a global. - if n != nodfp { - n.Name.SetUsed(true) - } - case PAUTO: - n.Name.SetUsed(true) - } - } - if !scratchUsed { - scratchUsed = v.Op.UsesScratch() - } - - } - } - - if f.Config.NeedsFpScratch && scratchUsed { - s.scratchFpMem = tempAt(src.NoXPos, s.curfn, types.Types[TUINT64]) - } - - sort.Sort(byStackVar(fn.Dcl)) - - // Reassign stack offsets of the locals that are used. - lastHasPtr := false - for i, n := range fn.Dcl { - if n.Op != ONAME || n.Class() != PAUTO { - continue - } - if !n.Name.Used() { - fn.Dcl = fn.Dcl[:i] - break - } - - dowidth(n.Type) - w := n.Type.Width - if w >= thearch.MAXWIDTH || w < 0 { - Fatalf("bad width") - } - if w == 0 && lastHasPtr { - // Pad between a pointer-containing object and a zero-sized object. - // This prevents a pointer to the zero-sized object from being interpreted - // as a pointer to the pointer-containing object (and causing it - // to be scanned when it shouldn't be). See issue 24993. - w = 1 - } - s.stksize += w - s.stksize = Rnd(s.stksize, int64(n.Type.Align)) - if n.Type.HasPointers() { - s.stkptrsize = s.stksize - lastHasPtr = true - } else { - lastHasPtr = false - } - if thearch.LinkArch.InFamily(sys.MIPS, sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64, sys.S390X) { - s.stksize = Rnd(s.stksize, int64(Widthptr)) - } - n.Xoffset = -s.stksize - } - - s.stksize = Rnd(s.stksize, int64(Widthreg)) - s.stkptrsize = Rnd(s.stkptrsize, int64(Widthreg)) -} - -func funccompile(fn *Node) { - if Curfn != nil { - Fatalf("funccompile %v inside %v", fn.Func.Nname.Sym, Curfn.Func.Nname.Sym) - } - - if fn.Type == nil { - if nerrors == 0 { - Fatalf("funccompile missing type") - } - return - } - - // assign parameter offsets - dowidth(fn.Type) - - if fn.Nbody.Len() == 0 { - // Initialize ABI wrappers if necessary. - fn.Func.initLSym(false) - emitptrargsmap(fn) - return - } - - dclcontext = PAUTO - Curfn = fn - - compile(fn) - - Curfn = nil - dclcontext = PEXTERN -} - -func compile(fn *Node) { - saveerrors() - - order(fn) - if nerrors != 0 { - return - } - - // Set up the function's LSym early to avoid data races with the assemblers. - // Do this before walk, as walk needs the LSym to set attributes/relocations - // (e.g. in markTypeUsedInInterface). - fn.Func.initLSym(true) - - walk(fn) - if nerrors != 0 { - return - } - if instrumenting { - instrument(fn) - } - - // From this point, there should be no uses of Curfn. Enforce that. - Curfn = nil - - if fn.funcname() == "_" { - // We don't need to generate code for this function, just report errors in its body. - // At this point we've generated any errors needed. - // (Beyond here we generate only non-spec errors, like "stack frame too large".) - // See issue 29870. - return - } - - // Make sure type syms are declared for all types that might - // be types of stack objects. We need to do this here - // because symbols must be allocated before the parallel - // phase of the compiler. - for _, n := range fn.Func.Dcl { - switch n.Class() { - case PPARAM, PPARAMOUT, PAUTO: - if livenessShouldTrack(n) && n.Name.Addrtaken() { - dtypesym(n.Type) - // Also make sure we allocate a linker symbol - // for the stack object data, for the same reason. - if fn.Func.lsym.Func().StackObjects == nil { - fn.Func.lsym.Func().StackObjects = Ctxt.Lookup(fn.Func.lsym.Name + ".stkobj") - } - } - } - } - - if compilenow(fn) { - compileSSA(fn, 0) - } else { - compilequeue = append(compilequeue, fn) - } -} - -// compilenow reports whether to compile immediately. -// If functions are not compiled immediately, -// they are enqueued in compilequeue, -// which is drained by compileFunctions. -func compilenow(fn *Node) bool { - // Issue 38068: if this function is a method AND an inline - // candidate AND was not inlined (yet), put it onto the compile - // queue instead of compiling it immediately. This is in case we - // wind up inlining it into a method wrapper that is generated by - // compiling a function later on in the xtop list. - if fn.IsMethod() && isInlinableButNotInlined(fn) { - return false - } - return nBackendWorkers == 1 && Debug_compilelater == 0 -} - -// isInlinableButNotInlined returns true if 'fn' was marked as an -// inline candidate but then never inlined (presumably because we -// found no call sites). -func isInlinableButNotInlined(fn *Node) bool { - if fn.Func.Nname.Func.Inl == nil { - return false - } - if fn.Sym == nil { - return true - } - return !fn.Sym.Linksym().WasInlined() -} - -const maxStackSize = 1 << 30 - -// compileSSA builds an SSA backend function, -// uses it to generate a plist, -// and flushes that plist to machine code. -// worker indicates which of the backend workers is doing the processing. -func compileSSA(fn *Node, worker int) { - f := buildssa(fn, worker) - // Note: check arg size to fix issue 25507. - if f.Frontend().(*ssafn).stksize >= maxStackSize || fn.Type.ArgWidth() >= maxStackSize { - largeStackFramesMu.Lock() - largeStackFrames = append(largeStackFrames, largeStack{locals: f.Frontend().(*ssafn).stksize, args: fn.Type.ArgWidth(), pos: fn.Pos}) - largeStackFramesMu.Unlock() - return - } - pp := newProgs(fn, worker) - defer pp.Free() - genssa(f, pp) - // Check frame size again. - // The check above included only the space needed for local variables. - // After genssa, the space needed includes local variables and the callee arg region. - // We must do this check prior to calling pp.Flush. - // If there are any oversized stack frames, - // the assembler may emit inscrutable complaints about invalid instructions. - if pp.Text.To.Offset >= maxStackSize { - largeStackFramesMu.Lock() - locals := f.Frontend().(*ssafn).stksize - largeStackFrames = append(largeStackFrames, largeStack{locals: locals, args: fn.Type.ArgWidth(), callee: pp.Text.To.Offset - locals, pos: fn.Pos}) - largeStackFramesMu.Unlock() - return - } - - pp.Flush() // assemble, fill in boilerplate, etc. - // fieldtrack must be called after pp.Flush. See issue 20014. - fieldtrack(pp.Text.From.Sym, fn.Func.FieldTrack) -} - -func init() { - if race.Enabled { - rand.Seed(time.Now().UnixNano()) - } -} - -// compileFunctions compiles all functions in compilequeue. -// It fans out nBackendWorkers to do the work -// and waits for them to complete. -func compileFunctions() { - if len(compilequeue) != 0 { - sizeCalculationDisabled = true // not safe to calculate sizes concurrently - if race.Enabled { - // Randomize compilation order to try to shake out races. - tmp := make([]*Node, len(compilequeue)) - perm := rand.Perm(len(compilequeue)) - for i, v := range perm { - tmp[v] = compilequeue[i] - } - copy(compilequeue, tmp) - } else { - // Compile the longest functions first, - // since they're most likely to be the slowest. - // This helps avoid stragglers. - sort.Slice(compilequeue, func(i, j int) bool { - return compilequeue[i].Nbody.Len() > compilequeue[j].Nbody.Len() - }) - } - var wg sync.WaitGroup - Ctxt.InParallel = true - c := make(chan *Node, nBackendWorkers) - for i := 0; i < nBackendWorkers; i++ { - wg.Add(1) - go func(worker int) { - for fn := range c { - compileSSA(fn, worker) - } - wg.Done() - }(i) - } - for _, fn := range compilequeue { - c <- fn - } - close(c) - compilequeue = nil - wg.Wait() - Ctxt.InParallel = false - sizeCalculationDisabled = false - } -} - -func debuginfo(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) { - fn := curfn.(*Node) - if fn.Func.Nname != nil { - if expect := fn.Func.Nname.Sym.Linksym(); fnsym != expect { - Fatalf("unexpected fnsym: %v != %v", fnsym, expect) - } - } - - var apdecls []*Node - // Populate decls for fn. - for _, n := range fn.Func.Dcl { - if n.Op != ONAME { // might be OTYPE or OLITERAL - continue - } - switch n.Class() { - case PAUTO: - if !n.Name.Used() { - // Text == nil -> generating abstract function - if fnsym.Func().Text != nil { - Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)") - } - continue - } - case PPARAM, PPARAMOUT: - default: - continue - } - apdecls = append(apdecls, n) - fnsym.Func().RecordAutoType(ngotype(n).Linksym()) - } - - decls, dwarfVars := createDwarfVars(fnsym, fn.Func, apdecls) - - // For each type referenced by the functions auto vars but not - // already referenced by a dwarf var, attach a dummy relocation to - // the function symbol to insure that the type included in DWARF - // processing during linking. - typesyms := []*obj.LSym{} - for t, _ := range fnsym.Func().Autot { - typesyms = append(typesyms, t) - } - sort.Sort(obj.BySymName(typesyms)) - for _, sym := range typesyms { - r := obj.Addrel(infosym) - r.Sym = sym - r.Type = objabi.R_USETYPE - } - fnsym.Func().Autot = nil - - var varScopes []ScopeID - for _, decl := range decls { - pos := declPos(decl) - varScopes = append(varScopes, findScope(fn.Func.Marks, pos)) - } - - scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes) - var inlcalls dwarf.InlCalls - if genDwarfInline > 0 { - inlcalls = assembleInlines(fnsym, dwarfVars) - } - return scopes, inlcalls -} - -func declPos(decl *Node) src.XPos { - if decl.Name.Defn != nil && (decl.Name.Captured() || decl.Name.Byval()) { - // It's not clear which position is correct for captured variables here: - // * decl.Pos is the wrong position for captured variables, in the inner - // function, but it is the right position in the outer function. - // * decl.Name.Defn is nil for captured variables that were arguments - // on the outer function, however the decl.Pos for those seems to be - // correct. - // * decl.Name.Defn is the "wrong" thing for variables declared in the - // header of a type switch, it's their position in the header, rather - // than the position of the case statement. In principle this is the - // right thing, but here we prefer the latter because it makes each - // instance of the header variable local to the lexical block of its - // case statement. - // This code is probably wrong for type switch variables that are also - // captured. - return decl.Name.Defn.Pos - } - return decl.Pos -} - -// createSimpleVars creates a DWARF entry for every variable declared in the -// function, claiming that they are permanently on the stack. -func createSimpleVars(fnsym *obj.LSym, apDecls []*Node) ([]*Node, []*dwarf.Var, map[*Node]bool) { - var vars []*dwarf.Var - var decls []*Node - selected := make(map[*Node]bool) - for _, n := range apDecls { - if n.IsAutoTmp() { - continue - } - - decls = append(decls, n) - vars = append(vars, createSimpleVar(fnsym, n)) - selected[n] = true - } - return decls, vars, selected -} - -func createSimpleVar(fnsym *obj.LSym, n *Node) *dwarf.Var { - var abbrev int - offs := n.Xoffset - - switch n.Class() { - case PAUTO: - abbrev = dwarf.DW_ABRV_AUTO - if Ctxt.FixedFrameSize() == 0 { - offs -= int64(Widthptr) - } - if objabi.Framepointer_enabled || objabi.GOARCH == "arm64" { - // There is a word space for FP on ARM64 even if the frame pointer is disabled - offs -= int64(Widthptr) - } - - case PPARAM, PPARAMOUT: - abbrev = dwarf.DW_ABRV_PARAM - offs += Ctxt.FixedFrameSize() - default: - Fatalf("createSimpleVar unexpected class %v for node %v", n.Class(), n) - } - - typename := dwarf.InfoPrefix + typesymname(n.Type) - delete(fnsym.Func().Autot, ngotype(n).Linksym()) - inlIndex := 0 - if genDwarfInline > 1 { - if n.Name.InlFormal() || n.Name.InlLocal() { - inlIndex = posInlIndex(n.Pos) + 1 - if n.Name.InlFormal() { - abbrev = dwarf.DW_ABRV_PARAM - } - } - } - declpos := Ctxt.InnermostPos(declPos(n)) - return &dwarf.Var{ - Name: n.Sym.Name, - IsReturnValue: n.Class() == PPARAMOUT, - IsInlFormal: n.Name.InlFormal(), - Abbrev: abbrev, - StackOffset: int32(offs), - Type: Ctxt.Lookup(typename), - DeclFile: declpos.RelFilename(), - DeclLine: declpos.RelLine(), - DeclCol: declpos.Col(), - InlIndex: int32(inlIndex), - ChildIndex: -1, - } -} - -// createComplexVars creates recomposed DWARF vars with location lists, -// suitable for describing optimized code. -func createComplexVars(fnsym *obj.LSym, fn *Func) ([]*Node, []*dwarf.Var, map[*Node]bool) { - debugInfo := fn.DebugInfo - - // Produce a DWARF variable entry for each user variable. - var decls []*Node - var vars []*dwarf.Var - ssaVars := make(map[*Node]bool) - - for varID, dvar := range debugInfo.Vars { - n := dvar.(*Node) - ssaVars[n] = true - for _, slot := range debugInfo.VarSlots[varID] { - ssaVars[debugInfo.Slots[slot].N.(*Node)] = true - } - - if dvar := createComplexVar(fnsym, fn, ssa.VarID(varID)); dvar != nil { - decls = append(decls, n) - vars = append(vars, dvar) - } - } - - return decls, vars, ssaVars -} - -// createDwarfVars process fn, returning a list of DWARF variables and the -// Nodes they represent. -func createDwarfVars(fnsym *obj.LSym, fn *Func, apDecls []*Node) ([]*Node, []*dwarf.Var) { - // Collect a raw list of DWARF vars. - var vars []*dwarf.Var - var decls []*Node - var selected map[*Node]bool - if Ctxt.Flag_locationlists && Ctxt.Flag_optimize && fn.DebugInfo != nil { - decls, vars, selected = createComplexVars(fnsym, fn) - } else { - decls, vars, selected = createSimpleVars(fnsym, apDecls) - } - - dcl := apDecls - if fnsym.WasInlined() { - dcl = preInliningDcls(fnsym) - } - - // If optimization is enabled, the list above will typically be - // missing some of the original pre-optimization variables in the - // function (they may have been promoted to registers, folded into - // constants, dead-coded away, etc). Input arguments not eligible - // for SSA optimization are also missing. Here we add back in entries - // for selected missing vars. Note that the recipe below creates a - // conservative location. The idea here is that we want to - // communicate to the user that "yes, there is a variable named X - // in this function, but no, I don't have enough information to - // reliably report its contents." - // For non-SSA-able arguments, however, the correct information - // is known -- they have a single home on the stack. - for _, n := range dcl { - if _, found := selected[n]; found { - continue - } - c := n.Sym.Name[0] - if c == '.' || n.Type.IsUntyped() { - continue - } - if n.Class() == PPARAM && !canSSAType(n.Type) { - // SSA-able args get location lists, and may move in and - // out of registers, so those are handled elsewhere. - // Autos and named output params seem to get handled - // with VARDEF, which creates location lists. - // Args not of SSA-able type are treated here; they - // are homed on the stack in a single place for the - // entire call. - vars = append(vars, createSimpleVar(fnsym, n)) - decls = append(decls, n) - continue - } - typename := dwarf.InfoPrefix + typesymname(n.Type) - decls = append(decls, n) - abbrev := dwarf.DW_ABRV_AUTO_LOCLIST - isReturnValue := (n.Class() == PPARAMOUT) - if n.Class() == PPARAM || n.Class() == PPARAMOUT { - abbrev = dwarf.DW_ABRV_PARAM_LOCLIST - } else if n.Class() == PAUTOHEAP { - // If dcl in question has been promoted to heap, do a bit - // of extra work to recover original class (auto or param); - // see issue 30908. This insures that we get the proper - // signature in the abstract function DIE, but leaves a - // misleading location for the param (we want pointer-to-heap - // and not stack). - // TODO(thanm): generate a better location expression - stackcopy := n.Name.Param.Stackcopy - if stackcopy != nil && (stackcopy.Class() == PPARAM || stackcopy.Class() == PPARAMOUT) { - abbrev = dwarf.DW_ABRV_PARAM_LOCLIST - isReturnValue = (stackcopy.Class() == PPARAMOUT) - } - } - inlIndex := 0 - if genDwarfInline > 1 { - if n.Name.InlFormal() || n.Name.InlLocal() { - inlIndex = posInlIndex(n.Pos) + 1 - if n.Name.InlFormal() { - abbrev = dwarf.DW_ABRV_PARAM_LOCLIST - } - } - } - declpos := Ctxt.InnermostPos(n.Pos) - vars = append(vars, &dwarf.Var{ - Name: n.Sym.Name, - IsReturnValue: isReturnValue, - Abbrev: abbrev, - StackOffset: int32(n.Xoffset), - Type: Ctxt.Lookup(typename), - DeclFile: declpos.RelFilename(), - DeclLine: declpos.RelLine(), - DeclCol: declpos.Col(), - InlIndex: int32(inlIndex), - ChildIndex: -1, - }) - // Record go type of to insure that it gets emitted by the linker. - fnsym.Func().RecordAutoType(ngotype(n).Linksym()) - } - - return decls, vars -} - -// Given a function that was inlined at some point during the -// compilation, return a sorted list of nodes corresponding to the -// autos/locals in that function prior to inlining. If this is a -// function that is not local to the package being compiled, then the -// names of the variables may have been "versioned" to avoid conflicts -// with local vars; disregard this versioning when sorting. -func preInliningDcls(fnsym *obj.LSym) []*Node { - fn := Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*Node) - var rdcl []*Node - for _, n := range fn.Func.Inl.Dcl { - c := n.Sym.Name[0] - // Avoid reporting "_" parameters, since if there are more than - // one, it can result in a collision later on, as in #23179. - if unversion(n.Sym.Name) == "_" || c == '.' || n.Type.IsUntyped() { - continue - } - rdcl = append(rdcl, n) - } - return rdcl -} - -// stackOffset returns the stack location of a LocalSlot relative to the -// stack pointer, suitable for use in a DWARF location entry. This has nothing -// to do with its offset in the user variable. -func stackOffset(slot ssa.LocalSlot) int32 { - n := slot.N.(*Node) - var base int64 - switch n.Class() { - case PAUTO: - if Ctxt.FixedFrameSize() == 0 { - base -= int64(Widthptr) - } - if objabi.Framepointer_enabled || objabi.GOARCH == "arm64" { - // There is a word space for FP on ARM64 even if the frame pointer is disabled - base -= int64(Widthptr) - } - case PPARAM, PPARAMOUT: - base += Ctxt.FixedFrameSize() - } - return int32(base + n.Xoffset + slot.Off) -} - -// createComplexVar builds a single DWARF variable entry and location list. -func createComplexVar(fnsym *obj.LSym, fn *Func, varID ssa.VarID) *dwarf.Var { - debug := fn.DebugInfo - n := debug.Vars[varID].(*Node) - - var abbrev int - switch n.Class() { - case PAUTO: - abbrev = dwarf.DW_ABRV_AUTO_LOCLIST - case PPARAM, PPARAMOUT: - abbrev = dwarf.DW_ABRV_PARAM_LOCLIST - default: - return nil - } - - gotype := ngotype(n).Linksym() - delete(fnsym.Func().Autot, gotype) - typename := dwarf.InfoPrefix + gotype.Name[len("type."):] - inlIndex := 0 - if genDwarfInline > 1 { - if n.Name.InlFormal() || n.Name.InlLocal() { - inlIndex = posInlIndex(n.Pos) + 1 - if n.Name.InlFormal() { - abbrev = dwarf.DW_ABRV_PARAM_LOCLIST - } - } - } - declpos := Ctxt.InnermostPos(n.Pos) - dvar := &dwarf.Var{ - Name: n.Sym.Name, - IsReturnValue: n.Class() == PPARAMOUT, - IsInlFormal: n.Name.InlFormal(), - Abbrev: abbrev, - Type: Ctxt.Lookup(typename), - // The stack offset is used as a sorting key, so for decomposed - // variables just give it the first one. It's not used otherwise. - // This won't work well if the first slot hasn't been assigned a stack - // location, but it's not obvious how to do better. - StackOffset: stackOffset(debug.Slots[debug.VarSlots[varID][0]]), - DeclFile: declpos.RelFilename(), - DeclLine: declpos.RelLine(), - DeclCol: declpos.Col(), - InlIndex: int32(inlIndex), - ChildIndex: -1, - } - list := debug.LocationLists[varID] - if len(list) != 0 { - dvar.PutLocationList = func(listSym, startPC dwarf.Sym) { - debug.PutLocationList(list, Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym)) - } - } - return dvar -} - -// fieldtrack adds R_USEFIELD relocations to fnsym to record any -// struct fields that it used. -func fieldtrack(fnsym *obj.LSym, tracked map[*types.Sym]struct{}) { - if fnsym == nil { - return - } - if objabi.Fieldtrack_enabled == 0 || len(tracked) == 0 { - return - } - - trackSyms := make([]*types.Sym, 0, len(tracked)) - for sym := range tracked { - trackSyms = append(trackSyms, sym) - } - sort.Sort(symByName(trackSyms)) - for _, sym := range trackSyms { - r := obj.Addrel(fnsym) - r.Sym = sym.Linksym() - r.Type = objabi.R_USEFIELD - } -} - -type symByName []*types.Sym - -func (a symByName) Len() int { return len(a) } -func (a symByName) Less(i, j int) bool { return a[i].Name < a[j].Name } -func (a symByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/src/cmd/compile/internal/gc/pgen_test.go b/src/cmd/compile/internal/gc/pgen_test.go deleted file mode 100644 index b1db29825c2c1efa1bc84834cbfb5eb65af5e5f5..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/pgen_test.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "reflect" - "sort" - "testing" -) - -func typeWithoutPointers() *types.Type { - t := types.New(TSTRUCT) - f := &types.Field{Type: types.New(TINT)} - t.SetFields([]*types.Field{f}) - return t -} - -func typeWithPointers() *types.Type { - t := types.New(TSTRUCT) - f := &types.Field{Type: types.NewPtr(types.New(TINT))} - t.SetFields([]*types.Field{f}) - return t -} - -func markUsed(n *Node) *Node { - n.Name.SetUsed(true) - return n -} - -func markNeedZero(n *Node) *Node { - n.Name.SetNeedzero(true) - return n -} - -func nodeWithClass(n Node, c Class) *Node { - n.SetClass(c) - n.Name = new(Name) - return &n -} - -// Test all code paths for cmpstackvarlt. -func TestCmpstackvar(t *testing.T) { - testdata := []struct { - a, b *Node - lt bool - }{ - { - nodeWithClass(Node{}, PAUTO), - nodeWithClass(Node{}, PFUNC), - false, - }, - { - nodeWithClass(Node{}, PFUNC), - nodeWithClass(Node{}, PAUTO), - true, - }, - { - nodeWithClass(Node{Xoffset: 0}, PFUNC), - nodeWithClass(Node{Xoffset: 10}, PFUNC), - true, - }, - { - nodeWithClass(Node{Xoffset: 20}, PFUNC), - nodeWithClass(Node{Xoffset: 10}, PFUNC), - false, - }, - { - nodeWithClass(Node{Xoffset: 10}, PFUNC), - nodeWithClass(Node{Xoffset: 10}, PFUNC), - false, - }, - { - nodeWithClass(Node{Xoffset: 10}, PPARAM), - nodeWithClass(Node{Xoffset: 20}, PPARAMOUT), - true, - }, - { - nodeWithClass(Node{Xoffset: 10}, PPARAMOUT), - nodeWithClass(Node{Xoffset: 20}, PPARAM), - true, - }, - { - markUsed(nodeWithClass(Node{}, PAUTO)), - nodeWithClass(Node{}, PAUTO), - true, - }, - { - nodeWithClass(Node{}, PAUTO), - markUsed(nodeWithClass(Node{}, PAUTO)), - false, - }, - { - nodeWithClass(Node{Type: typeWithoutPointers()}, PAUTO), - nodeWithClass(Node{Type: typeWithPointers()}, PAUTO), - false, - }, - { - nodeWithClass(Node{Type: typeWithPointers()}, PAUTO), - nodeWithClass(Node{Type: typeWithoutPointers()}, PAUTO), - true, - }, - { - markNeedZero(nodeWithClass(Node{Type: &types.Type{}}, PAUTO)), - nodeWithClass(Node{Type: &types.Type{}, Name: &Name{}}, PAUTO), - true, - }, - { - nodeWithClass(Node{Type: &types.Type{}, Name: &Name{}}, PAUTO), - markNeedZero(nodeWithClass(Node{Type: &types.Type{}}, PAUTO)), - false, - }, - { - nodeWithClass(Node{Type: &types.Type{Width: 1}, Name: &Name{}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{Width: 2}, Name: &Name{}}, PAUTO), - false, - }, - { - nodeWithClass(Node{Type: &types.Type{Width: 2}, Name: &Name{}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{Width: 1}, Name: &Name{}}, PAUTO), - true, - }, - { - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "xyz"}}, PAUTO), - true, - }, - { - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO), - false, - }, - { - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "xyz"}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO), - false, - }, - } - for _, d := range testdata { - got := cmpstackvarlt(d.a, d.b) - if got != d.lt { - t.Errorf("want %#v < %#v", d.a, d.b) - } - // If we expect a < b to be true, check that b < a is false. - if d.lt && cmpstackvarlt(d.b, d.a) { - t.Errorf("unexpected %#v < %#v", d.b, d.a) - } - } -} - -func TestStackvarSort(t *testing.T) { - inp := []*Node{ - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO), - nodeWithClass(Node{Xoffset: 0, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC), - nodeWithClass(Node{Xoffset: 10, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC), - nodeWithClass(Node{Xoffset: 20, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC), - markUsed(nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO)), - nodeWithClass(Node{Type: typeWithoutPointers(), Sym: &types.Sym{}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO), - markNeedZero(nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO)), - nodeWithClass(Node{Type: &types.Type{Width: 1}, Sym: &types.Sym{}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{Width: 2}, Sym: &types.Sym{}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "xyz"}}, PAUTO), - } - want := []*Node{ - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC), - nodeWithClass(Node{Xoffset: 0, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC), - nodeWithClass(Node{Xoffset: 10, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC), - nodeWithClass(Node{Xoffset: 20, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC), - markUsed(nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO)), - markNeedZero(nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO)), - nodeWithClass(Node{Type: &types.Type{Width: 2}, Sym: &types.Sym{}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{Width: 1}, Sym: &types.Sym{}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO), - nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "xyz"}}, PAUTO), - nodeWithClass(Node{Type: typeWithoutPointers(), Sym: &types.Sym{}}, PAUTO), - } - sort.Sort(byStackVar(inp)) - if !reflect.DeepEqual(want, inp) { - t.Error("sort failed") - for i := range inp { - g := inp[i] - w := want[i] - eq := reflect.DeepEqual(w, g) - if !eq { - t.Log(i, w, g) - } - } - } -} diff --git a/src/cmd/compile/internal/gc/pprof.go b/src/cmd/compile/internal/gc/pprof.go index 256c6592591a67fa56e114d81ffc8a8d6c258004..5f9b030621cb8acadcdf2cb53e42633c6419625f 100644 --- a/src/cmd/compile/internal/gc/pprof.go +++ b/src/cmd/compile/internal/gc/pprof.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build go1.8 // +build go1.8 package gc diff --git a/src/cmd/compile/internal/gc/range.go b/src/cmd/compile/internal/gc/range.go deleted file mode 100644 index 1b4d765d4239e014694295d3e5f4f99f7854a12c..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/range.go +++ /dev/null @@ -1,628 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/sys" - "unicode/utf8" -) - -// range -func typecheckrange(n *Node) { - // Typechecking order is important here: - // 0. first typecheck range expression (slice/map/chan), - // it is evaluated only once and so logically it is not part of the loop. - // 1. typecheck produced values, - // this part can declare new vars and so it must be typechecked before body, - // because body can contain a closure that captures the vars. - // 2. decldepth++ to denote loop body. - // 3. typecheck body. - // 4. decldepth--. - typecheckrangeExpr(n) - - // second half of dance, the first half being typecheckrangeExpr - n.SetTypecheck(1) - ls := n.List.Slice() - for i1, n1 := range ls { - if n1.Typecheck() == 0 { - ls[i1] = typecheck(ls[i1], ctxExpr|ctxAssign) - } - } - - decldepth++ - typecheckslice(n.Nbody.Slice(), ctxStmt) - decldepth-- -} - -func typecheckrangeExpr(n *Node) { - n.Right = typecheck(n.Right, ctxExpr) - - t := n.Right.Type - if t == nil { - return - } - // delicate little dance. see typecheckas2 - ls := n.List.Slice() - for i1, n1 := range ls { - if n1.Name == nil || n1.Name.Defn != n { - ls[i1] = typecheck(ls[i1], ctxExpr|ctxAssign) - } - } - - if t.IsPtr() && t.Elem().IsArray() { - t = t.Elem() - } - n.Type = t - - var t1, t2 *types.Type - toomany := false - switch t.Etype { - default: - yyerrorl(n.Pos, "cannot range over %L", n.Right) - return - - case TARRAY, TSLICE: - t1 = types.Types[TINT] - t2 = t.Elem() - - case TMAP: - t1 = t.Key() - t2 = t.Elem() - - case TCHAN: - if !t.ChanDir().CanRecv() { - yyerrorl(n.Pos, "invalid operation: range %v (receive from send-only type %v)", n.Right, n.Right.Type) - return - } - - t1 = t.Elem() - t2 = nil - if n.List.Len() == 2 { - toomany = true - } - - case TSTRING: - t1 = types.Types[TINT] - t2 = types.Runetype - } - - if n.List.Len() > 2 || toomany { - yyerrorl(n.Pos, "too many variables in range") - } - - var v1, v2 *Node - if n.List.Len() != 0 { - v1 = n.List.First() - } - if n.List.Len() > 1 { - v2 = n.List.Second() - } - - // this is not only an optimization but also a requirement in the spec. - // "if the second iteration variable is the blank identifier, the range - // clause is equivalent to the same clause with only the first variable - // present." - if v2.isBlank() { - if v1 != nil { - n.List.Set1(v1) - } - v2 = nil - } - - if v1 != nil { - if v1.Name != nil && v1.Name.Defn == n { - v1.Type = t1 - } else if v1.Type != nil { - if op, why := assignop(t1, v1.Type); op == OXXX { - yyerrorl(n.Pos, "cannot assign type %v to %L in range%s", t1, v1, why) - } - } - checkassign(n, v1) - } - - if v2 != nil { - if v2.Name != nil && v2.Name.Defn == n { - v2.Type = t2 - } else if v2.Type != nil { - if op, why := assignop(t2, v2.Type); op == OXXX { - yyerrorl(n.Pos, "cannot assign type %v to %L in range%s", t2, v2, why) - } - } - checkassign(n, v2) - } -} - -func cheapComputableIndex(width int64) bool { - switch thearch.LinkArch.Family { - // MIPS does not have R+R addressing - // Arm64 may lack ability to generate this code in our assembler, - // but the architecture supports it. - case sys.PPC64, sys.S390X: - return width == 1 - case sys.AMD64, sys.I386, sys.ARM64, sys.ARM: - switch width { - case 1, 2, 4, 8: - return true - } - } - return false -} - -// walkrange transforms various forms of ORANGE into -// simpler forms. The result must be assigned back to n. -// Node n may also be modified in place, and may also be -// the returned node. -func walkrange(n *Node) *Node { - if isMapClear(n) { - m := n.Right - lno := setlineno(m) - n = mapClear(m) - lineno = lno - return n - } - - // variable name conventions: - // ohv1, hv1, hv2: hidden (old) val 1, 2 - // ha, hit: hidden aggregate, iterator - // hn, hp: hidden len, pointer - // hb: hidden bool - // a, v1, v2: not hidden aggregate, val 1, 2 - - t := n.Type - - a := n.Right - lno := setlineno(a) - n.Right = nil - - var v1, v2 *Node - l := n.List.Len() - if l > 0 { - v1 = n.List.First() - } - - if l > 1 { - v2 = n.List.Second() - } - - if v2.isBlank() { - v2 = nil - } - - if v1.isBlank() && v2 == nil { - v1 = nil - } - - if v1 == nil && v2 != nil { - Fatalf("walkrange: v2 != nil while v1 == nil") - } - - // n.List has no meaning anymore, clear it - // to avoid erroneous processing by racewalk. - n.List.Set(nil) - - var ifGuard *Node - - translatedLoopOp := OFOR - - var body []*Node - var init []*Node - switch t.Etype { - default: - Fatalf("walkrange") - - case TARRAY, TSLICE: - if arrayClear(n, v1, v2, a) { - lineno = lno - return n - } - - // order.stmt arranged for a copy of the array/slice variable if needed. - ha := a - - hv1 := temp(types.Types[TINT]) - hn := temp(types.Types[TINT]) - - init = append(init, nod(OAS, hv1, nil)) - init = append(init, nod(OAS, hn, nod(OLEN, ha, nil))) - - n.Left = nod(OLT, hv1, hn) - n.Right = nod(OAS, hv1, nod(OADD, hv1, nodintconst(1))) - - // for range ha { body } - if v1 == nil { - break - } - - // for v1 := range ha { body } - if v2 == nil { - body = []*Node{nod(OAS, v1, hv1)} - break - } - - // for v1, v2 := range ha { body } - if cheapComputableIndex(n.Type.Elem().Width) { - // v1, v2 = hv1, ha[hv1] - tmp := nod(OINDEX, ha, hv1) - tmp.SetBounded(true) - // Use OAS2 to correctly handle assignments - // of the form "v1, a[v1] := range". - a := nod(OAS2, nil, nil) - a.List.Set2(v1, v2) - a.Rlist.Set2(hv1, tmp) - body = []*Node{a} - break - } - - // TODO(austin): OFORUNTIL is a strange beast, but is - // necessary for expressing the control flow we need - // while also making "break" and "continue" work. It - // would be nice to just lower ORANGE during SSA, but - // racewalk needs to see many of the operations - // involved in ORANGE's implementation. If racewalk - // moves into SSA, consider moving ORANGE into SSA and - // eliminating OFORUNTIL. - - // TODO(austin): OFORUNTIL inhibits bounds-check - // elimination on the index variable (see #20711). - // Enhance the prove pass to understand this. - ifGuard = nod(OIF, nil, nil) - ifGuard.Left = nod(OLT, hv1, hn) - translatedLoopOp = OFORUNTIL - - hp := temp(types.NewPtr(n.Type.Elem())) - tmp := nod(OINDEX, ha, nodintconst(0)) - tmp.SetBounded(true) - init = append(init, nod(OAS, hp, nod(OADDR, tmp, nil))) - - // Use OAS2 to correctly handle assignments - // of the form "v1, a[v1] := range". - a := nod(OAS2, nil, nil) - a.List.Set2(v1, v2) - a.Rlist.Set2(hv1, nod(ODEREF, hp, nil)) - body = append(body, a) - - // Advance pointer as part of the late increment. - // - // This runs *after* the condition check, so we know - // advancing the pointer is safe and won't go past the - // end of the allocation. - a = nod(OAS, hp, addptr(hp, t.Elem().Width)) - a = typecheck(a, ctxStmt) - n.List.Set1(a) - - case TMAP: - // order.stmt allocated the iterator for us. - // we only use a once, so no copy needed. - ha := a - - hit := prealloc[n] - th := hit.Type - n.Left = nil - keysym := th.Field(0).Sym // depends on layout of iterator struct. See reflect.go:hiter - elemsym := th.Field(1).Sym // ditto - - fn := syslook("mapiterinit") - - fn = substArgTypes(fn, t.Key(), t.Elem(), th) - init = append(init, mkcall1(fn, nil, nil, typename(t), ha, nod(OADDR, hit, nil))) - n.Left = nod(ONE, nodSym(ODOT, hit, keysym), nodnil()) - - fn = syslook("mapiternext") - fn = substArgTypes(fn, th) - n.Right = mkcall1(fn, nil, nil, nod(OADDR, hit, nil)) - - key := nodSym(ODOT, hit, keysym) - key = nod(ODEREF, key, nil) - if v1 == nil { - body = nil - } else if v2 == nil { - body = []*Node{nod(OAS, v1, key)} - } else { - elem := nodSym(ODOT, hit, elemsym) - elem = nod(ODEREF, elem, nil) - a := nod(OAS2, nil, nil) - a.List.Set2(v1, v2) - a.Rlist.Set2(key, elem) - body = []*Node{a} - } - - case TCHAN: - // order.stmt arranged for a copy of the channel variable. - ha := a - - n.Left = nil - - hv1 := temp(t.Elem()) - hv1.SetTypecheck(1) - if t.Elem().HasPointers() { - init = append(init, nod(OAS, hv1, nil)) - } - hb := temp(types.Types[TBOOL]) - - n.Left = nod(ONE, hb, nodbool(false)) - a := nod(OAS2RECV, nil, nil) - a.SetTypecheck(1) - a.List.Set2(hv1, hb) - a.Right = nod(ORECV, ha, nil) - n.Left.Ninit.Set1(a) - if v1 == nil { - body = nil - } else { - body = []*Node{nod(OAS, v1, hv1)} - } - // Zero hv1. This prevents hv1 from being the sole, inaccessible - // reference to an otherwise GC-able value during the next channel receive. - // See issue 15281. - body = append(body, nod(OAS, hv1, nil)) - - case TSTRING: - // Transform string range statements like "for v1, v2 = range a" into - // - // ha := a - // for hv1 := 0; hv1 < len(ha); { - // hv1t := hv1 - // hv2 := rune(ha[hv1]) - // if hv2 < utf8.RuneSelf { - // hv1++ - // } else { - // hv2, hv1 = decoderune(ha, hv1) - // } - // v1, v2 = hv1t, hv2 - // // original body - // } - - // order.stmt arranged for a copy of the string variable. - ha := a - - hv1 := temp(types.Types[TINT]) - hv1t := temp(types.Types[TINT]) - hv2 := temp(types.Runetype) - - // hv1 := 0 - init = append(init, nod(OAS, hv1, nil)) - - // hv1 < len(ha) - n.Left = nod(OLT, hv1, nod(OLEN, ha, nil)) - - if v1 != nil { - // hv1t = hv1 - body = append(body, nod(OAS, hv1t, hv1)) - } - - // hv2 := rune(ha[hv1]) - nind := nod(OINDEX, ha, hv1) - nind.SetBounded(true) - body = append(body, nod(OAS, hv2, conv(nind, types.Runetype))) - - // if hv2 < utf8.RuneSelf - nif := nod(OIF, nil, nil) - nif.Left = nod(OLT, hv2, nodintconst(utf8.RuneSelf)) - - // hv1++ - nif.Nbody.Set1(nod(OAS, hv1, nod(OADD, hv1, nodintconst(1)))) - - // } else { - eif := nod(OAS2, nil, nil) - nif.Rlist.Set1(eif) - - // hv2, hv1 = decoderune(ha, hv1) - eif.List.Set2(hv2, hv1) - fn := syslook("decoderune") - eif.Rlist.Set1(mkcall1(fn, fn.Type.Results(), nil, ha, hv1)) - - body = append(body, nif) - - if v1 != nil { - if v2 != nil { - // v1, v2 = hv1t, hv2 - a := nod(OAS2, nil, nil) - a.List.Set2(v1, v2) - a.Rlist.Set2(hv1t, hv2) - body = append(body, a) - } else { - // v1 = hv1t - body = append(body, nod(OAS, v1, hv1t)) - } - } - } - - n.Op = translatedLoopOp - typecheckslice(init, ctxStmt) - - if ifGuard != nil { - ifGuard.Ninit.Append(init...) - ifGuard = typecheck(ifGuard, ctxStmt) - } else { - n.Ninit.Append(init...) - } - - typecheckslice(n.Left.Ninit.Slice(), ctxStmt) - - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - n.Right = typecheck(n.Right, ctxStmt) - typecheckslice(body, ctxStmt) - n.Nbody.Prepend(body...) - - if ifGuard != nil { - ifGuard.Nbody.Set1(n) - n = ifGuard - } - - n = walkstmt(n) - - lineno = lno - return n -} - -// isMapClear checks if n is of the form: -// -// for k := range m { -// delete(m, k) -// } -// -// where == for keys of map m is reflexive. -func isMapClear(n *Node) bool { - if Debug.N != 0 || instrumenting { - return false - } - - if n.Op != ORANGE || n.Type.Etype != TMAP || n.List.Len() != 1 { - return false - } - - k := n.List.First() - if k == nil || k.isBlank() { - return false - } - - // Require k to be a new variable name. - if k.Name == nil || k.Name.Defn != n { - return false - } - - if n.Nbody.Len() != 1 { - return false - } - - stmt := n.Nbody.First() // only stmt in body - if stmt == nil || stmt.Op != ODELETE { - return false - } - - m := n.Right - if !samesafeexpr(stmt.List.First(), m) || !samesafeexpr(stmt.List.Second(), k) { - return false - } - - // Keys where equality is not reflexive can not be deleted from maps. - if !isreflexive(m.Type.Key()) { - return false - } - - return true -} - -// mapClear constructs a call to runtime.mapclear for the map m. -func mapClear(m *Node) *Node { - t := m.Type - - // instantiate mapclear(typ *type, hmap map[any]any) - fn := syslook("mapclear") - fn = substArgTypes(fn, t.Key(), t.Elem()) - n := mkcall1(fn, nil, nil, typename(t), m) - - n = typecheck(n, ctxStmt) - n = walkstmt(n) - - return n -} - -// Lower n into runtime·memclr if possible, for -// fast zeroing of slices and arrays (issue 5373). -// Look for instances of -// -// for i := range a { -// a[i] = zero -// } -// -// in which the evaluation of a is side-effect-free. -// -// Parameters are as in walkrange: "for v1, v2 = range a". -func arrayClear(n, v1, v2, a *Node) bool { - if Debug.N != 0 || instrumenting { - return false - } - - if v1 == nil || v2 != nil { - return false - } - - if n.Nbody.Len() != 1 || n.Nbody.First() == nil { - return false - } - - stmt := n.Nbody.First() // only stmt in body - if stmt.Op != OAS || stmt.Left.Op != OINDEX { - return false - } - - if !samesafeexpr(stmt.Left.Left, a) || !samesafeexpr(stmt.Left.Right, v1) { - return false - } - - elemsize := n.Type.Elem().Width - if elemsize <= 0 || !isZero(stmt.Right) { - return false - } - - // Convert to - // if len(a) != 0 { - // hp = &a[0] - // hn = len(a)*sizeof(elem(a)) - // memclr{NoHeap,Has}Pointers(hp, hn) - // i = len(a) - 1 - // } - n.Op = OIF - - n.Nbody.Set(nil) - n.Left = nod(ONE, nod(OLEN, a, nil), nodintconst(0)) - - // hp = &a[0] - hp := temp(types.Types[TUNSAFEPTR]) - - tmp := nod(OINDEX, a, nodintconst(0)) - tmp.SetBounded(true) - tmp = nod(OADDR, tmp, nil) - tmp = convnop(tmp, types.Types[TUNSAFEPTR]) - n.Nbody.Append(nod(OAS, hp, tmp)) - - // hn = len(a) * sizeof(elem(a)) - hn := temp(types.Types[TUINTPTR]) - - tmp = nod(OLEN, a, nil) - tmp = nod(OMUL, tmp, nodintconst(elemsize)) - tmp = conv(tmp, types.Types[TUINTPTR]) - n.Nbody.Append(nod(OAS, hn, tmp)) - - var fn *Node - if a.Type.Elem().HasPointers() { - // memclrHasPointers(hp, hn) - Curfn.Func.setWBPos(stmt.Pos) - fn = mkcall("memclrHasPointers", nil, nil, hp, hn) - } else { - // memclrNoHeapPointers(hp, hn) - fn = mkcall("memclrNoHeapPointers", nil, nil, hp, hn) - } - - n.Nbody.Append(fn) - - // i = len(a) - 1 - v1 = nod(OAS, v1, nod(OSUB, nod(OLEN, a, nil), nodintconst(1))) - - n.Nbody.Append(v1) - - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - typecheckslice(n.Nbody.Slice(), ctxStmt) - n = walkstmt(n) - return true -} - -// addptr returns (*T)(uintptr(p) + n). -func addptr(p *Node, n int64) *Node { - t := p.Type - - p = nod(OCONVNOP, p, nil) - p.Type = types.Types[TUINTPTR] - - p = nod(OADD, p, nodintconst(n)) - - p = nod(OCONVNOP, p, nil) - p.Type = t - - return p -} diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go deleted file mode 100644 index 9401eba7a5021af1ecdd7dea5555363dcfa17d53..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/reflect.go +++ /dev/null @@ -1,1901 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/gcprog" - "cmd/internal/obj" - "cmd/internal/objabi" - "cmd/internal/src" - "fmt" - "os" - "sort" - "strings" - "sync" -) - -type itabEntry struct { - t, itype *types.Type - lsym *obj.LSym // symbol of the itab itself - - // symbols of each method in - // the itab, sorted by byte offset; - // filled in by peekitabs - entries []*obj.LSym -} - -type ptabEntry struct { - s *types.Sym - t *types.Type -} - -// runtime interface and reflection data structures -var ( - signatmu sync.Mutex // protects signatset and signatslice - signatset = make(map[*types.Type]struct{}) - signatslice []*types.Type - - itabs []itabEntry - ptabs []ptabEntry -) - -type Sig struct { - name *types.Sym - isym *types.Sym - tsym *types.Sym - type_ *types.Type - mtype *types.Type -} - -// Builds a type representing a Bucket structure for -// the given map type. This type is not visible to users - -// we include only enough information to generate a correct GC -// program for it. -// Make sure this stays in sync with runtime/map.go. -const ( - BUCKETSIZE = 8 - MAXKEYSIZE = 128 - MAXELEMSIZE = 128 -) - -func structfieldSize() int { return 3 * Widthptr } // Sizeof(runtime.structfield{}) -func imethodSize() int { return 4 + 4 } // Sizeof(runtime.imethod{}) -func commonSize() int { return 4*Widthptr + 8 + 8 } // Sizeof(runtime._type{}) - -func uncommonSize(t *types.Type) int { // Sizeof(runtime.uncommontype{}) - if t.Sym == nil && len(methods(t)) == 0 { - return 0 - } - return 4 + 2 + 2 + 4 + 4 -} - -func makefield(name string, t *types.Type) *types.Field { - f := types.NewField() - f.Type = t - f.Sym = (*types.Pkg)(nil).Lookup(name) - return f -} - -// bmap makes the map bucket type given the type of the map. -func bmap(t *types.Type) *types.Type { - if t.MapType().Bucket != nil { - return t.MapType().Bucket - } - - bucket := types.New(TSTRUCT) - keytype := t.Key() - elemtype := t.Elem() - dowidth(keytype) - dowidth(elemtype) - if keytype.Width > MAXKEYSIZE { - keytype = types.NewPtr(keytype) - } - if elemtype.Width > MAXELEMSIZE { - elemtype = types.NewPtr(elemtype) - } - - field := make([]*types.Field, 0, 5) - - // The first field is: uint8 topbits[BUCKETSIZE]. - arr := types.NewArray(types.Types[TUINT8], BUCKETSIZE) - field = append(field, makefield("topbits", arr)) - - arr = types.NewArray(keytype, BUCKETSIZE) - arr.SetNoalg(true) - keys := makefield("keys", arr) - field = append(field, keys) - - arr = types.NewArray(elemtype, BUCKETSIZE) - arr.SetNoalg(true) - elems := makefield("elems", arr) - field = append(field, elems) - - // If keys and elems have no pointers, the map implementation - // can keep a list of overflow pointers on the side so that - // buckets can be marked as having no pointers. - // Arrange for the bucket to have no pointers by changing - // the type of the overflow field to uintptr in this case. - // See comment on hmap.overflow in runtime/map.go. - otyp := types.NewPtr(bucket) - if !elemtype.HasPointers() && !keytype.HasPointers() { - otyp = types.Types[TUINTPTR] - } - overflow := makefield("overflow", otyp) - field = append(field, overflow) - - // link up fields - bucket.SetNoalg(true) - bucket.SetFields(field[:]) - dowidth(bucket) - - // Check invariants that map code depends on. - if !IsComparable(t.Key()) { - Fatalf("unsupported map key type for %v", t) - } - if BUCKETSIZE < 8 { - Fatalf("bucket size too small for proper alignment") - } - if keytype.Align > BUCKETSIZE { - Fatalf("key align too big for %v", t) - } - if elemtype.Align > BUCKETSIZE { - Fatalf("elem align too big for %v", t) - } - if keytype.Width > MAXKEYSIZE { - Fatalf("key size to large for %v", t) - } - if elemtype.Width > MAXELEMSIZE { - Fatalf("elem size to large for %v", t) - } - if t.Key().Width > MAXKEYSIZE && !keytype.IsPtr() { - Fatalf("key indirect incorrect for %v", t) - } - if t.Elem().Width > MAXELEMSIZE && !elemtype.IsPtr() { - Fatalf("elem indirect incorrect for %v", t) - } - if keytype.Width%int64(keytype.Align) != 0 { - Fatalf("key size not a multiple of key align for %v", t) - } - if elemtype.Width%int64(elemtype.Align) != 0 { - Fatalf("elem size not a multiple of elem align for %v", t) - } - if bucket.Align%keytype.Align != 0 { - Fatalf("bucket align not multiple of key align %v", t) - } - if bucket.Align%elemtype.Align != 0 { - Fatalf("bucket align not multiple of elem align %v", t) - } - if keys.Offset%int64(keytype.Align) != 0 { - Fatalf("bad alignment of keys in bmap for %v", t) - } - if elems.Offset%int64(elemtype.Align) != 0 { - Fatalf("bad alignment of elems in bmap for %v", t) - } - - // Double-check that overflow field is final memory in struct, - // with no padding at end. - if overflow.Offset != bucket.Width-int64(Widthptr) { - Fatalf("bad offset of overflow in bmap for %v", t) - } - - t.MapType().Bucket = bucket - - bucket.StructType().Map = t - return bucket -} - -// hmap builds a type representing a Hmap structure for the given map type. -// Make sure this stays in sync with runtime/map.go. -func hmap(t *types.Type) *types.Type { - if t.MapType().Hmap != nil { - return t.MapType().Hmap - } - - bmap := bmap(t) - - // build a struct: - // type hmap struct { - // count int - // flags uint8 - // B uint8 - // noverflow uint16 - // hash0 uint32 - // buckets *bmap - // oldbuckets *bmap - // nevacuate uintptr - // extra unsafe.Pointer // *mapextra - // } - // must match runtime/map.go:hmap. - fields := []*types.Field{ - makefield("count", types.Types[TINT]), - makefield("flags", types.Types[TUINT8]), - makefield("B", types.Types[TUINT8]), - makefield("noverflow", types.Types[TUINT16]), - makefield("hash0", types.Types[TUINT32]), // Used in walk.go for OMAKEMAP. - makefield("buckets", types.NewPtr(bmap)), // Used in walk.go for OMAKEMAP. - makefield("oldbuckets", types.NewPtr(bmap)), - makefield("nevacuate", types.Types[TUINTPTR]), - makefield("extra", types.Types[TUNSAFEPTR]), - } - - hmap := types.New(TSTRUCT) - hmap.SetNoalg(true) - hmap.SetFields(fields) - dowidth(hmap) - - // The size of hmap should be 48 bytes on 64 bit - // and 28 bytes on 32 bit platforms. - if size := int64(8 + 5*Widthptr); hmap.Width != size { - Fatalf("hmap size not correct: got %d, want %d", hmap.Width, size) - } - - t.MapType().Hmap = hmap - hmap.StructType().Map = t - return hmap -} - -// hiter builds a type representing an Hiter structure for the given map type. -// Make sure this stays in sync with runtime/map.go. -func hiter(t *types.Type) *types.Type { - if t.MapType().Hiter != nil { - return t.MapType().Hiter - } - - hmap := hmap(t) - bmap := bmap(t) - - // build a struct: - // type hiter struct { - // key *Key - // elem *Elem - // t unsafe.Pointer // *MapType - // h *hmap - // buckets *bmap - // bptr *bmap - // overflow unsafe.Pointer // *[]*bmap - // oldoverflow unsafe.Pointer // *[]*bmap - // startBucket uintptr - // offset uint8 - // wrapped bool - // B uint8 - // i uint8 - // bucket uintptr - // checkBucket uintptr - // } - // must match runtime/map.go:hiter. - fields := []*types.Field{ - makefield("key", types.NewPtr(t.Key())), // Used in range.go for TMAP. - makefield("elem", types.NewPtr(t.Elem())), // Used in range.go for TMAP. - makefield("t", types.Types[TUNSAFEPTR]), - makefield("h", types.NewPtr(hmap)), - makefield("buckets", types.NewPtr(bmap)), - makefield("bptr", types.NewPtr(bmap)), - makefield("overflow", types.Types[TUNSAFEPTR]), - makefield("oldoverflow", types.Types[TUNSAFEPTR]), - makefield("startBucket", types.Types[TUINTPTR]), - makefield("offset", types.Types[TUINT8]), - makefield("wrapped", types.Types[TBOOL]), - makefield("B", types.Types[TUINT8]), - makefield("i", types.Types[TUINT8]), - makefield("bucket", types.Types[TUINTPTR]), - makefield("checkBucket", types.Types[TUINTPTR]), - } - - // build iterator struct holding the above fields - hiter := types.New(TSTRUCT) - hiter.SetNoalg(true) - hiter.SetFields(fields) - dowidth(hiter) - if hiter.Width != int64(12*Widthptr) { - Fatalf("hash_iter size not correct %d %d", hiter.Width, 12*Widthptr) - } - t.MapType().Hiter = hiter - hiter.StructType().Map = t - return hiter -} - -// deferstruct makes a runtime._defer structure, with additional space for -// stksize bytes of args. -func deferstruct(stksize int64) *types.Type { - makefield := func(name string, typ *types.Type) *types.Field { - f := types.NewField() - f.Type = typ - // Unlike the global makefield function, this one needs to set Pkg - // because these types might be compared (in SSA CSE sorting). - // TODO: unify this makefield and the global one above. - f.Sym = &types.Sym{Name: name, Pkg: localpkg} - return f - } - argtype := types.NewArray(types.Types[TUINT8], stksize) - argtype.Width = stksize - argtype.Align = 1 - // These fields must match the ones in runtime/runtime2.go:_defer and - // cmd/compile/internal/gc/ssa.go:(*state).call. - fields := []*types.Field{ - makefield("siz", types.Types[TUINT32]), - makefield("started", types.Types[TBOOL]), - makefield("heap", types.Types[TBOOL]), - makefield("openDefer", types.Types[TBOOL]), - makefield("sp", types.Types[TUINTPTR]), - makefield("pc", types.Types[TUINTPTR]), - // Note: the types here don't really matter. Defer structures - // are always scanned explicitly during stack copying and GC, - // so we make them uintptr type even though they are real pointers. - makefield("fn", types.Types[TUINTPTR]), - makefield("_panic", types.Types[TUINTPTR]), - makefield("link", types.Types[TUINTPTR]), - makefield("framepc", types.Types[TUINTPTR]), - makefield("varp", types.Types[TUINTPTR]), - makefield("fd", types.Types[TUINTPTR]), - makefield("args", argtype), - } - - // build struct holding the above fields - s := types.New(TSTRUCT) - s.SetNoalg(true) - s.SetFields(fields) - s.Width = widstruct(s, s, 0, 1) - s.Align = uint8(Widthptr) - return s -} - -// f is method type, with receiver. -// return function type, receiver as first argument (or not). -func methodfunc(f *types.Type, receiver *types.Type) *types.Type { - inLen := f.Params().Fields().Len() - if receiver != nil { - inLen++ - } - in := make([]*Node, 0, inLen) - - if receiver != nil { - d := anonfield(receiver) - in = append(in, d) - } - - for _, t := range f.Params().Fields().Slice() { - d := anonfield(t.Type) - d.SetIsDDD(t.IsDDD()) - in = append(in, d) - } - - outLen := f.Results().Fields().Len() - out := make([]*Node, 0, outLen) - for _, t := range f.Results().Fields().Slice() { - d := anonfield(t.Type) - out = append(out, d) - } - - t := functype(nil, in, out) - if f.Nname() != nil { - // Link to name of original method function. - t.SetNname(f.Nname()) - } - - return t -} - -// methods returns the methods of the non-interface type t, sorted by name. -// Generates stub functions as needed. -func methods(t *types.Type) []*Sig { - // method type - mt := methtype(t) - - if mt == nil { - return nil - } - expandmeth(mt) - - // type stored in interface word - it := t - - if !isdirectiface(it) { - it = types.NewPtr(t) - } - - // make list of methods for t, - // generating code if necessary. - var ms []*Sig - for _, f := range mt.AllMethods().Slice() { - if !f.IsMethod() { - Fatalf("non-method on %v method %v %v\n", mt, f.Sym, f) - } - if f.Type.Recv() == nil { - Fatalf("receiver with no type on %v method %v %v\n", mt, f.Sym, f) - } - if f.Nointerface() { - continue - } - - method := f.Sym - if method == nil { - break - } - - // get receiver type for this particular method. - // if pointer receiver but non-pointer t and - // this is not an embedded pointer inside a struct, - // method does not apply. - if !isMethodApplicable(t, f) { - continue - } - - sig := &Sig{ - name: method, - isym: methodSym(it, method), - tsym: methodSym(t, method), - type_: methodfunc(f.Type, t), - mtype: methodfunc(f.Type, nil), - } - ms = append(ms, sig) - - this := f.Type.Recv().Type - - if !sig.isym.Siggen() { - sig.isym.SetSiggen(true) - if !types.Identical(this, it) { - genwrapper(it, f, sig.isym) - } - } - - if !sig.tsym.Siggen() { - sig.tsym.SetSiggen(true) - if !types.Identical(this, t) { - genwrapper(t, f, sig.tsym) - } - } - } - - return ms -} - -// imethods returns the methods of the interface type t, sorted by name. -func imethods(t *types.Type) []*Sig { - var methods []*Sig - for _, f := range t.Fields().Slice() { - if f.Type.Etype != TFUNC || f.Sym == nil { - continue - } - if f.Sym.IsBlank() { - Fatalf("unexpected blank symbol in interface method set") - } - if n := len(methods); n > 0 { - last := methods[n-1] - if !last.name.Less(f.Sym) { - Fatalf("sigcmp vs sortinter %v %v", last.name, f.Sym) - } - } - - sig := &Sig{ - name: f.Sym, - mtype: f.Type, - type_: methodfunc(f.Type, nil), - } - methods = append(methods, sig) - - // NOTE(rsc): Perhaps an oversight that - // IfaceType.Method is not in the reflect data. - // Generate the method body, so that compiled - // code can refer to it. - isym := methodSym(t, f.Sym) - if !isym.Siggen() { - isym.SetSiggen(true) - genwrapper(t, f, isym) - } - } - - return methods -} - -func dimportpath(p *types.Pkg) { - if p.Pathsym != nil { - return - } - - // If we are compiling the runtime package, there are two runtime packages around - // -- localpkg and Runtimepkg. We don't want to produce import path symbols for - // both of them, so just produce one for localpkg. - if myimportpath == "runtime" && p == Runtimepkg { - return - } - - str := p.Path - if p == localpkg { - // Note: myimportpath != "", or else dgopkgpath won't call dimportpath. - str = myimportpath - } - - s := Ctxt.Lookup("type..importpath." + p.Prefix + ".") - ot := dnameData(s, 0, str, "", nil, false) - ggloblsym(s, int32(ot), obj.DUPOK|obj.RODATA) - s.Set(obj.AttrContentAddressable, true) - p.Pathsym = s -} - -func dgopkgpath(s *obj.LSym, ot int, pkg *types.Pkg) int { - if pkg == nil { - return duintptr(s, ot, 0) - } - - if pkg == localpkg && myimportpath == "" { - // If we don't know the full import path of the package being compiled - // (i.e. -p was not passed on the compiler command line), emit a reference to - // type..importpath.""., which the linker will rewrite using the correct import path. - // Every package that imports this one directly defines the symbol. - // See also https://groups.google.com/forum/#!topic/golang-dev/myb9s53HxGQ. - ns := Ctxt.Lookup(`type..importpath."".`) - return dsymptr(s, ot, ns, 0) - } - - dimportpath(pkg) - return dsymptr(s, ot, pkg.Pathsym, 0) -} - -// dgopkgpathOff writes an offset relocation in s at offset ot to the pkg path symbol. -func dgopkgpathOff(s *obj.LSym, ot int, pkg *types.Pkg) int { - if pkg == nil { - return duint32(s, ot, 0) - } - if pkg == localpkg && myimportpath == "" { - // If we don't know the full import path of the package being compiled - // (i.e. -p was not passed on the compiler command line), emit a reference to - // type..importpath.""., which the linker will rewrite using the correct import path. - // Every package that imports this one directly defines the symbol. - // See also https://groups.google.com/forum/#!topic/golang-dev/myb9s53HxGQ. - ns := Ctxt.Lookup(`type..importpath."".`) - return dsymptrOff(s, ot, ns) - } - - dimportpath(pkg) - return dsymptrOff(s, ot, pkg.Pathsym) -} - -// dnameField dumps a reflect.name for a struct field. -func dnameField(lsym *obj.LSym, ot int, spkg *types.Pkg, ft *types.Field) int { - if !types.IsExported(ft.Sym.Name) && ft.Sym.Pkg != spkg { - Fatalf("package mismatch for %v", ft.Sym) - } - nsym := dname(ft.Sym.Name, ft.Note, nil, types.IsExported(ft.Sym.Name)) - return dsymptr(lsym, ot, nsym, 0) -} - -// dnameData writes the contents of a reflect.name into s at offset ot. -func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported bool) int { - if len(name) > 1<<16-1 { - Fatalf("name too long: %s", name) - } - if len(tag) > 1<<16-1 { - Fatalf("tag too long: %s", tag) - } - - // Encode name and tag. See reflect/type.go for details. - var bits byte - l := 1 + 2 + len(name) - if exported { - bits |= 1 << 0 - } - if len(tag) > 0 { - l += 2 + len(tag) - bits |= 1 << 1 - } - if pkg != nil { - bits |= 1 << 2 - } - b := make([]byte, l) - b[0] = bits - b[1] = uint8(len(name) >> 8) - b[2] = uint8(len(name)) - copy(b[3:], name) - if len(tag) > 0 { - tb := b[3+len(name):] - tb[0] = uint8(len(tag) >> 8) - tb[1] = uint8(len(tag)) - copy(tb[2:], tag) - } - - ot = int(s.WriteBytes(Ctxt, int64(ot), b)) - - if pkg != nil { - ot = dgopkgpathOff(s, ot, pkg) - } - - return ot -} - -var dnameCount int - -// dname creates a reflect.name for a struct field or method. -func dname(name, tag string, pkg *types.Pkg, exported bool) *obj.LSym { - // Write out data as "type.." to signal two things to the - // linker, first that when dynamically linking, the symbol - // should be moved to a relro section, and second that the - // contents should not be decoded as a type. - sname := "type..namedata." - if pkg == nil { - // In the common case, share data with other packages. - if name == "" { - if exported { - sname += "-noname-exported." + tag - } else { - sname += "-noname-unexported." + tag - } - } else { - if exported { - sname += name + "." + tag - } else { - sname += name + "-" + tag - } - } - } else { - sname = fmt.Sprintf(`%s"".%d`, sname, dnameCount) - dnameCount++ - } - s := Ctxt.Lookup(sname) - if len(s.P) > 0 { - return s - } - ot := dnameData(s, 0, name, tag, pkg, exported) - ggloblsym(s, int32(ot), obj.DUPOK|obj.RODATA) - s.Set(obj.AttrContentAddressable, true) - return s -} - -// dextratype dumps the fields of a runtime.uncommontype. -// dataAdd is the offset in bytes after the header where the -// backing array of the []method field is written (by dextratypeData). -func dextratype(lsym *obj.LSym, ot int, t *types.Type, dataAdd int) int { - m := methods(t) - if t.Sym == nil && len(m) == 0 { - return ot - } - noff := int(Rnd(int64(ot), int64(Widthptr))) - if noff != ot { - Fatalf("unexpected alignment in dextratype for %v", t) - } - - for _, a := range m { - dtypesym(a.type_) - } - - ot = dgopkgpathOff(lsym, ot, typePkg(t)) - - dataAdd += uncommonSize(t) - mcount := len(m) - if mcount != int(uint16(mcount)) { - Fatalf("too many methods on %v: %d", t, mcount) - } - xcount := sort.Search(mcount, func(i int) bool { return !types.IsExported(m[i].name.Name) }) - if dataAdd != int(uint32(dataAdd)) { - Fatalf("methods are too far away on %v: %d", t, dataAdd) - } - - ot = duint16(lsym, ot, uint16(mcount)) - ot = duint16(lsym, ot, uint16(xcount)) - ot = duint32(lsym, ot, uint32(dataAdd)) - ot = duint32(lsym, ot, 0) - return ot -} - -func typePkg(t *types.Type) *types.Pkg { - tsym := t.Sym - if tsym == nil { - switch t.Etype { - case TARRAY, TSLICE, TPTR, TCHAN: - if t.Elem() != nil { - tsym = t.Elem().Sym - } - } - } - if tsym != nil && t != types.Types[t.Etype] && t != types.Errortype { - return tsym.Pkg - } - return nil -} - -// dextratypeData dumps the backing array for the []method field of -// runtime.uncommontype. -func dextratypeData(lsym *obj.LSym, ot int, t *types.Type) int { - for _, a := range methods(t) { - // ../../../../runtime/type.go:/method - exported := types.IsExported(a.name.Name) - var pkg *types.Pkg - if !exported && a.name.Pkg != typePkg(t) { - pkg = a.name.Pkg - } - nsym := dname(a.name.Name, "", pkg, exported) - - ot = dsymptrOff(lsym, ot, nsym) - ot = dmethodptrOff(lsym, ot, dtypesym(a.mtype)) - ot = dmethodptrOff(lsym, ot, a.isym.Linksym()) - ot = dmethodptrOff(lsym, ot, a.tsym.Linksym()) - } - return ot -} - -func dmethodptrOff(s *obj.LSym, ot int, x *obj.LSym) int { - duint32(s, ot, 0) - r := obj.Addrel(s) - r.Off = int32(ot) - r.Siz = 4 - r.Sym = x - r.Type = objabi.R_METHODOFF - return ot + 4 -} - -var kinds = []int{ - TINT: objabi.KindInt, - TUINT: objabi.KindUint, - TINT8: objabi.KindInt8, - TUINT8: objabi.KindUint8, - TINT16: objabi.KindInt16, - TUINT16: objabi.KindUint16, - TINT32: objabi.KindInt32, - TUINT32: objabi.KindUint32, - TINT64: objabi.KindInt64, - TUINT64: objabi.KindUint64, - TUINTPTR: objabi.KindUintptr, - TFLOAT32: objabi.KindFloat32, - TFLOAT64: objabi.KindFloat64, - TBOOL: objabi.KindBool, - TSTRING: objabi.KindString, - TPTR: objabi.KindPtr, - TSTRUCT: objabi.KindStruct, - TINTER: objabi.KindInterface, - TCHAN: objabi.KindChan, - TMAP: objabi.KindMap, - TARRAY: objabi.KindArray, - TSLICE: objabi.KindSlice, - TFUNC: objabi.KindFunc, - TCOMPLEX64: objabi.KindComplex64, - TCOMPLEX128: objabi.KindComplex128, - TUNSAFEPTR: objabi.KindUnsafePointer, -} - -// typeptrdata returns the length in bytes of the prefix of t -// containing pointer data. Anything after this offset is scalar data. -func typeptrdata(t *types.Type) int64 { - if !t.HasPointers() { - return 0 - } - - switch t.Etype { - case TPTR, - TUNSAFEPTR, - TFUNC, - TCHAN, - TMAP: - return int64(Widthptr) - - case TSTRING: - // struct { byte *str; intgo len; } - return int64(Widthptr) - - case TINTER: - // struct { Itab *tab; void *data; } or - // struct { Type *type; void *data; } - // Note: see comment in plive.go:onebitwalktype1. - return 2 * int64(Widthptr) - - case TSLICE: - // struct { byte *array; uintgo len; uintgo cap; } - return int64(Widthptr) - - case TARRAY: - // haspointers already eliminated t.NumElem() == 0. - return (t.NumElem()-1)*t.Elem().Width + typeptrdata(t.Elem()) - - case TSTRUCT: - // Find the last field that has pointers. - var lastPtrField *types.Field - for _, t1 := range t.Fields().Slice() { - if t1.Type.HasPointers() { - lastPtrField = t1 - } - } - return lastPtrField.Offset + typeptrdata(lastPtrField.Type) - - default: - Fatalf("typeptrdata: unexpected type, %v", t) - return 0 - } -} - -// tflag is documented in reflect/type.go. -// -// tflag values must be kept in sync with copies in: -// cmd/compile/internal/gc/reflect.go -// cmd/link/internal/ld/decodesym.go -// reflect/type.go -// runtime/type.go -const ( - tflagUncommon = 1 << 0 - tflagExtraStar = 1 << 1 - tflagNamed = 1 << 2 - tflagRegularMemory = 1 << 3 -) - -var ( - memhashvarlen *obj.LSym - memequalvarlen *obj.LSym -) - -// dcommontype dumps the contents of a reflect.rtype (runtime._type). -func dcommontype(lsym *obj.LSym, t *types.Type) int { - dowidth(t) - eqfunc := geneq(t) - - sptrWeak := true - var sptr *obj.LSym - if !t.IsPtr() || t.IsPtrElem() { - tptr := types.NewPtr(t) - if t.Sym != nil || methods(tptr) != nil { - sptrWeak = false - } - sptr = dtypesym(tptr) - } - - gcsym, useGCProg, ptrdata := dgcsym(t) - - // ../../../../reflect/type.go:/^type.rtype - // actual type structure - // type rtype struct { - // size uintptr - // ptrdata uintptr - // hash uint32 - // tflag tflag - // align uint8 - // fieldAlign uint8 - // kind uint8 - // equal func(unsafe.Pointer, unsafe.Pointer) bool - // gcdata *byte - // str nameOff - // ptrToThis typeOff - // } - ot := 0 - ot = duintptr(lsym, ot, uint64(t.Width)) - ot = duintptr(lsym, ot, uint64(ptrdata)) - ot = duint32(lsym, ot, typehash(t)) - - var tflag uint8 - if uncommonSize(t) != 0 { - tflag |= tflagUncommon - } - if t.Sym != nil && t.Sym.Name != "" { - tflag |= tflagNamed - } - if IsRegularMemory(t) { - tflag |= tflagRegularMemory - } - - exported := false - p := t.LongString() - // If we're writing out type T, - // we are very likely to write out type *T as well. - // Use the string "*T"[1:] for "T", so that the two - // share storage. This is a cheap way to reduce the - // amount of space taken up by reflect strings. - if !strings.HasPrefix(p, "*") { - p = "*" + p - tflag |= tflagExtraStar - if t.Sym != nil { - exported = types.IsExported(t.Sym.Name) - } - } else { - if t.Elem() != nil && t.Elem().Sym != nil { - exported = types.IsExported(t.Elem().Sym.Name) - } - } - - ot = duint8(lsym, ot, tflag) - - // runtime (and common sense) expects alignment to be a power of two. - i := int(t.Align) - - if i == 0 { - i = 1 - } - if i&(i-1) != 0 { - Fatalf("invalid alignment %d for %v", t.Align, t) - } - ot = duint8(lsym, ot, t.Align) // align - ot = duint8(lsym, ot, t.Align) // fieldAlign - - i = kinds[t.Etype] - if isdirectiface(t) { - i |= objabi.KindDirectIface - } - if useGCProg { - i |= objabi.KindGCProg - } - ot = duint8(lsym, ot, uint8(i)) // kind - if eqfunc != nil { - ot = dsymptr(lsym, ot, eqfunc, 0) // equality function - } else { - ot = duintptr(lsym, ot, 0) // type we can't do == with - } - ot = dsymptr(lsym, ot, gcsym, 0) // gcdata - - nsym := dname(p, "", nil, exported) - ot = dsymptrOff(lsym, ot, nsym) // str - // ptrToThis - if sptr == nil { - ot = duint32(lsym, ot, 0) - } else if sptrWeak { - ot = dsymptrWeakOff(lsym, ot, sptr) - } else { - ot = dsymptrOff(lsym, ot, sptr) - } - - return ot -} - -// typeHasNoAlg reports whether t does not have any associated hash/eq -// algorithms because t, or some component of t, is marked Noalg. -func typeHasNoAlg(t *types.Type) bool { - a, bad := algtype1(t) - return a == ANOEQ && bad.Noalg() -} - -func typesymname(t *types.Type) string { - name := t.ShortString() - // Use a separate symbol name for Noalg types for #17752. - if typeHasNoAlg(t) { - name = "noalg." + name - } - return name -} - -// Fake package for runtime type info (headers) -// Don't access directly, use typeLookup below. -var ( - typepkgmu sync.Mutex // protects typepkg lookups - typepkg = types.NewPkg("type", "type") -) - -func typeLookup(name string) *types.Sym { - typepkgmu.Lock() - s := typepkg.Lookup(name) - typepkgmu.Unlock() - return s -} - -func typesym(t *types.Type) *types.Sym { - return typeLookup(typesymname(t)) -} - -// tracksym returns the symbol for tracking use of field/method f, assumed -// to be a member of struct/interface type t. -func tracksym(t *types.Type, f *types.Field) *types.Sym { - return trackpkg.Lookup(t.ShortString() + "." + f.Sym.Name) -} - -func typesymprefix(prefix string, t *types.Type) *types.Sym { - p := prefix + "." + t.ShortString() - s := typeLookup(p) - - // This function is for looking up type-related generated functions - // (e.g. eq and hash). Make sure they are indeed generated. - signatmu.Lock() - addsignat(t) - signatmu.Unlock() - - //print("algsym: %s -> %+S\n", p, s); - - return s -} - -func typenamesym(t *types.Type) *types.Sym { - if t == nil || (t.IsPtr() && t.Elem() == nil) || t.IsUntyped() { - Fatalf("typenamesym %v", t) - } - s := typesym(t) - signatmu.Lock() - addsignat(t) - signatmu.Unlock() - return s -} - -func typename(t *types.Type) *Node { - s := typenamesym(t) - if s.Def == nil { - n := newnamel(src.NoXPos, s) - n.Type = types.Types[TUINT8] - n.SetClass(PEXTERN) - n.SetTypecheck(1) - s.Def = asTypesNode(n) - } - - n := nod(OADDR, asNode(s.Def), nil) - n.Type = types.NewPtr(asNode(s.Def).Type) - n.SetTypecheck(1) - return n -} - -func itabname(t, itype *types.Type) *Node { - if t == nil || (t.IsPtr() && t.Elem() == nil) || t.IsUntyped() || !itype.IsInterface() || itype.IsEmptyInterface() { - Fatalf("itabname(%v, %v)", t, itype) - } - s := itabpkg.Lookup(t.ShortString() + "," + itype.ShortString()) - if s.Def == nil { - n := newname(s) - n.Type = types.Types[TUINT8] - n.SetClass(PEXTERN) - n.SetTypecheck(1) - s.Def = asTypesNode(n) - itabs = append(itabs, itabEntry{t: t, itype: itype, lsym: s.Linksym()}) - } - - n := nod(OADDR, asNode(s.Def), nil) - n.Type = types.NewPtr(asNode(s.Def).Type) - n.SetTypecheck(1) - return n -} - -// isreflexive reports whether t has a reflexive equality operator. -// That is, if x==x for all x of type t. -func isreflexive(t *types.Type) bool { - switch t.Etype { - case TBOOL, - TINT, - TUINT, - TINT8, - TUINT8, - TINT16, - TUINT16, - TINT32, - TUINT32, - TINT64, - TUINT64, - TUINTPTR, - TPTR, - TUNSAFEPTR, - TSTRING, - TCHAN: - return true - - case TFLOAT32, - TFLOAT64, - TCOMPLEX64, - TCOMPLEX128, - TINTER: - return false - - case TARRAY: - return isreflexive(t.Elem()) - - case TSTRUCT: - for _, t1 := range t.Fields().Slice() { - if !isreflexive(t1.Type) { - return false - } - } - return true - - default: - Fatalf("bad type for map key: %v", t) - return false - } -} - -// needkeyupdate reports whether map updates with t as a key -// need the key to be updated. -func needkeyupdate(t *types.Type) bool { - switch t.Etype { - case TBOOL, TINT, TUINT, TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, - TINT64, TUINT64, TUINTPTR, TPTR, TUNSAFEPTR, TCHAN: - return false - - case TFLOAT32, TFLOAT64, TCOMPLEX64, TCOMPLEX128, // floats and complex can be +0/-0 - TINTER, - TSTRING: // strings might have smaller backing stores - return true - - case TARRAY: - return needkeyupdate(t.Elem()) - - case TSTRUCT: - for _, t1 := range t.Fields().Slice() { - if needkeyupdate(t1.Type) { - return true - } - } - return false - - default: - Fatalf("bad type for map key: %v", t) - return true - } -} - -// hashMightPanic reports whether the hash of a map key of type t might panic. -func hashMightPanic(t *types.Type) bool { - switch t.Etype { - case TINTER: - return true - - case TARRAY: - return hashMightPanic(t.Elem()) - - case TSTRUCT: - for _, t1 := range t.Fields().Slice() { - if hashMightPanic(t1.Type) { - return true - } - } - return false - - default: - return false - } -} - -// formalType replaces byte and rune aliases with real types. -// They've been separate internally to make error messages -// better, but we have to merge them in the reflect tables. -func formalType(t *types.Type) *types.Type { - if t == types.Bytetype || t == types.Runetype { - return types.Types[t.Etype] - } - return t -} - -func dtypesym(t *types.Type) *obj.LSym { - t = formalType(t) - if t.IsUntyped() { - Fatalf("dtypesym %v", t) - } - - s := typesym(t) - lsym := s.Linksym() - if s.Siggen() { - return lsym - } - s.SetSiggen(true) - - // special case (look for runtime below): - // when compiling package runtime, - // emit the type structures for int, float, etc. - tbase := t - - if t.IsPtr() && t.Sym == nil && t.Elem().Sym != nil { - tbase = t.Elem() - } - dupok := 0 - if tbase.Sym == nil { - dupok = obj.DUPOK - } - - if myimportpath != "runtime" || (tbase != types.Types[tbase.Etype] && tbase != types.Bytetype && tbase != types.Runetype && tbase != types.Errortype) { // int, float, etc - // named types from other files are defined only by those files - if tbase.Sym != nil && tbase.Sym.Pkg != localpkg { - if i, ok := typeSymIdx[tbase]; ok { - lsym.Pkg = tbase.Sym.Pkg.Prefix - if t != tbase { - lsym.SymIdx = int32(i[1]) - } else { - lsym.SymIdx = int32(i[0]) - } - lsym.Set(obj.AttrIndexed, true) - } - return lsym - } - // TODO(mdempsky): Investigate whether this can happen. - if tbase.Etype == TFORW { - return lsym - } - } - - ot := 0 - switch t.Etype { - default: - ot = dcommontype(lsym, t) - ot = dextratype(lsym, ot, t, 0) - - case TARRAY: - // ../../../../runtime/type.go:/arrayType - s1 := dtypesym(t.Elem()) - t2 := types.NewSlice(t.Elem()) - s2 := dtypesym(t2) - ot = dcommontype(lsym, t) - ot = dsymptr(lsym, ot, s1, 0) - ot = dsymptr(lsym, ot, s2, 0) - ot = duintptr(lsym, ot, uint64(t.NumElem())) - ot = dextratype(lsym, ot, t, 0) - - case TSLICE: - // ../../../../runtime/type.go:/sliceType - s1 := dtypesym(t.Elem()) - ot = dcommontype(lsym, t) - ot = dsymptr(lsym, ot, s1, 0) - ot = dextratype(lsym, ot, t, 0) - - case TCHAN: - // ../../../../runtime/type.go:/chanType - s1 := dtypesym(t.Elem()) - ot = dcommontype(lsym, t) - ot = dsymptr(lsym, ot, s1, 0) - ot = duintptr(lsym, ot, uint64(t.ChanDir())) - ot = dextratype(lsym, ot, t, 0) - - case TFUNC: - for _, t1 := range t.Recvs().Fields().Slice() { - dtypesym(t1.Type) - } - isddd := false - for _, t1 := range t.Params().Fields().Slice() { - isddd = t1.IsDDD() - dtypesym(t1.Type) - } - for _, t1 := range t.Results().Fields().Slice() { - dtypesym(t1.Type) - } - - ot = dcommontype(lsym, t) - inCount := t.NumRecvs() + t.NumParams() - outCount := t.NumResults() - if isddd { - outCount |= 1 << 15 - } - ot = duint16(lsym, ot, uint16(inCount)) - ot = duint16(lsym, ot, uint16(outCount)) - if Widthptr == 8 { - ot += 4 // align for *rtype - } - - dataAdd := (inCount + t.NumResults()) * Widthptr - ot = dextratype(lsym, ot, t, dataAdd) - - // Array of rtype pointers follows funcType. - for _, t1 := range t.Recvs().Fields().Slice() { - ot = dsymptr(lsym, ot, dtypesym(t1.Type), 0) - } - for _, t1 := range t.Params().Fields().Slice() { - ot = dsymptr(lsym, ot, dtypesym(t1.Type), 0) - } - for _, t1 := range t.Results().Fields().Slice() { - ot = dsymptr(lsym, ot, dtypesym(t1.Type), 0) - } - - case TINTER: - m := imethods(t) - n := len(m) - for _, a := range m { - dtypesym(a.type_) - } - - // ../../../../runtime/type.go:/interfaceType - ot = dcommontype(lsym, t) - - var tpkg *types.Pkg - if t.Sym != nil && t != types.Types[t.Etype] && t != types.Errortype { - tpkg = t.Sym.Pkg - } - ot = dgopkgpath(lsym, ot, tpkg) - - ot = dsymptr(lsym, ot, lsym, ot+3*Widthptr+uncommonSize(t)) - ot = duintptr(lsym, ot, uint64(n)) - ot = duintptr(lsym, ot, uint64(n)) - dataAdd := imethodSize() * n - ot = dextratype(lsym, ot, t, dataAdd) - - for _, a := range m { - // ../../../../runtime/type.go:/imethod - exported := types.IsExported(a.name.Name) - var pkg *types.Pkg - if !exported && a.name.Pkg != tpkg { - pkg = a.name.Pkg - } - nsym := dname(a.name.Name, "", pkg, exported) - - ot = dsymptrOff(lsym, ot, nsym) - ot = dsymptrOff(lsym, ot, dtypesym(a.type_)) - } - - // ../../../../runtime/type.go:/mapType - case TMAP: - s1 := dtypesym(t.Key()) - s2 := dtypesym(t.Elem()) - s3 := dtypesym(bmap(t)) - hasher := genhash(t.Key()) - - ot = dcommontype(lsym, t) - ot = dsymptr(lsym, ot, s1, 0) - ot = dsymptr(lsym, ot, s2, 0) - ot = dsymptr(lsym, ot, s3, 0) - ot = dsymptr(lsym, ot, hasher, 0) - var flags uint32 - // Note: flags must match maptype accessors in ../../../../runtime/type.go - // and maptype builder in ../../../../reflect/type.go:MapOf. - if t.Key().Width > MAXKEYSIZE { - ot = duint8(lsym, ot, uint8(Widthptr)) - flags |= 1 // indirect key - } else { - ot = duint8(lsym, ot, uint8(t.Key().Width)) - } - - if t.Elem().Width > MAXELEMSIZE { - ot = duint8(lsym, ot, uint8(Widthptr)) - flags |= 2 // indirect value - } else { - ot = duint8(lsym, ot, uint8(t.Elem().Width)) - } - ot = duint16(lsym, ot, uint16(bmap(t).Width)) - if isreflexive(t.Key()) { - flags |= 4 // reflexive key - } - if needkeyupdate(t.Key()) { - flags |= 8 // need key update - } - if hashMightPanic(t.Key()) { - flags |= 16 // hash might panic - } - ot = duint32(lsym, ot, flags) - ot = dextratype(lsym, ot, t, 0) - - case TPTR: - if t.Elem().Etype == TANY { - // ../../../../runtime/type.go:/UnsafePointerType - ot = dcommontype(lsym, t) - ot = dextratype(lsym, ot, t, 0) - - break - } - - // ../../../../runtime/type.go:/ptrType - s1 := dtypesym(t.Elem()) - - ot = dcommontype(lsym, t) - ot = dsymptr(lsym, ot, s1, 0) - ot = dextratype(lsym, ot, t, 0) - - // ../../../../runtime/type.go:/structType - // for security, only the exported fields. - case TSTRUCT: - fields := t.Fields().Slice() - for _, t1 := range fields { - dtypesym(t1.Type) - } - - // All non-exported struct field names within a struct - // type must originate from a single package. By - // identifying and recording that package within the - // struct type descriptor, we can omit that - // information from the field descriptors. - var spkg *types.Pkg - for _, f := range fields { - if !types.IsExported(f.Sym.Name) { - spkg = f.Sym.Pkg - break - } - } - - ot = dcommontype(lsym, t) - ot = dgopkgpath(lsym, ot, spkg) - ot = dsymptr(lsym, ot, lsym, ot+3*Widthptr+uncommonSize(t)) - ot = duintptr(lsym, ot, uint64(len(fields))) - ot = duintptr(lsym, ot, uint64(len(fields))) - - dataAdd := len(fields) * structfieldSize() - ot = dextratype(lsym, ot, t, dataAdd) - - for _, f := range fields { - // ../../../../runtime/type.go:/structField - ot = dnameField(lsym, ot, spkg, f) - ot = dsymptr(lsym, ot, dtypesym(f.Type), 0) - offsetAnon := uint64(f.Offset) << 1 - if offsetAnon>>1 != uint64(f.Offset) { - Fatalf("%v: bad field offset for %s", t, f.Sym.Name) - } - if f.Embedded != 0 { - offsetAnon |= 1 - } - ot = duintptr(lsym, ot, offsetAnon) - } - } - - ot = dextratypeData(lsym, ot, t) - ggloblsym(lsym, int32(ot), int16(dupok|obj.RODATA)) - - // The linker will leave a table of all the typelinks for - // types in the binary, so the runtime can find them. - // - // When buildmode=shared, all types are in typelinks so the - // runtime can deduplicate type pointers. - keep := Ctxt.Flag_dynlink - if !keep && t.Sym == nil { - // For an unnamed type, we only need the link if the type can - // be created at run time by reflect.PtrTo and similar - // functions. If the type exists in the program, those - // functions must return the existing type structure rather - // than creating a new one. - switch t.Etype { - case TPTR, TARRAY, TCHAN, TFUNC, TMAP, TSLICE, TSTRUCT: - keep = true - } - } - // Do not put Noalg types in typelinks. See issue #22605. - if typeHasNoAlg(t) { - keep = false - } - lsym.Set(obj.AttrMakeTypelink, keep) - - return lsym -} - -// ifaceMethodOffset returns the offset of the i-th method in the interface -// type descriptor, ityp. -func ifaceMethodOffset(ityp *types.Type, i int64) int64 { - // interface type descriptor layout is struct { - // _type // commonSize - // pkgpath // 1 word - // []imethod // 3 words (pointing to [...]imethod below) - // uncommontype // uncommonSize - // [...]imethod - // } - // The size of imethod is 8. - return int64(commonSize()+4*Widthptr+uncommonSize(ityp)) + i*8 -} - -// for each itabEntry, gather the methods on -// the concrete type that implement the interface -func peekitabs() { - for i := range itabs { - tab := &itabs[i] - methods := genfun(tab.t, tab.itype) - if len(methods) == 0 { - continue - } - tab.entries = methods - } -} - -// for the given concrete type and interface -// type, return the (sorted) set of methods -// on the concrete type that implement the interface -func genfun(t, it *types.Type) []*obj.LSym { - if t == nil || it == nil { - return nil - } - sigs := imethods(it) - methods := methods(t) - out := make([]*obj.LSym, 0, len(sigs)) - // TODO(mdempsky): Short circuit before calling methods(t)? - // See discussion on CL 105039. - if len(sigs) == 0 { - return nil - } - - // both sigs and methods are sorted by name, - // so we can find the intersect in a single pass - for _, m := range methods { - if m.name == sigs[0].name { - out = append(out, m.isym.Linksym()) - sigs = sigs[1:] - if len(sigs) == 0 { - break - } - } - } - - if len(sigs) != 0 { - Fatalf("incomplete itab") - } - - return out -} - -// itabsym uses the information gathered in -// peekitabs to de-virtualize interface methods. -// Since this is called by the SSA backend, it shouldn't -// generate additional Nodes, Syms, etc. -func itabsym(it *obj.LSym, offset int64) *obj.LSym { - var syms []*obj.LSym - if it == nil { - return nil - } - - for i := range itabs { - e := &itabs[i] - if e.lsym == it { - syms = e.entries - break - } - } - if syms == nil { - return nil - } - - // keep this arithmetic in sync with *itab layout - methodnum := int((offset - 2*int64(Widthptr) - 8) / int64(Widthptr)) - if methodnum >= len(syms) { - return nil - } - return syms[methodnum] -} - -// addsignat ensures that a runtime type descriptor is emitted for t. -func addsignat(t *types.Type) { - if _, ok := signatset[t]; !ok { - signatset[t] = struct{}{} - signatslice = append(signatslice, t) - } -} - -func addsignats(dcls []*Node) { - // copy types from dcl list to signatset - for _, n := range dcls { - if n.Op == OTYPE { - addsignat(n.Type) - } - } -} - -func dumpsignats() { - // Process signatset. Use a loop, as dtypesym adds - // entries to signatset while it is being processed. - signats := make([]typeAndStr, len(signatslice)) - for len(signatslice) > 0 { - signats = signats[:0] - // Transfer entries to a slice and sort, for reproducible builds. - for _, t := range signatslice { - signats = append(signats, typeAndStr{t: t, short: typesymname(t), regular: t.String()}) - delete(signatset, t) - } - signatslice = signatslice[:0] - sort.Sort(typesByString(signats)) - for _, ts := range signats { - t := ts.t - dtypesym(t) - if t.Sym != nil { - dtypesym(types.NewPtr(t)) - } - } - } -} - -func dumptabs() { - // process itabs - for _, i := range itabs { - // dump empty itab symbol into i.sym - // type itab struct { - // inter *interfacetype - // _type *_type - // hash uint32 - // _ [4]byte - // fun [1]uintptr // variable sized - // } - o := dsymptr(i.lsym, 0, dtypesym(i.itype), 0) - o = dsymptr(i.lsym, o, dtypesym(i.t), 0) - o = duint32(i.lsym, o, typehash(i.t)) // copy of type hash - o += 4 // skip unused field - for _, fn := range genfun(i.t, i.itype) { - o = dsymptr(i.lsym, o, fn, 0) // method pointer for each method - } - // Nothing writes static itabs, so they are read only. - ggloblsym(i.lsym, int32(o), int16(obj.DUPOK|obj.RODATA)) - i.lsym.Set(obj.AttrContentAddressable, true) - } - - // process ptabs - if localpkg.Name == "main" && len(ptabs) > 0 { - ot := 0 - s := Ctxt.Lookup("go.plugin.tabs") - for _, p := range ptabs { - // Dump ptab symbol into go.pluginsym package. - // - // type ptab struct { - // name nameOff - // typ typeOff // pointer to symbol - // } - nsym := dname(p.s.Name, "", nil, true) - tsym := dtypesym(p.t) - ot = dsymptrOff(s, ot, nsym) - ot = dsymptrOff(s, ot, tsym) - // Plugin exports symbols as interfaces. Mark their types - // as UsedInIface. - tsym.Set(obj.AttrUsedInIface, true) - } - ggloblsym(s, int32(ot), int16(obj.RODATA)) - - ot = 0 - s = Ctxt.Lookup("go.plugin.exports") - for _, p := range ptabs { - ot = dsymptr(s, ot, p.s.Linksym(), 0) - } - ggloblsym(s, int32(ot), int16(obj.RODATA)) - } -} - -func dumpimportstrings() { - // generate import strings for imported packages - for _, p := range types.ImportedPkgList() { - dimportpath(p) - } -} - -func dumpbasictypes() { - // do basic types if compiling package runtime. - // they have to be in at least one package, - // and runtime is always loaded implicitly, - // so this is as good as any. - // another possible choice would be package main, - // but using runtime means fewer copies in object files. - if myimportpath == "runtime" { - for i := types.EType(1); i <= TBOOL; i++ { - dtypesym(types.NewPtr(types.Types[i])) - } - dtypesym(types.NewPtr(types.Types[TSTRING])) - dtypesym(types.NewPtr(types.Types[TUNSAFEPTR])) - - // emit type structs for error and func(error) string. - // The latter is the type of an auto-generated wrapper. - dtypesym(types.NewPtr(types.Errortype)) - - dtypesym(functype(nil, []*Node{anonfield(types.Errortype)}, []*Node{anonfield(types.Types[TSTRING])})) - - // add paths for runtime and main, which 6l imports implicitly. - dimportpath(Runtimepkg) - - if flag_race { - dimportpath(racepkg) - } - if flag_msan { - dimportpath(msanpkg) - } - dimportpath(types.NewPkg("main", "")) - } -} - -type typeAndStr struct { - t *types.Type - short string - regular string -} - -type typesByString []typeAndStr - -func (a typesByString) Len() int { return len(a) } -func (a typesByString) Less(i, j int) bool { - if a[i].short != a[j].short { - return a[i].short < a[j].short - } - // When the only difference between the types is whether - // they refer to byte or uint8, such as **byte vs **uint8, - // the types' ShortStrings can be identical. - // To preserve deterministic sort ordering, sort these by String(). - if a[i].regular != a[j].regular { - return a[i].regular < a[j].regular - } - // Identical anonymous interfaces defined in different locations - // will be equal for the above checks, but different in DWARF output. - // Sort by source position to ensure deterministic order. - // See issues 27013 and 30202. - if a[i].t.Etype == types.TINTER && a[i].t.Methods().Len() > 0 { - return a[i].t.Methods().Index(0).Pos.Before(a[j].t.Methods().Index(0).Pos) - } - return false -} -func (a typesByString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -// maxPtrmaskBytes is the maximum length of a GC ptrmask bitmap, -// which holds 1-bit entries describing where pointers are in a given type. -// Above this length, the GC information is recorded as a GC program, -// which can express repetition compactly. In either form, the -// information is used by the runtime to initialize the heap bitmap, -// and for large types (like 128 or more words), they are roughly the -// same speed. GC programs are never much larger and often more -// compact. (If large arrays are involved, they can be arbitrarily -// more compact.) -// -// The cutoff must be large enough that any allocation large enough to -// use a GC program is large enough that it does not share heap bitmap -// bytes with any other objects, allowing the GC program execution to -// assume an aligned start and not use atomic operations. In the current -// runtime, this means all malloc size classes larger than the cutoff must -// be multiples of four words. On 32-bit systems that's 16 bytes, and -// all size classes >= 16 bytes are 16-byte aligned, so no real constraint. -// On 64-bit systems, that's 32 bytes, and 32-byte alignment is guaranteed -// for size classes >= 256 bytes. On a 64-bit system, 256 bytes allocated -// is 32 pointers, the bits for which fit in 4 bytes. So maxPtrmaskBytes -// must be >= 4. -// -// We used to use 16 because the GC programs do have some constant overhead -// to get started, and processing 128 pointers seems to be enough to -// amortize that overhead well. -// -// To make sure that the runtime's chansend can call typeBitsBulkBarrier, -// we raised the limit to 2048, so that even 32-bit systems are guaranteed to -// use bitmaps for objects up to 64 kB in size. -// -// Also known to reflect/type.go. -// -const maxPtrmaskBytes = 2048 - -// dgcsym emits and returns a data symbol containing GC information for type t, -// along with a boolean reporting whether the UseGCProg bit should be set in -// the type kind, and the ptrdata field to record in the reflect type information. -func dgcsym(t *types.Type) (lsym *obj.LSym, useGCProg bool, ptrdata int64) { - ptrdata = typeptrdata(t) - if ptrdata/int64(Widthptr) <= maxPtrmaskBytes*8 { - lsym = dgcptrmask(t) - return - } - - useGCProg = true - lsym, ptrdata = dgcprog(t) - return -} - -// dgcptrmask emits and returns the symbol containing a pointer mask for type t. -func dgcptrmask(t *types.Type) *obj.LSym { - ptrmask := make([]byte, (typeptrdata(t)/int64(Widthptr)+7)/8) - fillptrmask(t, ptrmask) - p := fmt.Sprintf("gcbits.%x", ptrmask) - - sym := Runtimepkg.Lookup(p) - lsym := sym.Linksym() - if !sym.Uniq() { - sym.SetUniq(true) - for i, x := range ptrmask { - duint8(lsym, i, x) - } - ggloblsym(lsym, int32(len(ptrmask)), obj.DUPOK|obj.RODATA|obj.LOCAL) - lsym.Set(obj.AttrContentAddressable, true) - } - return lsym -} - -// fillptrmask fills in ptrmask with 1s corresponding to the -// word offsets in t that hold pointers. -// ptrmask is assumed to fit at least typeptrdata(t)/Widthptr bits. -func fillptrmask(t *types.Type, ptrmask []byte) { - for i := range ptrmask { - ptrmask[i] = 0 - } - if !t.HasPointers() { - return - } - - vec := bvalloc(8 * int32(len(ptrmask))) - onebitwalktype1(t, 0, vec) - - nptr := typeptrdata(t) / int64(Widthptr) - for i := int64(0); i < nptr; i++ { - if vec.Get(int32(i)) { - ptrmask[i/8] |= 1 << (uint(i) % 8) - } - } -} - -// dgcprog emits and returns the symbol containing a GC program for type t -// along with the size of the data described by the program (in the range [typeptrdata(t), t.Width]). -// In practice, the size is typeptrdata(t) except for non-trivial arrays. -// For non-trivial arrays, the program describes the full t.Width size. -func dgcprog(t *types.Type) (*obj.LSym, int64) { - dowidth(t) - if t.Width == BADWIDTH { - Fatalf("dgcprog: %v badwidth", t) - } - lsym := typesymprefix(".gcprog", t).Linksym() - var p GCProg - p.init(lsym) - p.emit(t, 0) - offset := p.w.BitIndex() * int64(Widthptr) - p.end() - if ptrdata := typeptrdata(t); offset < ptrdata || offset > t.Width { - Fatalf("dgcprog: %v: offset=%d but ptrdata=%d size=%d", t, offset, ptrdata, t.Width) - } - return lsym, offset -} - -type GCProg struct { - lsym *obj.LSym - symoff int - w gcprog.Writer -} - -var Debug_gcprog int // set by -d gcprog - -func (p *GCProg) init(lsym *obj.LSym) { - p.lsym = lsym - p.symoff = 4 // first 4 bytes hold program length - p.w.Init(p.writeByte) - if Debug_gcprog > 0 { - fmt.Fprintf(os.Stderr, "compile: start GCProg for %v\n", lsym) - p.w.Debug(os.Stderr) - } -} - -func (p *GCProg) writeByte(x byte) { - p.symoff = duint8(p.lsym, p.symoff, x) -} - -func (p *GCProg) end() { - p.w.End() - duint32(p.lsym, 0, uint32(p.symoff-4)) - ggloblsym(p.lsym, int32(p.symoff), obj.DUPOK|obj.RODATA|obj.LOCAL) - if Debug_gcprog > 0 { - fmt.Fprintf(os.Stderr, "compile: end GCProg for %v\n", p.lsym) - } -} - -func (p *GCProg) emit(t *types.Type, offset int64) { - dowidth(t) - if !t.HasPointers() { - return - } - if t.Width == int64(Widthptr) { - p.w.Ptr(offset / int64(Widthptr)) - return - } - switch t.Etype { - default: - Fatalf("GCProg.emit: unexpected type %v", t) - - case TSTRING: - p.w.Ptr(offset / int64(Widthptr)) - - case TINTER: - // Note: the first word isn't a pointer. See comment in plive.go:onebitwalktype1. - p.w.Ptr(offset/int64(Widthptr) + 1) - - case TSLICE: - p.w.Ptr(offset / int64(Widthptr)) - - case TARRAY: - if t.NumElem() == 0 { - // should have been handled by haspointers check above - Fatalf("GCProg.emit: empty array") - } - - // Flatten array-of-array-of-array to just a big array by multiplying counts. - count := t.NumElem() - elem := t.Elem() - for elem.IsArray() { - count *= elem.NumElem() - elem = elem.Elem() - } - - if !p.w.ShouldRepeat(elem.Width/int64(Widthptr), count) { - // Cheaper to just emit the bits. - for i := int64(0); i < count; i++ { - p.emit(elem, offset+i*elem.Width) - } - return - } - p.emit(elem, offset) - p.w.ZeroUntil((offset + elem.Width) / int64(Widthptr)) - p.w.Repeat(elem.Width/int64(Widthptr), count-1) - - case TSTRUCT: - for _, t1 := range t.Fields().Slice() { - p.emit(t1.Type, offset+t1.Offset) - } - } -} - -// zeroaddr returns the address of a symbol with at least -// size bytes of zeros. -func zeroaddr(size int64) *Node { - if size >= 1<<31 { - Fatalf("map elem too big %d", size) - } - if zerosize < size { - zerosize = size - } - s := mappkg.Lookup("zero") - if s.Def == nil { - x := newname(s) - x.Type = types.Types[TUINT8] - x.SetClass(PEXTERN) - x.SetTypecheck(1) - s.Def = asTypesNode(x) - } - z := nod(OADDR, asNode(s.Def), nil) - z.Type = types.NewPtr(types.Types[TUINT8]) - z.SetTypecheck(1) - return z -} diff --git a/src/cmd/compile/internal/gc/select.go b/src/cmd/compile/internal/gc/select.go deleted file mode 100644 index 97e0424ce07f8411d4b5b786d51d5d31369484b0..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/select.go +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import "cmd/compile/internal/types" - -// select -func typecheckselect(sel *Node) { - var def *Node - lno := setlineno(sel) - typecheckslice(sel.Ninit.Slice(), ctxStmt) - for _, ncase := range sel.List.Slice() { - if ncase.Op != OCASE { - setlineno(ncase) - Fatalf("typecheckselect %v", ncase.Op) - } - - if ncase.List.Len() == 0 { - // default - if def != nil { - yyerrorl(ncase.Pos, "multiple defaults in select (first at %v)", def.Line()) - } else { - def = ncase - } - } else if ncase.List.Len() > 1 { - yyerrorl(ncase.Pos, "select cases cannot be lists") - } else { - ncase.List.SetFirst(typecheck(ncase.List.First(), ctxStmt)) - n := ncase.List.First() - ncase.Left = n - ncase.List.Set(nil) - switch n.Op { - default: - pos := n.Pos - if n.Op == ONAME { - // We don't have the right position for ONAME nodes (see #15459 and - // others). Using ncase.Pos for now as it will provide the correct - // line number (assuming the expression follows the "case" keyword - // on the same line). This matches the approach before 1.10. - pos = ncase.Pos - } - yyerrorl(pos, "select case must be receive, send or assign recv") - - // convert x = <-c into OSELRECV(x, <-c). - // remove implicit conversions; the eventual assignment - // will reintroduce them. - case OAS: - if (n.Right.Op == OCONVNOP || n.Right.Op == OCONVIFACE) && n.Right.Implicit() { - n.Right = n.Right.Left - } - - if n.Right.Op != ORECV { - yyerrorl(n.Pos, "select assignment must have receive on right hand side") - break - } - - n.Op = OSELRECV - - // convert x, ok = <-c into OSELRECV2(x, <-c) with ntest=ok - case OAS2RECV: - if n.Right.Op != ORECV { - yyerrorl(n.Pos, "select assignment must have receive on right hand side") - break - } - - n.Op = OSELRECV2 - n.Left = n.List.First() - n.List.Set1(n.List.Second()) - - // convert <-c into OSELRECV(N, <-c) - case ORECV: - n = nodl(n.Pos, OSELRECV, nil, n) - - n.SetTypecheck(1) - ncase.Left = n - - case OSEND: - break - } - } - - typecheckslice(ncase.Nbody.Slice(), ctxStmt) - } - - lineno = lno -} - -func walkselect(sel *Node) { - lno := setlineno(sel) - if sel.Nbody.Len() != 0 { - Fatalf("double walkselect") - } - - init := sel.Ninit.Slice() - sel.Ninit.Set(nil) - - init = append(init, walkselectcases(&sel.List)...) - sel.List.Set(nil) - - sel.Nbody.Set(init) - walkstmtlist(sel.Nbody.Slice()) - - lineno = lno -} - -func walkselectcases(cases *Nodes) []*Node { - ncas := cases.Len() - sellineno := lineno - - // optimization: zero-case select - if ncas == 0 { - return []*Node{mkcall("block", nil, nil)} - } - - // optimization: one-case select: single op. - if ncas == 1 { - cas := cases.First() - setlineno(cas) - l := cas.Ninit.Slice() - if cas.Left != nil { // not default: - n := cas.Left - l = append(l, n.Ninit.Slice()...) - n.Ninit.Set(nil) - switch n.Op { - default: - Fatalf("select %v", n.Op) - - case OSEND: - // already ok - - case OSELRECV, OSELRECV2: - if n.Op == OSELRECV || n.List.Len() == 0 { - if n.Left == nil { - n = n.Right - } else { - n.Op = OAS - } - break - } - - if n.Left == nil { - nblank = typecheck(nblank, ctxExpr|ctxAssign) - n.Left = nblank - } - - n.Op = OAS2 - n.List.Prepend(n.Left) - n.Rlist.Set1(n.Right) - n.Right = nil - n.Left = nil - n.SetTypecheck(0) - n = typecheck(n, ctxStmt) - } - - l = append(l, n) - } - - l = append(l, cas.Nbody.Slice()...) - l = append(l, nod(OBREAK, nil, nil)) - return l - } - - // convert case value arguments to addresses. - // this rewrite is used by both the general code and the next optimization. - var dflt *Node - for _, cas := range cases.Slice() { - setlineno(cas) - n := cas.Left - if n == nil { - dflt = cas - continue - } - switch n.Op { - case OSEND: - n.Right = nod(OADDR, n.Right, nil) - n.Right = typecheck(n.Right, ctxExpr) - - case OSELRECV, OSELRECV2: - if n.Op == OSELRECV2 && n.List.Len() == 0 { - n.Op = OSELRECV - } - - if n.Left != nil { - n.Left = nod(OADDR, n.Left, nil) - n.Left = typecheck(n.Left, ctxExpr) - } - } - } - - // optimization: two-case select but one is default: single non-blocking op. - if ncas == 2 && dflt != nil { - cas := cases.First() - if cas == dflt { - cas = cases.Second() - } - - n := cas.Left - setlineno(n) - r := nod(OIF, nil, nil) - r.Ninit.Set(cas.Ninit.Slice()) - switch n.Op { - default: - Fatalf("select %v", n.Op) - - case OSEND: - // if selectnbsend(c, v) { body } else { default body } - ch := n.Left - r.Left = mkcall1(chanfn("selectnbsend", 2, ch.Type), types.Types[TBOOL], &r.Ninit, ch, n.Right) - - case OSELRECV: - // if selectnbrecv(&v, c) { body } else { default body } - ch := n.Right.Left - elem := n.Left - if elem == nil { - elem = nodnil() - } - r.Left = mkcall1(chanfn("selectnbrecv", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, ch) - - case OSELRECV2: - // if selectnbrecv2(&v, &received, c) { body } else { default body } - ch := n.Right.Left - elem := n.Left - if elem == nil { - elem = nodnil() - } - receivedp := nod(OADDR, n.List.First(), nil) - receivedp = typecheck(receivedp, ctxExpr) - r.Left = mkcall1(chanfn("selectnbrecv2", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, receivedp, ch) - } - - r.Left = typecheck(r.Left, ctxExpr) - r.Nbody.Set(cas.Nbody.Slice()) - r.Rlist.Set(append(dflt.Ninit.Slice(), dflt.Nbody.Slice()...)) - return []*Node{r, nod(OBREAK, nil, nil)} - } - - if dflt != nil { - ncas-- - } - casorder := make([]*Node, ncas) - nsends, nrecvs := 0, 0 - - var init []*Node - - // generate sel-struct - lineno = sellineno - selv := temp(types.NewArray(scasetype(), int64(ncas))) - r := nod(OAS, selv, nil) - r = typecheck(r, ctxStmt) - init = append(init, r) - - // No initialization for order; runtime.selectgo is responsible for that. - order := temp(types.NewArray(types.Types[TUINT16], 2*int64(ncas))) - - var pc0, pcs *Node - if flag_race { - pcs = temp(types.NewArray(types.Types[TUINTPTR], int64(ncas))) - pc0 = typecheck(nod(OADDR, nod(OINDEX, pcs, nodintconst(0)), nil), ctxExpr) - } else { - pc0 = nodnil() - } - - // register cases - for _, cas := range cases.Slice() { - setlineno(cas) - - init = append(init, cas.Ninit.Slice()...) - cas.Ninit.Set(nil) - - n := cas.Left - if n == nil { // default: - continue - } - - var i int - var c, elem *Node - switch n.Op { - default: - Fatalf("select %v", n.Op) - case OSEND: - i = nsends - nsends++ - c = n.Left - elem = n.Right - case OSELRECV, OSELRECV2: - nrecvs++ - i = ncas - nrecvs - c = n.Right.Left - elem = n.Left - } - - casorder[i] = cas - - setField := func(f string, val *Node) { - r := nod(OAS, nodSym(ODOT, nod(OINDEX, selv, nodintconst(int64(i))), lookup(f)), val) - r = typecheck(r, ctxStmt) - init = append(init, r) - } - - c = convnop(c, types.Types[TUNSAFEPTR]) - setField("c", c) - if elem != nil { - elem = convnop(elem, types.Types[TUNSAFEPTR]) - setField("elem", elem) - } - - // TODO(mdempsky): There should be a cleaner way to - // handle this. - if flag_race { - r = mkcall("selectsetpc", nil, nil, nod(OADDR, nod(OINDEX, pcs, nodintconst(int64(i))), nil)) - init = append(init, r) - } - } - if nsends+nrecvs != ncas { - Fatalf("walkselectcases: miscount: %v + %v != %v", nsends, nrecvs, ncas) - } - - // run the select - lineno = sellineno - chosen := temp(types.Types[TINT]) - recvOK := temp(types.Types[TBOOL]) - r = nod(OAS2, nil, nil) - r.List.Set2(chosen, recvOK) - fn := syslook("selectgo") - r.Rlist.Set1(mkcall1(fn, fn.Type.Results(), nil, bytePtrToIndex(selv, 0), bytePtrToIndex(order, 0), pc0, nodintconst(int64(nsends)), nodintconst(int64(nrecvs)), nodbool(dflt == nil))) - r = typecheck(r, ctxStmt) - init = append(init, r) - - // selv and order are no longer alive after selectgo. - init = append(init, nod(OVARKILL, selv, nil)) - init = append(init, nod(OVARKILL, order, nil)) - if flag_race { - init = append(init, nod(OVARKILL, pcs, nil)) - } - - // dispatch cases - dispatch := func(cond, cas *Node) { - cond = typecheck(cond, ctxExpr) - cond = defaultlit(cond, nil) - - r := nod(OIF, cond, nil) - - if n := cas.Left; n != nil && n.Op == OSELRECV2 { - x := nod(OAS, n.List.First(), recvOK) - x = typecheck(x, ctxStmt) - r.Nbody.Append(x) - } - - r.Nbody.AppendNodes(&cas.Nbody) - r.Nbody.Append(nod(OBREAK, nil, nil)) - init = append(init, r) - } - - if dflt != nil { - setlineno(dflt) - dispatch(nod(OLT, chosen, nodintconst(0)), dflt) - } - for i, cas := range casorder { - setlineno(cas) - dispatch(nod(OEQ, chosen, nodintconst(int64(i))), cas) - } - - return init -} - -// bytePtrToIndex returns a Node representing "(*byte)(&n[i])". -func bytePtrToIndex(n *Node, i int64) *Node { - s := nod(OADDR, nod(OINDEX, n, nodintconst(i)), nil) - t := types.NewPtr(types.Types[TUINT8]) - return convnop(s, t) -} - -var scase *types.Type - -// Keep in sync with src/runtime/select.go. -func scasetype() *types.Type { - if scase == nil { - scase = tostruct([]*Node{ - namedfield("c", types.Types[TUNSAFEPTR]), - namedfield("elem", types.Types[TUNSAFEPTR]), - }) - scase.SetNoalg(true) - } - return scase -} diff --git a/src/cmd/compile/internal/gc/sinit.go b/src/cmd/compile/internal/gc/sinit.go deleted file mode 100644 index 4d0837bc74675e73ebdee9b89b9647f175555474..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/sinit.go +++ /dev/null @@ -1,1172 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/obj" - "fmt" -) - -type InitEntry struct { - Xoffset int64 // struct, array only - Expr *Node // bytes of run-time computed expressions -} - -type InitPlan struct { - E []InitEntry -} - -// An InitSchedule is used to decompose assignment statements into -// static and dynamic initialization parts. Static initializations are -// handled by populating variables' linker symbol data, while dynamic -// initializations are accumulated to be executed in order. -type InitSchedule struct { - // out is the ordered list of dynamic initialization - // statements. - out []*Node - - initplans map[*Node]*InitPlan - inittemps map[*Node]*Node -} - -func (s *InitSchedule) append(n *Node) { - s.out = append(s.out, n) -} - -// staticInit adds an initialization statement n to the schedule. -func (s *InitSchedule) staticInit(n *Node) { - if !s.tryStaticInit(n) { - if Debug.P != 0 { - Dump("nonstatic", n) - } - s.append(n) - } -} - -// tryStaticInit attempts to statically execute an initialization -// statement and reports whether it succeeded. -func (s *InitSchedule) tryStaticInit(n *Node) bool { - // Only worry about simple "l = r" assignments. Multiple - // variable/expression OAS2 assignments have already been - // replaced by multiple simple OAS assignments, and the other - // OAS2* assignments mostly necessitate dynamic execution - // anyway. - if n.Op != OAS { - return false - } - if n.Left.isBlank() && candiscard(n.Right) { - return true - } - lno := setlineno(n) - defer func() { lineno = lno }() - return s.staticassign(n.Left, n.Right) -} - -// like staticassign but we are copying an already -// initialized value r. -func (s *InitSchedule) staticcopy(l *Node, r *Node) bool { - if r.Op != ONAME { - return false - } - if r.Class() == PFUNC { - pfuncsym(l, r) - return true - } - if r.Class() != PEXTERN || r.Sym.Pkg != localpkg { - return false - } - if r.Name.Defn == nil { // probably zeroed but perhaps supplied externally and of unknown value - return false - } - if r.Name.Defn.Op != OAS { - return false - } - if r.Type.IsString() { // perhaps overwritten by cmd/link -X (#34675) - return false - } - orig := r - r = r.Name.Defn.Right - - for r.Op == OCONVNOP && !types.Identical(r.Type, l.Type) { - r = r.Left - } - - switch r.Op { - case ONAME: - if s.staticcopy(l, r) { - return true - } - // We may have skipped past one or more OCONVNOPs, so - // use conv to ensure r is assignable to l (#13263). - s.append(nod(OAS, l, conv(r, l.Type))) - return true - - case OLITERAL: - if isZero(r) { - return true - } - litsym(l, r, int(l.Type.Width)) - return true - - case OADDR: - if a := r.Left; a.Op == ONAME { - addrsym(l, a) - return true - } - - case OPTRLIT: - switch r.Left.Op { - case OARRAYLIT, OSLICELIT, OSTRUCTLIT, OMAPLIT: - // copy pointer - addrsym(l, s.inittemps[r]) - return true - } - - case OSLICELIT: - // copy slice - a := s.inittemps[r] - slicesym(l, a, r.Right.Int64Val()) - return true - - case OARRAYLIT, OSTRUCTLIT: - p := s.initplans[r] - - n := l.copy() - for i := range p.E { - e := &p.E[i] - n.Xoffset = l.Xoffset + e.Xoffset - n.Type = e.Expr.Type - if e.Expr.Op == OLITERAL { - litsym(n, e.Expr, int(n.Type.Width)) - continue - } - ll := n.sepcopy() - if s.staticcopy(ll, e.Expr) { - continue - } - // Requires computation, but we're - // copying someone else's computation. - rr := orig.sepcopy() - rr.Type = ll.Type - rr.Xoffset += e.Xoffset - setlineno(rr) - s.append(nod(OAS, ll, rr)) - } - - return true - } - - return false -} - -func (s *InitSchedule) staticassign(l *Node, r *Node) bool { - for r.Op == OCONVNOP { - r = r.Left - } - - switch r.Op { - case ONAME: - return s.staticcopy(l, r) - - case OLITERAL: - if isZero(r) { - return true - } - litsym(l, r, int(l.Type.Width)) - return true - - case OADDR: - var nam Node - if stataddr(&nam, r.Left) { - addrsym(l, &nam) - return true - } - fallthrough - - case OPTRLIT: - switch r.Left.Op { - case OARRAYLIT, OSLICELIT, OMAPLIT, OSTRUCTLIT: - // Init pointer. - a := staticname(r.Left.Type) - - s.inittemps[r] = a - addrsym(l, a) - - // Init underlying literal. - if !s.staticassign(a, r.Left) { - s.append(nod(OAS, a, r.Left)) - } - return true - } - //dump("not static ptrlit", r); - - case OSTR2BYTES: - if l.Class() == PEXTERN && r.Left.Op == OLITERAL { - sval := r.Left.StringVal() - slicebytes(l, sval) - return true - } - - case OSLICELIT: - s.initplan(r) - // Init slice. - bound := r.Right.Int64Val() - ta := types.NewArray(r.Type.Elem(), bound) - ta.SetNoalg(true) - a := staticname(ta) - s.inittemps[r] = a - slicesym(l, a, bound) - // Fall through to init underlying array. - l = a - fallthrough - - case OARRAYLIT, OSTRUCTLIT: - s.initplan(r) - - p := s.initplans[r] - n := l.copy() - for i := range p.E { - e := &p.E[i] - n.Xoffset = l.Xoffset + e.Xoffset - n.Type = e.Expr.Type - if e.Expr.Op == OLITERAL { - litsym(n, e.Expr, int(n.Type.Width)) - continue - } - setlineno(e.Expr) - a := n.sepcopy() - if !s.staticassign(a, e.Expr) { - s.append(nod(OAS, a, e.Expr)) - } - } - - return true - - case OMAPLIT: - break - - case OCLOSURE: - if hasemptycvars(r) { - if Debug_closure > 0 { - Warnl(r.Pos, "closure converted to global") - } - // Closures with no captured variables are globals, - // so the assignment can be done at link time. - pfuncsym(l, r.Func.Closure.Func.Nname) - return true - } - closuredebugruntimecheck(r) - - case OCONVIFACE: - // This logic is mirrored in isStaticCompositeLiteral. - // If you change something here, change it there, and vice versa. - - // Determine the underlying concrete type and value we are converting from. - val := r - for val.Op == OCONVIFACE { - val = val.Left - } - if val.Type.IsInterface() { - // val is an interface type. - // If val is nil, we can statically initialize l; - // both words are zero and so there no work to do, so report success. - // If val is non-nil, we have no concrete type to record, - // and we won't be able to statically initialize its value, so report failure. - return Isconst(val, CTNIL) - } - - markTypeUsedInInterface(val.Type, l.Sym.Linksym()) - - var itab *Node - if l.Type.IsEmptyInterface() { - itab = typename(val.Type) - } else { - itab = itabname(val.Type, l.Type) - } - - // Create a copy of l to modify while we emit data. - n := l.copy() - - // Emit itab, advance offset. - addrsym(n, itab.Left) // itab is an OADDR node - n.Xoffset += int64(Widthptr) - - // Emit data. - if isdirectiface(val.Type) { - if Isconst(val, CTNIL) { - // Nil is zero, nothing to do. - return true - } - // Copy val directly into n. - n.Type = val.Type - setlineno(val) - a := n.sepcopy() - if !s.staticassign(a, val) { - s.append(nod(OAS, a, val)) - } - } else { - // Construct temp to hold val, write pointer to temp into n. - a := staticname(val.Type) - s.inittemps[val] = a - if !s.staticassign(a, val) { - s.append(nod(OAS, a, val)) - } - addrsym(n, a) - } - - return true - } - - //dump("not static", r); - return false -} - -// initContext is the context in which static data is populated. -// It is either in an init function or in any other function. -// Static data populated in an init function will be written either -// zero times (as a readonly, static data symbol) or -// one time (during init function execution). -// Either way, there is no opportunity for races or further modification, -// so the data can be written to a (possibly readonly) data symbol. -// Static data populated in any other function needs to be local to -// that function to allow multiple instances of that function -// to execute concurrently without clobbering each others' data. -type initContext uint8 - -const ( - inInitFunction initContext = iota - inNonInitFunction -) - -func (c initContext) String() string { - if c == inInitFunction { - return "inInitFunction" - } - return "inNonInitFunction" -} - -// from here down is the walk analysis -// of composite literals. -// most of the work is to generate -// data statements for the constant -// part of the composite literal. - -var statuniqgen int // name generator for static temps - -// staticname returns a name backed by a (writable) static data symbol. -// Use readonlystaticname for read-only node. -func staticname(t *types.Type) *Node { - // Don't use lookupN; it interns the resulting string, but these are all unique. - n := newname(lookup(fmt.Sprintf("%s%d", obj.StaticNamePref, statuniqgen))) - statuniqgen++ - addvar(n, t, PEXTERN) - return n -} - -// readonlystaticname returns a name backed by a read-only static data symbol. -func readonlystaticname(t *types.Type) *Node { - n := staticname(t) - n.MarkReadonly() - n.Sym.Linksym().Set(obj.AttrContentAddressable, true) - n.Sym.Linksym().Set(obj.AttrLocal, true) - return n -} - -func (n *Node) isSimpleName() bool { - return n.Op == ONAME && n.Class() != PAUTOHEAP && n.Class() != PEXTERN -} - -func litas(l *Node, r *Node, init *Nodes) { - a := nod(OAS, l, r) - a = typecheck(a, ctxStmt) - a = walkexpr(a, init) - init.Append(a) -} - -// initGenType is a bitmap indicating the types of generation that will occur for a static value. -type initGenType uint8 - -const ( - initDynamic initGenType = 1 << iota // contains some dynamic values, for which init code will be generated - initConst // contains some constant values, which may be written into data symbols -) - -// getdyn calculates the initGenType for n. -// If top is false, getdyn is recursing. -func getdyn(n *Node, top bool) initGenType { - switch n.Op { - default: - if n.isGoConst() { - return initConst - } - return initDynamic - - case OSLICELIT: - if !top { - return initDynamic - } - if n.Right.Int64Val()/4 > int64(n.List.Len()) { - // <25% of entries have explicit values. - // Very rough estimation, it takes 4 bytes of instructions - // to initialize 1 byte of result. So don't use a static - // initializer if the dynamic initialization code would be - // smaller than the static value. - // See issue 23780. - return initDynamic - } - - case OARRAYLIT, OSTRUCTLIT: - } - - var mode initGenType - for _, n1 := range n.List.Slice() { - switch n1.Op { - case OKEY: - n1 = n1.Right - case OSTRUCTKEY: - n1 = n1.Left - } - mode |= getdyn(n1, false) - if mode == initDynamic|initConst { - break - } - } - return mode -} - -// isStaticCompositeLiteral reports whether n is a compile-time constant. -func isStaticCompositeLiteral(n *Node) bool { - switch n.Op { - case OSLICELIT: - return false - case OARRAYLIT: - for _, r := range n.List.Slice() { - if r.Op == OKEY { - r = r.Right - } - if !isStaticCompositeLiteral(r) { - return false - } - } - return true - case OSTRUCTLIT: - for _, r := range n.List.Slice() { - if r.Op != OSTRUCTKEY { - Fatalf("isStaticCompositeLiteral: rhs not OSTRUCTKEY: %v", r) - } - if !isStaticCompositeLiteral(r.Left) { - return false - } - } - return true - case OLITERAL: - return true - case OCONVIFACE: - // See staticassign's OCONVIFACE case for comments. - val := n - for val.Op == OCONVIFACE { - val = val.Left - } - if val.Type.IsInterface() { - return Isconst(val, CTNIL) - } - if isdirectiface(val.Type) && Isconst(val, CTNIL) { - return true - } - return isStaticCompositeLiteral(val) - } - return false -} - -// initKind is a kind of static initialization: static, dynamic, or local. -// Static initialization represents literals and -// literal components of composite literals. -// Dynamic initialization represents non-literals and -// non-literal components of composite literals. -// LocalCode initialization represents initialization -// that occurs purely in generated code local to the function of use. -// Initialization code is sometimes generated in passes, -// first static then dynamic. -type initKind uint8 - -const ( - initKindStatic initKind = iota + 1 - initKindDynamic - initKindLocalCode -) - -// fixedlit handles struct, array, and slice literals. -// TODO: expand documentation. -func fixedlit(ctxt initContext, kind initKind, n *Node, var_ *Node, init *Nodes) { - isBlank := var_ == nblank - var splitnode func(*Node) (a *Node, value *Node) - switch n.Op { - case OARRAYLIT, OSLICELIT: - var k int64 - splitnode = func(r *Node) (*Node, *Node) { - if r.Op == OKEY { - k = indexconst(r.Left) - if k < 0 { - Fatalf("fixedlit: invalid index %v", r.Left) - } - r = r.Right - } - a := nod(OINDEX, var_, nodintconst(k)) - k++ - if isBlank { - a = nblank - } - return a, r - } - case OSTRUCTLIT: - splitnode = func(r *Node) (*Node, *Node) { - if r.Op != OSTRUCTKEY { - Fatalf("fixedlit: rhs not OSTRUCTKEY: %v", r) - } - if r.Sym.IsBlank() || isBlank { - return nblank, r.Left - } - setlineno(r) - return nodSym(ODOT, var_, r.Sym), r.Left - } - default: - Fatalf("fixedlit bad op: %v", n.Op) - } - - for _, r := range n.List.Slice() { - a, value := splitnode(r) - if a == nblank && candiscard(value) { - continue - } - - switch value.Op { - case OSLICELIT: - if (kind == initKindStatic && ctxt == inNonInitFunction) || (kind == initKindDynamic && ctxt == inInitFunction) { - slicelit(ctxt, value, a, init) - continue - } - - case OARRAYLIT, OSTRUCTLIT: - fixedlit(ctxt, kind, value, a, init) - continue - } - - islit := value.isGoConst() - if (kind == initKindStatic && !islit) || (kind == initKindDynamic && islit) { - continue - } - - // build list of assignments: var[index] = expr - setlineno(a) - a = nod(OAS, a, value) - a = typecheck(a, ctxStmt) - switch kind { - case initKindStatic: - genAsStatic(a) - case initKindDynamic, initKindLocalCode: - a = orderStmtInPlace(a, map[string][]*Node{}) - a = walkstmt(a) - init.Append(a) - default: - Fatalf("fixedlit: bad kind %d", kind) - } - - } -} - -func isSmallSliceLit(n *Node) bool { - if n.Op != OSLICELIT { - return false - } - - r := n.Right - - return smallintconst(r) && (n.Type.Elem().Width == 0 || r.Int64Val() <= smallArrayBytes/n.Type.Elem().Width) -} - -func slicelit(ctxt initContext, n *Node, var_ *Node, init *Nodes) { - // make an array type corresponding the number of elements we have - t := types.NewArray(n.Type.Elem(), n.Right.Int64Val()) - dowidth(t) - - if ctxt == inNonInitFunction { - // put everything into static array - vstat := staticname(t) - - fixedlit(ctxt, initKindStatic, n, vstat, init) - fixedlit(ctxt, initKindDynamic, n, vstat, init) - - // copy static to slice - var_ = typecheck(var_, ctxExpr|ctxAssign) - var nam Node - if !stataddr(&nam, var_) || nam.Class() != PEXTERN { - Fatalf("slicelit: %v", var_) - } - slicesym(&nam, vstat, t.NumElem()) - return - } - - // recipe for var = []t{...} - // 1. make a static array - // var vstat [...]t - // 2. assign (data statements) the constant part - // vstat = constpart{} - // 3. make an auto pointer to array and allocate heap to it - // var vauto *[...]t = new([...]t) - // 4. copy the static array to the auto array - // *vauto = vstat - // 5. for each dynamic part assign to the array - // vauto[i] = dynamic part - // 6. assign slice of allocated heap to var - // var = vauto[:] - // - // an optimization is done if there is no constant part - // 3. var vauto *[...]t = new([...]t) - // 5. vauto[i] = dynamic part - // 6. var = vauto[:] - - // if the literal contains constants, - // make static initialized array (1),(2) - var vstat *Node - - mode := getdyn(n, true) - if mode&initConst != 0 && !isSmallSliceLit(n) { - if ctxt == inInitFunction { - vstat = readonlystaticname(t) - } else { - vstat = staticname(t) - } - fixedlit(ctxt, initKindStatic, n, vstat, init) - } - - // make new auto *array (3 declare) - vauto := temp(types.NewPtr(t)) - - // set auto to point at new temp or heap (3 assign) - var a *Node - if x := prealloc[n]; x != nil { - // temp allocated during order.go for dddarg - if !types.Identical(t, x.Type) { - panic("dotdotdot base type does not match order's assigned type") - } - - if vstat == nil { - a = nod(OAS, x, nil) - a = typecheck(a, ctxStmt) - init.Append(a) // zero new temp - } else { - // Declare that we're about to initialize all of x. - // (Which happens at the *vauto = vstat below.) - init.Append(nod(OVARDEF, x, nil)) - } - - a = nod(OADDR, x, nil) - } else if n.Esc == EscNone { - a = temp(t) - if vstat == nil { - a = nod(OAS, temp(t), nil) - a = typecheck(a, ctxStmt) - init.Append(a) // zero new temp - a = a.Left - } else { - init.Append(nod(OVARDEF, a, nil)) - } - - a = nod(OADDR, a, nil) - } else { - a = nod(ONEW, nil, nil) - a.List.Set1(typenod(t)) - } - - a = nod(OAS, vauto, a) - a = typecheck(a, ctxStmt) - a = walkexpr(a, init) - init.Append(a) - - if vstat != nil { - // copy static to heap (4) - a = nod(ODEREF, vauto, nil) - - a = nod(OAS, a, vstat) - a = typecheck(a, ctxStmt) - a = walkexpr(a, init) - init.Append(a) - } - - // put dynamics into array (5) - var index int64 - for _, value := range n.List.Slice() { - if value.Op == OKEY { - index = indexconst(value.Left) - if index < 0 { - Fatalf("slicelit: invalid index %v", value.Left) - } - value = value.Right - } - a := nod(OINDEX, vauto, nodintconst(index)) - a.SetBounded(true) - index++ - - // TODO need to check bounds? - - switch value.Op { - case OSLICELIT: - break - - case OARRAYLIT, OSTRUCTLIT: - k := initKindDynamic - if vstat == nil { - // Generate both static and dynamic initializations. - // See issue #31987. - k = initKindLocalCode - } - fixedlit(ctxt, k, value, a, init) - continue - } - - if vstat != nil && value.isGoConst() { // already set by copy from static value - continue - } - - // build list of vauto[c] = expr - setlineno(value) - a = nod(OAS, a, value) - - a = typecheck(a, ctxStmt) - a = orderStmtInPlace(a, map[string][]*Node{}) - a = walkstmt(a) - init.Append(a) - } - - // make slice out of heap (6) - a = nod(OAS, var_, nod(OSLICE, vauto, nil)) - - a = typecheck(a, ctxStmt) - a = orderStmtInPlace(a, map[string][]*Node{}) - a = walkstmt(a) - init.Append(a) -} - -func maplit(n *Node, m *Node, init *Nodes) { - // make the map var - a := nod(OMAKE, nil, nil) - a.Esc = n.Esc - a.List.Set2(typenod(n.Type), nodintconst(int64(n.List.Len()))) - litas(m, a, init) - - entries := n.List.Slice() - - // The order pass already removed any dynamic (runtime-computed) entries. - // All remaining entries are static. Double-check that. - for _, r := range entries { - if !isStaticCompositeLiteral(r.Left) || !isStaticCompositeLiteral(r.Right) { - Fatalf("maplit: entry is not a literal: %v", r) - } - } - - if len(entries) > 25 { - // For a large number of entries, put them in an array and loop. - - // build types [count]Tindex and [count]Tvalue - tk := types.NewArray(n.Type.Key(), int64(len(entries))) - te := types.NewArray(n.Type.Elem(), int64(len(entries))) - - tk.SetNoalg(true) - te.SetNoalg(true) - - dowidth(tk) - dowidth(te) - - // make and initialize static arrays - vstatk := readonlystaticname(tk) - vstate := readonlystaticname(te) - - datak := nod(OARRAYLIT, nil, nil) - datae := nod(OARRAYLIT, nil, nil) - for _, r := range entries { - datak.List.Append(r.Left) - datae.List.Append(r.Right) - } - fixedlit(inInitFunction, initKindStatic, datak, vstatk, init) - fixedlit(inInitFunction, initKindStatic, datae, vstate, init) - - // loop adding structure elements to map - // for i = 0; i < len(vstatk); i++ { - // map[vstatk[i]] = vstate[i] - // } - i := temp(types.Types[TINT]) - rhs := nod(OINDEX, vstate, i) - rhs.SetBounded(true) - - kidx := nod(OINDEX, vstatk, i) - kidx.SetBounded(true) - lhs := nod(OINDEX, m, kidx) - - zero := nod(OAS, i, nodintconst(0)) - cond := nod(OLT, i, nodintconst(tk.NumElem())) - incr := nod(OAS, i, nod(OADD, i, nodintconst(1))) - body := nod(OAS, lhs, rhs) - - loop := nod(OFOR, cond, incr) - loop.Nbody.Set1(body) - loop.Ninit.Set1(zero) - - loop = typecheck(loop, ctxStmt) - loop = walkstmt(loop) - init.Append(loop) - return - } - // For a small number of entries, just add them directly. - - // Build list of var[c] = expr. - // Use temporaries so that mapassign1 can have addressable key, elem. - // TODO(josharian): avoid map key temporaries for mapfast_* assignments with literal keys. - tmpkey := temp(m.Type.Key()) - tmpelem := temp(m.Type.Elem()) - - for _, r := range entries { - index, elem := r.Left, r.Right - - setlineno(index) - a := nod(OAS, tmpkey, index) - a = typecheck(a, ctxStmt) - a = walkstmt(a) - init.Append(a) - - setlineno(elem) - a = nod(OAS, tmpelem, elem) - a = typecheck(a, ctxStmt) - a = walkstmt(a) - init.Append(a) - - setlineno(tmpelem) - a = nod(OAS, nod(OINDEX, m, tmpkey), tmpelem) - a = typecheck(a, ctxStmt) - a = walkstmt(a) - init.Append(a) - } - - a = nod(OVARKILL, tmpkey, nil) - a = typecheck(a, ctxStmt) - init.Append(a) - a = nod(OVARKILL, tmpelem, nil) - a = typecheck(a, ctxStmt) - init.Append(a) -} - -func anylit(n *Node, var_ *Node, init *Nodes) { - t := n.Type - switch n.Op { - default: - Fatalf("anylit: not lit, op=%v node=%v", n.Op, n) - - case ONAME: - a := nod(OAS, var_, n) - a = typecheck(a, ctxStmt) - init.Append(a) - - case OPTRLIT: - if !t.IsPtr() { - Fatalf("anylit: not ptr") - } - - var r *Node - if n.Right != nil { - // n.Right is stack temporary used as backing store. - init.Append(nod(OAS, n.Right, nil)) // zero backing store, just in case (#18410) - r = nod(OADDR, n.Right, nil) - r = typecheck(r, ctxExpr) - } else { - r = nod(ONEW, nil, nil) - r.SetTypecheck(1) - r.Type = t - r.Esc = n.Esc - } - - r = walkexpr(r, init) - a := nod(OAS, var_, r) - - a = typecheck(a, ctxStmt) - init.Append(a) - - var_ = nod(ODEREF, var_, nil) - var_ = typecheck(var_, ctxExpr|ctxAssign) - anylit(n.Left, var_, init) - - case OSTRUCTLIT, OARRAYLIT: - if !t.IsStruct() && !t.IsArray() { - Fatalf("anylit: not struct/array") - } - - if var_.isSimpleName() && n.List.Len() > 4 { - // lay out static data - vstat := readonlystaticname(t) - - ctxt := inInitFunction - if n.Op == OARRAYLIT { - ctxt = inNonInitFunction - } - fixedlit(ctxt, initKindStatic, n, vstat, init) - - // copy static to var - a := nod(OAS, var_, vstat) - - a = typecheck(a, ctxStmt) - a = walkexpr(a, init) - init.Append(a) - - // add expressions to automatic - fixedlit(inInitFunction, initKindDynamic, n, var_, init) - break - } - - var components int64 - if n.Op == OARRAYLIT { - components = t.NumElem() - } else { - components = int64(t.NumFields()) - } - // initialization of an array or struct with unspecified components (missing fields or arrays) - if var_.isSimpleName() || int64(n.List.Len()) < components { - a := nod(OAS, var_, nil) - a = typecheck(a, ctxStmt) - a = walkexpr(a, init) - init.Append(a) - } - - fixedlit(inInitFunction, initKindLocalCode, n, var_, init) - - case OSLICELIT: - slicelit(inInitFunction, n, var_, init) - - case OMAPLIT: - if !t.IsMap() { - Fatalf("anylit: not map") - } - maplit(n, var_, init) - } -} - -func oaslit(n *Node, init *Nodes) bool { - if n.Left == nil || n.Right == nil { - // not a special composite literal assignment - return false - } - if n.Left.Type == nil || n.Right.Type == nil { - // not a special composite literal assignment - return false - } - if !n.Left.isSimpleName() { - // not a special composite literal assignment - return false - } - if !types.Identical(n.Left.Type, n.Right.Type) { - // not a special composite literal assignment - return false - } - - switch n.Right.Op { - default: - // not a special composite literal assignment - return false - - case OSTRUCTLIT, OARRAYLIT, OSLICELIT, OMAPLIT: - if vmatch1(n.Left, n.Right) { - // not a special composite literal assignment - return false - } - anylit(n.Right, n.Left, init) - } - - n.Op = OEMPTY - n.Right = nil - return true -} - -func getlit(lit *Node) int { - if smallintconst(lit) { - return int(lit.Int64Val()) - } - return -1 -} - -// stataddr sets nam to the static address of n and reports whether it succeeded. -func stataddr(nam *Node, n *Node) bool { - if n == nil { - return false - } - - switch n.Op { - case ONAME: - *nam = *n - return true - - case ODOT: - if !stataddr(nam, n.Left) { - break - } - nam.Xoffset += n.Xoffset - nam.Type = n.Type - return true - - case OINDEX: - if n.Left.Type.IsSlice() { - break - } - if !stataddr(nam, n.Left) { - break - } - l := getlit(n.Right) - if l < 0 { - break - } - - // Check for overflow. - if n.Type.Width != 0 && thearch.MAXWIDTH/n.Type.Width <= int64(l) { - break - } - nam.Xoffset += int64(l) * n.Type.Width - nam.Type = n.Type - return true - } - - return false -} - -func (s *InitSchedule) initplan(n *Node) { - if s.initplans[n] != nil { - return - } - p := new(InitPlan) - s.initplans[n] = p - switch n.Op { - default: - Fatalf("initplan") - - case OARRAYLIT, OSLICELIT: - var k int64 - for _, a := range n.List.Slice() { - if a.Op == OKEY { - k = indexconst(a.Left) - if k < 0 { - Fatalf("initplan arraylit: invalid index %v", a.Left) - } - a = a.Right - } - s.addvalue(p, k*n.Type.Elem().Width, a) - k++ - } - - case OSTRUCTLIT: - for _, a := range n.List.Slice() { - if a.Op != OSTRUCTKEY { - Fatalf("initplan structlit") - } - if a.Sym.IsBlank() { - continue - } - s.addvalue(p, a.Xoffset, a.Left) - } - - case OMAPLIT: - for _, a := range n.List.Slice() { - if a.Op != OKEY { - Fatalf("initplan maplit") - } - s.addvalue(p, -1, a.Right) - } - } -} - -func (s *InitSchedule) addvalue(p *InitPlan, xoffset int64, n *Node) { - // special case: zero can be dropped entirely - if isZero(n) { - return - } - - // special case: inline struct and array (not slice) literals - if isvaluelit(n) { - s.initplan(n) - q := s.initplans[n] - for _, qe := range q.E { - // qe is a copy; we are not modifying entries in q.E - qe.Xoffset += xoffset - p.E = append(p.E, qe) - } - return - } - - // add to plan - p.E = append(p.E, InitEntry{Xoffset: xoffset, Expr: n}) -} - -func isZero(n *Node) bool { - switch n.Op { - case OLITERAL: - switch u := n.Val().U.(type) { - default: - Dump("unexpected literal", n) - Fatalf("isZero") - case *NilVal: - return true - case string: - return u == "" - case bool: - return !u - case *Mpint: - return u.CmpInt64(0) == 0 - case *Mpflt: - return u.CmpFloat64(0) == 0 - case *Mpcplx: - return u.Real.CmpFloat64(0) == 0 && u.Imag.CmpFloat64(0) == 0 - } - - case OARRAYLIT: - for _, n1 := range n.List.Slice() { - if n1.Op == OKEY { - n1 = n1.Right - } - if !isZero(n1) { - return false - } - } - return true - - case OSTRUCTLIT: - for _, n1 := range n.List.Slice() { - if !isZero(n1.Left) { - return false - } - } - return true - } - - return false -} - -func isvaluelit(n *Node) bool { - return n.Op == OARRAYLIT || n.Op == OSTRUCTLIT -} - -func genAsStatic(as *Node) { - if as.Left.Type == nil { - Fatalf("genAsStatic as.Left not typechecked") - } - - var nam Node - if !stataddr(&nam, as.Left) || (nam.Class() != PEXTERN && as.Left != nblank) { - Fatalf("genAsStatic: lhs %v", as.Left) - } - - switch { - case as.Right.Op == OLITERAL: - litsym(&nam, as.Right, int(as.Right.Type.Width)) - case as.Right.Op == ONAME && as.Right.Class() == PFUNC: - pfuncsym(&nam, as.Right) - default: - Fatalf("genAsStatic: rhs %v", as.Right) - } -} diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go deleted file mode 100644 index defefd76b342b9f46fc6ce8431aecebd5be5477b..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/subr.go +++ /dev/null @@ -1,1918 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/objabi" - "cmd/internal/src" - "crypto/md5" - "encoding/binary" - "fmt" - "os" - "runtime/debug" - "sort" - "strconv" - "strings" - "sync" - "unicode" - "unicode/utf8" -) - -type Error struct { - pos src.XPos - msg string -} - -var errors []Error - -// largeStack is info about a function whose stack frame is too large (rare). -type largeStack struct { - locals int64 - args int64 - callee int64 - pos src.XPos -} - -var ( - largeStackFramesMu sync.Mutex // protects largeStackFrames - largeStackFrames []largeStack -) - -func errorexit() { - flusherrors() - if outfile != "" { - os.Remove(outfile) - } - os.Exit(2) -} - -func adderrorname(n *Node) { - if n.Op != ODOT { - return - } - old := fmt.Sprintf("%v: undefined: %v\n", n.Line(), n.Left) - if len(errors) > 0 && errors[len(errors)-1].pos.Line() == n.Pos.Line() && errors[len(errors)-1].msg == old { - errors[len(errors)-1].msg = fmt.Sprintf("%v: undefined: %v in %v\n", n.Line(), n.Left, n) - } -} - -func adderr(pos src.XPos, format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - // Only add the position if know the position. - // See issue golang.org/issue/11361. - if pos.IsKnown() { - msg = fmt.Sprintf("%v: %s", linestr(pos), msg) - } - errors = append(errors, Error{ - pos: pos, - msg: msg + "\n", - }) -} - -// byPos sorts errors by source position. -type byPos []Error - -func (x byPos) Len() int { return len(x) } -func (x byPos) Less(i, j int) bool { return x[i].pos.Before(x[j].pos) } -func (x byPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } - -// flusherrors sorts errors seen so far by line number, prints them to stdout, -// and empties the errors array. -func flusherrors() { - Ctxt.Bso.Flush() - if len(errors) == 0 { - return - } - sort.Stable(byPos(errors)) - for i, err := range errors { - if i == 0 || err.msg != errors[i-1].msg { - fmt.Printf("%s", err.msg) - } - } - errors = errors[:0] -} - -func hcrash() { - if Debug.h != 0 { - flusherrors() - if outfile != "" { - os.Remove(outfile) - } - var x *int - *x = 0 - } -} - -func linestr(pos src.XPos) string { - return Ctxt.OutermostPos(pos).Format(Debug.C == 0, Debug.L == 1) -} - -// lasterror keeps track of the most recently issued error. -// It is used to avoid multiple error messages on the same -// line. -var lasterror struct { - syntax src.XPos // source position of last syntax error - other src.XPos // source position of last non-syntax error - msg string // error message of last non-syntax error -} - -// sameline reports whether two positions a, b are on the same line. -func sameline(a, b src.XPos) bool { - p := Ctxt.PosTable.Pos(a) - q := Ctxt.PosTable.Pos(b) - return p.Base() == q.Base() && p.Line() == q.Line() -} - -func yyerrorl(pos src.XPos, format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - - if strings.HasPrefix(msg, "syntax error") { - nsyntaxerrors++ - // only one syntax error per line, no matter what error - if sameline(lasterror.syntax, pos) { - return - } - lasterror.syntax = pos - } else { - // only one of multiple equal non-syntax errors per line - // (flusherrors shows only one of them, so we filter them - // here as best as we can (they may not appear in order) - // so that we don't count them here and exit early, and - // then have nothing to show for.) - if sameline(lasterror.other, pos) && lasterror.msg == msg { - return - } - lasterror.other = pos - lasterror.msg = msg - } - - adderr(pos, "%s", msg) - - hcrash() - nerrors++ - if nsavederrors+nerrors >= 10 && Debug.e == 0 { - flusherrors() - fmt.Printf("%v: too many errors\n", linestr(pos)) - errorexit() - } -} - -func yyerrorv(lang string, format string, args ...interface{}) { - what := fmt.Sprintf(format, args...) - yyerrorl(lineno, "%s requires %s or later (-lang was set to %s; check go.mod)", what, lang, flag_lang) -} - -func yyerror(format string, args ...interface{}) { - yyerrorl(lineno, format, args...) -} - -func Warn(fmt_ string, args ...interface{}) { - Warnl(lineno, fmt_, args...) -} - -func Warnl(line src.XPos, fmt_ string, args ...interface{}) { - adderr(line, fmt_, args...) - if Debug.m != 0 { - flusherrors() - } -} - -func Fatalf(fmt_ string, args ...interface{}) { - flusherrors() - - if Debug_panic != 0 || nsavederrors+nerrors == 0 { - fmt.Printf("%v: internal compiler error: ", linestr(lineno)) - fmt.Printf(fmt_, args...) - fmt.Printf("\n") - - // If this is a released compiler version, ask for a bug report. - if strings.HasPrefix(objabi.Version, "go") { - fmt.Printf("\n") - fmt.Printf("Please file a bug report including a short program that triggers the error.\n") - fmt.Printf("https://golang.org/issue/new\n") - } else { - // Not a release; dump a stack trace, too. - fmt.Println() - os.Stdout.Write(debug.Stack()) - fmt.Println() - } - } - - hcrash() - errorexit() -} - -// hasUniquePos reports whether n has a unique position that can be -// used for reporting error messages. -// -// It's primarily used to distinguish references to named objects, -// whose Pos will point back to their declaration position rather than -// their usage position. -func hasUniquePos(n *Node) bool { - switch n.Op { - case ONAME, OPACK: - return false - case OLITERAL, OTYPE: - if n.Sym != nil { - return false - } - } - - if !n.Pos.IsKnown() { - if Debug.K != 0 { - Warn("setlineno: unknown position (line 0)") - } - return false - } - - return true -} - -func setlineno(n *Node) src.XPos { - lno := lineno - if n != nil && hasUniquePos(n) { - lineno = n.Pos - } - return lno -} - -func lookup(name string) *types.Sym { - return localpkg.Lookup(name) -} - -// lookupN looks up the symbol starting with prefix and ending with -// the decimal n. If prefix is too long, lookupN panics. -func lookupN(prefix string, n int) *types.Sym { - var buf [20]byte // plenty long enough for all current users - copy(buf[:], prefix) - b := strconv.AppendInt(buf[:len(prefix)], int64(n), 10) - return localpkg.LookupBytes(b) -} - -// autolabel generates a new Name node for use with -// an automatically generated label. -// prefix is a short mnemonic (e.g. ".s" for switch) -// to help with debugging. -// It should begin with "." to avoid conflicts with -// user labels. -func autolabel(prefix string) *types.Sym { - if prefix[0] != '.' { - Fatalf("autolabel prefix must start with '.', have %q", prefix) - } - fn := Curfn - if Curfn == nil { - Fatalf("autolabel outside function") - } - n := fn.Func.Label - fn.Func.Label++ - return lookupN(prefix, int(n)) -} - -// find all the exported symbols in package opkg -// and make them available in the current package -func importdot(opkg *types.Pkg, pack *Node) { - n := 0 - for _, s := range opkg.Syms { - if s.Def == nil { - continue - } - if !types.IsExported(s.Name) || strings.ContainsRune(s.Name, 0xb7) { // 0xb7 = center dot - continue - } - s1 := lookup(s.Name) - if s1.Def != nil { - pkgerror := fmt.Sprintf("during import %q", opkg.Path) - redeclare(lineno, s1, pkgerror) - continue - } - - s1.Def = s.Def - s1.Block = s.Block - if asNode(s1.Def).Name == nil { - Dump("s1def", asNode(s1.Def)) - Fatalf("missing Name") - } - asNode(s1.Def).Name.Pack = pack - s1.Origpkg = opkg - n++ - } - - if n == 0 { - // can't possibly be used - there were no symbols - yyerrorl(pack.Pos, "imported and not used: %q", opkg.Path) - } -} - -func nod(op Op, nleft, nright *Node) *Node { - return nodl(lineno, op, nleft, nright) -} - -func nodl(pos src.XPos, op Op, nleft, nright *Node) *Node { - var n *Node - switch op { - case OCLOSURE, ODCLFUNC: - var x struct { - n Node - f Func - } - n = &x.n - n.Func = &x.f - case ONAME: - Fatalf("use newname instead") - case OLABEL, OPACK: - var x struct { - n Node - m Name - } - n = &x.n - n.Name = &x.m - default: - n = new(Node) - } - n.Op = op - n.Left = nleft - n.Right = nright - n.Pos = pos - n.Xoffset = BADWIDTH - n.Orig = n - return n -} - -// newname returns a new ONAME Node associated with symbol s. -func newname(s *types.Sym) *Node { - n := newnamel(lineno, s) - n.Name.Curfn = Curfn - return n -} - -// newnamel returns a new ONAME Node associated with symbol s at position pos. -// The caller is responsible for setting n.Name.Curfn. -func newnamel(pos src.XPos, s *types.Sym) *Node { - if s == nil { - Fatalf("newnamel nil") - } - - var x struct { - n Node - m Name - p Param - } - n := &x.n - n.Name = &x.m - n.Name.Param = &x.p - - n.Op = ONAME - n.Pos = pos - n.Orig = n - - n.Sym = s - return n -} - -// nodSym makes a Node with Op op and with the Left field set to left -// and the Sym field set to sym. This is for ODOT and friends. -func nodSym(op Op, left *Node, sym *types.Sym) *Node { - return nodlSym(lineno, op, left, sym) -} - -// nodlSym makes a Node with position Pos, with Op op, and with the Left field set to left -// and the Sym field set to sym. This is for ODOT and friends. -func nodlSym(pos src.XPos, op Op, left *Node, sym *types.Sym) *Node { - n := nodl(pos, op, left, nil) - n.Sym = sym - return n -} - -// rawcopy returns a shallow copy of n. -// Note: copy or sepcopy (rather than rawcopy) is usually the -// correct choice (see comment with Node.copy, below). -func (n *Node) rawcopy() *Node { - copy := *n - return © -} - -// sepcopy returns a separate shallow copy of n, with the copy's -// Orig pointing to itself. -func (n *Node) sepcopy() *Node { - copy := *n - copy.Orig = © - return © -} - -// copy returns shallow copy of n and adjusts the copy's Orig if -// necessary: In general, if n.Orig points to itself, the copy's -// Orig should point to itself as well. Otherwise, if n is modified, -// the copy's Orig node appears modified, too, and then doesn't -// represent the original node anymore. -// (This caused the wrong complit Op to be used when printing error -// messages; see issues #26855, #27765). -func (n *Node) copy() *Node { - copy := *n - if n.Orig == n { - copy.Orig = © - } - return © -} - -// methcmp sorts methods by symbol. -type methcmp []*types.Field - -func (x methcmp) Len() int { return len(x) } -func (x methcmp) Swap(i, j int) { x[i], x[j] = x[j], x[i] } -func (x methcmp) Less(i, j int) bool { return x[i].Sym.Less(x[j].Sym) } - -func nodintconst(v int64) *Node { - u := new(Mpint) - u.SetInt64(v) - return nodlit(Val{u}) -} - -func nodnil() *Node { - return nodlit(Val{new(NilVal)}) -} - -func nodbool(b bool) *Node { - return nodlit(Val{b}) -} - -func nodstr(s string) *Node { - return nodlit(Val{s}) -} - -// treecopy recursively copies n, with the exception of -// ONAME, OLITERAL, OTYPE, and ONONAME leaves. -// If pos.IsKnown(), it sets the source position of newly -// allocated nodes to pos. -func treecopy(n *Node, pos src.XPos) *Node { - if n == nil { - return nil - } - - switch n.Op { - default: - m := n.sepcopy() - m.Left = treecopy(n.Left, pos) - m.Right = treecopy(n.Right, pos) - m.List.Set(listtreecopy(n.List.Slice(), pos)) - if pos.IsKnown() { - m.Pos = pos - } - if m.Name != nil && n.Op != ODCLFIELD { - Dump("treecopy", n) - Fatalf("treecopy Name") - } - return m - - case OPACK: - // OPACK nodes are never valid in const value declarations, - // but allow them like any other declared symbol to avoid - // crashing (golang.org/issue/11361). - fallthrough - - case ONAME, ONONAME, OLITERAL, OTYPE: - return n - - } -} - -// isNil reports whether n represents the universal untyped zero value "nil". -func (n *Node) isNil() bool { - // Check n.Orig because constant propagation may produce typed nil constants, - // which don't exist in the Go spec. - return Isconst(n.Orig, CTNIL) -} - -func isptrto(t *types.Type, et types.EType) bool { - if t == nil { - return false - } - if !t.IsPtr() { - return false - } - t = t.Elem() - if t == nil { - return false - } - if t.Etype != et { - return false - } - return true -} - -func (n *Node) isBlank() bool { - if n == nil { - return false - } - return n.Sym.IsBlank() -} - -// methtype returns the underlying type, if any, -// that owns methods with receiver parameter t. -// The result is either a named type or an anonymous struct. -func methtype(t *types.Type) *types.Type { - if t == nil { - return nil - } - - // Strip away pointer if it's there. - if t.IsPtr() { - if t.Sym != nil { - return nil - } - t = t.Elem() - if t == nil { - return nil - } - } - - // Must be a named type or anonymous struct. - if t.Sym == nil && !t.IsStruct() { - return nil - } - - // Check types. - if issimple[t.Etype] { - return t - } - switch t.Etype { - case TARRAY, TCHAN, TFUNC, TMAP, TSLICE, TSTRING, TSTRUCT: - return t - } - return nil -} - -// Is type src assignment compatible to type dst? -// If so, return op code to use in conversion. -// If not, return OXXX. In this case, the string return parameter may -// hold a reason why. In all other cases, it'll be the empty string. -func assignop(src, dst *types.Type) (Op, string) { - if src == dst { - return OCONVNOP, "" - } - if src == nil || dst == nil || src.Etype == TFORW || dst.Etype == TFORW || src.Orig == nil || dst.Orig == nil { - return OXXX, "" - } - - // 1. src type is identical to dst. - if types.Identical(src, dst) { - return OCONVNOP, "" - } - - // 2. src and dst have identical underlying types - // and either src or dst is not a named type or - // both are empty interface types. - // For assignable but different non-empty interface types, - // we want to recompute the itab. Recomputing the itab ensures - // that itabs are unique (thus an interface with a compile-time - // type I has an itab with interface type I). - if types.Identical(src.Orig, dst.Orig) { - if src.IsEmptyInterface() { - // Conversion between two empty interfaces - // requires no code. - return OCONVNOP, "" - } - if (src.Sym == nil || dst.Sym == nil) && !src.IsInterface() { - // Conversion between two types, at least one unnamed, - // needs no conversion. The exception is nonempty interfaces - // which need to have their itab updated. - return OCONVNOP, "" - } - } - - // 3. dst is an interface type and src implements dst. - if dst.IsInterface() && src.Etype != TNIL { - var missing, have *types.Field - var ptr int - if implements(src, dst, &missing, &have, &ptr) { - return OCONVIFACE, "" - } - - // we'll have complained about this method anyway, suppress spurious messages. - if have != nil && have.Sym == missing.Sym && (have.Type.Broke() || missing.Type.Broke()) { - return OCONVIFACE, "" - } - - var why string - if isptrto(src, TINTER) { - why = fmt.Sprintf(":\n\t%v is pointer to interface, not interface", src) - } else if have != nil && have.Sym == missing.Sym && have.Nointerface() { - why = fmt.Sprintf(":\n\t%v does not implement %v (%v method is marked 'nointerface')", src, dst, missing.Sym) - } else if have != nil && have.Sym == missing.Sym { - why = fmt.Sprintf(":\n\t%v does not implement %v (wrong type for %v method)\n"+ - "\t\thave %v%0S\n\t\twant %v%0S", src, dst, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) - } else if ptr != 0 { - why = fmt.Sprintf(":\n\t%v does not implement %v (%v method has pointer receiver)", src, dst, missing.Sym) - } else if have != nil { - why = fmt.Sprintf(":\n\t%v does not implement %v (missing %v method)\n"+ - "\t\thave %v%0S\n\t\twant %v%0S", src, dst, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) - } else { - why = fmt.Sprintf(":\n\t%v does not implement %v (missing %v method)", src, dst, missing.Sym) - } - - return OXXX, why - } - - if isptrto(dst, TINTER) { - why := fmt.Sprintf(":\n\t%v is pointer to interface, not interface", dst) - return OXXX, why - } - - if src.IsInterface() && dst.Etype != TBLANK { - var missing, have *types.Field - var ptr int - var why string - if implements(dst, src, &missing, &have, &ptr) { - why = ": need type assertion" - } - return OXXX, why - } - - // 4. src is a bidirectional channel value, dst is a channel type, - // src and dst have identical element types, and - // either src or dst is not a named type. - if src.IsChan() && src.ChanDir() == types.Cboth && dst.IsChan() { - if types.Identical(src.Elem(), dst.Elem()) && (src.Sym == nil || dst.Sym == nil) { - return OCONVNOP, "" - } - } - - // 5. src is the predeclared identifier nil and dst is a nillable type. - if src.Etype == TNIL { - switch dst.Etype { - case TPTR, - TFUNC, - TMAP, - TCHAN, - TINTER, - TSLICE: - return OCONVNOP, "" - } - } - - // 6. rule about untyped constants - already converted by defaultlit. - - // 7. Any typed value can be assigned to the blank identifier. - if dst.Etype == TBLANK { - return OCONVNOP, "" - } - - return OXXX, "" -} - -// Can we convert a value of type src to a value of type dst? -// If so, return op code to use in conversion (maybe OCONVNOP). -// If not, return OXXX. In this case, the string return parameter may -// hold a reason why. In all other cases, it'll be the empty string. -// srcConstant indicates whether the value of type src is a constant. -func convertop(srcConstant bool, src, dst *types.Type) (Op, string) { - if src == dst { - return OCONVNOP, "" - } - if src == nil || dst == nil { - return OXXX, "" - } - - // Conversions from regular to go:notinheap are not allowed - // (unless it's unsafe.Pointer). These are runtime-specific - // rules. - // (a) Disallow (*T) to (*U) where T is go:notinheap but U isn't. - if src.IsPtr() && dst.IsPtr() && dst.Elem().NotInHeap() && !src.Elem().NotInHeap() { - why := fmt.Sprintf(":\n\t%v is incomplete (or unallocatable), but %v is not", dst.Elem(), src.Elem()) - return OXXX, why - } - // (b) Disallow string to []T where T is go:notinheap. - if src.IsString() && dst.IsSlice() && dst.Elem().NotInHeap() && (dst.Elem().Etype == types.Bytetype.Etype || dst.Elem().Etype == types.Runetype.Etype) { - why := fmt.Sprintf(":\n\t%v is incomplete (or unallocatable)", dst.Elem()) - return OXXX, why - } - - // 1. src can be assigned to dst. - op, why := assignop(src, dst) - if op != OXXX { - return op, why - } - - // The rules for interfaces are no different in conversions - // than assignments. If interfaces are involved, stop now - // with the good message from assignop. - // Otherwise clear the error. - if src.IsInterface() || dst.IsInterface() { - return OXXX, why - } - - // 2. Ignoring struct tags, src and dst have identical underlying types. - if types.IdenticalIgnoreTags(src.Orig, dst.Orig) { - return OCONVNOP, "" - } - - // 3. src and dst are unnamed pointer types and, ignoring struct tags, - // their base types have identical underlying types. - if src.IsPtr() && dst.IsPtr() && src.Sym == nil && dst.Sym == nil { - if types.IdenticalIgnoreTags(src.Elem().Orig, dst.Elem().Orig) { - return OCONVNOP, "" - } - } - - // 4. src and dst are both integer or floating point types. - if (src.IsInteger() || src.IsFloat()) && (dst.IsInteger() || dst.IsFloat()) { - if simtype[src.Etype] == simtype[dst.Etype] { - return OCONVNOP, "" - } - return OCONV, "" - } - - // 5. src and dst are both complex types. - if src.IsComplex() && dst.IsComplex() { - if simtype[src.Etype] == simtype[dst.Etype] { - return OCONVNOP, "" - } - return OCONV, "" - } - - // Special case for constant conversions: any numeric - // conversion is potentially okay. We'll validate further - // within evconst. See #38117. - if srcConstant && (src.IsInteger() || src.IsFloat() || src.IsComplex()) && (dst.IsInteger() || dst.IsFloat() || dst.IsComplex()) { - return OCONV, "" - } - - // 6. src is an integer or has type []byte or []rune - // and dst is a string type. - if src.IsInteger() && dst.IsString() { - return ORUNESTR, "" - } - - if src.IsSlice() && dst.IsString() { - if src.Elem().Etype == types.Bytetype.Etype { - return OBYTES2STR, "" - } - if src.Elem().Etype == types.Runetype.Etype { - return ORUNES2STR, "" - } - } - - // 7. src is a string and dst is []byte or []rune. - // String to slice. - if src.IsString() && dst.IsSlice() { - if dst.Elem().Etype == types.Bytetype.Etype { - return OSTR2BYTES, "" - } - if dst.Elem().Etype == types.Runetype.Etype { - return OSTR2RUNES, "" - } - } - - // 8. src is a pointer or uintptr and dst is unsafe.Pointer. - if (src.IsPtr() || src.IsUintptr()) && dst.IsUnsafePtr() { - return OCONVNOP, "" - } - - // 9. src is unsafe.Pointer and dst is a pointer or uintptr. - if src.IsUnsafePtr() && (dst.IsPtr() || dst.IsUintptr()) { - return OCONVNOP, "" - } - - // src is map and dst is a pointer to corresponding hmap. - // This rule is needed for the implementation detail that - // go gc maps are implemented as a pointer to a hmap struct. - if src.Etype == TMAP && dst.IsPtr() && - src.MapType().Hmap == dst.Elem() { - return OCONVNOP, "" - } - - return OXXX, "" -} - -func assignconv(n *Node, t *types.Type, context string) *Node { - return assignconvfn(n, t, func() string { return context }) -} - -// Convert node n for assignment to type t. -func assignconvfn(n *Node, t *types.Type, context func() string) *Node { - if n == nil || n.Type == nil || n.Type.Broke() { - return n - } - - if t.Etype == TBLANK && n.Type.Etype == TNIL { - yyerror("use of untyped nil") - } - - n = convlit1(n, t, false, context) - if n.Type == nil { - return n - } - if t.Etype == TBLANK { - return n - } - - // Convert ideal bool from comparison to plain bool - // if the next step is non-bool (like interface{}). - if n.Type == types.UntypedBool && !t.IsBoolean() { - if n.Op == ONAME || n.Op == OLITERAL { - r := nod(OCONVNOP, n, nil) - r.Type = types.Types[TBOOL] - r.SetTypecheck(1) - r.SetImplicit(true) - n = r - } - } - - if types.Identical(n.Type, t) { - return n - } - - op, why := assignop(n.Type, t) - if op == OXXX { - yyerror("cannot use %L as type %v in %s%s", n, t, context(), why) - op = OCONV - } - - r := nod(op, n, nil) - r.Type = t - r.SetTypecheck(1) - r.SetImplicit(true) - r.Orig = n.Orig - return r -} - -// IsMethod reports whether n is a method. -// n must be a function or a method. -func (n *Node) IsMethod() bool { - return n.Type.Recv() != nil -} - -// SliceBounds returns n's slice bounds: low, high, and max in expr[low:high:max]. -// n must be a slice expression. max is nil if n is a simple slice expression. -func (n *Node) SliceBounds() (low, high, max *Node) { - if n.List.Len() == 0 { - return nil, nil, nil - } - - switch n.Op { - case OSLICE, OSLICEARR, OSLICESTR: - s := n.List.Slice() - return s[0], s[1], nil - case OSLICE3, OSLICE3ARR: - s := n.List.Slice() - return s[0], s[1], s[2] - } - Fatalf("SliceBounds op %v: %v", n.Op, n) - return nil, nil, nil -} - -// SetSliceBounds sets n's slice bounds, where n is a slice expression. -// n must be a slice expression. If max is non-nil, n must be a full slice expression. -func (n *Node) SetSliceBounds(low, high, max *Node) { - switch n.Op { - case OSLICE, OSLICEARR, OSLICESTR: - if max != nil { - Fatalf("SetSliceBounds %v given three bounds", n.Op) - } - s := n.List.Slice() - if s == nil { - if low == nil && high == nil { - return - } - n.List.Set2(low, high) - return - } - s[0] = low - s[1] = high - return - case OSLICE3, OSLICE3ARR: - s := n.List.Slice() - if s == nil { - if low == nil && high == nil && max == nil { - return - } - n.List.Set3(low, high, max) - return - } - s[0] = low - s[1] = high - s[2] = max - return - } - Fatalf("SetSliceBounds op %v: %v", n.Op, n) -} - -// IsSlice3 reports whether o is a slice3 op (OSLICE3, OSLICE3ARR). -// o must be a slicing op. -func (o Op) IsSlice3() bool { - switch o { - case OSLICE, OSLICEARR, OSLICESTR: - return false - case OSLICE3, OSLICE3ARR: - return true - } - Fatalf("IsSlice3 op %v", o) - return false -} - -// backingArrayPtrLen extracts the pointer and length from a slice or string. -// This constructs two nodes referring to n, so n must be a cheapexpr. -func (n *Node) backingArrayPtrLen() (ptr, len *Node) { - var init Nodes - c := cheapexpr(n, &init) - if c != n || init.Len() != 0 { - Fatalf("backingArrayPtrLen not cheap: %v", n) - } - ptr = nod(OSPTR, n, nil) - if n.Type.IsString() { - ptr.Type = types.Types[TUINT8].PtrTo() - } else { - ptr.Type = n.Type.Elem().PtrTo() - } - len = nod(OLEN, n, nil) - len.Type = types.Types[TINT] - return ptr, len -} - -// labeledControl returns the control flow Node (for, switch, select) -// associated with the label n, if any. -func (n *Node) labeledControl() *Node { - if n.Op != OLABEL { - Fatalf("labeledControl %v", n.Op) - } - ctl := n.Name.Defn - if ctl == nil { - return nil - } - switch ctl.Op { - case OFOR, OFORUNTIL, OSWITCH, OSELECT: - return ctl - } - return nil -} - -func syslook(name string) *Node { - s := Runtimepkg.Lookup(name) - if s == nil || s.Def == nil { - Fatalf("syslook: can't find runtime.%s", name) - } - return asNode(s.Def) -} - -// typehash computes a hash value for type t to use in type switch statements. -func typehash(t *types.Type) uint32 { - p := t.LongString() - - // Using MD5 is overkill, but reduces accidental collisions. - h := md5.Sum([]byte(p)) - return binary.LittleEndian.Uint32(h[:4]) -} - -// updateHasCall checks whether expression n contains any function -// calls and sets the n.HasCall flag if so. -func updateHasCall(n *Node) { - if n == nil { - return - } - n.SetHasCall(calcHasCall(n)) -} - -func calcHasCall(n *Node) bool { - if n.Ninit.Len() != 0 { - // TODO(mdempsky): This seems overly conservative. - return true - } - - switch n.Op { - case OLITERAL, ONAME, OTYPE: - if n.HasCall() { - Fatalf("OLITERAL/ONAME/OTYPE should never have calls: %+v", n) - } - return false - case OCALL, OCALLFUNC, OCALLMETH, OCALLINTER: - return true - case OANDAND, OOROR: - // hard with instrumented code - if instrumenting { - return true - } - case OINDEX, OSLICE, OSLICEARR, OSLICE3, OSLICE3ARR, OSLICESTR, - ODEREF, ODOTPTR, ODOTTYPE, ODIV, OMOD: - // These ops might panic, make sure they are done - // before we start marshaling args for a call. See issue 16760. - return true - - // When using soft-float, these ops might be rewritten to function calls - // so we ensure they are evaluated first. - case OADD, OSUB, ONEG, OMUL: - if thearch.SoftFloat && (isFloat[n.Type.Etype] || isComplex[n.Type.Etype]) { - return true - } - case OLT, OEQ, ONE, OLE, OGE, OGT: - if thearch.SoftFloat && (isFloat[n.Left.Type.Etype] || isComplex[n.Left.Type.Etype]) { - return true - } - case OCONV: - if thearch.SoftFloat && ((isFloat[n.Type.Etype] || isComplex[n.Type.Etype]) || (isFloat[n.Left.Type.Etype] || isComplex[n.Left.Type.Etype])) { - return true - } - } - - if n.Left != nil && n.Left.HasCall() { - return true - } - if n.Right != nil && n.Right.HasCall() { - return true - } - return false -} - -func badtype(op Op, tl, tr *types.Type) { - var s string - if tl != nil { - s += fmt.Sprintf("\n\t%v", tl) - } - if tr != nil { - s += fmt.Sprintf("\n\t%v", tr) - } - - // common mistake: *struct and *interface. - if tl != nil && tr != nil && tl.IsPtr() && tr.IsPtr() { - if tl.Elem().IsStruct() && tr.Elem().IsInterface() { - s += "\n\t(*struct vs *interface)" - } else if tl.Elem().IsInterface() && tr.Elem().IsStruct() { - s += "\n\t(*interface vs *struct)" - } - } - - yyerror("illegal types for operand: %v%s", op, s) -} - -// brcom returns !(op). -// For example, brcom(==) is !=. -func brcom(op Op) Op { - switch op { - case OEQ: - return ONE - case ONE: - return OEQ - case OLT: - return OGE - case OGT: - return OLE - case OLE: - return OGT - case OGE: - return OLT - } - Fatalf("brcom: no com for %v\n", op) - return op -} - -// brrev returns reverse(op). -// For example, Brrev(<) is >. -func brrev(op Op) Op { - switch op { - case OEQ: - return OEQ - case ONE: - return ONE - case OLT: - return OGT - case OGT: - return OLT - case OLE: - return OGE - case OGE: - return OLE - } - Fatalf("brrev: no rev for %v\n", op) - return op -} - -// return side effect-free n, appending side effects to init. -// result is assignable if n is. -func safeexpr(n *Node, init *Nodes) *Node { - if n == nil { - return nil - } - - if n.Ninit.Len() != 0 { - walkstmtlist(n.Ninit.Slice()) - init.AppendNodes(&n.Ninit) - } - - switch n.Op { - case ONAME, OLITERAL: - return n - - case ODOT, OLEN, OCAP: - l := safeexpr(n.Left, init) - if l == n.Left { - return n - } - r := n.copy() - r.Left = l - r = typecheck(r, ctxExpr) - r = walkexpr(r, init) - return r - - case ODOTPTR, ODEREF: - l := safeexpr(n.Left, init) - if l == n.Left { - return n - } - a := n.copy() - a.Left = l - a = walkexpr(a, init) - return a - - case OINDEX, OINDEXMAP: - l := safeexpr(n.Left, init) - r := safeexpr(n.Right, init) - if l == n.Left && r == n.Right { - return n - } - a := n.copy() - a.Left = l - a.Right = r - a = walkexpr(a, init) - return a - - case OSTRUCTLIT, OARRAYLIT, OSLICELIT: - if isStaticCompositeLiteral(n) { - return n - } - } - - // make a copy; must not be used as an lvalue - if islvalue(n) { - Fatalf("missing lvalue case in safeexpr: %v", n) - } - return cheapexpr(n, init) -} - -func copyexpr(n *Node, t *types.Type, init *Nodes) *Node { - l := temp(t) - a := nod(OAS, l, n) - a = typecheck(a, ctxStmt) - a = walkexpr(a, init) - init.Append(a) - return l -} - -// return side-effect free and cheap n, appending side effects to init. -// result may not be assignable. -func cheapexpr(n *Node, init *Nodes) *Node { - switch n.Op { - case ONAME, OLITERAL: - return n - } - - return copyexpr(n, n.Type, init) -} - -// Code to resolve elided DOTs in embedded types. - -// A Dlist stores a pointer to a TFIELD Type embedded within -// a TSTRUCT or TINTER Type. -type Dlist struct { - field *types.Field -} - -// dotlist is used by adddot1 to record the path of embedded fields -// used to access a target field or method. -// Must be non-nil so that dotpath returns a non-nil slice even if d is zero. -var dotlist = make([]Dlist, 10) - -// lookdot0 returns the number of fields or methods named s associated -// with Type t. If exactly one exists, it will be returned in *save -// (if save is not nil). -func lookdot0(s *types.Sym, t *types.Type, save **types.Field, ignorecase bool) int { - u := t - if u.IsPtr() { - u = u.Elem() - } - - c := 0 - if u.IsStruct() || u.IsInterface() { - for _, f := range u.Fields().Slice() { - if f.Sym == s || (ignorecase && f.IsMethod() && strings.EqualFold(f.Sym.Name, s.Name)) { - if save != nil { - *save = f - } - c++ - } - } - } - - u = t - if t.Sym != nil && t.IsPtr() && !t.Elem().IsPtr() { - // If t is a defined pointer type, then x.m is shorthand for (*x).m. - u = t.Elem() - } - u = methtype(u) - if u != nil { - for _, f := range u.Methods().Slice() { - if f.Embedded == 0 && (f.Sym == s || (ignorecase && strings.EqualFold(f.Sym.Name, s.Name))) { - if save != nil { - *save = f - } - c++ - } - } - } - - return c -} - -// adddot1 returns the number of fields or methods named s at depth d in Type t. -// If exactly one exists, it will be returned in *save (if save is not nil), -// and dotlist will contain the path of embedded fields traversed to find it, -// in reverse order. If none exist, more will indicate whether t contains any -// embedded fields at depth d, so callers can decide whether to retry at -// a greater depth. -func adddot1(s *types.Sym, t *types.Type, d int, save **types.Field, ignorecase bool) (c int, more bool) { - if t.Recur() { - return - } - t.SetRecur(true) - defer t.SetRecur(false) - - var u *types.Type - d-- - if d < 0 { - // We've reached our target depth. If t has any fields/methods - // named s, then we're done. Otherwise, we still need to check - // below for embedded fields. - c = lookdot0(s, t, save, ignorecase) - if c != 0 { - return c, false - } - } - - u = t - if u.IsPtr() { - u = u.Elem() - } - if !u.IsStruct() && !u.IsInterface() { - return c, false - } - - for _, f := range u.Fields().Slice() { - if f.Embedded == 0 || f.Sym == nil { - continue - } - if d < 0 { - // Found an embedded field at target depth. - return c, true - } - a, more1 := adddot1(s, f.Type, d, save, ignorecase) - if a != 0 && c == 0 { - dotlist[d].field = f - } - c += a - if more1 { - more = true - } - } - - return c, more -} - -// dotpath computes the unique shortest explicit selector path to fully qualify -// a selection expression x.f, where x is of type t and f is the symbol s. -// If no such path exists, dotpath returns nil. -// If there are multiple shortest paths to the same depth, ambig is true. -func dotpath(s *types.Sym, t *types.Type, save **types.Field, ignorecase bool) (path []Dlist, ambig bool) { - // The embedding of types within structs imposes a tree structure onto - // types: structs parent the types they embed, and types parent their - // fields or methods. Our goal here is to find the shortest path to - // a field or method named s in the subtree rooted at t. To accomplish - // that, we iteratively perform depth-first searches of increasing depth - // until we either find the named field/method or exhaust the tree. - for d := 0; ; d++ { - if d > len(dotlist) { - dotlist = append(dotlist, Dlist{}) - } - if c, more := adddot1(s, t, d, save, ignorecase); c == 1 { - return dotlist[:d], false - } else if c > 1 { - return nil, true - } else if !more { - return nil, false - } - } -} - -// in T.field -// find missing fields that -// will give shortest unique addressing. -// modify the tree with missing type names. -func adddot(n *Node) *Node { - n.Left = typecheck(n.Left, ctxType|ctxExpr) - if n.Left.Diag() { - n.SetDiag(true) - } - t := n.Left.Type - if t == nil { - return n - } - - if n.Left.Op == OTYPE { - return n - } - - s := n.Sym - if s == nil { - return n - } - - switch path, ambig := dotpath(s, t, nil, false); { - case path != nil: - // rebuild elided dots - for c := len(path) - 1; c >= 0; c-- { - n.Left = nodSym(ODOT, n.Left, path[c].field.Sym) - n.Left.SetImplicit(true) - } - case ambig: - yyerror("ambiguous selector %v", n) - n.Left = nil - } - - return n -} - -// Code to help generate trampoline functions for methods on embedded -// types. These are approx the same as the corresponding adddot -// routines except that they expect to be called with unique tasks and -// they return the actual methods. - -type Symlink struct { - field *types.Field -} - -var slist []Symlink - -func expand0(t *types.Type) { - u := t - if u.IsPtr() { - u = u.Elem() - } - - if u.IsInterface() { - for _, f := range u.Fields().Slice() { - if f.Sym.Uniq() { - continue - } - f.Sym.SetUniq(true) - slist = append(slist, Symlink{field: f}) - } - - return - } - - u = methtype(t) - if u != nil { - for _, f := range u.Methods().Slice() { - if f.Sym.Uniq() { - continue - } - f.Sym.SetUniq(true) - slist = append(slist, Symlink{field: f}) - } - } -} - -func expand1(t *types.Type, top bool) { - if t.Recur() { - return - } - t.SetRecur(true) - - if !top { - expand0(t) - } - - u := t - if u.IsPtr() { - u = u.Elem() - } - - if u.IsStruct() || u.IsInterface() { - for _, f := range u.Fields().Slice() { - if f.Embedded == 0 { - continue - } - if f.Sym == nil { - continue - } - expand1(f.Type, false) - } - } - - t.SetRecur(false) -} - -func expandmeth(t *types.Type) { - if t == nil || t.AllMethods().Len() != 0 { - return - } - - // mark top-level method symbols - // so that expand1 doesn't consider them. - for _, f := range t.Methods().Slice() { - f.Sym.SetUniq(true) - } - - // generate all reachable methods - slist = slist[:0] - expand1(t, true) - - // check each method to be uniquely reachable - var ms []*types.Field - for i, sl := range slist { - slist[i].field = nil - sl.field.Sym.SetUniq(false) - - var f *types.Field - path, _ := dotpath(sl.field.Sym, t, &f, false) - if path == nil { - continue - } - - // dotpath may have dug out arbitrary fields, we only want methods. - if !f.IsMethod() { - continue - } - - // add it to the base type method list - f = f.Copy() - f.Embedded = 1 // needs a trampoline - for _, d := range path { - if d.field.Type.IsPtr() { - f.Embedded = 2 - break - } - } - ms = append(ms, f) - } - - for _, f := range t.Methods().Slice() { - f.Sym.SetUniq(false) - } - - ms = append(ms, t.Methods().Slice()...) - sort.Sort(methcmp(ms)) - t.AllMethods().Set(ms) -} - -// Given funarg struct list, return list of ODCLFIELD Node fn args. -func structargs(tl *types.Type, mustname bool) []*Node { - var args []*Node - gen := 0 - for _, t := range tl.Fields().Slice() { - s := t.Sym - if mustname && (s == nil || s.Name == "_") { - // invent a name so that we can refer to it in the trampoline - s = lookupN(".anon", gen) - gen++ - } - a := symfield(s, t.Type) - a.Pos = t.Pos - a.SetIsDDD(t.IsDDD()) - args = append(args, a) - } - - return args -} - -// Generate a wrapper function to convert from -// a receiver of type T to a receiver of type U. -// That is, -// -// func (t T) M() { -// ... -// } -// -// already exists; this function generates -// -// func (u U) M() { -// u.M() -// } -// -// where the types T and U are such that u.M() is valid -// and calls the T.M method. -// The resulting function is for use in method tables. -// -// rcvr - U -// method - M func (t T)(), a TFIELD type struct -// newnam - the eventual mangled name of this function -func genwrapper(rcvr *types.Type, method *types.Field, newnam *types.Sym) { - if false && Debug.r != 0 { - fmt.Printf("genwrapper rcvrtype=%v method=%v newnam=%v\n", rcvr, method, newnam) - } - - // Only generate (*T).M wrappers for T.M in T's own package. - if rcvr.IsPtr() && rcvr.Elem() == method.Type.Recv().Type && - rcvr.Elem().Sym != nil && rcvr.Elem().Sym.Pkg != localpkg { - return - } - - // Only generate I.M wrappers for I in I's own package - // but keep doing it for error.Error (was issue #29304). - if rcvr.IsInterface() && rcvr.Sym != nil && rcvr.Sym.Pkg != localpkg && rcvr != types.Errortype { - return - } - - lineno = autogeneratedPos - dclcontext = PEXTERN - - tfn := nod(OTFUNC, nil, nil) - tfn.Left = namedfield(".this", rcvr) - tfn.List.Set(structargs(method.Type.Params(), true)) - tfn.Rlist.Set(structargs(method.Type.Results(), false)) - - fn := dclfunc(newnam, tfn) - fn.Func.SetDupok(true) - - nthis := asNode(tfn.Type.Recv().Nname) - - methodrcvr := method.Type.Recv().Type - - // generate nil pointer check for better error - if rcvr.IsPtr() && rcvr.Elem() == methodrcvr { - // generating wrapper from *T to T. - n := nod(OIF, nil, nil) - n.Left = nod(OEQ, nthis, nodnil()) - call := nod(OCALL, syslook("panicwrap"), nil) - n.Nbody.Set1(call) - fn.Nbody.Append(n) - } - - dot := adddot(nodSym(OXDOT, nthis, method.Sym)) - - // generate call - // It's not possible to use a tail call when dynamic linking on ppc64le. The - // bad scenario is when a local call is made to the wrapper: the wrapper will - // call the implementation, which might be in a different module and so set - // the TOC to the appropriate value for that module. But if it returns - // directly to the wrapper's caller, nothing will reset it to the correct - // value for that function. - if !instrumenting && rcvr.IsPtr() && methodrcvr.IsPtr() && method.Embedded != 0 && !isifacemethod(method.Type) && !(thearch.LinkArch.Name == "ppc64le" && Ctxt.Flag_dynlink) { - // generate tail call: adjust pointer receiver and jump to embedded method. - dot = dot.Left // skip final .M - // TODO(mdempsky): Remove dependency on dotlist. - if !dotlist[0].field.Type.IsPtr() { - dot = nod(OADDR, dot, nil) - } - as := nod(OAS, nthis, convnop(dot, rcvr)) - fn.Nbody.Append(as) - fn.Nbody.Append(nodSym(ORETJMP, nil, methodSym(methodrcvr, method.Sym))) - } else { - fn.Func.SetWrapper(true) // ignore frame for panic+recover matching - call := nod(OCALL, dot, nil) - call.List.Set(paramNnames(tfn.Type)) - call.SetIsDDD(tfn.Type.IsVariadic()) - if method.Type.NumResults() > 0 { - n := nod(ORETURN, nil, nil) - n.List.Set1(call) - call = n - } - fn.Nbody.Append(call) - } - - if false && Debug.r != 0 { - dumplist("genwrapper body", fn.Nbody) - } - - funcbody() - if debug_dclstack != 0 { - testdclstack() - } - - fn = typecheck(fn, ctxStmt) - - Curfn = fn - typecheckslice(fn.Nbody.Slice(), ctxStmt) - - // Inline calls within (*T).M wrappers. This is safe because we only - // generate those wrappers within the same compilation unit as (T).M. - // TODO(mdempsky): Investigate why we can't enable this more generally. - if rcvr.IsPtr() && rcvr.Elem() == method.Type.Recv().Type && rcvr.Elem().Sym != nil { - inlcalls(fn) - } - escapeFuncs([]*Node{fn}, false) - - Curfn = nil - xtop = append(xtop, fn) -} - -func paramNnames(ft *types.Type) []*Node { - args := make([]*Node, ft.NumParams()) - for i, f := range ft.Params().FieldSlice() { - args[i] = asNode(f.Nname) - } - return args -} - -func hashmem(t *types.Type) *Node { - sym := Runtimepkg.Lookup("memhash") - - n := newname(sym) - setNodeNameFunc(n) - n.Type = functype(nil, []*Node{ - anonfield(types.NewPtr(t)), - anonfield(types.Types[TUINTPTR]), - anonfield(types.Types[TUINTPTR]), - }, []*Node{ - anonfield(types.Types[TUINTPTR]), - }) - return n -} - -func ifacelookdot(s *types.Sym, t *types.Type, ignorecase bool) (m *types.Field, followptr bool) { - if t == nil { - return nil, false - } - - path, ambig := dotpath(s, t, &m, ignorecase) - if path == nil { - if ambig { - yyerror("%v.%v is ambiguous", t, s) - } - return nil, false - } - - for _, d := range path { - if d.field.Type.IsPtr() { - followptr = true - break - } - } - - if !m.IsMethod() { - yyerror("%v.%v is a field, not a method", t, s) - return nil, followptr - } - - return m, followptr -} - -func implements(t, iface *types.Type, m, samename **types.Field, ptr *int) bool { - t0 := t - if t == nil { - return false - } - - if t.IsInterface() { - i := 0 - tms := t.Fields().Slice() - for _, im := range iface.Fields().Slice() { - for i < len(tms) && tms[i].Sym != im.Sym { - i++ - } - if i == len(tms) { - *m = im - *samename = nil - *ptr = 0 - return false - } - tm := tms[i] - if !types.Identical(tm.Type, im.Type) { - *m = im - *samename = tm - *ptr = 0 - return false - } - } - - return true - } - - t = methtype(t) - var tms []*types.Field - if t != nil { - expandmeth(t) - tms = t.AllMethods().Slice() - } - i := 0 - for _, im := range iface.Fields().Slice() { - if im.Broke() { - continue - } - for i < len(tms) && tms[i].Sym != im.Sym { - i++ - } - if i == len(tms) { - *m = im - *samename, _ = ifacelookdot(im.Sym, t, true) - *ptr = 0 - return false - } - tm := tms[i] - if tm.Nointerface() || !types.Identical(tm.Type, im.Type) { - *m = im - *samename = tm - *ptr = 0 - return false - } - followptr := tm.Embedded == 2 - - // if pointer receiver in method, - // the method does not exist for value types. - rcvr := tm.Type.Recv().Type - if rcvr.IsPtr() && !t0.IsPtr() && !followptr && !isifacemethod(tm.Type) { - if false && Debug.r != 0 { - yyerror("interface pointer mismatch") - } - - *m = im - *samename = nil - *ptr = 1 - return false - } - } - - // We're going to emit an OCONVIFACE. - // Call itabname so that (t, iface) - // gets added to itabs early, which allows - // us to de-virtualize calls through this - // type/interface pair later. See peekitabs in reflect.go - if isdirectiface(t0) && !iface.IsEmptyInterface() { - itabname(t0, iface) - } - return true -} - -func listtreecopy(l []*Node, pos src.XPos) []*Node { - var out []*Node - for _, n := range l { - out = append(out, treecopy(n, pos)) - } - return out -} - -func liststmt(l []*Node) *Node { - n := nod(OBLOCK, nil, nil) - n.List.Set(l) - if len(l) != 0 { - n.Pos = l[0].Pos - } - return n -} - -func (l Nodes) asblock() *Node { - n := nod(OBLOCK, nil, nil) - n.List = l - if l.Len() != 0 { - n.Pos = l.First().Pos - } - return n -} - -func ngotype(n *Node) *types.Sym { - if n.Type != nil { - return typenamesym(n.Type) - } - return nil -} - -// The result of addinit MUST be assigned back to n, e.g. -// n.Left = addinit(n.Left, init) -func addinit(n *Node, init []*Node) *Node { - if len(init) == 0 { - return n - } - if n.mayBeShared() { - // Introduce OCONVNOP to hold init list. - n = nod(OCONVNOP, n, nil) - n.Type = n.Left.Type - n.SetTypecheck(1) - } - - n.Ninit.Prepend(init...) - n.SetHasCall(true) - return n -} - -// The linker uses the magic symbol prefixes "go." and "type." -// Avoid potential confusion between import paths and symbols -// by rejecting these reserved imports for now. Also, people -// "can do weird things in GOPATH and we'd prefer they didn't -// do _that_ weird thing" (per rsc). See also #4257. -var reservedimports = []string{ - "go", - "type", -} - -func isbadimport(path string, allowSpace bool) bool { - if strings.Contains(path, "\x00") { - yyerror("import path contains NUL") - return true - } - - for _, ri := range reservedimports { - if path == ri { - yyerror("import path %q is reserved and cannot be used", path) - return true - } - } - - for _, r := range path { - if r == utf8.RuneError { - yyerror("import path contains invalid UTF-8 sequence: %q", path) - return true - } - - if r < 0x20 || r == 0x7f { - yyerror("import path contains control character: %q", path) - return true - } - - if r == '\\' { - yyerror("import path contains backslash; use slash: %q", path) - return true - } - - if !allowSpace && unicode.IsSpace(r) { - yyerror("import path contains space character: %q", path) - return true - } - - if strings.ContainsRune("!\"#$%&'()*,:;<=>?[]^`{|}", r) { - yyerror("import path contains invalid character '%c': %q", r, path) - return true - } - } - - return false -} - -// Can this type be stored directly in an interface word? -// Yes, if the representation is a single pointer. -func isdirectiface(t *types.Type) bool { - if t.Broke() { - return false - } - - switch t.Etype { - case TPTR: - // Pointers to notinheap types must be stored indirectly. See issue 42076. - return !t.Elem().NotInHeap() - case TCHAN, - TMAP, - TFUNC, - TUNSAFEPTR: - return true - - case TARRAY: - // Array of 1 direct iface type can be direct. - return t.NumElem() == 1 && isdirectiface(t.Elem()) - - case TSTRUCT: - // Struct with 1 field of direct iface type can be direct. - return t.NumFields() == 1 && isdirectiface(t.Field(0).Type) - } - - return false -} - -// itabType loads the _type field from a runtime.itab struct. -func itabType(itab *Node) *Node { - typ := nodSym(ODOTPTR, itab, nil) - typ.Type = types.NewPtr(types.Types[TUINT8]) - typ.SetTypecheck(1) - typ.Xoffset = int64(Widthptr) // offset of _type in runtime.itab - typ.SetBounded(true) // guaranteed not to fault - return typ -} - -// ifaceData loads the data field from an interface. -// The concrete type must be known to have type t. -// It follows the pointer if !isdirectiface(t). -func ifaceData(pos src.XPos, n *Node, t *types.Type) *Node { - if t.IsInterface() { - Fatalf("ifaceData interface: %v", t) - } - ptr := nodlSym(pos, OIDATA, n, nil) - if isdirectiface(t) { - ptr.Type = t - ptr.SetTypecheck(1) - return ptr - } - ptr.Type = types.NewPtr(t) - ptr.SetTypecheck(1) - ind := nodl(pos, ODEREF, ptr, nil) - ind.Type = t - ind.SetTypecheck(1) - ind.SetBounded(true) - return ind -} - -// typePos returns the position associated with t. -// This is where t was declared or where it appeared as a type expression. -func typePos(t *types.Type) src.XPos { - n := asNode(t.Nod) - if n == nil || !n.Pos.IsKnown() { - Fatalf("bad type: %v", t) - } - return n.Pos -} diff --git a/src/cmd/compile/internal/gc/swt.go b/src/cmd/compile/internal/gc/swt.go deleted file mode 100644 index 8d9fbe300e84bcdf075b9406abcb3c7f848b27a4..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/swt.go +++ /dev/null @@ -1,756 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/src" - "sort" -) - -// typecheckswitch typechecks a switch statement. -func typecheckswitch(n *Node) { - typecheckslice(n.Ninit.Slice(), ctxStmt) - if n.Left != nil && n.Left.Op == OTYPESW { - typecheckTypeSwitch(n) - } else { - typecheckExprSwitch(n) - } -} - -func typecheckTypeSwitch(n *Node) { - n.Left.Right = typecheck(n.Left.Right, ctxExpr) - t := n.Left.Right.Type - if t != nil && !t.IsInterface() { - yyerrorl(n.Pos, "cannot type switch on non-interface value %L", n.Left.Right) - t = nil - } - - // We don't actually declare the type switch's guarded - // declaration itself. So if there are no cases, we won't - // notice that it went unused. - if v := n.Left.Left; v != nil && !v.isBlank() && n.List.Len() == 0 { - yyerrorl(v.Pos, "%v declared but not used", v.Sym) - } - - var defCase, nilCase *Node - var ts typeSet - for _, ncase := range n.List.Slice() { - ls := ncase.List.Slice() - if len(ls) == 0 { // default: - if defCase != nil { - yyerrorl(ncase.Pos, "multiple defaults in switch (first at %v)", defCase.Line()) - } else { - defCase = ncase - } - } - - for i := range ls { - ls[i] = typecheck(ls[i], ctxExpr|ctxType) - n1 := ls[i] - if t == nil || n1.Type == nil { - continue - } - - var missing, have *types.Field - var ptr int - switch { - case n1.isNil(): // case nil: - if nilCase != nil { - yyerrorl(ncase.Pos, "multiple nil cases in type switch (first at %v)", nilCase.Line()) - } else { - nilCase = ncase - } - case n1.Op != OTYPE: - yyerrorl(ncase.Pos, "%L is not a type", n1) - case !n1.Type.IsInterface() && !implements(n1.Type, t, &missing, &have, &ptr) && !missing.Broke(): - if have != nil && !have.Broke() { - yyerrorl(ncase.Pos, "impossible type switch case: %L cannot have dynamic type %v"+ - " (wrong type for %v method)\n\thave %v%S\n\twant %v%S", n.Left.Right, n1.Type, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) - } else if ptr != 0 { - yyerrorl(ncase.Pos, "impossible type switch case: %L cannot have dynamic type %v"+ - " (%v method has pointer receiver)", n.Left.Right, n1.Type, missing.Sym) - } else { - yyerrorl(ncase.Pos, "impossible type switch case: %L cannot have dynamic type %v"+ - " (missing %v method)", n.Left.Right, n1.Type, missing.Sym) - } - } - - if n1.Op == OTYPE { - ts.add(ncase.Pos, n1.Type) - } - } - - if ncase.Rlist.Len() != 0 { - // Assign the clause variable's type. - vt := t - if len(ls) == 1 { - if ls[0].Op == OTYPE { - vt = ls[0].Type - } else if ls[0].Op != OLITERAL { // TODO(mdempsky): Should be !ls[0].isNil() - // Invalid single-type case; - // mark variable as broken. - vt = nil - } - } - - // TODO(mdempsky): It should be possible to - // still typecheck the case body. - if vt == nil { - continue - } - - nvar := ncase.Rlist.First() - nvar.Type = vt - nvar = typecheck(nvar, ctxExpr|ctxAssign) - ncase.Rlist.SetFirst(nvar) - } - - typecheckslice(ncase.Nbody.Slice(), ctxStmt) - } -} - -type typeSet struct { - m map[string][]typeSetEntry -} - -type typeSetEntry struct { - pos src.XPos - typ *types.Type -} - -func (s *typeSet) add(pos src.XPos, typ *types.Type) { - if s.m == nil { - s.m = make(map[string][]typeSetEntry) - } - - // LongString does not uniquely identify types, so we need to - // disambiguate collisions with types.Identical. - // TODO(mdempsky): Add a method that *is* unique. - ls := typ.LongString() - prevs := s.m[ls] - for _, prev := range prevs { - if types.Identical(typ, prev.typ) { - yyerrorl(pos, "duplicate case %v in type switch\n\tprevious case at %s", typ, linestr(prev.pos)) - return - } - } - s.m[ls] = append(prevs, typeSetEntry{pos, typ}) -} - -func typecheckExprSwitch(n *Node) { - t := types.Types[TBOOL] - if n.Left != nil { - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - t = n.Left.Type - } - - var nilonly string - if t != nil { - switch { - case t.IsMap(): - nilonly = "map" - case t.Etype == TFUNC: - nilonly = "func" - case t.IsSlice(): - nilonly = "slice" - - case !IsComparable(t): - if t.IsStruct() { - yyerrorl(n.Pos, "cannot switch on %L (struct containing %v cannot be compared)", n.Left, IncomparableField(t).Type) - } else { - yyerrorl(n.Pos, "cannot switch on %L", n.Left) - } - t = nil - } - } - - var defCase *Node - var cs constSet - for _, ncase := range n.List.Slice() { - ls := ncase.List.Slice() - if len(ls) == 0 { // default: - if defCase != nil { - yyerrorl(ncase.Pos, "multiple defaults in switch (first at %v)", defCase.Line()) - } else { - defCase = ncase - } - } - - for i := range ls { - setlineno(ncase) - ls[i] = typecheck(ls[i], ctxExpr) - ls[i] = defaultlit(ls[i], t) - n1 := ls[i] - if t == nil || n1.Type == nil { - continue - } - - if nilonly != "" && !n1.isNil() { - yyerrorl(ncase.Pos, "invalid case %v in switch (can only compare %s %v to nil)", n1, nilonly, n.Left) - } else if t.IsInterface() && !n1.Type.IsInterface() && !IsComparable(n1.Type) { - yyerrorl(ncase.Pos, "invalid case %L in switch (incomparable type)", n1) - } else { - op1, _ := assignop(n1.Type, t) - op2, _ := assignop(t, n1.Type) - if op1 == OXXX && op2 == OXXX { - if n.Left != nil { - yyerrorl(ncase.Pos, "invalid case %v in switch on %v (mismatched types %v and %v)", n1, n.Left, n1.Type, t) - } else { - yyerrorl(ncase.Pos, "invalid case %v in switch (mismatched types %v and bool)", n1, n1.Type) - } - } - } - - // Don't check for duplicate bools. Although the spec allows it, - // (1) the compiler hasn't checked it in the past, so compatibility mandates it, and - // (2) it would disallow useful things like - // case GOARCH == "arm" && GOARM == "5": - // case GOARCH == "arm": - // which would both evaluate to false for non-ARM compiles. - if !n1.Type.IsBoolean() { - cs.add(ncase.Pos, n1, "case", "switch") - } - } - - typecheckslice(ncase.Nbody.Slice(), ctxStmt) - } -} - -// walkswitch walks a switch statement. -func walkswitch(sw *Node) { - // Guard against double walk, see #25776. - if sw.List.Len() == 0 && sw.Nbody.Len() > 0 { - return // Was fatal, but eliminating every possible source of double-walking is hard - } - - if sw.Left != nil && sw.Left.Op == OTYPESW { - walkTypeSwitch(sw) - } else { - walkExprSwitch(sw) - } -} - -// walkExprSwitch generates an AST implementing sw. sw is an -// expression switch. -func walkExprSwitch(sw *Node) { - lno := setlineno(sw) - - cond := sw.Left - sw.Left = nil - - // convert switch {...} to switch true {...} - if cond == nil { - cond = nodbool(true) - cond = typecheck(cond, ctxExpr) - cond = defaultlit(cond, nil) - } - - // Given "switch string(byteslice)", - // with all cases being side-effect free, - // use a zero-cost alias of the byte slice. - // Do this before calling walkexpr on cond, - // because walkexpr will lower the string - // conversion into a runtime call. - // See issue 24937 for more discussion. - if cond.Op == OBYTES2STR && allCaseExprsAreSideEffectFree(sw) { - cond.Op = OBYTES2STRTMP - } - - cond = walkexpr(cond, &sw.Ninit) - if cond.Op != OLITERAL { - cond = copyexpr(cond, cond.Type, &sw.Nbody) - } - - lineno = lno - - s := exprSwitch{ - exprname: cond, - } - - var defaultGoto *Node - var body Nodes - for _, ncase := range sw.List.Slice() { - label := autolabel(".s") - jmp := npos(ncase.Pos, nodSym(OGOTO, nil, label)) - - // Process case dispatch. - if ncase.List.Len() == 0 { - if defaultGoto != nil { - Fatalf("duplicate default case not detected during typechecking") - } - defaultGoto = jmp - } - - for _, n1 := range ncase.List.Slice() { - s.Add(ncase.Pos, n1, jmp) - } - - // Process body. - body.Append(npos(ncase.Pos, nodSym(OLABEL, nil, label))) - body.Append(ncase.Nbody.Slice()...) - if fall, pos := hasFall(ncase.Nbody.Slice()); !fall { - br := nod(OBREAK, nil, nil) - br.Pos = pos - body.Append(br) - } - } - sw.List.Set(nil) - - if defaultGoto == nil { - br := nod(OBREAK, nil, nil) - br.Pos = br.Pos.WithNotStmt() - defaultGoto = br - } - - s.Emit(&sw.Nbody) - sw.Nbody.Append(defaultGoto) - sw.Nbody.AppendNodes(&body) - walkstmtlist(sw.Nbody.Slice()) -} - -// An exprSwitch walks an expression switch. -type exprSwitch struct { - exprname *Node // value being switched on - - done Nodes - clauses []exprClause -} - -type exprClause struct { - pos src.XPos - lo, hi *Node - jmp *Node -} - -func (s *exprSwitch) Add(pos src.XPos, expr, jmp *Node) { - c := exprClause{pos: pos, lo: expr, hi: expr, jmp: jmp} - if okforcmp[s.exprname.Type.Etype] && expr.Op == OLITERAL { - s.clauses = append(s.clauses, c) - return - } - - s.flush() - s.clauses = append(s.clauses, c) - s.flush() -} - -func (s *exprSwitch) Emit(out *Nodes) { - s.flush() - out.AppendNodes(&s.done) -} - -func (s *exprSwitch) flush() { - cc := s.clauses - s.clauses = nil - if len(cc) == 0 { - return - } - - // Caution: If len(cc) == 1, then cc[0] might not an OLITERAL. - // The code below is structured to implicitly handle this case - // (e.g., sort.Slice doesn't need to invoke the less function - // when there's only a single slice element). - - if s.exprname.Type.IsString() && len(cc) >= 2 { - // Sort strings by length and then by value. It is - // much cheaper to compare lengths than values, and - // all we need here is consistency. We respect this - // sorting below. - sort.Slice(cc, func(i, j int) bool { - si := cc[i].lo.StringVal() - sj := cc[j].lo.StringVal() - if len(si) != len(sj) { - return len(si) < len(sj) - } - return si < sj - }) - - // runLen returns the string length associated with a - // particular run of exprClauses. - runLen := func(run []exprClause) int64 { return int64(len(run[0].lo.StringVal())) } - - // Collapse runs of consecutive strings with the same length. - var runs [][]exprClause - start := 0 - for i := 1; i < len(cc); i++ { - if runLen(cc[start:]) != runLen(cc[i:]) { - runs = append(runs, cc[start:i]) - start = i - } - } - runs = append(runs, cc[start:]) - - // Perform two-level binary search. - nlen := nod(OLEN, s.exprname, nil) - binarySearch(len(runs), &s.done, - func(i int) *Node { - return nod(OLE, nlen, nodintconst(runLen(runs[i-1]))) - }, - func(i int, nif *Node) { - run := runs[i] - nif.Left = nod(OEQ, nlen, nodintconst(runLen(run))) - s.search(run, &nif.Nbody) - }, - ) - return - } - - sort.Slice(cc, func(i, j int) bool { - return compareOp(cc[i].lo.Val(), OLT, cc[j].lo.Val()) - }) - - // Merge consecutive integer cases. - if s.exprname.Type.IsInteger() { - merged := cc[:1] - for _, c := range cc[1:] { - last := &merged[len(merged)-1] - if last.jmp == c.jmp && last.hi.Int64Val()+1 == c.lo.Int64Val() { - last.hi = c.lo - } else { - merged = append(merged, c) - } - } - cc = merged - } - - s.search(cc, &s.done) -} - -func (s *exprSwitch) search(cc []exprClause, out *Nodes) { - binarySearch(len(cc), out, - func(i int) *Node { - return nod(OLE, s.exprname, cc[i-1].hi) - }, - func(i int, nif *Node) { - c := &cc[i] - nif.Left = c.test(s.exprname) - nif.Nbody.Set1(c.jmp) - }, - ) -} - -func (c *exprClause) test(exprname *Node) *Node { - // Integer range. - if c.hi != c.lo { - low := nodl(c.pos, OGE, exprname, c.lo) - high := nodl(c.pos, OLE, exprname, c.hi) - return nodl(c.pos, OANDAND, low, high) - } - - // Optimize "switch true { ...}" and "switch false { ... }". - if Isconst(exprname, CTBOOL) && !c.lo.Type.IsInterface() { - if exprname.BoolVal() { - return c.lo - } else { - return nodl(c.pos, ONOT, c.lo, nil) - } - } - - return nodl(c.pos, OEQ, exprname, c.lo) -} - -func allCaseExprsAreSideEffectFree(sw *Node) bool { - // In theory, we could be more aggressive, allowing any - // side-effect-free expressions in cases, but it's a bit - // tricky because some of that information is unavailable due - // to the introduction of temporaries during order. - // Restricting to constants is simple and probably powerful - // enough. - - for _, ncase := range sw.List.Slice() { - if ncase.Op != OCASE { - Fatalf("switch string(byteslice) bad op: %v", ncase.Op) - } - for _, v := range ncase.List.Slice() { - if v.Op != OLITERAL { - return false - } - } - } - return true -} - -// hasFall reports whether stmts ends with a "fallthrough" statement. -func hasFall(stmts []*Node) (bool, src.XPos) { - // Search backwards for the index of the fallthrough - // statement. Do not assume it'll be in the last - // position, since in some cases (e.g. when the statement - // list contains autotmp_ variables), one or more OVARKILL - // nodes will be at the end of the list. - - i := len(stmts) - 1 - for i >= 0 && stmts[i].Op == OVARKILL { - i-- - } - if i < 0 { - return false, src.NoXPos - } - return stmts[i].Op == OFALL, stmts[i].Pos -} - -// walkTypeSwitch generates an AST that implements sw, where sw is a -// type switch. -func walkTypeSwitch(sw *Node) { - var s typeSwitch - s.facename = sw.Left.Right - sw.Left = nil - - s.facename = walkexpr(s.facename, &sw.Ninit) - s.facename = copyexpr(s.facename, s.facename.Type, &sw.Nbody) - s.okname = temp(types.Types[TBOOL]) - - // Get interface descriptor word. - // For empty interfaces this will be the type. - // For non-empty interfaces this will be the itab. - itab := nod(OITAB, s.facename, nil) - - // For empty interfaces, do: - // if e._type == nil { - // do nil case if it exists, otherwise default - // } - // h := e._type.hash - // Use a similar strategy for non-empty interfaces. - ifNil := nod(OIF, nil, nil) - ifNil.Left = nod(OEQ, itab, nodnil()) - lineno = lineno.WithNotStmt() // disable statement marks after the first check. - ifNil.Left = typecheck(ifNil.Left, ctxExpr) - ifNil.Left = defaultlit(ifNil.Left, nil) - // ifNil.Nbody assigned at end. - sw.Nbody.Append(ifNil) - - // Load hash from type or itab. - dotHash := nodSym(ODOTPTR, itab, nil) - dotHash.Type = types.Types[TUINT32] - dotHash.SetTypecheck(1) - if s.facename.Type.IsEmptyInterface() { - dotHash.Xoffset = int64(2 * Widthptr) // offset of hash in runtime._type - } else { - dotHash.Xoffset = int64(2 * Widthptr) // offset of hash in runtime.itab - } - dotHash.SetBounded(true) // guaranteed not to fault - s.hashname = copyexpr(dotHash, dotHash.Type, &sw.Nbody) - - br := nod(OBREAK, nil, nil) - var defaultGoto, nilGoto *Node - var body Nodes - for _, ncase := range sw.List.Slice() { - var caseVar *Node - if ncase.Rlist.Len() != 0 { - caseVar = ncase.Rlist.First() - } - - // For single-type cases with an interface type, - // we initialize the case variable as part of the type assertion. - // In other cases, we initialize it in the body. - var singleType *types.Type - if ncase.List.Len() == 1 && ncase.List.First().Op == OTYPE { - singleType = ncase.List.First().Type - } - caseVarInitialized := false - - label := autolabel(".s") - jmp := npos(ncase.Pos, nodSym(OGOTO, nil, label)) - - if ncase.List.Len() == 0 { // default: - if defaultGoto != nil { - Fatalf("duplicate default case not detected during typechecking") - } - defaultGoto = jmp - } - - for _, n1 := range ncase.List.Slice() { - if n1.isNil() { // case nil: - if nilGoto != nil { - Fatalf("duplicate nil case not detected during typechecking") - } - nilGoto = jmp - continue - } - - if singleType != nil && singleType.IsInterface() { - s.Add(ncase.Pos, n1.Type, caseVar, jmp) - caseVarInitialized = true - } else { - s.Add(ncase.Pos, n1.Type, nil, jmp) - } - } - - body.Append(npos(ncase.Pos, nodSym(OLABEL, nil, label))) - if caseVar != nil && !caseVarInitialized { - val := s.facename - if singleType != nil { - // We have a single concrete type. Extract the data. - if singleType.IsInterface() { - Fatalf("singleType interface should have been handled in Add") - } - val = ifaceData(ncase.Pos, s.facename, singleType) - } - l := []*Node{ - nodl(ncase.Pos, ODCL, caseVar, nil), - nodl(ncase.Pos, OAS, caseVar, val), - } - typecheckslice(l, ctxStmt) - body.Append(l...) - } - body.Append(ncase.Nbody.Slice()...) - body.Append(br) - } - sw.List.Set(nil) - - if defaultGoto == nil { - defaultGoto = br - } - if nilGoto == nil { - nilGoto = defaultGoto - } - ifNil.Nbody.Set1(nilGoto) - - s.Emit(&sw.Nbody) - sw.Nbody.Append(defaultGoto) - sw.Nbody.AppendNodes(&body) - - walkstmtlist(sw.Nbody.Slice()) -} - -// A typeSwitch walks a type switch. -type typeSwitch struct { - // Temporary variables (i.e., ONAMEs) used by type switch dispatch logic: - facename *Node // value being type-switched on - hashname *Node // type hash of the value being type-switched on - okname *Node // boolean used for comma-ok type assertions - - done Nodes - clauses []typeClause -} - -type typeClause struct { - hash uint32 - body Nodes -} - -func (s *typeSwitch) Add(pos src.XPos, typ *types.Type, caseVar, jmp *Node) { - var body Nodes - if caseVar != nil { - l := []*Node{ - nodl(pos, ODCL, caseVar, nil), - nodl(pos, OAS, caseVar, nil), - } - typecheckslice(l, ctxStmt) - body.Append(l...) - } else { - caseVar = nblank - } - - // cv, ok = iface.(type) - as := nodl(pos, OAS2, nil, nil) - as.List.Set2(caseVar, s.okname) // cv, ok = - dot := nodl(pos, ODOTTYPE, s.facename, nil) - dot.Type = typ // iface.(type) - as.Rlist.Set1(dot) - as = typecheck(as, ctxStmt) - as = walkexpr(as, &body) - body.Append(as) - - // if ok { goto label } - nif := nodl(pos, OIF, nil, nil) - nif.Left = s.okname - nif.Nbody.Set1(jmp) - body.Append(nif) - - if !typ.IsInterface() { - s.clauses = append(s.clauses, typeClause{ - hash: typehash(typ), - body: body, - }) - return - } - - s.flush() - s.done.AppendNodes(&body) -} - -func (s *typeSwitch) Emit(out *Nodes) { - s.flush() - out.AppendNodes(&s.done) -} - -func (s *typeSwitch) flush() { - cc := s.clauses - s.clauses = nil - if len(cc) == 0 { - return - } - - sort.Slice(cc, func(i, j int) bool { return cc[i].hash < cc[j].hash }) - - // Combine adjacent cases with the same hash. - merged := cc[:1] - for _, c := range cc[1:] { - last := &merged[len(merged)-1] - if last.hash == c.hash { - last.body.AppendNodes(&c.body) - } else { - merged = append(merged, c) - } - } - cc = merged - - binarySearch(len(cc), &s.done, - func(i int) *Node { - return nod(OLE, s.hashname, nodintconst(int64(cc[i-1].hash))) - }, - func(i int, nif *Node) { - // TODO(mdempsky): Omit hash equality check if - // there's only one type. - c := cc[i] - nif.Left = nod(OEQ, s.hashname, nodintconst(int64(c.hash))) - nif.Nbody.AppendNodes(&c.body) - }, - ) -} - -// binarySearch constructs a binary search tree for handling n cases, -// and appends it to out. It's used for efficiently implementing -// switch statements. -// -// less(i) should return a boolean expression. If it evaluates true, -// then cases before i will be tested; otherwise, cases i and later. -// -// base(i, nif) should setup nif (an OIF node) to test case i. In -// particular, it should set nif.Left and nif.Nbody. -func binarySearch(n int, out *Nodes, less func(i int) *Node, base func(i int, nif *Node)) { - const binarySearchMin = 4 // minimum number of cases for binary search - - var do func(lo, hi int, out *Nodes) - do = func(lo, hi int, out *Nodes) { - n := hi - lo - if n < binarySearchMin { - for i := lo; i < hi; i++ { - nif := nod(OIF, nil, nil) - base(i, nif) - lineno = lineno.WithNotStmt() - nif.Left = typecheck(nif.Left, ctxExpr) - nif.Left = defaultlit(nif.Left, nil) - out.Append(nif) - out = &nif.Rlist - } - return - } - - half := lo + n/2 - nif := nod(OIF, nil, nil) - nif.Left = less(half) - lineno = lineno.WithNotStmt() - nif.Left = typecheck(nif.Left, ctxExpr) - nif.Left = defaultlit(nif.Left, nil) - do(lo, half, &nif.Nbody) - do(half, hi, &nif.Rlist) - out.Append(nif) - } - - do(0, n, out) -} diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go deleted file mode 100644 index 7b4a315e05d4df214795a0fd11229b1b2bfaef31..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/syntax.go +++ /dev/null @@ -1,1196 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// “Abstract” syntax representation. - -package gc - -import ( - "cmd/compile/internal/ssa" - "cmd/compile/internal/types" - "cmd/internal/obj" - "cmd/internal/objabi" - "cmd/internal/src" - "sort" -) - -// A Node is a single node in the syntax tree. -// Actually the syntax tree is a syntax DAG, because there is only one -// node with Op=ONAME for a given instance of a variable x. -// The same is true for Op=OTYPE and Op=OLITERAL. See Node.mayBeShared. -type Node struct { - // Tree structure. - // Generic recursive walks should follow these fields. - Left *Node - Right *Node - Ninit Nodes - Nbody Nodes - List Nodes - Rlist Nodes - - // most nodes - Type *types.Type - Orig *Node // original form, for printing, and tracking copies of ONAMEs - - // func - Func *Func - - // ONAME, OTYPE, OPACK, OLABEL, some OLITERAL - Name *Name - - Sym *types.Sym // various - E interface{} // Opt or Val, see methods below - - // Various. Usually an offset into a struct. For example: - // - ONAME nodes that refer to local variables use it to identify their stack frame position. - // - ODOT, ODOTPTR, and ORESULT use it to indicate offset relative to their base address. - // - OSTRUCTKEY uses it to store the named field's offset. - // - Named OLITERALs use it to store their ambient iota value. - // - OINLMARK stores an index into the inlTree data structure. - // - OCLOSURE uses it to store ambient iota value, if any. - // Possibly still more uses. If you find any, document them. - Xoffset int64 - - Pos src.XPos - - flags bitset32 - - Esc uint16 // EscXXX - - Op Op - aux uint8 -} - -func (n *Node) ResetAux() { - n.aux = 0 -} - -func (n *Node) SubOp() Op { - switch n.Op { - case OASOP, ONAME: - default: - Fatalf("unexpected op: %v", n.Op) - } - return Op(n.aux) -} - -func (n *Node) SetSubOp(op Op) { - switch n.Op { - case OASOP, ONAME: - default: - Fatalf("unexpected op: %v", n.Op) - } - n.aux = uint8(op) -} - -func (n *Node) IndexMapLValue() bool { - if n.Op != OINDEXMAP { - Fatalf("unexpected op: %v", n.Op) - } - return n.aux != 0 -} - -func (n *Node) SetIndexMapLValue(b bool) { - if n.Op != OINDEXMAP { - Fatalf("unexpected op: %v", n.Op) - } - if b { - n.aux = 1 - } else { - n.aux = 0 - } -} - -func (n *Node) TChanDir() types.ChanDir { - if n.Op != OTCHAN { - Fatalf("unexpected op: %v", n.Op) - } - return types.ChanDir(n.aux) -} - -func (n *Node) SetTChanDir(dir types.ChanDir) { - if n.Op != OTCHAN { - Fatalf("unexpected op: %v", n.Op) - } - n.aux = uint8(dir) -} - -func (n *Node) IsSynthetic() bool { - name := n.Sym.Name - return name[0] == '.' || name[0] == '~' -} - -// IsAutoTmp indicates if n was created by the compiler as a temporary, -// based on the setting of the .AutoTemp flag in n's Name. -func (n *Node) IsAutoTmp() bool { - if n == nil || n.Op != ONAME { - return false - } - return n.Name.AutoTemp() -} - -const ( - nodeClass, _ = iota, 1 << iota // PPARAM, PAUTO, PEXTERN, etc; three bits; first in the list because frequently accessed - _, _ // second nodeClass bit - _, _ // third nodeClass bit - nodeWalkdef, _ // tracks state during typecheckdef; 2 == loop detected; two bits - _, _ // second nodeWalkdef bit - nodeTypecheck, _ // tracks state during typechecking; 2 == loop detected; two bits - _, _ // second nodeTypecheck bit - nodeInitorder, _ // tracks state during init1; two bits - _, _ // second nodeInitorder bit - _, nodeHasBreak - _, nodeNoInline // used internally by inliner to indicate that a function call should not be inlined; set for OCALLFUNC and OCALLMETH only - _, nodeImplicit // implicit OADDR or ODEREF; ++/-- statement represented as OASOP - _, nodeIsDDD // is the argument variadic - _, nodeDiag // already printed error about this - _, nodeColas // OAS resulting from := - _, nodeNonNil // guaranteed to be non-nil - _, nodeTransient // storage can be reused immediately after this statement - _, nodeBounded // bounds check unnecessary - _, nodeHasCall // expression contains a function call - _, nodeLikely // if statement condition likely - _, nodeHasVal // node.E contains a Val - _, nodeHasOpt // node.E contains an Opt - _, nodeEmbedded // ODCLFIELD embedded type -) - -func (n *Node) Class() Class { return Class(n.flags.get3(nodeClass)) } -func (n *Node) Walkdef() uint8 { return n.flags.get2(nodeWalkdef) } -func (n *Node) Typecheck() uint8 { return n.flags.get2(nodeTypecheck) } -func (n *Node) Initorder() uint8 { return n.flags.get2(nodeInitorder) } - -func (n *Node) HasBreak() bool { return n.flags&nodeHasBreak != 0 } -func (n *Node) NoInline() bool { return n.flags&nodeNoInline != 0 } -func (n *Node) Implicit() bool { return n.flags&nodeImplicit != 0 } -func (n *Node) IsDDD() bool { return n.flags&nodeIsDDD != 0 } -func (n *Node) Diag() bool { return n.flags&nodeDiag != 0 } -func (n *Node) Colas() bool { return n.flags&nodeColas != 0 } -func (n *Node) NonNil() bool { return n.flags&nodeNonNil != 0 } -func (n *Node) Transient() bool { return n.flags&nodeTransient != 0 } -func (n *Node) Bounded() bool { return n.flags&nodeBounded != 0 } -func (n *Node) HasCall() bool { return n.flags&nodeHasCall != 0 } -func (n *Node) Likely() bool { return n.flags&nodeLikely != 0 } -func (n *Node) HasVal() bool { return n.flags&nodeHasVal != 0 } -func (n *Node) HasOpt() bool { return n.flags&nodeHasOpt != 0 } -func (n *Node) Embedded() bool { return n.flags&nodeEmbedded != 0 } - -func (n *Node) SetClass(b Class) { n.flags.set3(nodeClass, uint8(b)) } -func (n *Node) SetWalkdef(b uint8) { n.flags.set2(nodeWalkdef, b) } -func (n *Node) SetTypecheck(b uint8) { n.flags.set2(nodeTypecheck, b) } -func (n *Node) SetInitorder(b uint8) { n.flags.set2(nodeInitorder, b) } - -func (n *Node) SetHasBreak(b bool) { n.flags.set(nodeHasBreak, b) } -func (n *Node) SetNoInline(b bool) { n.flags.set(nodeNoInline, b) } -func (n *Node) SetImplicit(b bool) { n.flags.set(nodeImplicit, b) } -func (n *Node) SetIsDDD(b bool) { n.flags.set(nodeIsDDD, b) } -func (n *Node) SetDiag(b bool) { n.flags.set(nodeDiag, b) } -func (n *Node) SetColas(b bool) { n.flags.set(nodeColas, b) } -func (n *Node) SetTransient(b bool) { n.flags.set(nodeTransient, b) } -func (n *Node) SetHasCall(b bool) { n.flags.set(nodeHasCall, b) } -func (n *Node) SetLikely(b bool) { n.flags.set(nodeLikely, b) } -func (n *Node) SetHasVal(b bool) { n.flags.set(nodeHasVal, b) } -func (n *Node) SetHasOpt(b bool) { n.flags.set(nodeHasOpt, b) } -func (n *Node) SetEmbedded(b bool) { n.flags.set(nodeEmbedded, b) } - -// MarkNonNil marks a pointer n as being guaranteed non-nil, -// on all code paths, at all times. -// During conversion to SSA, non-nil pointers won't have nil checks -// inserted before dereferencing. See state.exprPtr. -func (n *Node) MarkNonNil() { - if !n.Type.IsPtr() && !n.Type.IsUnsafePtr() { - Fatalf("MarkNonNil(%v), type %v", n, n.Type) - } - n.flags.set(nodeNonNil, true) -} - -// SetBounded indicates whether operation n does not need safety checks. -// When n is an index or slice operation, n does not need bounds checks. -// When n is a dereferencing operation, n does not need nil checks. -// When n is a makeslice+copy operation, n does not need length and cap checks. -func (n *Node) SetBounded(b bool) { - switch n.Op { - case OINDEX, OSLICE, OSLICEARR, OSLICE3, OSLICE3ARR, OSLICESTR: - // No bounds checks needed. - case ODOTPTR, ODEREF: - // No nil check needed. - case OMAKESLICECOPY: - // No length and cap checks needed - // since new slice and copied over slice data have same length. - default: - Fatalf("SetBounded(%v)", n) - } - n.flags.set(nodeBounded, b) -} - -// MarkReadonly indicates that n is an ONAME with readonly contents. -func (n *Node) MarkReadonly() { - if n.Op != ONAME { - Fatalf("Node.MarkReadonly %v", n.Op) - } - n.Name.SetReadonly(true) - // Mark the linksym as readonly immediately - // so that the SSA backend can use this information. - // It will be overridden later during dumpglobls. - n.Sym.Linksym().Type = objabi.SRODATA -} - -// Val returns the Val for the node. -func (n *Node) Val() Val { - if !n.HasVal() { - return Val{} - } - return Val{n.E} -} - -// SetVal sets the Val for the node, which must not have been used with SetOpt. -func (n *Node) SetVal(v Val) { - if n.HasOpt() { - Debug.h = 1 - Dump("have Opt", n) - Fatalf("have Opt") - } - n.SetHasVal(true) - n.E = v.U -} - -// Opt returns the optimizer data for the node. -func (n *Node) Opt() interface{} { - if !n.HasOpt() { - return nil - } - return n.E -} - -// SetOpt sets the optimizer data for the node, which must not have been used with SetVal. -// SetOpt(nil) is ignored for Vals to simplify call sites that are clearing Opts. -func (n *Node) SetOpt(x interface{}) { - if x == nil && n.HasVal() { - return - } - if n.HasVal() { - Debug.h = 1 - Dump("have Val", n) - Fatalf("have Val") - } - n.SetHasOpt(true) - n.E = x -} - -func (n *Node) Iota() int64 { - return n.Xoffset -} - -func (n *Node) SetIota(x int64) { - n.Xoffset = x -} - -// mayBeShared reports whether n may occur in multiple places in the AST. -// Extra care must be taken when mutating such a node. -func (n *Node) mayBeShared() bool { - switch n.Op { - case ONAME, OLITERAL, OTYPE: - return true - } - return false -} - -// isMethodExpression reports whether n represents a method expression T.M. -func (n *Node) isMethodExpression() bool { - return n.Op == ONAME && n.Left != nil && n.Left.Op == OTYPE && n.Right != nil && n.Right.Op == ONAME -} - -// funcname returns the name (without the package) of the function n. -func (n *Node) funcname() string { - if n == nil || n.Func == nil || n.Func.Nname == nil { - return "" - } - return n.Func.Nname.Sym.Name -} - -// pkgFuncName returns the name of the function referenced by n, with package prepended. -// This differs from the compiler's internal convention where local functions lack a package -// because the ultimate consumer of this is a human looking at an IDE; package is only empty -// if the compilation package is actually the empty string. -func (n *Node) pkgFuncName() string { - var s *types.Sym - if n == nil { - return "" - } - if n.Op == ONAME { - s = n.Sym - } else { - if n.Func == nil || n.Func.Nname == nil { - return "" - } - s = n.Func.Nname.Sym - } - pkg := s.Pkg - - p := myimportpath - if pkg != nil && pkg.Path != "" { - p = pkg.Path - } - if p == "" { - return s.Name - } - return p + "." + s.Name -} - -// The compiler needs *Node to be assignable to cmd/compile/internal/ssa.Sym. -func (n *Node) CanBeAnSSASym() { -} - -// Name holds Node fields used only by named nodes (ONAME, OTYPE, OPACK, OLABEL, some OLITERAL). -type Name struct { - Pack *Node // real package for import . names - Pkg *types.Pkg // pkg for OPACK nodes - // For a local variable (not param) or extern, the initializing assignment (OAS or OAS2). - // For a closure var, the ONAME node of the outer captured variable - Defn *Node - // The ODCLFUNC node (for a static function/method or a closure) in which - // local variable or param is declared. - Curfn *Node - Param *Param // additional fields for ONAME, OTYPE - Decldepth int32 // declaration loop depth, increased for every loop or label - // Unique number for ONAME nodes within a function. Function outputs - // (results) are numbered starting at one, followed by function inputs - // (parameters), and then local variables. Vargen is used to distinguish - // local variables/params with the same name. - Vargen int32 - flags bitset16 -} - -const ( - nameCaptured = 1 << iota // is the variable captured by a closure - nameReadonly - nameByval // is the variable captured by value or by reference - nameNeedzero // if it contains pointers, needs to be zeroed on function entry - nameAutoTemp // is the variable a temporary (implies no dwarf info. reset if escapes to heap) - nameUsed // for variable declared and not used error - nameIsClosureVar // PAUTOHEAP closure pseudo-variable; original at n.Name.Defn - nameIsOutputParamHeapAddr // pointer to a result parameter's heap copy - nameAssigned // is the variable ever assigned to - nameAddrtaken // address taken, even if not moved to heap - nameInlFormal // PAUTO created by inliner, derived from callee formal - nameInlLocal // PAUTO created by inliner, derived from callee local - nameOpenDeferSlot // if temporary var storing info for open-coded defers - nameLibfuzzerExtraCounter // if PEXTERN should be assigned to __libfuzzer_extra_counters section -) - -func (n *Name) Captured() bool { return n.flags&nameCaptured != 0 } -func (n *Name) Readonly() bool { return n.flags&nameReadonly != 0 } -func (n *Name) Byval() bool { return n.flags&nameByval != 0 } -func (n *Name) Needzero() bool { return n.flags&nameNeedzero != 0 } -func (n *Name) AutoTemp() bool { return n.flags&nameAutoTemp != 0 } -func (n *Name) Used() bool { return n.flags&nameUsed != 0 } -func (n *Name) IsClosureVar() bool { return n.flags&nameIsClosureVar != 0 } -func (n *Name) IsOutputParamHeapAddr() bool { return n.flags&nameIsOutputParamHeapAddr != 0 } -func (n *Name) Assigned() bool { return n.flags&nameAssigned != 0 } -func (n *Name) Addrtaken() bool { return n.flags&nameAddrtaken != 0 } -func (n *Name) InlFormal() bool { return n.flags&nameInlFormal != 0 } -func (n *Name) InlLocal() bool { return n.flags&nameInlLocal != 0 } -func (n *Name) OpenDeferSlot() bool { return n.flags&nameOpenDeferSlot != 0 } -func (n *Name) LibfuzzerExtraCounter() bool { return n.flags&nameLibfuzzerExtraCounter != 0 } - -func (n *Name) SetCaptured(b bool) { n.flags.set(nameCaptured, b) } -func (n *Name) SetReadonly(b bool) { n.flags.set(nameReadonly, b) } -func (n *Name) SetByval(b bool) { n.flags.set(nameByval, b) } -func (n *Name) SetNeedzero(b bool) { n.flags.set(nameNeedzero, b) } -func (n *Name) SetAutoTemp(b bool) { n.flags.set(nameAutoTemp, b) } -func (n *Name) SetUsed(b bool) { n.flags.set(nameUsed, b) } -func (n *Name) SetIsClosureVar(b bool) { n.flags.set(nameIsClosureVar, b) } -func (n *Name) SetIsOutputParamHeapAddr(b bool) { n.flags.set(nameIsOutputParamHeapAddr, b) } -func (n *Name) SetAssigned(b bool) { n.flags.set(nameAssigned, b) } -func (n *Name) SetAddrtaken(b bool) { n.flags.set(nameAddrtaken, b) } -func (n *Name) SetInlFormal(b bool) { n.flags.set(nameInlFormal, b) } -func (n *Name) SetInlLocal(b bool) { n.flags.set(nameInlLocal, b) } -func (n *Name) SetOpenDeferSlot(b bool) { n.flags.set(nameOpenDeferSlot, b) } -func (n *Name) SetLibfuzzerExtraCounter(b bool) { n.flags.set(nameLibfuzzerExtraCounter, b) } - -type Param struct { - Ntype *Node - Heapaddr *Node // temp holding heap address of param - - // ONAME PAUTOHEAP - Stackcopy *Node // the PPARAM/PPARAMOUT on-stack slot (moved func params only) - - // ONAME closure linkage - // Consider: - // - // func f() { - // x := 1 // x1 - // func() { - // use(x) // x2 - // func() { - // use(x) // x3 - // --- parser is here --- - // }() - // }() - // } - // - // There is an original declaration of x and then a chain of mentions of x - // leading into the current function. Each time x is mentioned in a new closure, - // we create a variable representing x for use in that specific closure, - // since the way you get to x is different in each closure. - // - // Let's number the specific variables as shown in the code: - // x1 is the original x, x2 is when mentioned in the closure, - // and x3 is when mentioned in the closure in the closure. - // - // We keep these linked (assume N > 1): - // - // - x1.Defn = original declaration statement for x (like most variables) - // - x1.Innermost = current innermost closure x (in this case x3), or nil for none - // - x1.IsClosureVar() = false - // - // - xN.Defn = x1, N > 1 - // - xN.IsClosureVar() = true, N > 1 - // - x2.Outer = nil - // - xN.Outer = x(N-1), N > 2 - // - // - // When we look up x in the symbol table, we always get x1. - // Then we can use x1.Innermost (if not nil) to get the x - // for the innermost known closure function, - // but the first reference in a closure will find either no x1.Innermost - // or an x1.Innermost with .Funcdepth < Funcdepth. - // In that case, a new xN must be created, linked in with: - // - // xN.Defn = x1 - // xN.Outer = x1.Innermost - // x1.Innermost = xN - // - // When we finish the function, we'll process its closure variables - // and find xN and pop it off the list using: - // - // x1 := xN.Defn - // x1.Innermost = xN.Outer - // - // We leave x1.Innermost set so that we can still get to the original - // variable quickly. Not shown here, but once we're - // done parsing a function and no longer need xN.Outer for the - // lexical x reference links as described above, funcLit - // recomputes xN.Outer as the semantic x reference link tree, - // even filling in x in intermediate closures that might not - // have mentioned it along the way to inner closures that did. - // See funcLit for details. - // - // During the eventual compilation, then, for closure variables we have: - // - // xN.Defn = original variable - // xN.Outer = variable captured in next outward scope - // to make closure where xN appears - // - // Because of the sharding of pieces of the node, x.Defn means x.Name.Defn - // and x.Innermost/Outer means x.Name.Param.Innermost/Outer. - Innermost *Node - Outer *Node - - // OTYPE & ONAME //go:embed info, - // sharing storage to reduce gc.Param size. - // Extra is nil, or else *Extra is a *paramType or an *embedFileList. - Extra *interface{} -} - -type paramType struct { - flag PragmaFlag - alias bool -} - -type irEmbed struct { - Pos src.XPos - Patterns []string -} - -type embedList []irEmbed - -// Pragma returns the PragmaFlag for p, which must be for an OTYPE. -func (p *Param) Pragma() PragmaFlag { - if p.Extra == nil { - return 0 - } - return (*p.Extra).(*paramType).flag -} - -// SetPragma sets the PragmaFlag for p, which must be for an OTYPE. -func (p *Param) SetPragma(flag PragmaFlag) { - if p.Extra == nil { - if flag == 0 { - return - } - p.Extra = new(interface{}) - *p.Extra = ¶mType{flag: flag} - return - } - (*p.Extra).(*paramType).flag = flag -} - -// Alias reports whether p, which must be for an OTYPE, is a type alias. -func (p *Param) Alias() bool { - if p.Extra == nil { - return false - } - t, ok := (*p.Extra).(*paramType) - if !ok { - return false - } - return t.alias -} - -// SetAlias sets whether p, which must be for an OTYPE, is a type alias. -func (p *Param) SetAlias(alias bool) { - if p.Extra == nil { - if !alias { - return - } - p.Extra = new(interface{}) - *p.Extra = ¶mType{alias: alias} - return - } - (*p.Extra).(*paramType).alias = alias -} - -// EmbedList returns the list of embedded files for p, -// which must be for an ONAME var. -func (p *Param) EmbedList() []irEmbed { - if p.Extra == nil { - return nil - } - return *(*p.Extra).(*embedList) -} - -// SetEmbedList sets the list of embedded files for p, -// which must be for an ONAME var. -func (p *Param) SetEmbedList(list []irEmbed) { - if p.Extra == nil { - if len(list) == 0 { - return - } - f := embedList(list) - p.Extra = new(interface{}) - *p.Extra = &f - return - } - *(*p.Extra).(*embedList) = list -} - -// Functions -// -// A simple function declaration is represented as an ODCLFUNC node f -// and an ONAME node n. They're linked to one another through -// f.Func.Nname == n and n.Name.Defn == f. When functions are -// referenced by name in an expression, the function's ONAME node is -// used directly. -// -// Function names have n.Class() == PFUNC. This distinguishes them -// from variables of function type. -// -// Confusingly, n.Func and f.Func both exist, but commonly point to -// different Funcs. (Exception: an OCALLPART's Func does point to its -// ODCLFUNC's Func.) -// -// A method declaration is represented like functions, except n.Sym -// will be the qualified method name (e.g., "T.m") and -// f.Func.Shortname is the bare method name (e.g., "m"). -// -// Method expressions are represented as ONAME/PFUNC nodes like -// function names, but their Left and Right fields still point to the -// type and method, respectively. They can be distinguished from -// normal functions with isMethodExpression. Also, unlike function -// name nodes, method expression nodes exist for each method -// expression. The declaration ONAME can be accessed with -// x.Type.Nname(), where x is the method expression ONAME node. -// -// Method values are represented by ODOTMETH/ODOTINTER when called -// immediately, and OCALLPART otherwise. They are like method -// expressions, except that for ODOTMETH/ODOTINTER the method name is -// stored in Sym instead of Right. -// -// Closures are represented by OCLOSURE node c. They link back and -// forth with the ODCLFUNC via Func.Closure; that is, c.Func.Closure -// == f and f.Func.Closure == c. -// -// Function bodies are stored in f.Nbody, and inline function bodies -// are stored in n.Func.Inl. Pragmas are stored in f.Func.Pragma. -// -// Imported functions skip the ODCLFUNC, so n.Name.Defn is nil. They -// also use Dcl instead of Inldcl. - -// Func holds Node fields used only with function-like nodes. -type Func struct { - Shortname *types.Sym - // Extra entry code for the function. For example, allocate and initialize - // memory for escaping parameters. However, just for OCLOSURE, Enter is a - // list of ONAME nodes of captured variables - Enter Nodes - Exit Nodes - // ONAME nodes for closure params, each should have closurevar set - Cvars Nodes - // ONAME nodes for all params/locals for this func/closure, does NOT - // include closurevars until transformclosure runs. - Dcl []*Node - - // Parents records the parent scope of each scope within a - // function. The root scope (0) has no parent, so the i'th - // scope's parent is stored at Parents[i-1]. - Parents []ScopeID - - // Marks records scope boundary changes. - Marks []Mark - - // Closgen tracks how many closures have been generated within - // this function. Used by closurename for creating unique - // function names. - Closgen int - - FieldTrack map[*types.Sym]struct{} - DebugInfo *ssa.FuncDebug - Ntype *Node // signature - Top int // top context (ctxCallee, etc) - Closure *Node // OCLOSURE <-> ODCLFUNC (see header comment above) - Nname *Node // The ONAME node associated with an ODCLFUNC (both have same Type) - lsym *obj.LSym - - Inl *Inline - - Label int32 // largest auto-generated label in this function - - Endlineno src.XPos - WBPos src.XPos // position of first write barrier; see SetWBPos - - Pragma PragmaFlag // go:xxx function annotations - - flags bitset16 - numDefers int // number of defer calls in the function - numReturns int // number of explicit returns in the function - - // nwbrCalls records the LSyms of functions called by this - // function for go:nowritebarrierrec analysis. Only filled in - // if nowritebarrierrecCheck != nil. - nwbrCalls *[]nowritebarrierrecCallSym -} - -// An Inline holds fields used for function bodies that can be inlined. -type Inline struct { - Cost int32 // heuristic cost of inlining this function - - // Copies of Func.Dcl and Nbody for use during inlining. - Dcl []*Node - Body []*Node -} - -// A Mark represents a scope boundary. -type Mark struct { - // Pos is the position of the token that marks the scope - // change. - Pos src.XPos - - // Scope identifies the innermost scope to the right of Pos. - Scope ScopeID -} - -// A ScopeID represents a lexical scope within a function. -type ScopeID int32 - -const ( - funcDupok = 1 << iota // duplicate definitions ok - funcWrapper // is method wrapper - funcNeedctxt // function uses context register (has closure variables) - funcReflectMethod // function calls reflect.Type.Method or MethodByName - // true if closure inside a function; false if a simple function or a - // closure in a global variable initialization - funcIsHiddenClosure - funcHasDefer // contains a defer statement - funcNilCheckDisabled // disable nil checks when compiling this function - funcInlinabilityChecked // inliner has already determined whether the function is inlinable - funcExportInline // include inline body in export data - funcInstrumentBody // add race/msan instrumentation during SSA construction - funcOpenCodedDeferDisallowed // can't do open-coded defers -) - -func (f *Func) Dupok() bool { return f.flags&funcDupok != 0 } -func (f *Func) Wrapper() bool { return f.flags&funcWrapper != 0 } -func (f *Func) Needctxt() bool { return f.flags&funcNeedctxt != 0 } -func (f *Func) ReflectMethod() bool { return f.flags&funcReflectMethod != 0 } -func (f *Func) IsHiddenClosure() bool { return f.flags&funcIsHiddenClosure != 0 } -func (f *Func) HasDefer() bool { return f.flags&funcHasDefer != 0 } -func (f *Func) NilCheckDisabled() bool { return f.flags&funcNilCheckDisabled != 0 } -func (f *Func) InlinabilityChecked() bool { return f.flags&funcInlinabilityChecked != 0 } -func (f *Func) ExportInline() bool { return f.flags&funcExportInline != 0 } -func (f *Func) InstrumentBody() bool { return f.flags&funcInstrumentBody != 0 } -func (f *Func) OpenCodedDeferDisallowed() bool { return f.flags&funcOpenCodedDeferDisallowed != 0 } - -func (f *Func) SetDupok(b bool) { f.flags.set(funcDupok, b) } -func (f *Func) SetWrapper(b bool) { f.flags.set(funcWrapper, b) } -func (f *Func) SetNeedctxt(b bool) { f.flags.set(funcNeedctxt, b) } -func (f *Func) SetReflectMethod(b bool) { f.flags.set(funcReflectMethod, b) } -func (f *Func) SetIsHiddenClosure(b bool) { f.flags.set(funcIsHiddenClosure, b) } -func (f *Func) SetHasDefer(b bool) { f.flags.set(funcHasDefer, b) } -func (f *Func) SetNilCheckDisabled(b bool) { f.flags.set(funcNilCheckDisabled, b) } -func (f *Func) SetInlinabilityChecked(b bool) { f.flags.set(funcInlinabilityChecked, b) } -func (f *Func) SetExportInline(b bool) { f.flags.set(funcExportInline, b) } -func (f *Func) SetInstrumentBody(b bool) { f.flags.set(funcInstrumentBody, b) } -func (f *Func) SetOpenCodedDeferDisallowed(b bool) { f.flags.set(funcOpenCodedDeferDisallowed, b) } - -func (f *Func) setWBPos(pos src.XPos) { - if Debug_wb != 0 { - Warnl(pos, "write barrier") - } - if !f.WBPos.IsKnown() { - f.WBPos = pos - } -} - -//go:generate stringer -type=Op -trimprefix=O - -type Op uint8 - -// Node ops. -const ( - OXXX Op = iota - - // names - ONAME // var or func name - // Unnamed arg or return value: f(int, string) (int, error) { etc } - // Also used for a qualified package identifier that hasn't been resolved yet. - ONONAME - OTYPE // type name - OPACK // import - OLITERAL // literal - - // expressions - OADD // Left + Right - OSUB // Left - Right - OOR // Left | Right - OXOR // Left ^ Right - OADDSTR // +{List} (string addition, list elements are strings) - OADDR // &Left - OANDAND // Left && Right - OAPPEND // append(List); after walk, Left may contain elem type descriptor - OBYTES2STR // Type(Left) (Type is string, Left is a []byte) - OBYTES2STRTMP // Type(Left) (Type is string, Left is a []byte, ephemeral) - ORUNES2STR // Type(Left) (Type is string, Left is a []rune) - OSTR2BYTES // Type(Left) (Type is []byte, Left is a string) - OSTR2BYTESTMP // Type(Left) (Type is []byte, Left is a string, ephemeral) - OSTR2RUNES // Type(Left) (Type is []rune, Left is a string) - // Left = Right or (if Colas=true) Left := Right - // If Colas, then Ninit includes a DCL node for Left. - OAS - // List = Rlist (x, y, z = a, b, c) or (if Colas=true) List := Rlist - // If Colas, then Ninit includes DCL nodes for List - OAS2 - OAS2DOTTYPE // List = Right (x, ok = I.(int)) - OAS2FUNC // List = Right (x, y = f()) - OAS2MAPR // List = Right (x, ok = m["foo"]) - OAS2RECV // List = Right (x, ok = <-c) - OASOP // Left Etype= Right (x += y) - OCALL // Left(List) (function call, method call or type conversion) - - // OCALLFUNC, OCALLMETH, and OCALLINTER have the same structure. - // Prior to walk, they are: Left(List), where List is all regular arguments. - // After walk, List is a series of assignments to temporaries, - // and Rlist is an updated set of arguments. - // Nbody is all OVARLIVE nodes that are attached to OCALLxxx. - // TODO(josharian/khr): Use Ninit instead of List for the assignments to temporaries. See CL 114797. - OCALLFUNC // Left(List/Rlist) (function call f(args)) - OCALLMETH // Left(List/Rlist) (direct method call x.Method(args)) - OCALLINTER // Left(List/Rlist) (interface method call x.Method(args)) - OCALLPART // Left.Right (method expression x.Method, not called) - OCAP // cap(Left) - OCLOSE // close(Left) - OCLOSURE // func Type { Func.Closure.Nbody } (func literal) - OCOMPLIT // Right{List} (composite literal, not yet lowered to specific form) - OMAPLIT // Type{List} (composite literal, Type is map) - OSTRUCTLIT // Type{List} (composite literal, Type is struct) - OARRAYLIT // Type{List} (composite literal, Type is array) - OSLICELIT // Type{List} (composite literal, Type is slice) Right.Int64() = slice length. - OPTRLIT // &Left (left is composite literal) - OCONV // Type(Left) (type conversion) - OCONVIFACE // Type(Left) (type conversion, to interface) - OCONVNOP // Type(Left) (type conversion, no effect) - OCOPY // copy(Left, Right) - ODCL // var Left (declares Left of type Left.Type) - - // Used during parsing but don't last. - ODCLFUNC // func f() or func (r) f() - ODCLFIELD // struct field, interface field, or func/method argument/return value. - ODCLCONST // const pi = 3.14 - ODCLTYPE // type Int int or type Int = int - - ODELETE // delete(List) - ODOT // Left.Sym (Left is of struct type) - ODOTPTR // Left.Sym (Left is of pointer to struct type) - ODOTMETH // Left.Sym (Left is non-interface, Right is method name) - ODOTINTER // Left.Sym (Left is interface, Right is method name) - OXDOT // Left.Sym (before rewrite to one of the preceding) - ODOTTYPE // Left.Right or Left.Type (.Right during parsing, .Type once resolved); after walk, .Right contains address of interface type descriptor and .Right.Right contains address of concrete type descriptor - ODOTTYPE2 // Left.Right or Left.Type (.Right during parsing, .Type once resolved; on rhs of OAS2DOTTYPE); after walk, .Right contains address of interface type descriptor - OEQ // Left == Right - ONE // Left != Right - OLT // Left < Right - OLE // Left <= Right - OGE // Left >= Right - OGT // Left > Right - ODEREF // *Left - OINDEX // Left[Right] (index of array or slice) - OINDEXMAP // Left[Right] (index of map) - OKEY // Left:Right (key:value in struct/array/map literal) - OSTRUCTKEY // Sym:Left (key:value in struct literal, after type checking) - OLEN // len(Left) - OMAKE // make(List) (before type checking converts to one of the following) - OMAKECHAN // make(Type, Left) (type is chan) - OMAKEMAP // make(Type, Left) (type is map) - OMAKESLICE // make(Type, Left, Right) (type is slice) - OMAKESLICECOPY // makeslicecopy(Type, Left, Right) (type is slice; Left is length and Right is the copied from slice) - // OMAKESLICECOPY is created by the order pass and corresponds to: - // s = make(Type, Left); copy(s, Right) - // - // Bounded can be set on the node when Left == len(Right) is known at compile time. - // - // This node is created so the walk pass can optimize this pattern which would - // otherwise be hard to detect after the order pass. - OMUL // Left * Right - ODIV // Left / Right - OMOD // Left % Right - OLSH // Left << Right - ORSH // Left >> Right - OAND // Left & Right - OANDNOT // Left &^ Right - ONEW // new(Left); corresponds to calls to new in source code - ONEWOBJ // runtime.newobject(n.Type); introduced by walk; Left is type descriptor - ONOT // !Left - OBITNOT // ^Left - OPLUS // +Left - ONEG // -Left - OOROR // Left || Right - OPANIC // panic(Left) - OPRINT // print(List) - OPRINTN // println(List) - OPAREN // (Left) - OSEND // Left <- Right - OSLICE // Left[List[0] : List[1]] (Left is untypechecked or slice) - OSLICEARR // Left[List[0] : List[1]] (Left is array) - OSLICESTR // Left[List[0] : List[1]] (Left is string) - OSLICE3 // Left[List[0] : List[1] : List[2]] (Left is untypedchecked or slice) - OSLICE3ARR // Left[List[0] : List[1] : List[2]] (Left is array) - OSLICEHEADER // sliceheader{Left, List[0], List[1]} (Left is unsafe.Pointer, List[0] is length, List[1] is capacity) - ORECOVER // recover() - ORECV // <-Left - ORUNESTR // Type(Left) (Type is string, Left is rune) - OSELRECV // Left = <-Right.Left: (appears as .Left of OCASE; Right.Op == ORECV) - OSELRECV2 // List = <-Right.Left: (appears as .Left of OCASE; count(List) == 2, Right.Op == ORECV) - OIOTA // iota - OREAL // real(Left) - OIMAG // imag(Left) - OCOMPLEX // complex(Left, Right) or complex(List[0]) where List[0] is a 2-result function call - OALIGNOF // unsafe.Alignof(Left) - OOFFSETOF // unsafe.Offsetof(Left) - OSIZEOF // unsafe.Sizeof(Left) - - // statements - OBLOCK // { List } (block of code) - OBREAK // break [Sym] - // OCASE: case List: Nbody (List==nil means default) - // For OTYPESW, List is a OTYPE node for the specified type (or OLITERAL - // for nil), and, if a type-switch variable is specified, Rlist is an - // ONAME for the version of the type-switch variable with the specified - // type. - OCASE - OCONTINUE // continue [Sym] - ODEFER // defer Left (Left must be call) - OEMPTY // no-op (empty statement) - OFALL // fallthrough - OFOR // for Ninit; Left; Right { Nbody } - // OFORUNTIL is like OFOR, but the test (Left) is applied after the body: - // Ninit - // top: { Nbody } // Execute the body at least once - // cont: Right - // if Left { // And then test the loop condition - // List // Before looping to top, execute List - // goto top - // } - // OFORUNTIL is created by walk. There's no way to write this in Go code. - OFORUNTIL - OGOTO // goto Sym - OIF // if Ninit; Left { Nbody } else { Rlist } - OLABEL // Sym: - OGO // go Left (Left must be call) - ORANGE // for List = range Right { Nbody } - ORETURN // return List - OSELECT // select { List } (List is list of OCASE) - OSWITCH // switch Ninit; Left { List } (List is a list of OCASE) - // OTYPESW: Left := Right.(type) (appears as .Left of OSWITCH) - // Left is nil if there is no type-switch variable - OTYPESW - - // types - OTCHAN // chan int - OTMAP // map[string]int - OTSTRUCT // struct{} - OTINTER // interface{} - // OTFUNC: func() - Left is receiver field, List is list of param fields, Rlist is - // list of result fields. - OTFUNC - OTARRAY // []int, [8]int, [N]int or [...]int - - // misc - ODDD // func f(args ...int) or f(l...) or var a = [...]int{0, 1, 2}. - OINLCALL // intermediary representation of an inlined call. - OEFACE // itable and data words of an empty-interface value. - OITAB // itable word of an interface value. - OIDATA // data word of an interface value in Left - OSPTR // base pointer of a slice or string. - OCLOSUREVAR // variable reference at beginning of closure function - OCFUNC // reference to c function pointer (not go func value) - OCHECKNIL // emit code to ensure pointer/interface not nil - OVARDEF // variable is about to be fully initialized - OVARKILL // variable is dead - OVARLIVE // variable is alive - ORESULT // result of a function call; Xoffset is stack offset - OINLMARK // start of an inlined body, with file/line of caller. Xoffset is an index into the inline tree. - - // arch-specific opcodes - ORETJMP // return to other function - OGETG // runtime.getg() (read g pointer) - - OEND -) - -// Nodes is a pointer to a slice of *Node. -// For fields that are not used in most nodes, this is used instead of -// a slice to save space. -type Nodes struct{ slice *[]*Node } - -// asNodes returns a slice of *Node as a Nodes value. -func asNodes(s []*Node) Nodes { - return Nodes{&s} -} - -// Slice returns the entries in Nodes as a slice. -// Changes to the slice entries (as in s[i] = n) will be reflected in -// the Nodes. -func (n Nodes) Slice() []*Node { - if n.slice == nil { - return nil - } - return *n.slice -} - -// Len returns the number of entries in Nodes. -func (n Nodes) Len() int { - if n.slice == nil { - return 0 - } - return len(*n.slice) -} - -// Index returns the i'th element of Nodes. -// It panics if n does not have at least i+1 elements. -func (n Nodes) Index(i int) *Node { - return (*n.slice)[i] -} - -// First returns the first element of Nodes (same as n.Index(0)). -// It panics if n has no elements. -func (n Nodes) First() *Node { - return (*n.slice)[0] -} - -// Second returns the second element of Nodes (same as n.Index(1)). -// It panics if n has fewer than two elements. -func (n Nodes) Second() *Node { - return (*n.slice)[1] -} - -// Set sets n to a slice. -// This takes ownership of the slice. -func (n *Nodes) Set(s []*Node) { - if len(s) == 0 { - n.slice = nil - } else { - // Copy s and take address of t rather than s to avoid - // allocation in the case where len(s) == 0 (which is - // over 3x more common, dynamically, for make.bash). - t := s - n.slice = &t - } -} - -// Set1 sets n to a slice containing a single node. -func (n *Nodes) Set1(n1 *Node) { - n.slice = &[]*Node{n1} -} - -// Set2 sets n to a slice containing two nodes. -func (n *Nodes) Set2(n1, n2 *Node) { - n.slice = &[]*Node{n1, n2} -} - -// Set3 sets n to a slice containing three nodes. -func (n *Nodes) Set3(n1, n2, n3 *Node) { - n.slice = &[]*Node{n1, n2, n3} -} - -// MoveNodes sets n to the contents of n2, then clears n2. -func (n *Nodes) MoveNodes(n2 *Nodes) { - n.slice = n2.slice - n2.slice = nil -} - -// SetIndex sets the i'th element of Nodes to node. -// It panics if n does not have at least i+1 elements. -func (n Nodes) SetIndex(i int, node *Node) { - (*n.slice)[i] = node -} - -// SetFirst sets the first element of Nodes to node. -// It panics if n does not have at least one elements. -func (n Nodes) SetFirst(node *Node) { - (*n.slice)[0] = node -} - -// SetSecond sets the second element of Nodes to node. -// It panics if n does not have at least two elements. -func (n Nodes) SetSecond(node *Node) { - (*n.slice)[1] = node -} - -// Addr returns the address of the i'th element of Nodes. -// It panics if n does not have at least i+1 elements. -func (n Nodes) Addr(i int) **Node { - return &(*n.slice)[i] -} - -// Append appends entries to Nodes. -func (n *Nodes) Append(a ...*Node) { - if len(a) == 0 { - return - } - if n.slice == nil { - s := make([]*Node, len(a)) - copy(s, a) - n.slice = &s - return - } - *n.slice = append(*n.slice, a...) -} - -// Prepend prepends entries to Nodes. -// If a slice is passed in, this will take ownership of it. -func (n *Nodes) Prepend(a ...*Node) { - if len(a) == 0 { - return - } - if n.slice == nil { - n.slice = &a - } else { - *n.slice = append(a, *n.slice...) - } -} - -// AppendNodes appends the contents of *n2 to n, then clears n2. -func (n *Nodes) AppendNodes(n2 *Nodes) { - switch { - case n2.slice == nil: - case n.slice == nil: - n.slice = n2.slice - default: - *n.slice = append(*n.slice, *n2.slice...) - } - n2.slice = nil -} - -// inspect invokes f on each node in an AST in depth-first order. -// If f(n) returns false, inspect skips visiting n's children. -func inspect(n *Node, f func(*Node) bool) { - if n == nil || !f(n) { - return - } - inspectList(n.Ninit, f) - inspect(n.Left, f) - inspect(n.Right, f) - inspectList(n.List, f) - inspectList(n.Nbody, f) - inspectList(n.Rlist, f) -} - -func inspectList(l Nodes, f func(*Node) bool) { - for _, n := range l.Slice() { - inspect(n, f) - } -} - -// nodeQueue is a FIFO queue of *Node. The zero value of nodeQueue is -// a ready-to-use empty queue. -type nodeQueue struct { - ring []*Node - head, tail int -} - -// empty reports whether q contains no Nodes. -func (q *nodeQueue) empty() bool { - return q.head == q.tail -} - -// pushRight appends n to the right of the queue. -func (q *nodeQueue) pushRight(n *Node) { - if len(q.ring) == 0 { - q.ring = make([]*Node, 16) - } else if q.head+len(q.ring) == q.tail { - // Grow the ring. - nring := make([]*Node, len(q.ring)*2) - // Copy the old elements. - part := q.ring[q.head%len(q.ring):] - if q.tail-q.head <= len(part) { - part = part[:q.tail-q.head] - copy(nring, part) - } else { - pos := copy(nring, part) - copy(nring[pos:], q.ring[:q.tail%len(q.ring)]) - } - q.ring, q.head, q.tail = nring, 0, q.tail-q.head - } - - q.ring[q.tail%len(q.ring)] = n - q.tail++ -} - -// popLeft pops a node from the left of the queue. It panics if q is -// empty. -func (q *nodeQueue) popLeft() *Node { - if q.empty() { - panic("dequeue empty") - } - n := q.ring[q.head%len(q.ring)] - q.head++ - return n -} - -// NodeSet is a set of Nodes. -type NodeSet map[*Node]struct{} - -// Has reports whether s contains n. -func (s NodeSet) Has(n *Node) bool { - _, isPresent := s[n] - return isPresent -} - -// Add adds n to s. -func (s *NodeSet) Add(n *Node) { - if *s == nil { - *s = make(map[*Node]struct{}) - } - (*s)[n] = struct{}{} -} - -// Sorted returns s sorted according to less. -func (s NodeSet) Sorted(less func(*Node, *Node) bool) []*Node { - var res []*Node - for n := range s { - res = append(res, n) - } - sort.Slice(res, func(i, j int) bool { return less(res[i], res[j]) }) - return res -} diff --git a/src/cmd/compile/internal/gc/trace.go b/src/cmd/compile/internal/gc/trace.go index ed4b5a268ddc69dd62115ab16d743a5fb8a8f8e3..8cdbd4b0f3a5da72fc3945e6d07badf7f262ca67 100644 --- a/src/cmd/compile/internal/gc/trace.go +++ b/src/cmd/compile/internal/gc/trace.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build go1.7 // +build go1.7 package gc @@ -9,6 +10,8 @@ package gc import ( "os" tracepkg "runtime/trace" + + "cmd/compile/internal/base" ) func init() { @@ -18,10 +21,10 @@ func init() { func traceHandlerGo17(traceprofile string) { f, err := os.Create(traceprofile) if err != nil { - Fatalf("%v", err) + base.Fatalf("%v", err) } if err := tracepkg.Start(f); err != nil { - Fatalf("%v", err) + base.Fatalf("%v", err) } - atExit(tracepkg.Stop) + base.AtExit(tracepkg.Stop) } diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go deleted file mode 100644 index c0b05035f033c1f1b6b6a54868ab2e2476471522..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/typecheck.go +++ /dev/null @@ -1,4019 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "fmt" - "strings" -) - -// To enable tracing support (-t flag), set enableTrace to true. -const enableTrace = false - -var trace bool -var traceIndent []byte -var skipDowidthForTracing bool - -func tracePrint(title string, n *Node) func(np **Node) { - indent := traceIndent - - // guard against nil - var pos, op string - var tc uint8 - if n != nil { - pos = linestr(n.Pos) - op = n.Op.String() - tc = n.Typecheck() - } - - skipDowidthForTracing = true - defer func() { skipDowidthForTracing = false }() - fmt.Printf("%s: %s%s %p %s %v tc=%d\n", pos, indent, title, n, op, n, tc) - traceIndent = append(traceIndent, ". "...) - - return func(np **Node) { - traceIndent = traceIndent[:len(traceIndent)-2] - - // if we have a result, use that - if np != nil { - n = *np - } - - // guard against nil - // use outer pos, op so we don't get empty pos/op if n == nil (nicer output) - var tc uint8 - var typ *types.Type - if n != nil { - pos = linestr(n.Pos) - op = n.Op.String() - tc = n.Typecheck() - typ = n.Type - } - - skipDowidthForTracing = true - defer func() { skipDowidthForTracing = false }() - fmt.Printf("%s: %s=> %p %s %v tc=%d type=%#L\n", pos, indent, n, op, n, tc, typ) - } -} - -const ( - ctxStmt = 1 << iota // evaluated at statement level - ctxExpr // evaluated in value context - ctxType // evaluated in type context - ctxCallee // call-only expressions are ok - ctxMultiOK // multivalue function returns are ok - ctxAssign // assigning to expression -) - -// type checks the whole tree of an expression. -// calculates expression types. -// evaluates compile time constants. -// marks variables that escape the local frame. -// rewrites n.Op to be more specific in some cases. - -var typecheckdefstack []*Node - -// resolve ONONAME to definition, if any. -func resolve(n *Node) (res *Node) { - if n == nil || n.Op != ONONAME { - return n - } - - // only trace if there's work to do - if enableTrace && trace { - defer tracePrint("resolve", n)(&res) - } - - if n.Sym.Pkg != localpkg { - if inimport { - Fatalf("recursive inimport") - } - inimport = true - expandDecl(n) - inimport = false - return n - } - - r := asNode(n.Sym.Def) - if r == nil { - return n - } - - if r.Op == OIOTA { - if x := getIotaValue(); x >= 0 { - return nodintconst(x) - } - return n - } - - return r -} - -func typecheckslice(l []*Node, top int) { - for i := range l { - l[i] = typecheck(l[i], top) - } -} - -var _typekind = []string{ - TINT: "int", - TUINT: "uint", - TINT8: "int8", - TUINT8: "uint8", - TINT16: "int16", - TUINT16: "uint16", - TINT32: "int32", - TUINT32: "uint32", - TINT64: "int64", - TUINT64: "uint64", - TUINTPTR: "uintptr", - TCOMPLEX64: "complex64", - TCOMPLEX128: "complex128", - TFLOAT32: "float32", - TFLOAT64: "float64", - TBOOL: "bool", - TSTRING: "string", - TPTR: "pointer", - TUNSAFEPTR: "unsafe.Pointer", - TSTRUCT: "struct", - TINTER: "interface", - TCHAN: "chan", - TMAP: "map", - TARRAY: "array", - TSLICE: "slice", - TFUNC: "func", - TNIL: "nil", - TIDEAL: "untyped number", -} - -func typekind(t *types.Type) string { - if t.IsUntyped() { - return fmt.Sprintf("%v", t) - } - et := t.Etype - if int(et) < len(_typekind) { - s := _typekind[et] - if s != "" { - return s - } - } - return fmt.Sprintf("etype=%d", et) -} - -func cycleFor(start *Node) []*Node { - // Find the start node in typecheck_tcstack. - // We know that it must exist because each time we mark - // a node with n.SetTypecheck(2) we push it on the stack, - // and each time we mark a node with n.SetTypecheck(2) we - // pop it from the stack. We hit a cycle when we encounter - // a node marked 2 in which case is must be on the stack. - i := len(typecheck_tcstack) - 1 - for i > 0 && typecheck_tcstack[i] != start { - i-- - } - - // collect all nodes with same Op - var cycle []*Node - for _, n := range typecheck_tcstack[i:] { - if n.Op == start.Op { - cycle = append(cycle, n) - } - } - - return cycle -} - -func cycleTrace(cycle []*Node) string { - var s string - for i, n := range cycle { - s += fmt.Sprintf("\n\t%v: %v uses %v", n.Line(), n, cycle[(i+1)%len(cycle)]) - } - return s -} - -var typecheck_tcstack []*Node - -// typecheck type checks node n. -// The result of typecheck MUST be assigned back to n, e.g. -// n.Left = typecheck(n.Left, top) -func typecheck(n *Node, top int) (res *Node) { - // cannot type check until all the source has been parsed - if !typecheckok { - Fatalf("early typecheck") - } - - if n == nil { - return nil - } - - // only trace if there's work to do - if enableTrace && trace { - defer tracePrint("typecheck", n)(&res) - } - - lno := setlineno(n) - - // Skip over parens. - for n.Op == OPAREN { - n = n.Left - } - - // Resolve definition of name and value of iota lazily. - n = resolve(n) - - // Skip typecheck if already done. - // But re-typecheck ONAME/OTYPE/OLITERAL/OPACK node in case context has changed. - if n.Typecheck() == 1 { - switch n.Op { - case ONAME, OTYPE, OLITERAL, OPACK: - break - - default: - lineno = lno - return n - } - } - - if n.Typecheck() == 2 { - // Typechecking loop. Trying printing a meaningful message, - // otherwise a stack trace of typechecking. - switch n.Op { - // We can already diagnose variables used as types. - case ONAME: - if top&(ctxExpr|ctxType) == ctxType { - yyerror("%v is not a type", n) - } - - case OTYPE: - // Only report a type cycle if we are expecting a type. - // Otherwise let other code report an error. - if top&ctxType == ctxType { - // A cycle containing only alias types is an error - // since it would expand indefinitely when aliases - // are substituted. - cycle := cycleFor(n) - for _, n1 := range cycle { - if n1.Name != nil && !n1.Name.Param.Alias() { - // Cycle is ok. But if n is an alias type and doesn't - // have a type yet, we have a recursive type declaration - // with aliases that we can't handle properly yet. - // Report an error rather than crashing later. - if n.Name != nil && n.Name.Param.Alias() && n.Type == nil { - lineno = n.Pos - Fatalf("cannot handle alias type declaration (issue #25838): %v", n) - } - lineno = lno - return n - } - } - yyerrorl(n.Pos, "invalid recursive type alias %v%s", n, cycleTrace(cycle)) - } - - case OLITERAL: - if top&(ctxExpr|ctxType) == ctxType { - yyerror("%v is not a type", n) - break - } - yyerrorl(n.Pos, "constant definition loop%s", cycleTrace(cycleFor(n))) - } - - if nsavederrors+nerrors == 0 { - var trace string - for i := len(typecheck_tcstack) - 1; i >= 0; i-- { - x := typecheck_tcstack[i] - trace += fmt.Sprintf("\n\t%v %v", x.Line(), x) - } - yyerror("typechecking loop involving %v%s", n, trace) - } - - lineno = lno - return n - } - - n.SetTypecheck(2) - - typecheck_tcstack = append(typecheck_tcstack, n) - n = typecheck1(n, top) - - n.SetTypecheck(1) - - last := len(typecheck_tcstack) - 1 - typecheck_tcstack[last] = nil - typecheck_tcstack = typecheck_tcstack[:last] - - lineno = lno - return n -} - -// indexlit implements typechecking of untyped values as -// array/slice indexes. It is almost equivalent to defaultlit -// but also accepts untyped numeric values representable as -// value of type int (see also checkmake for comparison). -// The result of indexlit MUST be assigned back to n, e.g. -// n.Left = indexlit(n.Left) -func indexlit(n *Node) *Node { - if n != nil && n.Type != nil && n.Type.Etype == TIDEAL { - return defaultlit(n, types.Types[TINT]) - } - return n -} - -// The result of typecheck1 MUST be assigned back to n, e.g. -// n.Left = typecheck1(n.Left, top) -func typecheck1(n *Node, top int) (res *Node) { - if enableTrace && trace { - defer tracePrint("typecheck1", n)(&res) - } - - switch n.Op { - case OLITERAL, ONAME, ONONAME, OTYPE: - if n.Sym == nil { - break - } - - if n.Op == ONAME && n.SubOp() != 0 && top&ctxCallee == 0 { - yyerror("use of builtin %v not in function call", n.Sym) - n.Type = nil - return n - } - - typecheckdef(n) - if n.Op == ONONAME { - n.Type = nil - return n - } - } - - ok := 0 - switch n.Op { - // until typecheck is complete, do nothing. - default: - Dump("typecheck", n) - - Fatalf("typecheck %v", n.Op) - - // names - case OLITERAL: - ok |= ctxExpr - - if n.Type == nil && n.Val().Ctype() == CTSTR { - n.Type = types.UntypedString - } - - case ONONAME: - ok |= ctxExpr - - case ONAME: - if n.Name.Decldepth == 0 { - n.Name.Decldepth = decldepth - } - if n.SubOp() != 0 { - ok |= ctxCallee - break - } - - if top&ctxAssign == 0 { - // not a write to the variable - if n.isBlank() { - yyerror("cannot use _ as value") - n.Type = nil - return n - } - - n.Name.SetUsed(true) - } - - ok |= ctxExpr - - case OPACK: - yyerror("use of package %v without selector", n.Sym) - n.Type = nil - return n - - case ODDD: - break - - // types (ODEREF is with exprs) - case OTYPE: - ok |= ctxType - - if n.Type == nil { - return n - } - - case OTARRAY: - ok |= ctxType - r := typecheck(n.Right, ctxType) - if r.Type == nil { - n.Type = nil - return n - } - - var t *types.Type - if n.Left == nil { - t = types.NewSlice(r.Type) - } else if n.Left.Op == ODDD { - if !n.Diag() { - n.SetDiag(true) - yyerror("use of [...] array outside of array literal") - } - n.Type = nil - return n - } else { - n.Left = indexlit(typecheck(n.Left, ctxExpr)) - l := n.Left - if consttype(l) != CTINT { - switch { - case l.Type == nil: - // Error already reported elsewhere. - case l.Type.IsInteger() && l.Op != OLITERAL: - yyerror("non-constant array bound %v", l) - default: - yyerror("invalid array bound %v", l) - } - n.Type = nil - return n - } - - v := l.Val() - if doesoverflow(v, types.Types[TINT]) { - yyerror("array bound is too large") - n.Type = nil - return n - } - - bound := v.U.(*Mpint).Int64() - if bound < 0 { - yyerror("array bound must be non-negative") - n.Type = nil - return n - } - t = types.NewArray(r.Type, bound) - } - - setTypeNode(n, t) - n.Left = nil - n.Right = nil - checkwidth(t) - - case OTMAP: - ok |= ctxType - n.Left = typecheck(n.Left, ctxType) - n.Right = typecheck(n.Right, ctxType) - l := n.Left - r := n.Right - if l.Type == nil || r.Type == nil { - n.Type = nil - return n - } - if l.Type.NotInHeap() { - yyerror("incomplete (or unallocatable) map key not allowed") - } - if r.Type.NotInHeap() { - yyerror("incomplete (or unallocatable) map value not allowed") - } - - setTypeNode(n, types.NewMap(l.Type, r.Type)) - mapqueue = append(mapqueue, n) // check map keys when all types are settled - n.Left = nil - n.Right = nil - - case OTCHAN: - ok |= ctxType - n.Left = typecheck(n.Left, ctxType) - l := n.Left - if l.Type == nil { - n.Type = nil - return n - } - if l.Type.NotInHeap() { - yyerror("chan of incomplete (or unallocatable) type not allowed") - } - - setTypeNode(n, types.NewChan(l.Type, n.TChanDir())) - n.Left = nil - n.ResetAux() - - case OTSTRUCT: - ok |= ctxType - setTypeNode(n, tostruct(n.List.Slice())) - n.List.Set(nil) - - case OTINTER: - ok |= ctxType - setTypeNode(n, tointerface(n.List.Slice())) - - case OTFUNC: - ok |= ctxType - setTypeNode(n, functype(n.Left, n.List.Slice(), n.Rlist.Slice())) - n.Left = nil - n.List.Set(nil) - n.Rlist.Set(nil) - - // type or expr - case ODEREF: - n.Left = typecheck(n.Left, ctxExpr|ctxType) - l := n.Left - t := l.Type - if t == nil { - n.Type = nil - return n - } - if l.Op == OTYPE { - ok |= ctxType - setTypeNode(n, types.NewPtr(l.Type)) - n.Left = nil - // Ensure l.Type gets dowidth'd for the backend. Issue 20174. - checkwidth(l.Type) - break - } - - if !t.IsPtr() { - if top&(ctxExpr|ctxStmt) != 0 { - yyerror("invalid indirect of %L", n.Left) - n.Type = nil - return n - } - - break - } - - ok |= ctxExpr - n.Type = t.Elem() - - // arithmetic exprs - case OASOP, - OADD, - OAND, - OANDAND, - OANDNOT, - ODIV, - OEQ, - OGE, - OGT, - OLE, - OLT, - OLSH, - ORSH, - OMOD, - OMUL, - ONE, - OOR, - OOROR, - OSUB, - OXOR: - var l *Node - var op Op - var r *Node - if n.Op == OASOP { - ok |= ctxStmt - n.Left = typecheck(n.Left, ctxExpr) - n.Right = typecheck(n.Right, ctxExpr) - l = n.Left - r = n.Right - checkassign(n, n.Left) - if l.Type == nil || r.Type == nil { - n.Type = nil - return n - } - if n.Implicit() && !okforarith[l.Type.Etype] { - yyerror("invalid operation: %v (non-numeric type %v)", n, l.Type) - n.Type = nil - return n - } - // TODO(marvin): Fix Node.EType type union. - op = n.SubOp() - } else { - ok |= ctxExpr - n.Left = typecheck(n.Left, ctxExpr) - n.Right = typecheck(n.Right, ctxExpr) - l = n.Left - r = n.Right - if l.Type == nil || r.Type == nil { - n.Type = nil - return n - } - op = n.Op - } - if op == OLSH || op == ORSH { - r = defaultlit(r, types.Types[TUINT]) - n.Right = r - t := r.Type - if !t.IsInteger() { - yyerror("invalid operation: %v (shift count type %v, must be integer)", n, r.Type) - n.Type = nil - return n - } - if t.IsSigned() && !langSupported(1, 13, curpkg()) { - yyerrorv("go1.13", "invalid operation: %v (signed shift count type %v)", n, r.Type) - n.Type = nil - return n - } - t = l.Type - if t != nil && t.Etype != TIDEAL && !t.IsInteger() { - yyerror("invalid operation: %v (shift of type %v)", n, t) - n.Type = nil - return n - } - - // no defaultlit for left - // the outer context gives the type - n.Type = l.Type - if (l.Type == types.UntypedFloat || l.Type == types.UntypedComplex) && r.Op == OLITERAL { - n.Type = types.UntypedInt - } - - break - } - - // For "x == x && len(s)", it's better to report that "len(s)" (type int) - // can't be used with "&&" than to report that "x == x" (type untyped bool) - // can't be converted to int (see issue #41500). - if n.Op == OANDAND || n.Op == OOROR { - if !n.Left.Type.IsBoolean() { - yyerror("invalid operation: %v (operator %v not defined on %s)", n, n.Op, typekind(n.Left.Type)) - n.Type = nil - return n - } - if !n.Right.Type.IsBoolean() { - yyerror("invalid operation: %v (operator %v not defined on %s)", n, n.Op, typekind(n.Right.Type)) - n.Type = nil - return n - } - } - - // ideal mixed with non-ideal - l, r = defaultlit2(l, r, false) - - n.Left = l - n.Right = r - if l.Type == nil || r.Type == nil { - n.Type = nil - return n - } - t := l.Type - if t.Etype == TIDEAL { - t = r.Type - } - et := t.Etype - if et == TIDEAL { - et = TINT - } - aop := OXXX - if iscmp[n.Op] && t.Etype != TIDEAL && !types.Identical(l.Type, r.Type) { - // comparison is okay as long as one side is - // assignable to the other. convert so they have - // the same type. - // - // the only conversion that isn't a no-op is concrete == interface. - // in that case, check comparability of the concrete type. - // The conversion allocates, so only do it if the concrete type is huge. - converted := false - if r.Type.Etype != TBLANK { - aop, _ = assignop(l.Type, r.Type) - if aop != OXXX { - if r.Type.IsInterface() && !l.Type.IsInterface() && !IsComparable(l.Type) { - yyerror("invalid operation: %v (operator %v not defined on %s)", n, op, typekind(l.Type)) - n.Type = nil - return n - } - - dowidth(l.Type) - if r.Type.IsInterface() == l.Type.IsInterface() || l.Type.Width >= 1<<16 { - l = nod(aop, l, nil) - l.Type = r.Type - l.SetTypecheck(1) - n.Left = l - } - - t = r.Type - converted = true - } - } - - if !converted && l.Type.Etype != TBLANK { - aop, _ = assignop(r.Type, l.Type) - if aop != OXXX { - if l.Type.IsInterface() && !r.Type.IsInterface() && !IsComparable(r.Type) { - yyerror("invalid operation: %v (operator %v not defined on %s)", n, op, typekind(r.Type)) - n.Type = nil - return n - } - - dowidth(r.Type) - if r.Type.IsInterface() == l.Type.IsInterface() || r.Type.Width >= 1<<16 { - r = nod(aop, r, nil) - r.Type = l.Type - r.SetTypecheck(1) - n.Right = r - } - - t = l.Type - } - } - - et = t.Etype - } - - if t.Etype != TIDEAL && !types.Identical(l.Type, r.Type) { - l, r = defaultlit2(l, r, true) - if l.Type == nil || r.Type == nil { - n.Type = nil - return n - } - if l.Type.IsInterface() == r.Type.IsInterface() || aop == 0 { - yyerror("invalid operation: %v (mismatched types %v and %v)", n, l.Type, r.Type) - n.Type = nil - return n - } - } - - if t.Etype == TIDEAL { - t = mixUntyped(l.Type, r.Type) - } - if dt := defaultType(t); !okfor[op][dt.Etype] { - yyerror("invalid operation: %v (operator %v not defined on %s)", n, op, typekind(t)) - n.Type = nil - return n - } - - // okfor allows any array == array, map == map, func == func. - // restrict to slice/map/func == nil and nil == slice/map/func. - if l.Type.IsArray() && !IsComparable(l.Type) { - yyerror("invalid operation: %v (%v cannot be compared)", n, l.Type) - n.Type = nil - return n - } - - if l.Type.IsSlice() && !l.isNil() && !r.isNil() { - yyerror("invalid operation: %v (slice can only be compared to nil)", n) - n.Type = nil - return n - } - - if l.Type.IsMap() && !l.isNil() && !r.isNil() { - yyerror("invalid operation: %v (map can only be compared to nil)", n) - n.Type = nil - return n - } - - if l.Type.Etype == TFUNC && !l.isNil() && !r.isNil() { - yyerror("invalid operation: %v (func can only be compared to nil)", n) - n.Type = nil - return n - } - - if l.Type.IsStruct() { - if f := IncomparableField(l.Type); f != nil { - yyerror("invalid operation: %v (struct containing %v cannot be compared)", n, f.Type) - n.Type = nil - return n - } - } - - if iscmp[n.Op] { - evconst(n) - t = types.UntypedBool - if n.Op != OLITERAL { - l, r = defaultlit2(l, r, true) - n.Left = l - n.Right = r - } - } - - if et == TSTRING && n.Op == OADD { - // create OADDSTR node with list of strings in x + y + z + (w + v) + ... - n.Op = OADDSTR - - if l.Op == OADDSTR { - n.List.Set(l.List.Slice()) - } else { - n.List.Set1(l) - } - if r.Op == OADDSTR { - n.List.AppendNodes(&r.List) - } else { - n.List.Append(r) - } - n.Left = nil - n.Right = nil - } - - if (op == ODIV || op == OMOD) && Isconst(r, CTINT) { - if r.Val().U.(*Mpint).CmpInt64(0) == 0 { - yyerror("division by zero") - n.Type = nil - return n - } - } - - n.Type = t - - case OBITNOT, ONEG, ONOT, OPLUS: - ok |= ctxExpr - n.Left = typecheck(n.Left, ctxExpr) - l := n.Left - t := l.Type - if t == nil { - n.Type = nil - return n - } - if !okfor[n.Op][defaultType(t).Etype] { - yyerror("invalid operation: %v (operator %v not defined on %s)", n, n.Op, typekind(t)) - n.Type = nil - return n - } - - n.Type = t - - // exprs - case OADDR: - ok |= ctxExpr - - n.Left = typecheck(n.Left, ctxExpr) - if n.Left.Type == nil { - n.Type = nil - return n - } - - switch n.Left.Op { - case OARRAYLIT, OMAPLIT, OSLICELIT, OSTRUCTLIT: - n.Op = OPTRLIT - - default: - checklvalue(n.Left, "take the address of") - r := outervalue(n.Left) - if r.Op == ONAME { - if r.Orig != r { - Fatalf("found non-orig name node %v", r) // TODO(mdempsky): What does this mean? - } - r.Name.SetAddrtaken(true) - if r.Name.IsClosureVar() && !capturevarscomplete { - // Mark the original variable as Addrtaken so that capturevars - // knows not to pass it by value. - // But if the capturevars phase is complete, don't touch it, - // in case l.Name's containing function has not yet been compiled. - r.Name.Defn.Name.SetAddrtaken(true) - } - } - n.Left = defaultlit(n.Left, nil) - if n.Left.Type == nil { - n.Type = nil - return n - } - } - - n.Type = types.NewPtr(n.Left.Type) - - case OCOMPLIT: - ok |= ctxExpr - n = typecheckcomplit(n) - if n.Type == nil { - return n - } - - case OXDOT, ODOT: - if n.Op == OXDOT { - n = adddot(n) - n.Op = ODOT - if n.Left == nil { - n.Type = nil - return n - } - } - - n.Left = typecheck(n.Left, ctxExpr|ctxType) - - n.Left = defaultlit(n.Left, nil) - - t := n.Left.Type - if t == nil { - adderrorname(n) - n.Type = nil - return n - } - - s := n.Sym - - if n.Left.Op == OTYPE { - n = typecheckMethodExpr(n) - if n.Type == nil { - return n - } - ok = ctxExpr - break - } - - if t.IsPtr() && !t.Elem().IsInterface() { - t = t.Elem() - if t == nil { - n.Type = nil - return n - } - n.Op = ODOTPTR - checkwidth(t) - } - - if n.Sym.IsBlank() { - yyerror("cannot refer to blank field or method") - n.Type = nil - return n - } - - if lookdot(n, t, 0) == nil { - // Legitimate field or method lookup failed, try to explain the error - switch { - case t.IsEmptyInterface(): - yyerror("%v undefined (type %v is interface with no methods)", n, n.Left.Type) - - case t.IsPtr() && t.Elem().IsInterface(): - // Pointer to interface is almost always a mistake. - yyerror("%v undefined (type %v is pointer to interface, not interface)", n, n.Left.Type) - - case lookdot(n, t, 1) != nil: - // Field or method matches by name, but it is not exported. - yyerror("%v undefined (cannot refer to unexported field or method %v)", n, n.Sym) - - default: - if mt := lookdot(n, t, 2); mt != nil && visible(mt.Sym) { // Case-insensitive lookup. - yyerror("%v undefined (type %v has no field or method %v, but does have %v)", n, n.Left.Type, n.Sym, mt.Sym) - } else { - yyerror("%v undefined (type %v has no field or method %v)", n, n.Left.Type, n.Sym) - } - } - n.Type = nil - return n - } - - switch n.Op { - case ODOTINTER, ODOTMETH: - if top&ctxCallee != 0 { - ok |= ctxCallee - } else { - typecheckpartialcall(n, s) - ok |= ctxExpr - } - - default: - ok |= ctxExpr - } - - case ODOTTYPE: - ok |= ctxExpr - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - l := n.Left - t := l.Type - if t == nil { - n.Type = nil - return n - } - if !t.IsInterface() { - yyerror("invalid type assertion: %v (non-interface type %v on left)", n, t) - n.Type = nil - return n - } - - if n.Right != nil { - n.Right = typecheck(n.Right, ctxType) - n.Type = n.Right.Type - n.Right = nil - if n.Type == nil { - return n - } - } - - if n.Type != nil && !n.Type.IsInterface() { - var missing, have *types.Field - var ptr int - if !implements(n.Type, t, &missing, &have, &ptr) { - if have != nil && have.Sym == missing.Sym { - yyerror("impossible type assertion:\n\t%v does not implement %v (wrong type for %v method)\n"+ - "\t\thave %v%0S\n\t\twant %v%0S", n.Type, t, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) - } else if ptr != 0 { - yyerror("impossible type assertion:\n\t%v does not implement %v (%v method has pointer receiver)", n.Type, t, missing.Sym) - } else if have != nil { - yyerror("impossible type assertion:\n\t%v does not implement %v (missing %v method)\n"+ - "\t\thave %v%0S\n\t\twant %v%0S", n.Type, t, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) - } else { - yyerror("impossible type assertion:\n\t%v does not implement %v (missing %v method)", n.Type, t, missing.Sym) - } - n.Type = nil - return n - } - } - - case OINDEX: - ok |= ctxExpr - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - n.Left = implicitstar(n.Left) - l := n.Left - n.Right = typecheck(n.Right, ctxExpr) - r := n.Right - t := l.Type - if t == nil || r.Type == nil { - n.Type = nil - return n - } - switch t.Etype { - default: - yyerror("invalid operation: %v (type %v does not support indexing)", n, t) - n.Type = nil - return n - - case TSTRING, TARRAY, TSLICE: - n.Right = indexlit(n.Right) - if t.IsString() { - n.Type = types.Bytetype - } else { - n.Type = t.Elem() - } - why := "string" - if t.IsArray() { - why = "array" - } else if t.IsSlice() { - why = "slice" - } - - if n.Right.Type != nil && !n.Right.Type.IsInteger() { - yyerror("non-integer %s index %v", why, n.Right) - break - } - - if !n.Bounded() && Isconst(n.Right, CTINT) { - x := n.Right.Int64Val() - if x < 0 { - yyerror("invalid %s index %v (index must be non-negative)", why, n.Right) - } else if t.IsArray() && x >= t.NumElem() { - yyerror("invalid array index %v (out of bounds for %d-element array)", n.Right, t.NumElem()) - } else if Isconst(n.Left, CTSTR) && x >= int64(len(n.Left.StringVal())) { - yyerror("invalid string index %v (out of bounds for %d-byte string)", n.Right, len(n.Left.StringVal())) - } else if n.Right.Val().U.(*Mpint).Cmp(maxintval[TINT]) > 0 { - yyerror("invalid %s index %v (index too large)", why, n.Right) - } - } - - case TMAP: - n.Right = assignconv(n.Right, t.Key(), "map index") - n.Type = t.Elem() - n.Op = OINDEXMAP - n.ResetAux() - } - - case ORECV: - ok |= ctxStmt | ctxExpr - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - l := n.Left - t := l.Type - if t == nil { - n.Type = nil - return n - } - if !t.IsChan() { - yyerror("invalid operation: %v (receive from non-chan type %v)", n, t) - n.Type = nil - return n - } - - if !t.ChanDir().CanRecv() { - yyerror("invalid operation: %v (receive from send-only type %v)", n, t) - n.Type = nil - return n - } - - n.Type = t.Elem() - - case OSEND: - ok |= ctxStmt - n.Left = typecheck(n.Left, ctxExpr) - n.Right = typecheck(n.Right, ctxExpr) - n.Left = defaultlit(n.Left, nil) - t := n.Left.Type - if t == nil { - n.Type = nil - return n - } - if !t.IsChan() { - yyerror("invalid operation: %v (send to non-chan type %v)", n, t) - n.Type = nil - return n - } - - if !t.ChanDir().CanSend() { - yyerror("invalid operation: %v (send to receive-only type %v)", n, t) - n.Type = nil - return n - } - - n.Right = assignconv(n.Right, t.Elem(), "send") - if n.Right.Type == nil { - n.Type = nil - return n - } - n.Type = nil - - case OSLICEHEADER: - // Errors here are Fatalf instead of yyerror because only the compiler - // can construct an OSLICEHEADER node. - // Components used in OSLICEHEADER that are supplied by parsed source code - // have already been typechecked in e.g. OMAKESLICE earlier. - ok |= ctxExpr - - t := n.Type - if t == nil { - Fatalf("no type specified for OSLICEHEADER") - } - - if !t.IsSlice() { - Fatalf("invalid type %v for OSLICEHEADER", n.Type) - } - - if n.Left == nil || n.Left.Type == nil || !n.Left.Type.IsUnsafePtr() { - Fatalf("need unsafe.Pointer for OSLICEHEADER") - } - - if x := n.List.Len(); x != 2 { - Fatalf("expected 2 params (len, cap) for OSLICEHEADER, got %d", x) - } - - n.Left = typecheck(n.Left, ctxExpr) - l := typecheck(n.List.First(), ctxExpr) - c := typecheck(n.List.Second(), ctxExpr) - l = defaultlit(l, types.Types[TINT]) - c = defaultlit(c, types.Types[TINT]) - - if Isconst(l, CTINT) && l.Int64Val() < 0 { - Fatalf("len for OSLICEHEADER must be non-negative") - } - - if Isconst(c, CTINT) && c.Int64Val() < 0 { - Fatalf("cap for OSLICEHEADER must be non-negative") - } - - if Isconst(l, CTINT) && Isconst(c, CTINT) && l.Val().U.(*Mpint).Cmp(c.Val().U.(*Mpint)) > 0 { - Fatalf("len larger than cap for OSLICEHEADER") - } - - n.List.SetFirst(l) - n.List.SetSecond(c) - - case OMAKESLICECOPY: - // Errors here are Fatalf instead of yyerror because only the compiler - // can construct an OMAKESLICECOPY node. - // Components used in OMAKESCLICECOPY that are supplied by parsed source code - // have already been typechecked in OMAKE and OCOPY earlier. - ok |= ctxExpr - - t := n.Type - - if t == nil { - Fatalf("no type specified for OMAKESLICECOPY") - } - - if !t.IsSlice() { - Fatalf("invalid type %v for OMAKESLICECOPY", n.Type) - } - - if n.Left == nil { - Fatalf("missing len argument for OMAKESLICECOPY") - } - - if n.Right == nil { - Fatalf("missing slice argument to copy for OMAKESLICECOPY") - } - - n.Left = typecheck(n.Left, ctxExpr) - n.Right = typecheck(n.Right, ctxExpr) - - n.Left = defaultlit(n.Left, types.Types[TINT]) - - if !n.Left.Type.IsInteger() && n.Type.Etype != TIDEAL { - yyerror("non-integer len argument in OMAKESLICECOPY") - } - - if Isconst(n.Left, CTINT) { - if n.Left.Val().U.(*Mpint).Cmp(maxintval[TINT]) > 0 { - Fatalf("len for OMAKESLICECOPY too large") - } - if n.Left.Int64Val() < 0 { - Fatalf("len for OMAKESLICECOPY must be non-negative") - } - } - - case OSLICE, OSLICE3: - ok |= ctxExpr - n.Left = typecheck(n.Left, ctxExpr) - low, high, max := n.SliceBounds() - hasmax := n.Op.IsSlice3() - low = typecheck(low, ctxExpr) - high = typecheck(high, ctxExpr) - max = typecheck(max, ctxExpr) - n.Left = defaultlit(n.Left, nil) - low = indexlit(low) - high = indexlit(high) - max = indexlit(max) - n.SetSliceBounds(low, high, max) - l := n.Left - if l.Type == nil { - n.Type = nil - return n - } - if l.Type.IsArray() { - if !islvalue(n.Left) { - yyerror("invalid operation %v (slice of unaddressable value)", n) - n.Type = nil - return n - } - - n.Left = nod(OADDR, n.Left, nil) - n.Left.SetImplicit(true) - n.Left = typecheck(n.Left, ctxExpr) - l = n.Left - } - t := l.Type - var tp *types.Type - if t.IsString() { - if hasmax { - yyerror("invalid operation %v (3-index slice of string)", n) - n.Type = nil - return n - } - n.Type = t - n.Op = OSLICESTR - } else if t.IsPtr() && t.Elem().IsArray() { - tp = t.Elem() - n.Type = types.NewSlice(tp.Elem()) - dowidth(n.Type) - if hasmax { - n.Op = OSLICE3ARR - } else { - n.Op = OSLICEARR - } - } else if t.IsSlice() { - n.Type = t - } else { - yyerror("cannot slice %v (type %v)", l, t) - n.Type = nil - return n - } - - if low != nil && !checksliceindex(l, low, tp) { - n.Type = nil - return n - } - if high != nil && !checksliceindex(l, high, tp) { - n.Type = nil - return n - } - if max != nil && !checksliceindex(l, max, tp) { - n.Type = nil - return n - } - if !checksliceconst(low, high) || !checksliceconst(low, max) || !checksliceconst(high, max) { - n.Type = nil - return n - } - - // call and call like - case OCALL: - typecheckslice(n.Ninit.Slice(), ctxStmt) // imported rewritten f(g()) calls (#30907) - n.Left = typecheck(n.Left, ctxExpr|ctxType|ctxCallee) - if n.Left.Diag() { - n.SetDiag(true) - } - - l := n.Left - - if l.Op == ONAME && l.SubOp() != 0 { - if n.IsDDD() && l.SubOp() != OAPPEND { - yyerror("invalid use of ... with builtin %v", l) - } - - // builtin: OLEN, OCAP, etc. - n.Op = l.SubOp() - n.Left = n.Right - n.Right = nil - n = typecheck1(n, top) - return n - } - - n.Left = defaultlit(n.Left, nil) - l = n.Left - if l.Op == OTYPE { - if n.IsDDD() { - if !l.Type.Broke() { - yyerror("invalid use of ... in type conversion to %v", l.Type) - } - n.SetDiag(true) - } - - // pick off before type-checking arguments - ok |= ctxExpr - - // turn CALL(type, arg) into CONV(arg) w/ type - n.Left = nil - - n.Op = OCONV - n.Type = l.Type - if !onearg(n, "conversion to %v", l.Type) { - n.Type = nil - return n - } - n = typecheck1(n, top) - return n - } - - typecheckargs(n) - t := l.Type - if t == nil { - n.Type = nil - return n - } - checkwidth(t) - - switch l.Op { - case ODOTINTER: - n.Op = OCALLINTER - - case ODOTMETH: - n.Op = OCALLMETH - - // typecheckaste was used here but there wasn't enough - // information further down the call chain to know if we - // were testing a method receiver for unexported fields. - // It isn't necessary, so just do a sanity check. - tp := t.Recv().Type - - if l.Left == nil || !types.Identical(l.Left.Type, tp) { - Fatalf("method receiver") - } - - default: - n.Op = OCALLFUNC - if t.Etype != TFUNC { - name := l.String() - if isBuiltinFuncName(name) && l.Name.Defn != nil { - // be more specific when the function - // name matches a predeclared function - yyerror("cannot call non-function %s (type %v), declared at %s", - name, t, linestr(l.Name.Defn.Pos)) - } else { - yyerror("cannot call non-function %s (type %v)", name, t) - } - n.Type = nil - return n - } - } - - typecheckaste(OCALL, n.Left, n.IsDDD(), t.Params(), n.List, func() string { return fmt.Sprintf("argument to %v", n.Left) }) - ok |= ctxStmt - if t.NumResults() == 0 { - break - } - ok |= ctxExpr - if t.NumResults() == 1 { - n.Type = l.Type.Results().Field(0).Type - - if n.Op == OCALLFUNC && n.Left.Op == ONAME && isRuntimePkg(n.Left.Sym.Pkg) && n.Left.Sym.Name == "getg" { - // Emit code for runtime.getg() directly instead of calling function. - // Most such rewrites (for example the similar one for math.Sqrt) should be done in walk, - // so that the ordering pass can make sure to preserve the semantics of the original code - // (in particular, the exact time of the function call) by introducing temporaries. - // In this case, we know getg() always returns the same result within a given function - // and we want to avoid the temporaries, so we do the rewrite earlier than is typical. - n.Op = OGETG - } - - break - } - - // multiple return - if top&(ctxMultiOK|ctxStmt) == 0 { - yyerror("multiple-value %v() in single-value context", l) - break - } - - n.Type = l.Type.Results() - - case OALIGNOF, OOFFSETOF, OSIZEOF: - ok |= ctxExpr - if !onearg(n, "%v", n.Op) { - n.Type = nil - return n - } - n.Type = types.Types[TUINTPTR] - - case OCAP, OLEN: - ok |= ctxExpr - if !onearg(n, "%v", n.Op) { - n.Type = nil - return n - } - - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - n.Left = implicitstar(n.Left) - l := n.Left - t := l.Type - if t == nil { - n.Type = nil - return n - } - - var ok bool - if n.Op == OLEN { - ok = okforlen[t.Etype] - } else { - ok = okforcap[t.Etype] - } - if !ok { - yyerror("invalid argument %L for %v", l, n.Op) - n.Type = nil - return n - } - - n.Type = types.Types[TINT] - - case OREAL, OIMAG: - ok |= ctxExpr - if !onearg(n, "%v", n.Op) { - n.Type = nil - return n - } - - n.Left = typecheck(n.Left, ctxExpr) - l := n.Left - t := l.Type - if t == nil { - n.Type = nil - return n - } - - // Determine result type. - switch t.Etype { - case TIDEAL: - n.Type = types.UntypedFloat - case TCOMPLEX64: - n.Type = types.Types[TFLOAT32] - case TCOMPLEX128: - n.Type = types.Types[TFLOAT64] - default: - yyerror("invalid argument %L for %v", l, n.Op) - n.Type = nil - return n - } - - case OCOMPLEX: - ok |= ctxExpr - typecheckargs(n) - if !twoarg(n) { - n.Type = nil - return n - } - l := n.Left - r := n.Right - if l.Type == nil || r.Type == nil { - n.Type = nil - return n - } - l, r = defaultlit2(l, r, false) - if l.Type == nil || r.Type == nil { - n.Type = nil - return n - } - n.Left = l - n.Right = r - - if !types.Identical(l.Type, r.Type) { - yyerror("invalid operation: %v (mismatched types %v and %v)", n, l.Type, r.Type) - n.Type = nil - return n - } - - var t *types.Type - switch l.Type.Etype { - default: - yyerror("invalid operation: %v (arguments have type %v, expected floating-point)", n, l.Type) - n.Type = nil - return n - - case TIDEAL: - t = types.UntypedComplex - - case TFLOAT32: - t = types.Types[TCOMPLEX64] - - case TFLOAT64: - t = types.Types[TCOMPLEX128] - } - n.Type = t - - case OCLOSE: - if !onearg(n, "%v", n.Op) { - n.Type = nil - return n - } - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - l := n.Left - t := l.Type - if t == nil { - n.Type = nil - return n - } - if !t.IsChan() { - yyerror("invalid operation: %v (non-chan type %v)", n, t) - n.Type = nil - return n - } - - if !t.ChanDir().CanSend() { - yyerror("invalid operation: %v (cannot close receive-only channel)", n) - n.Type = nil - return n - } - - ok |= ctxStmt - - case ODELETE: - ok |= ctxStmt - typecheckargs(n) - args := n.List - if args.Len() == 0 { - yyerror("missing arguments to delete") - n.Type = nil - return n - } - - if args.Len() == 1 { - yyerror("missing second (key) argument to delete") - n.Type = nil - return n - } - - if args.Len() != 2 { - yyerror("too many arguments to delete") - n.Type = nil - return n - } - - l := args.First() - r := args.Second() - if l.Type != nil && !l.Type.IsMap() { - yyerror("first argument to delete must be map; have %L", l.Type) - n.Type = nil - return n - } - - args.SetSecond(assignconv(r, l.Type.Key(), "delete")) - - case OAPPEND: - ok |= ctxExpr - typecheckargs(n) - args := n.List - if args.Len() == 0 { - yyerror("missing arguments to append") - n.Type = nil - return n - } - - t := args.First().Type - if t == nil { - n.Type = nil - return n - } - - n.Type = t - if !t.IsSlice() { - if Isconst(args.First(), CTNIL) { - yyerror("first argument to append must be typed slice; have untyped nil") - n.Type = nil - return n - } - - yyerror("first argument to append must be slice; have %L", t) - n.Type = nil - return n - } - - if n.IsDDD() { - if args.Len() == 1 { - yyerror("cannot use ... on first argument to append") - n.Type = nil - return n - } - - if args.Len() != 2 { - yyerror("too many arguments to append") - n.Type = nil - return n - } - - if t.Elem().IsKind(TUINT8) && args.Second().Type.IsString() { - args.SetSecond(defaultlit(args.Second(), types.Types[TSTRING])) - break - } - - args.SetSecond(assignconv(args.Second(), t.Orig, "append")) - break - } - - as := args.Slice()[1:] - for i, n := range as { - if n.Type == nil { - continue - } - as[i] = assignconv(n, t.Elem(), "append") - checkwidth(as[i].Type) // ensure width is calculated for backend - } - - case OCOPY: - ok |= ctxStmt | ctxExpr - typecheckargs(n) - if !twoarg(n) { - n.Type = nil - return n - } - n.Type = types.Types[TINT] - if n.Left.Type == nil || n.Right.Type == nil { - n.Type = nil - return n - } - n.Left = defaultlit(n.Left, nil) - n.Right = defaultlit(n.Right, nil) - if n.Left.Type == nil || n.Right.Type == nil { - n.Type = nil - return n - } - - // copy([]byte, string) - if n.Left.Type.IsSlice() && n.Right.Type.IsString() { - if types.Identical(n.Left.Type.Elem(), types.Bytetype) { - break - } - yyerror("arguments to copy have different element types: %L and string", n.Left.Type) - n.Type = nil - return n - } - - if !n.Left.Type.IsSlice() || !n.Right.Type.IsSlice() { - if !n.Left.Type.IsSlice() && !n.Right.Type.IsSlice() { - yyerror("arguments to copy must be slices; have %L, %L", n.Left.Type, n.Right.Type) - } else if !n.Left.Type.IsSlice() { - yyerror("first argument to copy should be slice; have %L", n.Left.Type) - } else { - yyerror("second argument to copy should be slice or string; have %L", n.Right.Type) - } - n.Type = nil - return n - } - - if !types.Identical(n.Left.Type.Elem(), n.Right.Type.Elem()) { - yyerror("arguments to copy have different element types: %L and %L", n.Left.Type, n.Right.Type) - n.Type = nil - return n - } - - case OCONV: - ok |= ctxExpr - checkwidth(n.Type) // ensure width is calculated for backend - n.Left = typecheck(n.Left, ctxExpr) - n.Left = convlit1(n.Left, n.Type, true, nil) - t := n.Left.Type - if t == nil || n.Type == nil { - n.Type = nil - return n - } - var why string - n.Op, why = convertop(n.Left.Op == OLITERAL, t, n.Type) - if n.Op == OXXX { - if !n.Diag() && !n.Type.Broke() && !n.Left.Diag() { - yyerror("cannot convert %L to type %v%s", n.Left, n.Type, why) - n.SetDiag(true) - } - n.Op = OCONV - n.Type = nil - return n - } - - switch n.Op { - case OCONVNOP: - if t.Etype == n.Type.Etype { - switch t.Etype { - case TFLOAT32, TFLOAT64, TCOMPLEX64, TCOMPLEX128: - // Floating point casts imply rounding and - // so the conversion must be kept. - n.Op = OCONV - } - } - - // do not convert to []byte literal. See CL 125796. - // generated code and compiler memory footprint is better without it. - case OSTR2BYTES: - break - - case OSTR2RUNES: - if n.Left.Op == OLITERAL { - n = stringtoruneslit(n) - } - } - - case OMAKE: - ok |= ctxExpr - args := n.List.Slice() - if len(args) == 0 { - yyerror("missing argument to make") - n.Type = nil - return n - } - - n.List.Set(nil) - l := args[0] - l = typecheck(l, ctxType) - t := l.Type - if t == nil { - n.Type = nil - return n - } - - i := 1 - switch t.Etype { - default: - yyerror("cannot make type %v", t) - n.Type = nil - return n - - case TSLICE: - if i >= len(args) { - yyerror("missing len argument to make(%v)", t) - n.Type = nil - return n - } - - l = args[i] - i++ - l = typecheck(l, ctxExpr) - var r *Node - if i < len(args) { - r = args[i] - i++ - r = typecheck(r, ctxExpr) - } - - if l.Type == nil || (r != nil && r.Type == nil) { - n.Type = nil - return n - } - if !checkmake(t, "len", &l) || r != nil && !checkmake(t, "cap", &r) { - n.Type = nil - return n - } - if Isconst(l, CTINT) && r != nil && Isconst(r, CTINT) && l.Val().U.(*Mpint).Cmp(r.Val().U.(*Mpint)) > 0 { - yyerror("len larger than cap in make(%v)", t) - n.Type = nil - return n - } - - n.Left = l - n.Right = r - n.Op = OMAKESLICE - - case TMAP: - if i < len(args) { - l = args[i] - i++ - l = typecheck(l, ctxExpr) - l = defaultlit(l, types.Types[TINT]) - if l.Type == nil { - n.Type = nil - return n - } - if !checkmake(t, "size", &l) { - n.Type = nil - return n - } - n.Left = l - } else { - n.Left = nodintconst(0) - } - n.Op = OMAKEMAP - - case TCHAN: - l = nil - if i < len(args) { - l = args[i] - i++ - l = typecheck(l, ctxExpr) - l = defaultlit(l, types.Types[TINT]) - if l.Type == nil { - n.Type = nil - return n - } - if !checkmake(t, "buffer", &l) { - n.Type = nil - return n - } - n.Left = l - } else { - n.Left = nodintconst(0) - } - n.Op = OMAKECHAN - } - - if i < len(args) { - yyerror("too many arguments to make(%v)", t) - n.Op = OMAKE - n.Type = nil - return n - } - - n.Type = t - - case ONEW: - ok |= ctxExpr - args := n.List - if args.Len() == 0 { - yyerror("missing argument to new") - n.Type = nil - return n - } - - l := args.First() - l = typecheck(l, ctxType) - t := l.Type - if t == nil { - n.Type = nil - return n - } - if args.Len() > 1 { - yyerror("too many arguments to new(%v)", t) - n.Type = nil - return n - } - - n.Left = l - n.Type = types.NewPtr(t) - - case OPRINT, OPRINTN: - ok |= ctxStmt - typecheckargs(n) - ls := n.List.Slice() - for i1, n1 := range ls { - // Special case for print: int constant is int64, not int. - if Isconst(n1, CTINT) { - ls[i1] = defaultlit(ls[i1], types.Types[TINT64]) - } else { - ls[i1] = defaultlit(ls[i1], nil) - } - } - - case OPANIC: - ok |= ctxStmt - if !onearg(n, "panic") { - n.Type = nil - return n - } - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, types.Types[TINTER]) - if n.Left.Type == nil { - n.Type = nil - return n - } - - case ORECOVER: - ok |= ctxExpr | ctxStmt - if n.List.Len() != 0 { - yyerror("too many arguments to recover") - n.Type = nil - return n - } - - n.Type = types.Types[TINTER] - - case OCLOSURE: - ok |= ctxExpr - typecheckclosure(n, top) - if n.Type == nil { - return n - } - - case OITAB: - ok |= ctxExpr - n.Left = typecheck(n.Left, ctxExpr) - t := n.Left.Type - if t == nil { - n.Type = nil - return n - } - if !t.IsInterface() { - Fatalf("OITAB of %v", t) - } - n.Type = types.NewPtr(types.Types[TUINTPTR]) - - case OIDATA: - // Whoever creates the OIDATA node must know a priori the concrete type at that moment, - // usually by just having checked the OITAB. - Fatalf("cannot typecheck interface data %v", n) - - case OSPTR: - ok |= ctxExpr - n.Left = typecheck(n.Left, ctxExpr) - t := n.Left.Type - if t == nil { - n.Type = nil - return n - } - if !t.IsSlice() && !t.IsString() { - Fatalf("OSPTR of %v", t) - } - if t.IsString() { - n.Type = types.NewPtr(types.Types[TUINT8]) - } else { - n.Type = types.NewPtr(t.Elem()) - } - - case OCLOSUREVAR: - ok |= ctxExpr - - case OCFUNC: - ok |= ctxExpr - n.Left = typecheck(n.Left, ctxExpr) - n.Type = types.Types[TUINTPTR] - - case OCONVNOP: - ok |= ctxExpr - n.Left = typecheck(n.Left, ctxExpr) - - // statements - case OAS: - ok |= ctxStmt - - typecheckas(n) - - // Code that creates temps does not bother to set defn, so do it here. - if n.Left.Op == ONAME && n.Left.IsAutoTmp() { - n.Left.Name.Defn = n - } - - case OAS2: - ok |= ctxStmt - typecheckas2(n) - - case OBREAK, - OCONTINUE, - ODCL, - OEMPTY, - OGOTO, - OFALL, - OVARKILL, - OVARLIVE: - ok |= ctxStmt - - case OLABEL: - ok |= ctxStmt - decldepth++ - if n.Sym.IsBlank() { - // Empty identifier is valid but useless. - // Eliminate now to simplify life later. - // See issues 7538, 11589, 11593. - n.Op = OEMPTY - n.Left = nil - } - - case ODEFER: - ok |= ctxStmt - n.Left = typecheck(n.Left, ctxStmt|ctxExpr) - if !n.Left.Diag() { - checkdefergo(n) - } - - case OGO: - ok |= ctxStmt - n.Left = typecheck(n.Left, ctxStmt|ctxExpr) - checkdefergo(n) - - case OFOR, OFORUNTIL: - ok |= ctxStmt - typecheckslice(n.Ninit.Slice(), ctxStmt) - decldepth++ - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - if n.Left != nil { - t := n.Left.Type - if t != nil && !t.IsBoolean() { - yyerror("non-bool %L used as for condition", n.Left) - } - } - n.Right = typecheck(n.Right, ctxStmt) - if n.Op == OFORUNTIL { - typecheckslice(n.List.Slice(), ctxStmt) - } - typecheckslice(n.Nbody.Slice(), ctxStmt) - decldepth-- - - case OIF: - ok |= ctxStmt - typecheckslice(n.Ninit.Slice(), ctxStmt) - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - if n.Left != nil { - t := n.Left.Type - if t != nil && !t.IsBoolean() { - yyerror("non-bool %L used as if condition", n.Left) - } - } - typecheckslice(n.Nbody.Slice(), ctxStmt) - typecheckslice(n.Rlist.Slice(), ctxStmt) - - case ORETURN: - ok |= ctxStmt - typecheckargs(n) - if Curfn == nil { - yyerror("return outside function") - n.Type = nil - return n - } - - if Curfn.Type.FuncType().Outnamed && n.List.Len() == 0 { - break - } - typecheckaste(ORETURN, nil, false, Curfn.Type.Results(), n.List, func() string { return "return argument" }) - - case ORETJMP: - ok |= ctxStmt - - case OSELECT: - ok |= ctxStmt - typecheckselect(n) - - case OSWITCH: - ok |= ctxStmt - typecheckswitch(n) - - case ORANGE: - ok |= ctxStmt - typecheckrange(n) - - case OTYPESW: - yyerror("use of .(type) outside type switch") - n.Type = nil - return n - - case ODCLFUNC: - ok |= ctxStmt - typecheckfunc(n) - - case ODCLCONST: - ok |= ctxStmt - n.Left = typecheck(n.Left, ctxExpr) - - case ODCLTYPE: - ok |= ctxStmt - n.Left = typecheck(n.Left, ctxType) - checkwidth(n.Left.Type) - } - - t := n.Type - if t != nil && !t.IsFuncArgStruct() && n.Op != OTYPE { - switch t.Etype { - case TFUNC, // might have TANY; wait until it's called - TANY, TFORW, TIDEAL, TNIL, TBLANK: - break - - default: - checkwidth(t) - } - } - - evconst(n) - if n.Op == OTYPE && top&ctxType == 0 { - if !n.Type.Broke() { - yyerror("type %v is not an expression", n.Type) - } - n.Type = nil - return n - } - - if top&(ctxExpr|ctxType) == ctxType && n.Op != OTYPE { - yyerror("%v is not a type", n) - n.Type = nil - return n - } - - // TODO(rsc): simplify - if (top&(ctxCallee|ctxExpr|ctxType) != 0) && top&ctxStmt == 0 && ok&(ctxExpr|ctxType|ctxCallee) == 0 { - yyerror("%v used as value", n) - n.Type = nil - return n - } - - if (top&ctxStmt != 0) && top&(ctxCallee|ctxExpr|ctxType) == 0 && ok&ctxStmt == 0 { - if !n.Diag() { - yyerror("%v evaluated but not used", n) - n.SetDiag(true) - } - - n.Type = nil - return n - } - - return n -} - -func typecheckargs(n *Node) { - if n.List.Len() != 1 || n.IsDDD() { - typecheckslice(n.List.Slice(), ctxExpr) - return - } - - typecheckslice(n.List.Slice(), ctxExpr|ctxMultiOK) - t := n.List.First().Type - if t == nil || !t.IsFuncArgStruct() { - return - } - - // Rewrite f(g()) into t1, t2, ... = g(); f(t1, t2, ...). - - // Save n as n.Orig for fmt.go. - if n.Orig == n { - n.Orig = n.sepcopy() - } - - as := nod(OAS2, nil, nil) - as.Rlist.AppendNodes(&n.List) - - // If we're outside of function context, then this call will - // be executed during the generated init function. However, - // init.go hasn't yet created it. Instead, associate the - // temporary variables with dummyInitFn for now, and init.go - // will reassociate them later when it's appropriate. - static := Curfn == nil - if static { - Curfn = dummyInitFn - } - for _, f := range t.FieldSlice() { - t := temp(f.Type) - as.Ninit.Append(nod(ODCL, t, nil)) - as.List.Append(t) - n.List.Append(t) - } - if static { - Curfn = nil - } - - as = typecheck(as, ctxStmt) - n.Ninit.Append(as) -} - -func checksliceindex(l *Node, r *Node, tp *types.Type) bool { - t := r.Type - if t == nil { - return false - } - if !t.IsInteger() { - yyerror("invalid slice index %v (type %v)", r, t) - return false - } - - if r.Op == OLITERAL { - if r.Int64Val() < 0 { - yyerror("invalid slice index %v (index must be non-negative)", r) - return false - } else if tp != nil && tp.NumElem() >= 0 && r.Int64Val() > tp.NumElem() { - yyerror("invalid slice index %v (out of bounds for %d-element array)", r, tp.NumElem()) - return false - } else if Isconst(l, CTSTR) && r.Int64Val() > int64(len(l.StringVal())) { - yyerror("invalid slice index %v (out of bounds for %d-byte string)", r, len(l.StringVal())) - return false - } else if r.Val().U.(*Mpint).Cmp(maxintval[TINT]) > 0 { - yyerror("invalid slice index %v (index too large)", r) - return false - } - } - - return true -} - -func checksliceconst(lo *Node, hi *Node) bool { - if lo != nil && hi != nil && lo.Op == OLITERAL && hi.Op == OLITERAL && lo.Val().U.(*Mpint).Cmp(hi.Val().U.(*Mpint)) > 0 { - yyerror("invalid slice index: %v > %v", lo, hi) - return false - } - - return true -} - -func checkdefergo(n *Node) { - what := "defer" - if n.Op == OGO { - what = "go" - } - - switch n.Left.Op { - // ok - case OCALLINTER, - OCALLMETH, - OCALLFUNC, - OCLOSE, - OCOPY, - ODELETE, - OPANIC, - OPRINT, - OPRINTN, - ORECOVER: - return - - case OAPPEND, - OCAP, - OCOMPLEX, - OIMAG, - OLEN, - OMAKE, - OMAKESLICE, - OMAKECHAN, - OMAKEMAP, - ONEW, - OREAL, - OLITERAL: // conversion or unsafe.Alignof, Offsetof, Sizeof - if n.Left.Orig != nil && n.Left.Orig.Op == OCONV { - break - } - yyerrorl(n.Pos, "%s discards result of %v", what, n.Left) - return - } - - // type is broken or missing, most likely a method call on a broken type - // we will warn about the broken type elsewhere. no need to emit a potentially confusing error - if n.Left.Type == nil || n.Left.Type.Broke() { - return - } - - if !n.Diag() { - // The syntax made sure it was a call, so this must be - // a conversion. - n.SetDiag(true) - yyerrorl(n.Pos, "%s requires function call, not conversion", what) - } -} - -// The result of implicitstar MUST be assigned back to n, e.g. -// n.Left = implicitstar(n.Left) -func implicitstar(n *Node) *Node { - // insert implicit * if needed for fixed array - t := n.Type - if t == nil || !t.IsPtr() { - return n - } - t = t.Elem() - if t == nil { - return n - } - if !t.IsArray() { - return n - } - n = nod(ODEREF, n, nil) - n.SetImplicit(true) - n = typecheck(n, ctxExpr) - return n -} - -func onearg(n *Node, f string, args ...interface{}) bool { - if n.Left != nil { - return true - } - if n.List.Len() == 0 { - p := fmt.Sprintf(f, args...) - yyerror("missing argument to %s: %v", p, n) - return false - } - - if n.List.Len() > 1 { - p := fmt.Sprintf(f, args...) - yyerror("too many arguments to %s: %v", p, n) - n.Left = n.List.First() - n.List.Set(nil) - return false - } - - n.Left = n.List.First() - n.List.Set(nil) - return true -} - -func twoarg(n *Node) bool { - if n.Left != nil { - return true - } - if n.List.Len() != 2 { - if n.List.Len() < 2 { - yyerror("not enough arguments in call to %v", n) - } else { - yyerror("too many arguments in call to %v", n) - } - return false - } - n.Left = n.List.First() - n.Right = n.List.Second() - n.List.Set(nil) - return true -} - -func lookdot1(errnode *Node, s *types.Sym, t *types.Type, fs *types.Fields, dostrcmp int) *types.Field { - var r *types.Field - for _, f := range fs.Slice() { - if dostrcmp != 0 && f.Sym.Name == s.Name { - return f - } - if dostrcmp == 2 && strings.EqualFold(f.Sym.Name, s.Name) { - return f - } - if f.Sym != s { - continue - } - if r != nil { - if errnode != nil { - yyerror("ambiguous selector %v", errnode) - } else if t.IsPtr() { - yyerror("ambiguous selector (%v).%v", t, s) - } else { - yyerror("ambiguous selector %v.%v", t, s) - } - break - } - - r = f - } - - return r -} - -// typecheckMethodExpr checks selector expressions (ODOT) where the -// base expression is a type expression (OTYPE). -func typecheckMethodExpr(n *Node) (res *Node) { - if enableTrace && trace { - defer tracePrint("typecheckMethodExpr", n)(&res) - } - - t := n.Left.Type - - // Compute the method set for t. - var ms *types.Fields - if t.IsInterface() { - ms = t.Fields() - } else { - mt := methtype(t) - if mt == nil { - yyerror("%v undefined (type %v has no method %v)", n, t, n.Sym) - n.Type = nil - return n - } - expandmeth(mt) - ms = mt.AllMethods() - - // The method expression T.m requires a wrapper when T - // is different from m's declared receiver type. We - // normally generate these wrappers while writing out - // runtime type descriptors, which is always done for - // types declared at package scope. However, we need - // to make sure to generate wrappers for anonymous - // receiver types too. - if mt.Sym == nil { - addsignat(t) - } - } - - s := n.Sym - m := lookdot1(n, s, t, ms, 0) - if m == nil { - if lookdot1(n, s, t, ms, 1) != nil { - yyerror("%v undefined (cannot refer to unexported method %v)", n, s) - } else if _, ambig := dotpath(s, t, nil, false); ambig { - yyerror("%v undefined (ambiguous selector)", n) // method or field - } else { - yyerror("%v undefined (type %v has no method %v)", n, t, s) - } - n.Type = nil - return n - } - - if !isMethodApplicable(t, m) { - yyerror("invalid method expression %v (needs pointer receiver: (*%v).%S)", n, t, s) - n.Type = nil - return n - } - - n.Op = ONAME - if n.Name == nil { - n.Name = new(Name) - } - n.Right = newname(n.Sym) - n.Sym = methodSym(t, n.Sym) - n.Type = methodfunc(m.Type, n.Left.Type) - n.Xoffset = 0 - n.SetClass(PFUNC) - // methodSym already marked n.Sym as a function. - - // Issue 25065. Make sure that we emit the symbol for a local method. - if Ctxt.Flag_dynlink && !inimport && (t.Sym == nil || t.Sym.Pkg == localpkg) { - makefuncsym(n.Sym) - } - - return n -} - -// isMethodApplicable reports whether method m can be called on a -// value of type t. This is necessary because we compute a single -// method set for both T and *T, but some *T methods are not -// applicable to T receivers. -func isMethodApplicable(t *types.Type, m *types.Field) bool { - return t.IsPtr() || !m.Type.Recv().Type.IsPtr() || isifacemethod(m.Type) || m.Embedded == 2 -} - -func derefall(t *types.Type) *types.Type { - for t != nil && t.IsPtr() { - t = t.Elem() - } - return t -} - -func lookdot(n *Node, t *types.Type, dostrcmp int) *types.Field { - s := n.Sym - - dowidth(t) - var f1 *types.Field - if t.IsStruct() || t.IsInterface() { - f1 = lookdot1(n, s, t, t.Fields(), dostrcmp) - } - - var f2 *types.Field - if n.Left.Type == t || n.Left.Type.Sym == nil { - mt := methtype(t) - if mt != nil { - f2 = lookdot1(n, s, mt, mt.Methods(), dostrcmp) - } - } - - if f1 != nil { - if dostrcmp > 1 || f1.Broke() { - // Already in the process of diagnosing an error. - return f1 - } - if f2 != nil { - yyerror("%v is both field and method", n.Sym) - } - if f1.Offset == BADWIDTH { - Fatalf("lookdot badwidth %v %p", f1, f1) - } - n.Xoffset = f1.Offset - n.Type = f1.Type - if t.IsInterface() { - if n.Left.Type.IsPtr() { - n.Left = nod(ODEREF, n.Left, nil) // implicitstar - n.Left.SetImplicit(true) - n.Left = typecheck(n.Left, ctxExpr) - } - - n.Op = ODOTINTER - } else { - n.SetOpt(f1) - } - - return f1 - } - - if f2 != nil { - if dostrcmp > 1 { - // Already in the process of diagnosing an error. - return f2 - } - tt := n.Left.Type - dowidth(tt) - rcvr := f2.Type.Recv().Type - if !types.Identical(rcvr, tt) { - if rcvr.IsPtr() && types.Identical(rcvr.Elem(), tt) { - checklvalue(n.Left, "call pointer method on") - n.Left = nod(OADDR, n.Left, nil) - n.Left.SetImplicit(true) - n.Left = typecheck(n.Left, ctxType|ctxExpr) - } else if tt.IsPtr() && (!rcvr.IsPtr() || rcvr.IsPtr() && rcvr.Elem().NotInHeap()) && types.Identical(tt.Elem(), rcvr) { - n.Left = nod(ODEREF, n.Left, nil) - n.Left.SetImplicit(true) - n.Left = typecheck(n.Left, ctxType|ctxExpr) - } else if tt.IsPtr() && tt.Elem().IsPtr() && types.Identical(derefall(tt), derefall(rcvr)) { - yyerror("calling method %v with receiver %L requires explicit dereference", n.Sym, n.Left) - for tt.IsPtr() { - // Stop one level early for method with pointer receiver. - if rcvr.IsPtr() && !tt.Elem().IsPtr() { - break - } - n.Left = nod(ODEREF, n.Left, nil) - n.Left.SetImplicit(true) - n.Left = typecheck(n.Left, ctxType|ctxExpr) - tt = tt.Elem() - } - } else { - Fatalf("method mismatch: %v for %v", rcvr, tt) - } - } - - pll := n - ll := n.Left - for ll.Left != nil && (ll.Op == ODOT || ll.Op == ODOTPTR || ll.Op == ODEREF) { - pll = ll - ll = ll.Left - } - if pll.Implicit() && ll.Type.IsPtr() && ll.Type.Sym != nil && asNode(ll.Type.Sym.Def) != nil && asNode(ll.Type.Sym.Def).Op == OTYPE { - // It is invalid to automatically dereference a named pointer type when selecting a method. - // Make n.Left == ll to clarify error message. - n.Left = ll - return nil - } - - n.Sym = methodSym(n.Left.Type, f2.Sym) - n.Xoffset = f2.Offset - n.Type = f2.Type - n.Op = ODOTMETH - - return f2 - } - - return nil -} - -func nokeys(l Nodes) bool { - for _, n := range l.Slice() { - if n.Op == OKEY || n.Op == OSTRUCTKEY { - return false - } - } - return true -} - -func hasddd(t *types.Type) bool { - for _, tl := range t.Fields().Slice() { - if tl.IsDDD() { - return true - } - } - - return false -} - -// typecheck assignment: type list = expression list -func typecheckaste(op Op, call *Node, isddd bool, tstruct *types.Type, nl Nodes, desc func() string) { - var t *types.Type - var i int - - lno := lineno - defer func() { lineno = lno }() - - if tstruct.Broke() { - return - } - - var n *Node - if nl.Len() == 1 { - n = nl.First() - } - - n1 := tstruct.NumFields() - n2 := nl.Len() - if !hasddd(tstruct) { - if n2 > n1 { - goto toomany - } - if n2 < n1 { - goto notenough - } - } else { - if !isddd { - if n2 < n1-1 { - goto notenough - } - } else { - if n2 > n1 { - goto toomany - } - if n2 < n1 { - goto notenough - } - } - } - - i = 0 - for _, tl := range tstruct.Fields().Slice() { - t = tl.Type - if tl.IsDDD() { - if isddd { - if i >= nl.Len() { - goto notenough - } - if nl.Len()-i > 1 { - goto toomany - } - n = nl.Index(i) - setlineno(n) - if n.Type != nil { - nl.SetIndex(i, assignconvfn(n, t, desc)) - } - return - } - - // TODO(mdempsky): Make into ... call with implicit slice. - for ; i < nl.Len(); i++ { - n = nl.Index(i) - setlineno(n) - if n.Type != nil { - nl.SetIndex(i, assignconvfn(n, t.Elem(), desc)) - } - } - return - } - - if i >= nl.Len() { - goto notenough - } - n = nl.Index(i) - setlineno(n) - if n.Type != nil { - nl.SetIndex(i, assignconvfn(n, t, desc)) - } - i++ - } - - if i < nl.Len() { - goto toomany - } - if isddd { - if call != nil { - yyerror("invalid use of ... in call to %v", call) - } else { - yyerror("invalid use of ... in %v", op) - } - } - return - -notenough: - if n == nil || (!n.Diag() && n.Type != nil) { - details := errorDetails(nl, tstruct, isddd) - if call != nil { - // call is the expression being called, not the overall call. - // Method expressions have the form T.M, and the compiler has - // rewritten those to ONAME nodes but left T in Left. - if call.isMethodExpression() { - yyerror("not enough arguments in call to method expression %v%s", call, details) - } else { - yyerror("not enough arguments in call to %v%s", call, details) - } - } else { - yyerror("not enough arguments to %v%s", op, details) - } - if n != nil { - n.SetDiag(true) - } - } - return - -toomany: - details := errorDetails(nl, tstruct, isddd) - if call != nil { - yyerror("too many arguments in call to %v%s", call, details) - } else { - yyerror("too many arguments to %v%s", op, details) - } -} - -func errorDetails(nl Nodes, tstruct *types.Type, isddd bool) string { - // If we don't know any type at a call site, let's suppress any return - // message signatures. See Issue https://golang.org/issues/19012. - if tstruct == nil { - return "" - } - // If any node has an unknown type, suppress it as well - for _, n := range nl.Slice() { - if n.Type == nil { - return "" - } - } - return fmt.Sprintf("\n\thave %s\n\twant %v", nl.sigerr(isddd), tstruct) -} - -// sigrepr is a type's representation to the outside world, -// in string representations of return signatures -// e.g in error messages about wrong arguments to return. -func sigrepr(t *types.Type, isddd bool) string { - switch t { - case types.UntypedString: - return "string" - case types.UntypedBool: - return "bool" - } - - if t.Etype == TIDEAL { - // "untyped number" is not commonly used - // outside of the compiler, so let's use "number". - // TODO(mdempsky): Revisit this. - return "number" - } - - // Turn []T... argument to ...T for clearer error message. - if isddd { - if !t.IsSlice() { - Fatalf("bad type for ... argument: %v", t) - } - return "..." + t.Elem().String() - } - return t.String() -} - -// sigerr returns the signature of the types at the call or return. -func (nl Nodes) sigerr(isddd bool) string { - if nl.Len() < 1 { - return "()" - } - - var typeStrings []string - for i, n := range nl.Slice() { - isdddArg := isddd && i == nl.Len()-1 - typeStrings = append(typeStrings, sigrepr(n.Type, isdddArg)) - } - - return fmt.Sprintf("(%s)", strings.Join(typeStrings, ", ")) -} - -// type check composite -func fielddup(name string, hash map[string]bool) { - if hash[name] { - yyerror("duplicate field name in struct literal: %s", name) - return - } - hash[name] = true -} - -// iscomptype reports whether type t is a composite literal type. -func iscomptype(t *types.Type) bool { - switch t.Etype { - case TARRAY, TSLICE, TSTRUCT, TMAP: - return true - default: - return false - } -} - -// pushtype adds elided type information for composite literals if -// appropriate, and returns the resulting expression. -func pushtype(n *Node, t *types.Type) *Node { - if n == nil || n.Op != OCOMPLIT || n.Right != nil { - return n - } - - switch { - case iscomptype(t): - // For T, return T{...}. - n.Right = typenod(t) - - case t.IsPtr() && iscomptype(t.Elem()): - // For *T, return &T{...}. - n.Right = typenod(t.Elem()) - - n = nodl(n.Pos, OADDR, n, nil) - n.SetImplicit(true) - } - - return n -} - -// The result of typecheckcomplit MUST be assigned back to n, e.g. -// n.Left = typecheckcomplit(n.Left) -func typecheckcomplit(n *Node) (res *Node) { - if enableTrace && trace { - defer tracePrint("typecheckcomplit", n)(&res) - } - - lno := lineno - defer func() { - lineno = lno - }() - - if n.Right == nil { - yyerrorl(n.Pos, "missing type in composite literal") - n.Type = nil - return n - } - - // Save original node (including n.Right) - n.Orig = n.copy() - - setlineno(n.Right) - - // Need to handle [...]T arrays specially. - if n.Right.Op == OTARRAY && n.Right.Left != nil && n.Right.Left.Op == ODDD { - n.Right.Right = typecheck(n.Right.Right, ctxType) - if n.Right.Right.Type == nil { - n.Type = nil - return n - } - elemType := n.Right.Right.Type - - length := typecheckarraylit(elemType, -1, n.List.Slice(), "array literal") - - n.Op = OARRAYLIT - n.Type = types.NewArray(elemType, length) - n.Right = nil - return n - } - - n.Right = typecheck(n.Right, ctxType) - t := n.Right.Type - if t == nil { - n.Type = nil - return n - } - n.Type = t - - switch t.Etype { - default: - yyerror("invalid composite literal type %v", t) - n.Type = nil - - case TARRAY: - typecheckarraylit(t.Elem(), t.NumElem(), n.List.Slice(), "array literal") - n.Op = OARRAYLIT - n.Right = nil - - case TSLICE: - length := typecheckarraylit(t.Elem(), -1, n.List.Slice(), "slice literal") - n.Op = OSLICELIT - n.Right = nodintconst(length) - - case TMAP: - var cs constSet - for i3, l := range n.List.Slice() { - setlineno(l) - if l.Op != OKEY { - n.List.SetIndex(i3, typecheck(l, ctxExpr)) - yyerror("missing key in map literal") - continue - } - - r := l.Left - r = pushtype(r, t.Key()) - r = typecheck(r, ctxExpr) - l.Left = assignconv(r, t.Key(), "map key") - cs.add(lineno, l.Left, "key", "map literal") - - r = l.Right - r = pushtype(r, t.Elem()) - r = typecheck(r, ctxExpr) - l.Right = assignconv(r, t.Elem(), "map value") - } - - n.Op = OMAPLIT - n.Right = nil - - case TSTRUCT: - // Need valid field offsets for Xoffset below. - dowidth(t) - - errored := false - if n.List.Len() != 0 && nokeys(n.List) { - // simple list of variables - ls := n.List.Slice() - for i, n1 := range ls { - setlineno(n1) - n1 = typecheck(n1, ctxExpr) - ls[i] = n1 - if i >= t.NumFields() { - if !errored { - yyerror("too many values in %v", n) - errored = true - } - continue - } - - f := t.Field(i) - s := f.Sym - if s != nil && !types.IsExported(s.Name) && s.Pkg != localpkg { - yyerror("implicit assignment of unexported field '%s' in %v literal", s.Name, t) - } - // No pushtype allowed here. Must name fields for that. - n1 = assignconv(n1, f.Type, "field value") - n1 = nodSym(OSTRUCTKEY, n1, f.Sym) - n1.Xoffset = f.Offset - ls[i] = n1 - } - if len(ls) < t.NumFields() { - yyerror("too few values in %v", n) - } - } else { - hash := make(map[string]bool) - - // keyed list - ls := n.List.Slice() - for i, l := range ls { - setlineno(l) - - if l.Op == OKEY { - key := l.Left - - l.Op = OSTRUCTKEY - l.Left = l.Right - l.Right = nil - - // An OXDOT uses the Sym field to hold - // the field to the right of the dot, - // so s will be non-nil, but an OXDOT - // is never a valid struct literal key. - if key.Sym == nil || key.Op == OXDOT || key.Sym.IsBlank() { - yyerror("invalid field name %v in struct initializer", key) - l.Left = typecheck(l.Left, ctxExpr) - continue - } - - // Sym might have resolved to name in other top-level - // package, because of import dot. Redirect to correct sym - // before we do the lookup. - s := key.Sym - if s.Pkg != localpkg && types.IsExported(s.Name) { - s1 := lookup(s.Name) - if s1.Origpkg == s.Pkg { - s = s1 - } - } - l.Sym = s - } - - if l.Op != OSTRUCTKEY { - if !errored { - yyerror("mixture of field:value and value initializers") - errored = true - } - ls[i] = typecheck(ls[i], ctxExpr) - continue - } - - f := lookdot1(nil, l.Sym, t, t.Fields(), 0) - if f == nil { - if ci := lookdot1(nil, l.Sym, t, t.Fields(), 2); ci != nil { // Case-insensitive lookup. - if visible(ci.Sym) { - yyerror("unknown field '%v' in struct literal of type %v (but does have %v)", l.Sym, t, ci.Sym) - } else if nonexported(l.Sym) && l.Sym.Name == ci.Sym.Name { // Ensure exactness before the suggestion. - yyerror("cannot refer to unexported field '%v' in struct literal of type %v", l.Sym, t) - } else { - yyerror("unknown field '%v' in struct literal of type %v", l.Sym, t) - } - continue - } - var f *types.Field - p, _ := dotpath(l.Sym, t, &f, true) - if p == nil || f.IsMethod() { - yyerror("unknown field '%v' in struct literal of type %v", l.Sym, t) - continue - } - // dotpath returns the parent embedded types in reverse order. - var ep []string - for ei := len(p) - 1; ei >= 0; ei-- { - ep = append(ep, p[ei].field.Sym.Name) - } - ep = append(ep, l.Sym.Name) - yyerror("cannot use promoted field %v in struct literal of type %v", strings.Join(ep, "."), t) - continue - } - fielddup(f.Sym.Name, hash) - l.Xoffset = f.Offset - - // No pushtype allowed here. Tried and rejected. - l.Left = typecheck(l.Left, ctxExpr) - l.Left = assignconv(l.Left, f.Type, "field value") - } - } - - n.Op = OSTRUCTLIT - n.Right = nil - } - - return n -} - -// typecheckarraylit type-checks a sequence of slice/array literal elements. -func typecheckarraylit(elemType *types.Type, bound int64, elts []*Node, ctx string) int64 { - // If there are key/value pairs, create a map to keep seen - // keys so we can check for duplicate indices. - var indices map[int64]bool - for _, elt := range elts { - if elt.Op == OKEY { - indices = make(map[int64]bool) - break - } - } - - var key, length int64 - for i, elt := range elts { - setlineno(elt) - vp := &elts[i] - if elt.Op == OKEY { - elt.Left = typecheck(elt.Left, ctxExpr) - key = indexconst(elt.Left) - if key < 0 { - if !elt.Left.Diag() { - if key == -2 { - yyerror("index too large") - } else { - yyerror("index must be non-negative integer constant") - } - elt.Left.SetDiag(true) - } - key = -(1 << 30) // stay negative for a while - } - vp = &elt.Right - } - - r := *vp - r = pushtype(r, elemType) - r = typecheck(r, ctxExpr) - *vp = assignconv(r, elemType, ctx) - - if key >= 0 { - if indices != nil { - if indices[key] { - yyerror("duplicate index in %s: %d", ctx, key) - } else { - indices[key] = true - } - } - - if bound >= 0 && key >= bound { - yyerror("array index %d out of bounds [0:%d]", key, bound) - bound = -1 - } - } - - key++ - if key > length { - length = key - } - } - - return length -} - -// visible reports whether sym is exported or locally defined. -func visible(sym *types.Sym) bool { - return sym != nil && (types.IsExported(sym.Name) || sym.Pkg == localpkg) -} - -// nonexported reports whether sym is an unexported field. -func nonexported(sym *types.Sym) bool { - return sym != nil && !types.IsExported(sym.Name) -} - -// lvalue etc -func islvalue(n *Node) bool { - switch n.Op { - case OINDEX: - if n.Left.Type != nil && n.Left.Type.IsArray() { - return islvalue(n.Left) - } - if n.Left.Type != nil && n.Left.Type.IsString() { - return false - } - fallthrough - case ODEREF, ODOTPTR, OCLOSUREVAR: - return true - - case ODOT: - return islvalue(n.Left) - - case ONAME: - if n.Class() == PFUNC { - return false - } - return true - } - - return false -} - -func checklvalue(n *Node, verb string) { - if !islvalue(n) { - yyerror("cannot %s %v", verb, n) - } -} - -func checkassign(stmt *Node, n *Node) { - // Variables declared in ORANGE are assigned on every iteration. - if n.Name == nil || n.Name.Defn != stmt || stmt.Op == ORANGE { - r := outervalue(n) - if r.Op == ONAME { - r.Name.SetAssigned(true) - if r.Name.IsClosureVar() { - r.Name.Defn.Name.SetAssigned(true) - } - } - } - - if islvalue(n) { - return - } - if n.Op == OINDEXMAP { - n.SetIndexMapLValue(true) - return - } - - // have already complained about n being invalid - if n.Type == nil { - return - } - - switch { - case n.Op == ODOT && n.Left.Op == OINDEXMAP: - yyerror("cannot assign to struct field %v in map", n) - case (n.Op == OINDEX && n.Left.Type.IsString()) || n.Op == OSLICESTR: - yyerror("cannot assign to %v (strings are immutable)", n) - case n.Op == OLITERAL && n.Sym != nil && n.isGoConst(): - yyerror("cannot assign to %v (declared const)", n) - default: - yyerror("cannot assign to %v", n) - } - n.Type = nil -} - -func checkassignlist(stmt *Node, l Nodes) { - for _, n := range l.Slice() { - checkassign(stmt, n) - } -} - -// samesafeexpr checks whether it is safe to reuse one of l and r -// instead of computing both. samesafeexpr assumes that l and r are -// used in the same statement or expression. In order for it to be -// safe to reuse l or r, they must: -// * be the same expression -// * not have side-effects (no function calls, no channel ops); -// however, panics are ok -// * not cause inappropriate aliasing; e.g. two string to []byte -// conversions, must result in two distinct slices -// -// The handling of OINDEXMAP is subtle. OINDEXMAP can occur both -// as an lvalue (map assignment) and an rvalue (map access). This is -// currently OK, since the only place samesafeexpr gets used on an -// lvalue expression is for OSLICE and OAPPEND optimizations, and it -// is correct in those settings. -func samesafeexpr(l *Node, r *Node) bool { - if l.Op != r.Op || !types.Identical(l.Type, r.Type) { - return false - } - - switch l.Op { - case ONAME, OCLOSUREVAR: - return l == r - - case ODOT, ODOTPTR: - return l.Sym != nil && r.Sym != nil && l.Sym == r.Sym && samesafeexpr(l.Left, r.Left) - - case ODEREF, OCONVNOP, - ONOT, OBITNOT, OPLUS, ONEG: - return samesafeexpr(l.Left, r.Left) - - case OCONV: - // Some conversions can't be reused, such as []byte(str). - // Allow only numeric-ish types. This is a bit conservative. - return issimple[l.Type.Etype] && samesafeexpr(l.Left, r.Left) - - case OINDEX, OINDEXMAP, - OADD, OSUB, OOR, OXOR, OMUL, OLSH, ORSH, OAND, OANDNOT, ODIV, OMOD: - return samesafeexpr(l.Left, r.Left) && samesafeexpr(l.Right, r.Right) - - case OLITERAL: - return eqval(l.Val(), r.Val()) - } - - return false -} - -// type check assignment. -// if this assignment is the definition of a var on the left side, -// fill in the var's type. -func typecheckas(n *Node) { - if enableTrace && trace { - defer tracePrint("typecheckas", n)(nil) - } - - // delicate little dance. - // the definition of n may refer to this assignment - // as its definition, in which case it will call typecheckas. - // in that case, do not call typecheck back, or it will cycle. - // if the variable has a type (ntype) then typechecking - // will not look at defn, so it is okay (and desirable, - // so that the conversion below happens). - n.Left = resolve(n.Left) - - if n.Left.Name == nil || n.Left.Name.Defn != n || n.Left.Name.Param.Ntype != nil { - n.Left = typecheck(n.Left, ctxExpr|ctxAssign) - } - - // Use ctxMultiOK so we can emit an "N variables but M values" error - // to be consistent with typecheckas2 (#26616). - n.Right = typecheck(n.Right, ctxExpr|ctxMultiOK) - checkassign(n, n.Left) - if n.Right != nil && n.Right.Type != nil { - if n.Right.Type.IsFuncArgStruct() { - yyerror("assignment mismatch: 1 variable but %v returns %d values", n.Right.Left, n.Right.Type.NumFields()) - // Multi-value RHS isn't actually valid for OAS; nil out - // to indicate failed typechecking. - n.Right.Type = nil - } else if n.Left.Type != nil { - n.Right = assignconv(n.Right, n.Left.Type, "assignment") - } - } - - if n.Left.Name != nil && n.Left.Name.Defn == n && n.Left.Name.Param.Ntype == nil { - n.Right = defaultlit(n.Right, nil) - n.Left.Type = n.Right.Type - } - - // second half of dance. - // now that right is done, typecheck the left - // just to get it over with. see dance above. - n.SetTypecheck(1) - - if n.Left.Typecheck() == 0 { - n.Left = typecheck(n.Left, ctxExpr|ctxAssign) - } - if !n.Left.isBlank() { - checkwidth(n.Left.Type) // ensure width is calculated for backend - } -} - -func checkassignto(src *types.Type, dst *Node) { - if op, why := assignop(src, dst.Type); op == OXXX { - yyerror("cannot assign %v to %L in multiple assignment%s", src, dst, why) - return - } -} - -func typecheckas2(n *Node) { - if enableTrace && trace { - defer tracePrint("typecheckas2", n)(nil) - } - - ls := n.List.Slice() - for i1, n1 := range ls { - // delicate little dance. - n1 = resolve(n1) - ls[i1] = n1 - - if n1.Name == nil || n1.Name.Defn != n || n1.Name.Param.Ntype != nil { - ls[i1] = typecheck(ls[i1], ctxExpr|ctxAssign) - } - } - - cl := n.List.Len() - cr := n.Rlist.Len() - if cl > 1 && cr == 1 { - n.Rlist.SetFirst(typecheck(n.Rlist.First(), ctxExpr|ctxMultiOK)) - } else { - typecheckslice(n.Rlist.Slice(), ctxExpr) - } - checkassignlist(n, n.List) - - var l *Node - var r *Node - if cl == cr { - // easy - ls := n.List.Slice() - rs := n.Rlist.Slice() - for il, nl := range ls { - nr := rs[il] - if nl.Type != nil && nr.Type != nil { - rs[il] = assignconv(nr, nl.Type, "assignment") - } - if nl.Name != nil && nl.Name.Defn == n && nl.Name.Param.Ntype == nil { - rs[il] = defaultlit(rs[il], nil) - nl.Type = rs[il].Type - } - } - - goto out - } - - l = n.List.First() - r = n.Rlist.First() - - // x,y,z = f() - if cr == 1 { - if r.Type == nil { - goto out - } - switch r.Op { - case OCALLMETH, OCALLINTER, OCALLFUNC: - if !r.Type.IsFuncArgStruct() { - break - } - cr = r.Type.NumFields() - if cr != cl { - goto mismatch - } - n.Op = OAS2FUNC - n.Right = r - n.Rlist.Set(nil) - for i, l := range n.List.Slice() { - f := r.Type.Field(i) - if f.Type != nil && l.Type != nil { - checkassignto(f.Type, l) - } - if l.Name != nil && l.Name.Defn == n && l.Name.Param.Ntype == nil { - l.Type = f.Type - } - } - goto out - } - } - - // x, ok = y - if cl == 2 && cr == 1 { - if r.Type == nil { - goto out - } - switch r.Op { - case OINDEXMAP, ORECV, ODOTTYPE: - switch r.Op { - case OINDEXMAP: - n.Op = OAS2MAPR - case ORECV: - n.Op = OAS2RECV - case ODOTTYPE: - n.Op = OAS2DOTTYPE - r.Op = ODOTTYPE2 - } - n.Right = r - n.Rlist.Set(nil) - if l.Type != nil { - checkassignto(r.Type, l) - } - if l.Name != nil && l.Name.Defn == n { - l.Type = r.Type - } - l := n.List.Second() - if l.Type != nil && !l.Type.IsBoolean() { - checkassignto(types.Types[TBOOL], l) - } - if l.Name != nil && l.Name.Defn == n && l.Name.Param.Ntype == nil { - l.Type = types.Types[TBOOL] - } - goto out - } - } - -mismatch: - switch r.Op { - default: - yyerror("assignment mismatch: %d variables but %d values", cl, cr) - case OCALLFUNC, OCALLMETH, OCALLINTER: - yyerror("assignment mismatch: %d variables but %v returns %d values", cl, r.Left, cr) - } - - // second half of dance -out: - n.SetTypecheck(1) - ls = n.List.Slice() - for i1, n1 := range ls { - if n1.Typecheck() == 0 { - ls[i1] = typecheck(ls[i1], ctxExpr|ctxAssign) - } - } -} - -// type check function definition -func typecheckfunc(n *Node) { - if enableTrace && trace { - defer tracePrint("typecheckfunc", n)(nil) - } - - for _, ln := range n.Func.Dcl { - if ln.Op == ONAME && (ln.Class() == PPARAM || ln.Class() == PPARAMOUT) { - ln.Name.Decldepth = 1 - } - } - - n.Func.Nname = typecheck(n.Func.Nname, ctxExpr|ctxAssign) - t := n.Func.Nname.Type - if t == nil { - return - } - n.Type = t - t.FuncType().Nname = asTypesNode(n.Func.Nname) - rcvr := t.Recv() - if rcvr != nil && n.Func.Shortname != nil { - m := addmethod(n.Func.Shortname, t, true, n.Func.Pragma&Nointerface != 0) - if m == nil { - return - } - - n.Func.Nname.Sym = methodSym(rcvr.Type, n.Func.Shortname) - declare(n.Func.Nname, PFUNC) - } - - if Ctxt.Flag_dynlink && !inimport && n.Func.Nname != nil { - makefuncsym(n.Func.Nname.Sym) - } -} - -// The result of stringtoruneslit MUST be assigned back to n, e.g. -// n.Left = stringtoruneslit(n.Left) -func stringtoruneslit(n *Node) *Node { - if n.Left.Op != OLITERAL || n.Left.Val().Ctype() != CTSTR { - Fatalf("stringtoarraylit %v", n) - } - - var l []*Node - i := 0 - for _, r := range n.Left.StringVal() { - l = append(l, nod(OKEY, nodintconst(int64(i)), nodintconst(int64(r)))) - i++ - } - - nn := nod(OCOMPLIT, nil, typenod(n.Type)) - nn.List.Set(l) - nn = typecheck(nn, ctxExpr) - return nn -} - -var mapqueue []*Node - -func checkMapKeys() { - for _, n := range mapqueue { - k := n.Type.MapType().Key - if !k.Broke() && !IsComparable(k) { - yyerrorl(n.Pos, "invalid map key type %v", k) - } - } - mapqueue = nil -} - -func setUnderlying(t, underlying *types.Type) { - if underlying.Etype == TFORW { - // This type isn't computed yet; when it is, update n. - underlying.ForwardType().Copyto = append(underlying.ForwardType().Copyto, t) - return - } - - n := asNode(t.Nod) - ft := t.ForwardType() - cache := t.Cache - - // TODO(mdempsky): Fix Type rekinding. - *t = *underlying - - // Restore unnecessarily clobbered attributes. - t.Nod = asTypesNode(n) - t.Sym = n.Sym - if n.Name != nil { - t.Vargen = n.Name.Vargen - } - t.Cache = cache - t.SetDeferwidth(false) - - // spec: "The declared type does not inherit any methods bound - // to the existing type, but the method set of an interface - // type [...] remains unchanged." - if !t.IsInterface() { - *t.Methods() = types.Fields{} - *t.AllMethods() = types.Fields{} - } - - // Propagate go:notinheap pragma from the Name to the Type. - if n.Name != nil && n.Name.Param != nil && n.Name.Param.Pragma()&NotInHeap != 0 { - t.SetNotInHeap(true) - } - - // Update types waiting on this type. - for _, w := range ft.Copyto { - setUnderlying(w, t) - } - - // Double-check use of type as embedded type. - if ft.Embedlineno.IsKnown() { - if t.IsPtr() || t.IsUnsafePtr() { - yyerrorl(ft.Embedlineno, "embedded type cannot be a pointer") - } - } -} - -func typecheckdeftype(n *Node) { - if enableTrace && trace { - defer tracePrint("typecheckdeftype", n)(nil) - } - - n.SetTypecheck(1) - n.Name.Param.Ntype = typecheck(n.Name.Param.Ntype, ctxType) - t := n.Name.Param.Ntype.Type - if t == nil { - n.SetDiag(true) - n.Type = nil - } else if n.Type == nil { - n.SetDiag(true) - } else { - // copy new type and clear fields - // that don't come along. - setUnderlying(n.Type, t) - } -} - -func typecheckdef(n *Node) { - if enableTrace && trace { - defer tracePrint("typecheckdef", n)(nil) - } - - lno := setlineno(n) - - if n.Op == ONONAME { - if !n.Diag() { - n.SetDiag(true) - - // Note: adderrorname looks for this string and - // adds context about the outer expression - yyerrorl(lineno, "undefined: %v", n.Sym) - } - lineno = lno - return - } - - if n.Walkdef() == 1 { - lineno = lno - return - } - - typecheckdefstack = append(typecheckdefstack, n) - if n.Walkdef() == 2 { - flusherrors() - fmt.Printf("typecheckdef loop:") - for i := len(typecheckdefstack) - 1; i >= 0; i-- { - n := typecheckdefstack[i] - fmt.Printf(" %v", n.Sym) - } - fmt.Printf("\n") - Fatalf("typecheckdef loop") - } - - n.SetWalkdef(2) - - if n.Type != nil || n.Sym == nil { // builtin or no name - goto ret - } - - switch n.Op { - default: - Fatalf("typecheckdef %v", n.Op) - - case OLITERAL: - if n.Name.Param.Ntype != nil { - n.Name.Param.Ntype = typecheck(n.Name.Param.Ntype, ctxType) - n.Type = n.Name.Param.Ntype.Type - n.Name.Param.Ntype = nil - if n.Type == nil { - n.SetDiag(true) - goto ret - } - } - - e := n.Name.Defn - n.Name.Defn = nil - if e == nil { - Dump("typecheckdef nil defn", n) - yyerrorl(n.Pos, "xxx") - } - - e = typecheck(e, ctxExpr) - if e.Type == nil { - goto ret - } - if !e.isGoConst() { - if !e.Diag() { - if Isconst(e, CTNIL) { - yyerrorl(n.Pos, "const initializer cannot be nil") - } else { - yyerrorl(n.Pos, "const initializer %v is not a constant", e) - } - e.SetDiag(true) - } - goto ret - } - - t := n.Type - if t != nil { - if !okforconst[t.Etype] { - yyerrorl(n.Pos, "invalid constant type %v", t) - goto ret - } - - if !e.Type.IsUntyped() && !types.Identical(t, e.Type) { - yyerrorl(n.Pos, "cannot use %L as type %v in const initializer", e, t) - goto ret - } - - e = convlit(e, t) - } - - n.SetVal(e.Val()) - n.Type = e.Type - - case ONAME: - if n.Name.Param.Ntype != nil { - n.Name.Param.Ntype = typecheck(n.Name.Param.Ntype, ctxType) - n.Type = n.Name.Param.Ntype.Type - if n.Type == nil { - n.SetDiag(true) - goto ret - } - } - - if n.Type != nil { - break - } - if n.Name.Defn == nil { - if n.SubOp() != 0 { // like OPRINTN - break - } - if nsavederrors+nerrors > 0 { - // Can have undefined variables in x := foo - // that make x have an n.name.Defn == nil. - // If there are other errors anyway, don't - // bother adding to the noise. - break - } - - Fatalf("var without type, init: %v", n.Sym) - } - - if n.Name.Defn.Op == ONAME { - n.Name.Defn = typecheck(n.Name.Defn, ctxExpr) - n.Type = n.Name.Defn.Type - break - } - - n.Name.Defn = typecheck(n.Name.Defn, ctxStmt) // fills in n.Type - - case OTYPE: - if p := n.Name.Param; p.Alias() { - // Type alias declaration: Simply use the rhs type - no need - // to create a new type. - // If we have a syntax error, p.Ntype may be nil. - if p.Ntype != nil { - p.Ntype = typecheck(p.Ntype, ctxType) - n.Type = p.Ntype.Type - if n.Type == nil { - n.SetDiag(true) - goto ret - } - // For package-level type aliases, set n.Sym.Def so we can identify - // it as a type alias during export. See also #31959. - if n.Name.Curfn == nil { - n.Sym.Def = asTypesNode(p.Ntype) - } - } - break - } - - // regular type declaration - defercheckwidth() - n.SetWalkdef(1) - setTypeNode(n, types.New(TFORW)) - n.Type.Sym = n.Sym - nerrors0 := nerrors - typecheckdeftype(n) - if n.Type.Etype == TFORW && nerrors > nerrors0 { - // Something went wrong during type-checking, - // but it was reported. Silence future errors. - n.Type.SetBroke(true) - } - resumecheckwidth() - } - -ret: - if n.Op != OLITERAL && n.Type != nil && n.Type.IsUntyped() { - Fatalf("got %v for %v", n.Type, n) - } - last := len(typecheckdefstack) - 1 - if typecheckdefstack[last] != n { - Fatalf("typecheckdefstack mismatch") - } - typecheckdefstack[last] = nil - typecheckdefstack = typecheckdefstack[:last] - - lineno = lno - n.SetWalkdef(1) -} - -func checkmake(t *types.Type, arg string, np **Node) bool { - n := *np - if !n.Type.IsInteger() && n.Type.Etype != TIDEAL { - yyerror("non-integer %s argument in make(%v) - %v", arg, t, n.Type) - return false - } - - // Do range checks for constants before defaultlit - // to avoid redundant "constant NNN overflows int" errors. - switch consttype(n) { - case CTINT, CTRUNE, CTFLT, CTCPLX: - v := toint(n.Val()).U.(*Mpint) - if v.CmpInt64(0) < 0 { - yyerror("negative %s argument in make(%v)", arg, t) - return false - } - if v.Cmp(maxintval[TINT]) > 0 { - yyerror("%s argument too large in make(%v)", arg, t) - return false - } - } - - // defaultlit is necessary for non-constants too: n might be 1.1< 0 { - return - } - switch n.Op { - case OIF: - if !Isconst(n.Left, CTBOOL) || n.Nbody.Len() > 0 || n.Rlist.Len() > 0 { - return - } - case OFOR: - if !Isconst(n.Left, CTBOOL) || n.Left.BoolVal() { - return - } - default: - return - } - } - - fn.Nbody.Set([]*Node{nod(OEMPTY, nil, nil)}) -} - -func deadcodeslice(nn Nodes) { - var lastLabel = -1 - for i, n := range nn.Slice() { - if n != nil && n.Op == OLABEL { - lastLabel = i - } - } - for i, n := range nn.Slice() { - // Cut is set to true when all nodes after i'th position - // should be removed. - // In other words, it marks whole slice "tail" as dead. - cut := false - if n == nil { - continue - } - if n.Op == OIF { - n.Left = deadcodeexpr(n.Left) - if Isconst(n.Left, CTBOOL) { - var body Nodes - if n.Left.BoolVal() { - n.Rlist = Nodes{} - body = n.Nbody - } else { - n.Nbody = Nodes{} - body = n.Rlist - } - // If "then" or "else" branch ends with panic or return statement, - // it is safe to remove all statements after this node. - // isterminating is not used to avoid goto-related complications. - // We must be careful not to deadcode-remove labels, as they - // might be the target of a goto. See issue 28616. - if body := body.Slice(); len(body) != 0 { - switch body[(len(body) - 1)].Op { - case ORETURN, ORETJMP, OPANIC: - if i > lastLabel { - cut = true - } - } - } - } - } - - deadcodeslice(n.Ninit) - deadcodeslice(n.Nbody) - deadcodeslice(n.List) - deadcodeslice(n.Rlist) - if cut { - *nn.slice = nn.Slice()[:i+1] - break - } - } -} - -func deadcodeexpr(n *Node) *Node { - // Perform dead-code elimination on short-circuited boolean - // expressions involving constants with the intent of - // producing a constant 'if' condition. - switch n.Op { - case OANDAND: - n.Left = deadcodeexpr(n.Left) - n.Right = deadcodeexpr(n.Right) - if Isconst(n.Left, CTBOOL) { - if n.Left.BoolVal() { - return n.Right // true && x => x - } else { - return n.Left // false && x => false - } - } - case OOROR: - n.Left = deadcodeexpr(n.Left) - n.Right = deadcodeexpr(n.Right) - if Isconst(n.Left, CTBOOL) { - if n.Left.BoolVal() { - return n.Left // true || x => true - } else { - return n.Right // false || x => x - } - } - } - return n -} - -// setTypeNode sets n to an OTYPE node representing t. -func setTypeNode(n *Node, t *types.Type) { - n.Op = OTYPE - n.Type = t - n.Type.Nod = asTypesNode(n) -} - -// getIotaValue returns the current value for "iota", -// or -1 if not within a ConstSpec. -func getIotaValue() int64 { - if i := len(typecheckdefstack); i > 0 { - if x := typecheckdefstack[i-1]; x.Op == OLITERAL { - return x.Iota() - } - } - - if Curfn != nil && Curfn.Iota() >= 0 { - return Curfn.Iota() - } - - return -1 -} - -// curpkg returns the current package, based on Curfn. -func curpkg() *types.Pkg { - fn := Curfn - if fn == nil { - // Initialization expressions for package-scope variables. - return localpkg - } - - // TODO(mdempsky): Standardize on either ODCLFUNC or ONAME for - // Curfn, rather than mixing them. - if fn.Op == ODCLFUNC { - fn = fn.Func.Nname - } - - return fnpkg(fn) -} diff --git a/src/cmd/compile/internal/gc/types.go b/src/cmd/compile/internal/gc/types.go deleted file mode 100644 index 748f8458bdb51ce372b894d260248578a97f706d..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/types.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" -) - -// convenience constants -const ( - Txxx = types.Txxx - - TINT8 = types.TINT8 - TUINT8 = types.TUINT8 - TINT16 = types.TINT16 - TUINT16 = types.TUINT16 - TINT32 = types.TINT32 - TUINT32 = types.TUINT32 - TINT64 = types.TINT64 - TUINT64 = types.TUINT64 - TINT = types.TINT - TUINT = types.TUINT - TUINTPTR = types.TUINTPTR - - TCOMPLEX64 = types.TCOMPLEX64 - TCOMPLEX128 = types.TCOMPLEX128 - - TFLOAT32 = types.TFLOAT32 - TFLOAT64 = types.TFLOAT64 - - TBOOL = types.TBOOL - - TPTR = types.TPTR - TFUNC = types.TFUNC - TSLICE = types.TSLICE - TARRAY = types.TARRAY - TSTRUCT = types.TSTRUCT - TCHAN = types.TCHAN - TMAP = types.TMAP - TINTER = types.TINTER - TFORW = types.TFORW - TANY = types.TANY - TSTRING = types.TSTRING - TUNSAFEPTR = types.TUNSAFEPTR - - // pseudo-types for literals - TIDEAL = types.TIDEAL - TNIL = types.TNIL - TBLANK = types.TBLANK - - // pseudo-types for frame layout - TFUNCARGS = types.TFUNCARGS - TCHANARGS = types.TCHANARGS - - NTYPE = types.NTYPE -) diff --git a/src/cmd/compile/internal/gc/types_acc.go b/src/cmd/compile/internal/gc/types_acc.go deleted file mode 100644 index 7240f726f6296ee33d07d4c6b91107c0d48220de..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/types_acc.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file implements convertions between *types.Node and *Node. -// TODO(gri) try to eliminate these soon - -package gc - -import ( - "cmd/compile/internal/types" - "unsafe" -) - -func asNode(n *types.Node) *Node { return (*Node)(unsafe.Pointer(n)) } -func asTypesNode(n *Node) *types.Node { return (*types.Node)(unsafe.Pointer(n)) } diff --git a/src/cmd/compile/internal/gc/universe.go b/src/cmd/compile/internal/gc/universe.go deleted file mode 100644 index ff8cabd8e38c215a558066fb68b71455d845c783..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/universe.go +++ /dev/null @@ -1,453 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// TODO(gri) This file should probably become part of package types. - -package gc - -import "cmd/compile/internal/types" - -// builtinpkg is a fake package that declares the universe block. -var builtinpkg *types.Pkg - -var basicTypes = [...]struct { - name string - etype types.EType -}{ - {"int8", TINT8}, - {"int16", TINT16}, - {"int32", TINT32}, - {"int64", TINT64}, - {"uint8", TUINT8}, - {"uint16", TUINT16}, - {"uint32", TUINT32}, - {"uint64", TUINT64}, - {"float32", TFLOAT32}, - {"float64", TFLOAT64}, - {"complex64", TCOMPLEX64}, - {"complex128", TCOMPLEX128}, - {"bool", TBOOL}, - {"string", TSTRING}, -} - -var typedefs = [...]struct { - name string - etype types.EType - sameas32 types.EType - sameas64 types.EType -}{ - {"int", TINT, TINT32, TINT64}, - {"uint", TUINT, TUINT32, TUINT64}, - {"uintptr", TUINTPTR, TUINT32, TUINT64}, -} - -var builtinFuncs = [...]struct { - name string - op Op -}{ - {"append", OAPPEND}, - {"cap", OCAP}, - {"close", OCLOSE}, - {"complex", OCOMPLEX}, - {"copy", OCOPY}, - {"delete", ODELETE}, - {"imag", OIMAG}, - {"len", OLEN}, - {"make", OMAKE}, - {"new", ONEW}, - {"panic", OPANIC}, - {"print", OPRINT}, - {"println", OPRINTN}, - {"real", OREAL}, - {"recover", ORECOVER}, -} - -// isBuiltinFuncName reports whether name matches a builtin function -// name. -func isBuiltinFuncName(name string) bool { - for _, fn := range &builtinFuncs { - if fn.name == name { - return true - } - } - return false -} - -var unsafeFuncs = [...]struct { - name string - op Op -}{ - {"Alignof", OALIGNOF}, - {"Offsetof", OOFFSETOF}, - {"Sizeof", OSIZEOF}, -} - -// initUniverse initializes the universe block. -func initUniverse() { - lexinit() - typeinit() - lexinit1() -} - -// lexinit initializes known symbols and the basic types. -func lexinit() { - for _, s := range &basicTypes { - etype := s.etype - if int(etype) >= len(types.Types) { - Fatalf("lexinit: %s bad etype", s.name) - } - s2 := builtinpkg.Lookup(s.name) - t := types.Types[etype] - if t == nil { - t = types.New(etype) - t.Sym = s2 - if etype != TANY && etype != TSTRING { - dowidth(t) - } - types.Types[etype] = t - } - s2.Def = asTypesNode(typenod(t)) - asNode(s2.Def).Name = new(Name) - } - - for _, s := range &builtinFuncs { - s2 := builtinpkg.Lookup(s.name) - s2.Def = asTypesNode(newname(s2)) - asNode(s2.Def).SetSubOp(s.op) - } - - for _, s := range &unsafeFuncs { - s2 := unsafepkg.Lookup(s.name) - s2.Def = asTypesNode(newname(s2)) - asNode(s2.Def).SetSubOp(s.op) - } - - types.UntypedString = types.New(TSTRING) - types.UntypedBool = types.New(TBOOL) - types.Types[TANY] = types.New(TANY) - - s := builtinpkg.Lookup("true") - s.Def = asTypesNode(nodbool(true)) - asNode(s.Def).Sym = lookup("true") - asNode(s.Def).Name = new(Name) - asNode(s.Def).Type = types.UntypedBool - - s = builtinpkg.Lookup("false") - s.Def = asTypesNode(nodbool(false)) - asNode(s.Def).Sym = lookup("false") - asNode(s.Def).Name = new(Name) - asNode(s.Def).Type = types.UntypedBool - - s = lookup("_") - s.Block = -100 - s.Def = asTypesNode(newname(s)) - types.Types[TBLANK] = types.New(TBLANK) - asNode(s.Def).Type = types.Types[TBLANK] - nblank = asNode(s.Def) - - s = builtinpkg.Lookup("_") - s.Block = -100 - s.Def = asTypesNode(newname(s)) - types.Types[TBLANK] = types.New(TBLANK) - asNode(s.Def).Type = types.Types[TBLANK] - - types.Types[TNIL] = types.New(TNIL) - s = builtinpkg.Lookup("nil") - var v Val - v.U = new(NilVal) - s.Def = asTypesNode(nodlit(v)) - asNode(s.Def).Sym = s - asNode(s.Def).Name = new(Name) - - s = builtinpkg.Lookup("iota") - s.Def = asTypesNode(nod(OIOTA, nil, nil)) - asNode(s.Def).Sym = s - asNode(s.Def).Name = new(Name) -} - -func typeinit() { - if Widthptr == 0 { - Fatalf("typeinit before betypeinit") - } - - for et := types.EType(0); et < NTYPE; et++ { - simtype[et] = et - } - - types.Types[TPTR] = types.New(TPTR) - dowidth(types.Types[TPTR]) - - t := types.New(TUNSAFEPTR) - types.Types[TUNSAFEPTR] = t - t.Sym = unsafepkg.Lookup("Pointer") - t.Sym.Def = asTypesNode(typenod(t)) - asNode(t.Sym.Def).Name = new(Name) - dowidth(types.Types[TUNSAFEPTR]) - - for et := TINT8; et <= TUINT64; et++ { - isInt[et] = true - } - isInt[TINT] = true - isInt[TUINT] = true - isInt[TUINTPTR] = true - - isFloat[TFLOAT32] = true - isFloat[TFLOAT64] = true - - isComplex[TCOMPLEX64] = true - isComplex[TCOMPLEX128] = true - - // initialize okfor - for et := types.EType(0); et < NTYPE; et++ { - if isInt[et] || et == TIDEAL { - okforeq[et] = true - okforcmp[et] = true - okforarith[et] = true - okforadd[et] = true - okforand[et] = true - okforconst[et] = true - issimple[et] = true - minintval[et] = new(Mpint) - maxintval[et] = new(Mpint) - } - - if isFloat[et] { - okforeq[et] = true - okforcmp[et] = true - okforadd[et] = true - okforarith[et] = true - okforconst[et] = true - issimple[et] = true - minfltval[et] = newMpflt() - maxfltval[et] = newMpflt() - } - - if isComplex[et] { - okforeq[et] = true - okforadd[et] = true - okforarith[et] = true - okforconst[et] = true - issimple[et] = true - } - } - - issimple[TBOOL] = true - - okforadd[TSTRING] = true - - okforbool[TBOOL] = true - - okforcap[TARRAY] = true - okforcap[TCHAN] = true - okforcap[TSLICE] = true - - okforconst[TBOOL] = true - okforconst[TSTRING] = true - - okforlen[TARRAY] = true - okforlen[TCHAN] = true - okforlen[TMAP] = true - okforlen[TSLICE] = true - okforlen[TSTRING] = true - - okforeq[TPTR] = true - okforeq[TUNSAFEPTR] = true - okforeq[TINTER] = true - okforeq[TCHAN] = true - okforeq[TSTRING] = true - okforeq[TBOOL] = true - okforeq[TMAP] = true // nil only; refined in typecheck - okforeq[TFUNC] = true // nil only; refined in typecheck - okforeq[TSLICE] = true // nil only; refined in typecheck - okforeq[TARRAY] = true // only if element type is comparable; refined in typecheck - okforeq[TSTRUCT] = true // only if all struct fields are comparable; refined in typecheck - - okforcmp[TSTRING] = true - - var i int - for i = 0; i < len(okfor); i++ { - okfor[i] = okfornone[:] - } - - // binary - okfor[OADD] = okforadd[:] - okfor[OAND] = okforand[:] - okfor[OANDAND] = okforbool[:] - okfor[OANDNOT] = okforand[:] - okfor[ODIV] = okforarith[:] - okfor[OEQ] = okforeq[:] - okfor[OGE] = okforcmp[:] - okfor[OGT] = okforcmp[:] - okfor[OLE] = okforcmp[:] - okfor[OLT] = okforcmp[:] - okfor[OMOD] = okforand[:] - okfor[OMUL] = okforarith[:] - okfor[ONE] = okforeq[:] - okfor[OOR] = okforand[:] - okfor[OOROR] = okforbool[:] - okfor[OSUB] = okforarith[:] - okfor[OXOR] = okforand[:] - okfor[OLSH] = okforand[:] - okfor[ORSH] = okforand[:] - - // unary - okfor[OBITNOT] = okforand[:] - okfor[ONEG] = okforarith[:] - okfor[ONOT] = okforbool[:] - okfor[OPLUS] = okforarith[:] - - // special - okfor[OCAP] = okforcap[:] - okfor[OLEN] = okforlen[:] - - // comparison - iscmp[OLT] = true - iscmp[OGT] = true - iscmp[OGE] = true - iscmp[OLE] = true - iscmp[OEQ] = true - iscmp[ONE] = true - - maxintval[TINT8].SetString("0x7f") - minintval[TINT8].SetString("-0x80") - maxintval[TINT16].SetString("0x7fff") - minintval[TINT16].SetString("-0x8000") - maxintval[TINT32].SetString("0x7fffffff") - minintval[TINT32].SetString("-0x80000000") - maxintval[TINT64].SetString("0x7fffffffffffffff") - minintval[TINT64].SetString("-0x8000000000000000") - - maxintval[TUINT8].SetString("0xff") - maxintval[TUINT16].SetString("0xffff") - maxintval[TUINT32].SetString("0xffffffff") - maxintval[TUINT64].SetString("0xffffffffffffffff") - - // f is valid float if min < f < max. (min and max are not themselves valid.) - maxfltval[TFLOAT32].SetString("33554431p103") // 2^24-1 p (127-23) + 1/2 ulp - minfltval[TFLOAT32].SetString("-33554431p103") - maxfltval[TFLOAT64].SetString("18014398509481983p970") // 2^53-1 p (1023-52) + 1/2 ulp - minfltval[TFLOAT64].SetString("-18014398509481983p970") - - maxfltval[TCOMPLEX64] = maxfltval[TFLOAT32] - minfltval[TCOMPLEX64] = minfltval[TFLOAT32] - maxfltval[TCOMPLEX128] = maxfltval[TFLOAT64] - minfltval[TCOMPLEX128] = minfltval[TFLOAT64] - - types.Types[TINTER] = types.New(TINTER) // empty interface - - // simple aliases - simtype[TMAP] = TPTR - simtype[TCHAN] = TPTR - simtype[TFUNC] = TPTR - simtype[TUNSAFEPTR] = TPTR - - slicePtrOffset = 0 - sliceLenOffset = Rnd(slicePtrOffset+int64(Widthptr), int64(Widthptr)) - sliceCapOffset = Rnd(sliceLenOffset+int64(Widthptr), int64(Widthptr)) - sizeofSlice = Rnd(sliceCapOffset+int64(Widthptr), int64(Widthptr)) - - // string is same as slice wo the cap - sizeofString = Rnd(sliceLenOffset+int64(Widthptr), int64(Widthptr)) - - dowidth(types.Types[TSTRING]) - dowidth(types.UntypedString) -} - -func makeErrorInterface() *types.Type { - field := types.NewField() - field.Type = types.Types[TSTRING] - f := functypefield(fakeRecvField(), nil, []*types.Field{field}) - - field = types.NewField() - field.Sym = lookup("Error") - field.Type = f - - t := types.New(TINTER) - t.SetInterface([]*types.Field{field}) - return t -} - -func lexinit1() { - // error type - s := builtinpkg.Lookup("error") - types.Errortype = makeErrorInterface() - types.Errortype.Sym = s - types.Errortype.Orig = makeErrorInterface() - s.Def = asTypesNode(typenod(types.Errortype)) - dowidth(types.Errortype) - - // We create separate byte and rune types for better error messages - // rather than just creating type alias *types.Sym's for the uint8 and - // int32 types. Hence, (bytetype|runtype).Sym.isAlias() is false. - // TODO(gri) Should we get rid of this special case (at the cost - // of less informative error messages involving bytes and runes)? - // (Alternatively, we could introduce an OTALIAS node representing - // type aliases, albeit at the cost of having to deal with it everywhere). - - // byte alias - s = builtinpkg.Lookup("byte") - types.Bytetype = types.New(TUINT8) - types.Bytetype.Sym = s - s.Def = asTypesNode(typenod(types.Bytetype)) - asNode(s.Def).Name = new(Name) - dowidth(types.Bytetype) - - // rune alias - s = builtinpkg.Lookup("rune") - types.Runetype = types.New(TINT32) - types.Runetype.Sym = s - s.Def = asTypesNode(typenod(types.Runetype)) - asNode(s.Def).Name = new(Name) - dowidth(types.Runetype) - - // backend-dependent builtin types (e.g. int). - for _, s := range &typedefs { - s1 := builtinpkg.Lookup(s.name) - - sameas := s.sameas32 - if Widthptr == 8 { - sameas = s.sameas64 - } - - simtype[s.etype] = sameas - minfltval[s.etype] = minfltval[sameas] - maxfltval[s.etype] = maxfltval[sameas] - minintval[s.etype] = minintval[sameas] - maxintval[s.etype] = maxintval[sameas] - - t := types.New(s.etype) - t.Sym = s1 - types.Types[s.etype] = t - s1.Def = asTypesNode(typenod(t)) - asNode(s1.Def).Name = new(Name) - s1.Origpkg = builtinpkg - - dowidth(t) - } -} - -// finishUniverse makes the universe block visible within the current package. -func finishUniverse() { - // Operationally, this is similar to a dot import of builtinpkg, except - // that we silently skip symbols that are already declared in the - // package block rather than emitting a redeclared symbol error. - - for _, s := range builtinpkg.Syms { - if s.Def == nil { - continue - } - s1 := lookup(s.Name) - if s1.Def != nil { - continue - } - - s1.Def = s.Def - s1.Block = s.Block - } - - nodfp = newname(lookup(".fp")) - nodfp.Type = types.Types[TINT32] - nodfp.SetClass(PPARAM) - nodfp.Name.SetUsed(true) -} diff --git a/src/cmd/compile/internal/gc/unsafe.go b/src/cmd/compile/internal/gc/unsafe.go deleted file mode 100644 index 2233961561230abe5b59188df68ab6267f3ed28e..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/unsafe.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -// evalunsafe evaluates a package unsafe operation and returns the result. -func evalunsafe(n *Node) int64 { - switch n.Op { - case OALIGNOF, OSIZEOF: - n.Left = typecheck(n.Left, ctxExpr) - n.Left = defaultlit(n.Left, nil) - tr := n.Left.Type - if tr == nil { - return 0 - } - dowidth(tr) - if n.Op == OALIGNOF { - return int64(tr.Align) - } - return tr.Width - - case OOFFSETOF: - // must be a selector. - if n.Left.Op != OXDOT { - yyerror("invalid expression %v", n) - return 0 - } - - // Remember base of selector to find it back after dot insertion. - // Since r->left may be mutated by typechecking, check it explicitly - // first to track it correctly. - n.Left.Left = typecheck(n.Left.Left, ctxExpr) - base := n.Left.Left - - n.Left = typecheck(n.Left, ctxExpr) - if n.Left.Type == nil { - return 0 - } - switch n.Left.Op { - case ODOT, ODOTPTR: - break - case OCALLPART: - yyerror("invalid expression %v: argument is a method value", n) - return 0 - default: - yyerror("invalid expression %v", n) - return 0 - } - - // Sum offsets for dots until we reach base. - var v int64 - for r := n.Left; r != base; r = r.Left { - switch r.Op { - case ODOTPTR: - // For Offsetof(s.f), s may itself be a pointer, - // but accessing f must not otherwise involve - // indirection via embedded pointer types. - if r.Left != base { - yyerror("invalid expression %v: selector implies indirection of embedded %v", n, r.Left) - return 0 - } - fallthrough - case ODOT: - v += r.Xoffset - default: - Dump("unsafenmagic", n.Left) - Fatalf("impossible %#v node after dot insertion", r.Op) - } - } - return v - } - - Fatalf("unexpected op %v", n.Op) - return 0 -} diff --git a/src/cmd/compile/internal/gc/util.go b/src/cmd/compile/internal/gc/util.go index 58be2f82530a90cb694a7820ade234304a065ea4..4baddbc029a8d2dc587650a1b99243bc20b9b619 100644 --- a/src/cmd/compile/internal/gc/util.go +++ b/src/cmd/compile/internal/gc/util.go @@ -8,59 +8,35 @@ import ( "os" "runtime" "runtime/pprof" -) - -// Line returns n's position as a string. If n has been inlined, -// it uses the outermost position where n has been inlined. -func (n *Node) Line() string { - return linestr(n.Pos) -} -var atExitFuncs []func() - -func atExit(f func()) { - atExitFuncs = append(atExitFuncs, f) -} - -func Exit(code int) { - for i := len(atExitFuncs) - 1; i >= 0; i-- { - f := atExitFuncs[i] - atExitFuncs = atExitFuncs[:i] - f() - } - os.Exit(code) -} + "cmd/compile/internal/base" +) var ( - blockprofile string - cpuprofile string - memprofile string memprofilerate int64 - traceprofile string traceHandler func(string) - mutexprofile string ) func startProfile() { - if cpuprofile != "" { - f, err := os.Create(cpuprofile) + if base.Flag.CPUProfile != "" { + f, err := os.Create(base.Flag.CPUProfile) if err != nil { - Fatalf("%v", err) + base.Fatalf("%v", err) } if err := pprof.StartCPUProfile(f); err != nil { - Fatalf("%v", err) + base.Fatalf("%v", err) } - atExit(pprof.StopCPUProfile) + base.AtExit(pprof.StopCPUProfile) } - if memprofile != "" { + if base.Flag.MemProfile != "" { if memprofilerate != 0 { runtime.MemProfileRate = int(memprofilerate) } - f, err := os.Create(memprofile) + f, err := os.Create(base.Flag.MemProfile) if err != nil { - Fatalf("%v", err) + base.Fatalf("%v", err) } - atExit(func() { + base.AtExit(func() { // Profile all outstanding allocations. runtime.GC() // compilebench parses the memory profile to extract memstats, @@ -68,36 +44,36 @@ func startProfile() { // See golang.org/issue/18641 and runtime/pprof/pprof.go:writeHeap. const writeLegacyFormat = 1 if err := pprof.Lookup("heap").WriteTo(f, writeLegacyFormat); err != nil { - Fatalf("%v", err) + base.Fatalf("%v", err) } }) } else { // Not doing memory profiling; disable it entirely. runtime.MemProfileRate = 0 } - if blockprofile != "" { - f, err := os.Create(blockprofile) + if base.Flag.BlockProfile != "" { + f, err := os.Create(base.Flag.BlockProfile) if err != nil { - Fatalf("%v", err) + base.Fatalf("%v", err) } runtime.SetBlockProfileRate(1) - atExit(func() { + base.AtExit(func() { pprof.Lookup("block").WriteTo(f, 0) f.Close() }) } - if mutexprofile != "" { - f, err := os.Create(mutexprofile) + if base.Flag.MutexProfile != "" { + f, err := os.Create(base.Flag.MutexProfile) if err != nil { - Fatalf("%v", err) + base.Fatalf("%v", err) } startMutexProfiling() - atExit(func() { + base.AtExit(func() { pprof.Lookup("mutex").WriteTo(f, 0) f.Close() }) } - if traceprofile != "" && traceHandler != nil { - traceHandler(traceprofile) + if base.Flag.TraceProfile != "" && traceHandler != nil { + traceHandler(base.Flag.TraceProfile) } } diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go deleted file mode 100644 index 02a7269ff83ed4538818108fd04f9cf10c00a770..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/walk.go +++ /dev/null @@ -1,4125 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/types" - "cmd/internal/obj" - "cmd/internal/objabi" - "cmd/internal/sys" - "encoding/binary" - "fmt" - "strings" -) - -// The constant is known to runtime. -const tmpstringbufsize = 32 -const zeroValSize = 1024 // must match value of runtime/map.go:maxZero - -func walk(fn *Node) { - Curfn = fn - - if Debug.W != 0 { - s := fmt.Sprintf("\nbefore walk %v", Curfn.Func.Nname.Sym) - dumplist(s, Curfn.Nbody) - } - - lno := lineno - - // Final typecheck for any unused variables. - for i, ln := range fn.Func.Dcl { - if ln.Op == ONAME && (ln.Class() == PAUTO || ln.Class() == PAUTOHEAP) { - ln = typecheck(ln, ctxExpr|ctxAssign) - fn.Func.Dcl[i] = ln - } - } - - // Propagate the used flag for typeswitch variables up to the NONAME in its definition. - for _, ln := range fn.Func.Dcl { - if ln.Op == ONAME && (ln.Class() == PAUTO || ln.Class() == PAUTOHEAP) && ln.Name.Defn != nil && ln.Name.Defn.Op == OTYPESW && ln.Name.Used() { - ln.Name.Defn.Left.Name.SetUsed(true) - } - } - - for _, ln := range fn.Func.Dcl { - if ln.Op != ONAME || (ln.Class() != PAUTO && ln.Class() != PAUTOHEAP) || ln.Sym.Name[0] == '&' || ln.Name.Used() { - continue - } - if defn := ln.Name.Defn; defn != nil && defn.Op == OTYPESW { - if defn.Left.Name.Used() { - continue - } - yyerrorl(defn.Left.Pos, "%v declared but not used", ln.Sym) - defn.Left.Name.SetUsed(true) // suppress repeats - } else { - yyerrorl(ln.Pos, "%v declared but not used", ln.Sym) - } - } - - lineno = lno - if nerrors != 0 { - return - } - walkstmtlist(Curfn.Nbody.Slice()) - if Debug.W != 0 { - s := fmt.Sprintf("after walk %v", Curfn.Func.Nname.Sym) - dumplist(s, Curfn.Nbody) - } - - zeroResults() - heapmoves() - if Debug.W != 0 && Curfn.Func.Enter.Len() > 0 { - s := fmt.Sprintf("enter %v", Curfn.Func.Nname.Sym) - dumplist(s, Curfn.Func.Enter) - } -} - -func walkstmtlist(s []*Node) { - for i := range s { - s[i] = walkstmt(s[i]) - } -} - -func paramoutheap(fn *Node) bool { - for _, ln := range fn.Func.Dcl { - switch ln.Class() { - case PPARAMOUT: - if ln.isParamStackCopy() || ln.Name.Addrtaken() { - return true - } - - case PAUTO: - // stop early - parameters are over - return false - } - } - - return false -} - -// The result of walkstmt MUST be assigned back to n, e.g. -// n.Left = walkstmt(n.Left) -func walkstmt(n *Node) *Node { - if n == nil { - return n - } - - setlineno(n) - - walkstmtlist(n.Ninit.Slice()) - - switch n.Op { - default: - if n.Op == ONAME { - yyerror("%v is not a top level statement", n.Sym) - } else { - yyerror("%v is not a top level statement", n.Op) - } - Dump("nottop", n) - - case OAS, - OASOP, - OAS2, - OAS2DOTTYPE, - OAS2RECV, - OAS2FUNC, - OAS2MAPR, - OCLOSE, - OCOPY, - OCALLMETH, - OCALLINTER, - OCALL, - OCALLFUNC, - ODELETE, - OSEND, - OPRINT, - OPRINTN, - OPANIC, - OEMPTY, - ORECOVER, - OGETG: - if n.Typecheck() == 0 { - Fatalf("missing typecheck: %+v", n) - } - wascopy := n.Op == OCOPY - init := n.Ninit - n.Ninit.Set(nil) - n = walkexpr(n, &init) - n = addinit(n, init.Slice()) - if wascopy && n.Op == OCONVNOP { - n.Op = OEMPTY // don't leave plain values as statements. - } - - // special case for a receive where we throw away - // the value received. - case ORECV: - if n.Typecheck() == 0 { - Fatalf("missing typecheck: %+v", n) - } - init := n.Ninit - n.Ninit.Set(nil) - - n.Left = walkexpr(n.Left, &init) - n = mkcall1(chanfn("chanrecv1", 2, n.Left.Type), nil, &init, n.Left, nodnil()) - n = walkexpr(n, &init) - - n = addinit(n, init.Slice()) - - case OBREAK, - OCONTINUE, - OFALL, - OGOTO, - OLABEL, - ODCLCONST, - ODCLTYPE, - OCHECKNIL, - OVARDEF, - OVARKILL, - OVARLIVE: - break - - case ODCL: - v := n.Left - if v.Class() == PAUTOHEAP { - if compiling_runtime { - yyerror("%v escapes to heap, not allowed in runtime", v) - } - if prealloc[v] == nil { - prealloc[v] = callnew(v.Type) - } - nn := nod(OAS, v.Name.Param.Heapaddr, prealloc[v]) - nn.SetColas(true) - nn = typecheck(nn, ctxStmt) - return walkstmt(nn) - } - - case OBLOCK: - walkstmtlist(n.List.Slice()) - - case OCASE: - yyerror("case statement out of place") - - case ODEFER: - Curfn.Func.SetHasDefer(true) - Curfn.Func.numDefers++ - if Curfn.Func.numDefers > maxOpenDefers { - // Don't allow open-coded defers if there are more than - // 8 defers in the function, since we use a single - // byte to record active defers. - Curfn.Func.SetOpenCodedDeferDisallowed(true) - } - if n.Esc != EscNever { - // If n.Esc is not EscNever, then this defer occurs in a loop, - // so open-coded defers cannot be used in this function. - Curfn.Func.SetOpenCodedDeferDisallowed(true) - } - fallthrough - case OGO: - switch n.Left.Op { - case OPRINT, OPRINTN: - n.Left = wrapCall(n.Left, &n.Ninit) - - case ODELETE: - if mapfast(n.Left.List.First().Type) == mapslow { - n.Left = wrapCall(n.Left, &n.Ninit) - } else { - n.Left = walkexpr(n.Left, &n.Ninit) - } - - case OCOPY: - n.Left = copyany(n.Left, &n.Ninit, true) - - case OCALLFUNC, OCALLMETH, OCALLINTER: - if n.Left.Nbody.Len() > 0 { - n.Left = wrapCall(n.Left, &n.Ninit) - } else { - n.Left = walkexpr(n.Left, &n.Ninit) - } - - default: - n.Left = walkexpr(n.Left, &n.Ninit) - } - - case OFOR, OFORUNTIL: - if n.Left != nil { - walkstmtlist(n.Left.Ninit.Slice()) - init := n.Left.Ninit - n.Left.Ninit.Set(nil) - n.Left = walkexpr(n.Left, &init) - n.Left = addinit(n.Left, init.Slice()) - } - - n.Right = walkstmt(n.Right) - if n.Op == OFORUNTIL { - walkstmtlist(n.List.Slice()) - } - walkstmtlist(n.Nbody.Slice()) - - case OIF: - n.Left = walkexpr(n.Left, &n.Ninit) - walkstmtlist(n.Nbody.Slice()) - walkstmtlist(n.Rlist.Slice()) - - case ORETURN: - Curfn.Func.numReturns++ - if n.List.Len() == 0 { - break - } - if (Curfn.Type.FuncType().Outnamed && n.List.Len() > 1) || paramoutheap(Curfn) || Curfn.Func.HasDefer() { - // assign to the function out parameters, - // so that reorder3 can fix up conflicts - var rl []*Node - - for _, ln := range Curfn.Func.Dcl { - cl := ln.Class() - if cl == PAUTO || cl == PAUTOHEAP { - break - } - if cl == PPARAMOUT { - if ln.isParamStackCopy() { - ln = walkexpr(typecheck(nod(ODEREF, ln.Name.Param.Heapaddr, nil), ctxExpr), nil) - } - rl = append(rl, ln) - } - } - - if got, want := n.List.Len(), len(rl); got != want { - // order should have rewritten multi-value function calls - // with explicit OAS2FUNC nodes. - Fatalf("expected %v return arguments, have %v", want, got) - } - - // move function calls out, to make reorder3's job easier. - walkexprlistsafe(n.List.Slice(), &n.Ninit) - - ll := ascompatee(n.Op, rl, n.List.Slice(), &n.Ninit) - n.List.Set(reorder3(ll)) - break - } - walkexprlist(n.List.Slice(), &n.Ninit) - - // For each return parameter (lhs), assign the corresponding result (rhs). - lhs := Curfn.Type.Results() - rhs := n.List.Slice() - res := make([]*Node, lhs.NumFields()) - for i, nl := range lhs.FieldSlice() { - nname := asNode(nl.Nname) - if nname.isParamHeapCopy() { - nname = nname.Name.Param.Stackcopy - } - a := nod(OAS, nname, rhs[i]) - res[i] = convas(a, &n.Ninit) - } - n.List.Set(res) - - case ORETJMP: - break - - case OINLMARK: - break - - case OSELECT: - walkselect(n) - - case OSWITCH: - walkswitch(n) - - case ORANGE: - n = walkrange(n) - } - - if n.Op == ONAME { - Fatalf("walkstmt ended up with name: %+v", n) - } - return n -} - -// walk the whole tree of the body of an -// expression or simple statement. -// the types expressions are calculated. -// compile-time constants are evaluated. -// complex side effects like statements are appended to init -func walkexprlist(s []*Node, init *Nodes) { - for i := range s { - s[i] = walkexpr(s[i], init) - } -} - -func walkexprlistsafe(s []*Node, init *Nodes) { - for i, n := range s { - s[i] = safeexpr(n, init) - s[i] = walkexpr(s[i], init) - } -} - -func walkexprlistcheap(s []*Node, init *Nodes) { - for i, n := range s { - s[i] = cheapexpr(n, init) - s[i] = walkexpr(s[i], init) - } -} - -// convFuncName builds the runtime function name for interface conversion. -// It also reports whether the function expects the data by address. -// Not all names are possible. For example, we never generate convE2E or convE2I. -func convFuncName(from, to *types.Type) (fnname string, needsaddr bool) { - tkind := to.Tie() - switch from.Tie() { - case 'I': - if tkind == 'I' { - return "convI2I", false - } - case 'T': - switch { - case from.Size() == 2 && from.Align == 2: - return "convT16", false - case from.Size() == 4 && from.Align == 4 && !from.HasPointers(): - return "convT32", false - case from.Size() == 8 && from.Align == types.Types[TUINT64].Align && !from.HasPointers(): - return "convT64", false - } - if sc := from.SoleComponent(); sc != nil { - switch { - case sc.IsString(): - return "convTstring", false - case sc.IsSlice(): - return "convTslice", false - } - } - - switch tkind { - case 'E': - if !from.HasPointers() { - return "convT2Enoptr", true - } - return "convT2E", true - case 'I': - if !from.HasPointers() { - return "convT2Inoptr", true - } - return "convT2I", true - } - } - Fatalf("unknown conv func %c2%c", from.Tie(), to.Tie()) - panic("unreachable") -} - -// The result of walkexpr MUST be assigned back to n, e.g. -// n.Left = walkexpr(n.Left, init) -func walkexpr(n *Node, init *Nodes) *Node { - if n == nil { - return n - } - - // Eagerly checkwidth all expressions for the back end. - if n.Type != nil && !n.Type.WidthCalculated() { - switch n.Type.Etype { - case TBLANK, TNIL, TIDEAL: - default: - checkwidth(n.Type) - } - } - - if init == &n.Ninit { - // not okay to use n->ninit when walking n, - // because we might replace n with some other node - // and would lose the init list. - Fatalf("walkexpr init == &n->ninit") - } - - if n.Ninit.Len() != 0 { - walkstmtlist(n.Ninit.Slice()) - init.AppendNodes(&n.Ninit) - } - - lno := setlineno(n) - - if Debug.w > 1 { - Dump("before walk expr", n) - } - - if n.Typecheck() != 1 { - Fatalf("missed typecheck: %+v", n) - } - - if n.Type.IsUntyped() { - Fatalf("expression has untyped type: %+v", n) - } - - if n.Op == ONAME && n.Class() == PAUTOHEAP { - nn := nod(ODEREF, n.Name.Param.Heapaddr, nil) - nn = typecheck(nn, ctxExpr) - nn = walkexpr(nn, init) - nn.Left.MarkNonNil() - return nn - } - -opswitch: - switch n.Op { - default: - Dump("walk", n) - Fatalf("walkexpr: switch 1 unknown op %+S", n) - - case ONONAME, OEMPTY, OGETG, ONEWOBJ: - - case OTYPE, ONAME, OLITERAL: - // TODO(mdempsky): Just return n; see discussion on CL 38655. - // Perhaps refactor to use Node.mayBeShared for these instead. - // If these return early, make sure to still call - // stringsym for constant strings. - - case ONOT, ONEG, OPLUS, OBITNOT, OREAL, OIMAG, ODOTMETH, ODOTINTER, - ODEREF, OSPTR, OITAB, OIDATA, OADDR: - n.Left = walkexpr(n.Left, init) - - case OEFACE, OAND, OANDNOT, OSUB, OMUL, OADD, OOR, OXOR, OLSH, ORSH: - n.Left = walkexpr(n.Left, init) - n.Right = walkexpr(n.Right, init) - - case ODOT, ODOTPTR: - usefield(n) - n.Left = walkexpr(n.Left, init) - - case ODOTTYPE, ODOTTYPE2: - n.Left = walkexpr(n.Left, init) - // Set up interface type addresses for back end. - n.Right = typename(n.Type) - if n.Op == ODOTTYPE { - n.Right.Right = typename(n.Left.Type) - } - if !n.Type.IsInterface() && !n.Left.Type.IsEmptyInterface() { - n.List.Set1(itabname(n.Type, n.Left.Type)) - } - - case OLEN, OCAP: - if isRuneCount(n) { - // Replace len([]rune(string)) with runtime.countrunes(string). - n = mkcall("countrunes", n.Type, init, conv(n.Left.Left, types.Types[TSTRING])) - break - } - - n.Left = walkexpr(n.Left, init) - - // replace len(*[10]int) with 10. - // delayed until now to preserve side effects. - t := n.Left.Type - - if t.IsPtr() { - t = t.Elem() - } - if t.IsArray() { - safeexpr(n.Left, init) - setintconst(n, t.NumElem()) - n.SetTypecheck(1) - } - - case OCOMPLEX: - // Use results from call expression as arguments for complex. - if n.Left == nil && n.Right == nil { - n.Left = n.List.First() - n.Right = n.List.Second() - } - n.Left = walkexpr(n.Left, init) - n.Right = walkexpr(n.Right, init) - - case OEQ, ONE, OLT, OLE, OGT, OGE: - n = walkcompare(n, init) - - case OANDAND, OOROR: - n.Left = walkexpr(n.Left, init) - - // cannot put side effects from n.Right on init, - // because they cannot run before n.Left is checked. - // save elsewhere and store on the eventual n.Right. - var ll Nodes - - n.Right = walkexpr(n.Right, &ll) - n.Right = addinit(n.Right, ll.Slice()) - - case OPRINT, OPRINTN: - n = walkprint(n, init) - - case OPANIC: - n = mkcall("gopanic", nil, init, n.Left) - - case ORECOVER: - n = mkcall("gorecover", n.Type, init, nod(OADDR, nodfp, nil)) - - case OCLOSUREVAR, OCFUNC: - - case OCALLINTER, OCALLFUNC, OCALLMETH: - if n.Op == OCALLINTER || n.Op == OCALLMETH { - // We expect both interface call reflect.Type.Method and concrete - // call reflect.(*rtype).Method. - usemethod(n) - } - if n.Op == OCALLINTER { - markUsedIfaceMethod(n) - } - - if n.Op == OCALLFUNC && n.Left.Op == OCLOSURE { - // Transform direct call of a closure to call of a normal function. - // transformclosure already did all preparation work. - - // Prepend captured variables to argument list. - n.List.Prepend(n.Left.Func.Enter.Slice()...) - - n.Left.Func.Enter.Set(nil) - - // Replace OCLOSURE with ONAME/PFUNC. - n.Left = n.Left.Func.Closure.Func.Nname - - // Update type of OCALLFUNC node. - // Output arguments had not changed, but their offsets could. - if n.Left.Type.NumResults() == 1 { - n.Type = n.Left.Type.Results().Field(0).Type - } else { - n.Type = n.Left.Type.Results() - } - } - - walkCall(n, init) - - case OAS, OASOP: - init.AppendNodes(&n.Ninit) - - // Recognize m[k] = append(m[k], ...) so we can reuse - // the mapassign call. - mapAppend := n.Left.Op == OINDEXMAP && n.Right.Op == OAPPEND - if mapAppend && !samesafeexpr(n.Left, n.Right.List.First()) { - Fatalf("not same expressions: %v != %v", n.Left, n.Right.List.First()) - } - - n.Left = walkexpr(n.Left, init) - n.Left = safeexpr(n.Left, init) - - if mapAppend { - n.Right.List.SetFirst(n.Left) - } - - if n.Op == OASOP { - // Rewrite x op= y into x = x op y. - n.Right = nod(n.SubOp(), n.Left, n.Right) - n.Right = typecheck(n.Right, ctxExpr) - - n.Op = OAS - n.ResetAux() - } - - if oaslit(n, init) { - break - } - - if n.Right == nil { - // TODO(austin): Check all "implicit zeroing" - break - } - - if !instrumenting && isZero(n.Right) { - break - } - - switch n.Right.Op { - default: - n.Right = walkexpr(n.Right, init) - - case ORECV: - // x = <-c; n.Left is x, n.Right.Left is c. - // order.stmt made sure x is addressable. - n.Right.Left = walkexpr(n.Right.Left, init) - - n1 := nod(OADDR, n.Left, nil) - r := n.Right.Left // the channel - n = mkcall1(chanfn("chanrecv1", 2, r.Type), nil, init, r, n1) - n = walkexpr(n, init) - break opswitch - - case OAPPEND: - // x = append(...) - r := n.Right - if r.Type.Elem().NotInHeap() { - yyerror("%v can't be allocated in Go; it is incomplete (or unallocatable)", r.Type.Elem()) - } - switch { - case isAppendOfMake(r): - // x = append(y, make([]T, y)...) - r = extendslice(r, init) - case r.IsDDD(): - r = appendslice(r, init) // also works for append(slice, string). - default: - r = walkappend(r, init, n) - } - n.Right = r - if r.Op == OAPPEND { - // Left in place for back end. - // Do not add a new write barrier. - // Set up address of type for back end. - r.Left = typename(r.Type.Elem()) - break opswitch - } - // Otherwise, lowered for race detector. - // Treat as ordinary assignment. - } - - if n.Left != nil && n.Right != nil { - n = convas(n, init) - } - - case OAS2: - init.AppendNodes(&n.Ninit) - walkexprlistsafe(n.List.Slice(), init) - walkexprlistsafe(n.Rlist.Slice(), init) - ll := ascompatee(OAS, n.List.Slice(), n.Rlist.Slice(), init) - ll = reorder3(ll) - n = liststmt(ll) - - // a,b,... = fn() - case OAS2FUNC: - init.AppendNodes(&n.Ninit) - - r := n.Right - walkexprlistsafe(n.List.Slice(), init) - r = walkexpr(r, init) - - if isIntrinsicCall(r) { - n.Right = r - break - } - init.Append(r) - - ll := ascompatet(n.List, r.Type) - n = liststmt(ll) - - // x, y = <-c - // order.stmt made sure x is addressable or blank. - case OAS2RECV: - init.AppendNodes(&n.Ninit) - - r := n.Right - walkexprlistsafe(n.List.Slice(), init) - r.Left = walkexpr(r.Left, init) - var n1 *Node - if n.List.First().isBlank() { - n1 = nodnil() - } else { - n1 = nod(OADDR, n.List.First(), nil) - } - fn := chanfn("chanrecv2", 2, r.Left.Type) - ok := n.List.Second() - call := mkcall1(fn, types.Types[TBOOL], init, r.Left, n1) - n = nod(OAS, ok, call) - n = typecheck(n, ctxStmt) - - // a,b = m[i] - case OAS2MAPR: - init.AppendNodes(&n.Ninit) - - r := n.Right - walkexprlistsafe(n.List.Slice(), init) - r.Left = walkexpr(r.Left, init) - r.Right = walkexpr(r.Right, init) - t := r.Left.Type - - fast := mapfast(t) - var key *Node - if fast != mapslow { - // fast versions take key by value - key = r.Right - } else { - // standard version takes key by reference - // order.expr made sure key is addressable. - key = nod(OADDR, r.Right, nil) - } - - // from: - // a,b = m[i] - // to: - // var,b = mapaccess2*(t, m, i) - // a = *var - a := n.List.First() - - if w := t.Elem().Width; w <= zeroValSize { - fn := mapfn(mapaccess2[fast], t) - r = mkcall1(fn, fn.Type.Results(), init, typename(t), r.Left, key) - } else { - fn := mapfn("mapaccess2_fat", t) - z := zeroaddr(w) - r = mkcall1(fn, fn.Type.Results(), init, typename(t), r.Left, key, z) - } - - // mapaccess2* returns a typed bool, but due to spec changes, - // the boolean result of i.(T) is now untyped so we make it the - // same type as the variable on the lhs. - if ok := n.List.Second(); !ok.isBlank() && ok.Type.IsBoolean() { - r.Type.Field(1).Type = ok.Type - } - n.Right = r - n.Op = OAS2FUNC - - // don't generate a = *var if a is _ - if !a.isBlank() { - var_ := temp(types.NewPtr(t.Elem())) - var_.SetTypecheck(1) - var_.MarkNonNil() // mapaccess always returns a non-nil pointer - n.List.SetFirst(var_) - n = walkexpr(n, init) - init.Append(n) - n = nod(OAS, a, nod(ODEREF, var_, nil)) - } - - n = typecheck(n, ctxStmt) - n = walkexpr(n, init) - - case ODELETE: - init.AppendNodes(&n.Ninit) - map_ := n.List.First() - key := n.List.Second() - map_ = walkexpr(map_, init) - key = walkexpr(key, init) - - t := map_.Type - fast := mapfast(t) - if fast == mapslow { - // order.stmt made sure key is addressable. - key = nod(OADDR, key, nil) - } - n = mkcall1(mapfndel(mapdelete[fast], t), nil, init, typename(t), map_, key) - - case OAS2DOTTYPE: - walkexprlistsafe(n.List.Slice(), init) - n.Right = walkexpr(n.Right, init) - - case OCONVIFACE: - n.Left = walkexpr(n.Left, init) - - fromType := n.Left.Type - toType := n.Type - - if !fromType.IsInterface() && !Curfn.Func.Nname.isBlank() { // skip unnamed functions (func _()) - markTypeUsedInInterface(fromType, Curfn.Func.lsym) - } - - // typeword generates the type word of the interface value. - typeword := func() *Node { - if toType.IsEmptyInterface() { - return typename(fromType) - } - return itabname(fromType, toType) - } - - // Optimize convT2E or convT2I as a two-word copy when T is pointer-shaped. - if isdirectiface(fromType) { - l := nod(OEFACE, typeword(), n.Left) - l.Type = toType - l.SetTypecheck(n.Typecheck()) - n = l - break - } - - if staticuint64s == nil { - staticuint64s = newname(Runtimepkg.Lookup("staticuint64s")) - staticuint64s.SetClass(PEXTERN) - // The actual type is [256]uint64, but we use [256*8]uint8 so we can address - // individual bytes. - staticuint64s.Type = types.NewArray(types.Types[TUINT8], 256*8) - zerobase = newname(Runtimepkg.Lookup("zerobase")) - zerobase.SetClass(PEXTERN) - zerobase.Type = types.Types[TUINTPTR] - } - - // Optimize convT2{E,I} for many cases in which T is not pointer-shaped, - // by using an existing addressable value identical to n.Left - // or creating one on the stack. - var value *Node - switch { - case fromType.Size() == 0: - // n.Left is zero-sized. Use zerobase. - cheapexpr(n.Left, init) // Evaluate n.Left for side-effects. See issue 19246. - value = zerobase - case fromType.IsBoolean() || (fromType.Size() == 1 && fromType.IsInteger()): - // n.Left is a bool/byte. Use staticuint64s[n.Left * 8] on little-endian - // and staticuint64s[n.Left * 8 + 7] on big-endian. - n.Left = cheapexpr(n.Left, init) - // byteindex widens n.Left so that the multiplication doesn't overflow. - index := nod(OLSH, byteindex(n.Left), nodintconst(3)) - if thearch.LinkArch.ByteOrder == binary.BigEndian { - index = nod(OADD, index, nodintconst(7)) - } - value = nod(OINDEX, staticuint64s, index) - value.SetBounded(true) - case n.Left.Class() == PEXTERN && n.Left.Name != nil && n.Left.Name.Readonly(): - // n.Left is a readonly global; use it directly. - value = n.Left - case !fromType.IsInterface() && n.Esc == EscNone && fromType.Width <= 1024: - // n.Left does not escape. Use a stack temporary initialized to n.Left. - value = temp(fromType) - init.Append(typecheck(nod(OAS, value, n.Left), ctxStmt)) - } - - if value != nil { - // Value is identical to n.Left. - // Construct the interface directly: {type/itab, &value}. - l := nod(OEFACE, typeword(), typecheck(nod(OADDR, value, nil), ctxExpr)) - l.Type = toType - l.SetTypecheck(n.Typecheck()) - n = l - break - } - - // Implement interface to empty interface conversion. - // tmp = i.itab - // if tmp != nil { - // tmp = tmp.type - // } - // e = iface{tmp, i.data} - if toType.IsEmptyInterface() && fromType.IsInterface() && !fromType.IsEmptyInterface() { - // Evaluate the input interface. - c := temp(fromType) - init.Append(nod(OAS, c, n.Left)) - - // Get the itab out of the interface. - tmp := temp(types.NewPtr(types.Types[TUINT8])) - init.Append(nod(OAS, tmp, typecheck(nod(OITAB, c, nil), ctxExpr))) - - // Get the type out of the itab. - nif := nod(OIF, typecheck(nod(ONE, tmp, nodnil()), ctxExpr), nil) - nif.Nbody.Set1(nod(OAS, tmp, itabType(tmp))) - init.Append(nif) - - // Build the result. - e := nod(OEFACE, tmp, ifaceData(n.Pos, c, types.NewPtr(types.Types[TUINT8]))) - e.Type = toType // assign type manually, typecheck doesn't understand OEFACE. - e.SetTypecheck(1) - n = e - break - } - - fnname, needsaddr := convFuncName(fromType, toType) - - if !needsaddr && !fromType.IsInterface() { - // Use a specialized conversion routine that only returns a data pointer. - // ptr = convT2X(val) - // e = iface{typ/tab, ptr} - fn := syslook(fnname) - dowidth(fromType) - fn = substArgTypes(fn, fromType) - dowidth(fn.Type) - call := nod(OCALL, fn, nil) - call.List.Set1(n.Left) - call = typecheck(call, ctxExpr) - call = walkexpr(call, init) - call = safeexpr(call, init) - e := nod(OEFACE, typeword(), call) - e.Type = toType - e.SetTypecheck(1) - n = e - break - } - - var tab *Node - if fromType.IsInterface() { - // convI2I - tab = typename(toType) - } else { - // convT2x - tab = typeword() - } - - v := n.Left - if needsaddr { - // Types of large or unknown size are passed by reference. - // Orderexpr arranged for n.Left to be a temporary for all - // the conversions it could see. Comparison of an interface - // with a non-interface, especially in a switch on interface value - // with non-interface cases, is not visible to order.stmt, so we - // have to fall back on allocating a temp here. - if !islvalue(v) { - v = copyexpr(v, v.Type, init) - } - v = nod(OADDR, v, nil) - } - - dowidth(fromType) - fn := syslook(fnname) - fn = substArgTypes(fn, fromType, toType) - dowidth(fn.Type) - n = nod(OCALL, fn, nil) - n.List.Set2(tab, v) - n = typecheck(n, ctxExpr) - n = walkexpr(n, init) - - case OCONV, OCONVNOP: - n.Left = walkexpr(n.Left, init) - if n.Op == OCONVNOP && checkPtr(Curfn, 1) { - if n.Type.IsPtr() && n.Left.Type.IsUnsafePtr() { // unsafe.Pointer to *T - n = walkCheckPtrAlignment(n, init, nil) - break - } - if n.Type.IsUnsafePtr() && n.Left.Type.IsUintptr() { // uintptr to unsafe.Pointer - n = walkCheckPtrArithmetic(n, init) - break - } - } - param, result := rtconvfn(n.Left.Type, n.Type) - if param == Txxx { - break - } - fn := basicnames[param] + "to" + basicnames[result] - n = conv(mkcall(fn, types.Types[result], init, conv(n.Left, types.Types[param])), n.Type) - - case ODIV, OMOD: - n.Left = walkexpr(n.Left, init) - n.Right = walkexpr(n.Right, init) - - // rewrite complex div into function call. - et := n.Left.Type.Etype - - if isComplex[et] && n.Op == ODIV { - t := n.Type - n = mkcall("complex128div", types.Types[TCOMPLEX128], init, conv(n.Left, types.Types[TCOMPLEX128]), conv(n.Right, types.Types[TCOMPLEX128])) - n = conv(n, t) - break - } - - // Nothing to do for float divisions. - if isFloat[et] { - break - } - - // rewrite 64-bit div and mod on 32-bit architectures. - // TODO: Remove this code once we can introduce - // runtime calls late in SSA processing. - if Widthreg < 8 && (et == TINT64 || et == TUINT64) { - if n.Right.Op == OLITERAL { - // Leave div/mod by constant powers of 2 or small 16-bit constants. - // The SSA backend will handle those. - switch et { - case TINT64: - c := n.Right.Int64Val() - if c < 0 { - c = -c - } - if c != 0 && c&(c-1) == 0 { - break opswitch - } - case TUINT64: - c := uint64(n.Right.Int64Val()) - if c < 1<<16 { - break opswitch - } - if c != 0 && c&(c-1) == 0 { - break opswitch - } - } - } - var fn string - if et == TINT64 { - fn = "int64" - } else { - fn = "uint64" - } - if n.Op == ODIV { - fn += "div" - } else { - fn += "mod" - } - n = mkcall(fn, n.Type, init, conv(n.Left, types.Types[et]), conv(n.Right, types.Types[et])) - } - - case OINDEX: - n.Left = walkexpr(n.Left, init) - - // save the original node for bounds checking elision. - // If it was a ODIV/OMOD walk might rewrite it. - r := n.Right - - n.Right = walkexpr(n.Right, init) - - // if range of type cannot exceed static array bound, - // disable bounds check. - if n.Bounded() { - break - } - t := n.Left.Type - if t != nil && t.IsPtr() { - t = t.Elem() - } - if t.IsArray() { - n.SetBounded(bounded(r, t.NumElem())) - if Debug.m != 0 && n.Bounded() && !Isconst(n.Right, CTINT) { - Warn("index bounds check elided") - } - if smallintconst(n.Right) && !n.Bounded() { - yyerror("index out of bounds") - } - } else if Isconst(n.Left, CTSTR) { - n.SetBounded(bounded(r, int64(len(n.Left.StringVal())))) - if Debug.m != 0 && n.Bounded() && !Isconst(n.Right, CTINT) { - Warn("index bounds check elided") - } - if smallintconst(n.Right) && !n.Bounded() { - yyerror("index out of bounds") - } - } - - if Isconst(n.Right, CTINT) { - if n.Right.Val().U.(*Mpint).CmpInt64(0) < 0 || n.Right.Val().U.(*Mpint).Cmp(maxintval[TINT]) > 0 { - yyerror("index out of bounds") - } - } - - case OINDEXMAP: - // Replace m[k] with *map{access1,assign}(maptype, m, &k) - n.Left = walkexpr(n.Left, init) - n.Right = walkexpr(n.Right, init) - map_ := n.Left - key := n.Right - t := map_.Type - if n.IndexMapLValue() { - // This m[k] expression is on the left-hand side of an assignment. - fast := mapfast(t) - if fast == mapslow { - // standard version takes key by reference. - // order.expr made sure key is addressable. - key = nod(OADDR, key, nil) - } - n = mkcall1(mapfn(mapassign[fast], t), nil, init, typename(t), map_, key) - } else { - // m[k] is not the target of an assignment. - fast := mapfast(t) - if fast == mapslow { - // standard version takes key by reference. - // order.expr made sure key is addressable. - key = nod(OADDR, key, nil) - } - - if w := t.Elem().Width; w <= zeroValSize { - n = mkcall1(mapfn(mapaccess1[fast], t), types.NewPtr(t.Elem()), init, typename(t), map_, key) - } else { - z := zeroaddr(w) - n = mkcall1(mapfn("mapaccess1_fat", t), types.NewPtr(t.Elem()), init, typename(t), map_, key, z) - } - } - n.Type = types.NewPtr(t.Elem()) - n.MarkNonNil() // mapaccess1* and mapassign always return non-nil pointers. - n = nod(ODEREF, n, nil) - n.Type = t.Elem() - n.SetTypecheck(1) - - case ORECV: - Fatalf("walkexpr ORECV") // should see inside OAS only - - case OSLICEHEADER: - n.Left = walkexpr(n.Left, init) - n.List.SetFirst(walkexpr(n.List.First(), init)) - n.List.SetSecond(walkexpr(n.List.Second(), init)) - - case OSLICE, OSLICEARR, OSLICESTR, OSLICE3, OSLICE3ARR: - checkSlice := checkPtr(Curfn, 1) && n.Op == OSLICE3ARR && n.Left.Op == OCONVNOP && n.Left.Left.Type.IsUnsafePtr() - if checkSlice { - n.Left.Left = walkexpr(n.Left.Left, init) - } else { - n.Left = walkexpr(n.Left, init) - } - low, high, max := n.SliceBounds() - low = walkexpr(low, init) - if low != nil && isZero(low) { - // Reduce x[0:j] to x[:j] and x[0:j:k] to x[:j:k]. - low = nil - } - high = walkexpr(high, init) - max = walkexpr(max, init) - n.SetSliceBounds(low, high, max) - if checkSlice { - n.Left = walkCheckPtrAlignment(n.Left, init, max) - } - if n.Op.IsSlice3() { - if max != nil && max.Op == OCAP && samesafeexpr(n.Left, max.Left) { - // Reduce x[i:j:cap(x)] to x[i:j]. - if n.Op == OSLICE3 { - n.Op = OSLICE - } else { - n.Op = OSLICEARR - } - n = reduceSlice(n) - } - } else { - n = reduceSlice(n) - } - - case ONEW: - if n.Type.Elem().NotInHeap() { - yyerror("%v can't be allocated in Go; it is incomplete (or unallocatable)", n.Type.Elem()) - } - if n.Esc == EscNone { - if n.Type.Elem().Width >= maxImplicitStackVarSize { - Fatalf("large ONEW with EscNone: %v", n) - } - r := temp(n.Type.Elem()) - r = nod(OAS, r, nil) // zero temp - r = typecheck(r, ctxStmt) - init.Append(r) - r = nod(OADDR, r.Left, nil) - r = typecheck(r, ctxExpr) - n = r - } else { - n = callnew(n.Type.Elem()) - } - - case OADDSTR: - n = addstr(n, init) - - case OAPPEND: - // order should make sure we only see OAS(node, OAPPEND), which we handle above. - Fatalf("append outside assignment") - - case OCOPY: - n = copyany(n, init, instrumenting && !compiling_runtime) - - // cannot use chanfn - closechan takes any, not chan any - case OCLOSE: - fn := syslook("closechan") - - fn = substArgTypes(fn, n.Left.Type) - n = mkcall1(fn, nil, init, n.Left) - - case OMAKECHAN: - // When size fits into int, use makechan instead of - // makechan64, which is faster and shorter on 32 bit platforms. - size := n.Left - fnname := "makechan64" - argtype := types.Types[TINT64] - - // Type checking guarantees that TIDEAL size is positive and fits in an int. - // The case of size overflow when converting TUINT or TUINTPTR to TINT - // will be handled by the negative range checks in makechan during runtime. - if size.Type.IsKind(TIDEAL) || maxintval[size.Type.Etype].Cmp(maxintval[TUINT]) <= 0 { - fnname = "makechan" - argtype = types.Types[TINT] - } - - n = mkcall1(chanfn(fnname, 1, n.Type), n.Type, init, typename(n.Type), conv(size, argtype)) - - case OMAKEMAP: - t := n.Type - hmapType := hmap(t) - hint := n.Left - - // var h *hmap - var h *Node - if n.Esc == EscNone { - // Allocate hmap on stack. - - // var hv hmap - hv := temp(hmapType) - zero := nod(OAS, hv, nil) - zero = typecheck(zero, ctxStmt) - init.Append(zero) - // h = &hv - h = nod(OADDR, hv, nil) - - // Allocate one bucket pointed to by hmap.buckets on stack if hint - // is not larger than BUCKETSIZE. In case hint is larger than - // BUCKETSIZE runtime.makemap will allocate the buckets on the heap. - // Maximum key and elem size is 128 bytes, larger objects - // are stored with an indirection. So max bucket size is 2048+eps. - if !Isconst(hint, CTINT) || - hint.Val().U.(*Mpint).CmpInt64(BUCKETSIZE) <= 0 { - - // In case hint is larger than BUCKETSIZE runtime.makemap - // will allocate the buckets on the heap, see #20184 - // - // if hint <= BUCKETSIZE { - // var bv bmap - // b = &bv - // h.buckets = b - // } - - nif := nod(OIF, nod(OLE, hint, nodintconst(BUCKETSIZE)), nil) - nif.SetLikely(true) - - // var bv bmap - bv := temp(bmap(t)) - zero = nod(OAS, bv, nil) - nif.Nbody.Append(zero) - - // b = &bv - b := nod(OADDR, bv, nil) - - // h.buckets = b - bsym := hmapType.Field(5).Sym // hmap.buckets see reflect.go:hmap - na := nod(OAS, nodSym(ODOT, h, bsym), b) - nif.Nbody.Append(na) - - nif = typecheck(nif, ctxStmt) - nif = walkstmt(nif) - init.Append(nif) - } - } - - if Isconst(hint, CTINT) && hint.Val().U.(*Mpint).CmpInt64(BUCKETSIZE) <= 0 { - // Handling make(map[any]any) and - // make(map[any]any, hint) where hint <= BUCKETSIZE - // special allows for faster map initialization and - // improves binary size by using calls with fewer arguments. - // For hint <= BUCKETSIZE overLoadFactor(hint, 0) is false - // and no buckets will be allocated by makemap. Therefore, - // no buckets need to be allocated in this code path. - if n.Esc == EscNone { - // Only need to initialize h.hash0 since - // hmap h has been allocated on the stack already. - // h.hash0 = fastrand() - rand := mkcall("fastrand", types.Types[TUINT32], init) - hashsym := hmapType.Field(4).Sym // hmap.hash0 see reflect.go:hmap - a := nod(OAS, nodSym(ODOT, h, hashsym), rand) - a = typecheck(a, ctxStmt) - a = walkexpr(a, init) - init.Append(a) - n = convnop(h, t) - } else { - // Call runtime.makehmap to allocate an - // hmap on the heap and initialize hmap's hash0 field. - fn := syslook("makemap_small") - fn = substArgTypes(fn, t.Key(), t.Elem()) - n = mkcall1(fn, n.Type, init) - } - } else { - if n.Esc != EscNone { - h = nodnil() - } - // Map initialization with a variable or large hint is - // more complicated. We therefore generate a call to - // runtime.makemap to initialize hmap and allocate the - // map buckets. - - // When hint fits into int, use makemap instead of - // makemap64, which is faster and shorter on 32 bit platforms. - fnname := "makemap64" - argtype := types.Types[TINT64] - - // Type checking guarantees that TIDEAL hint is positive and fits in an int. - // See checkmake call in TMAP case of OMAKE case in OpSwitch in typecheck1 function. - // The case of hint overflow when converting TUINT or TUINTPTR to TINT - // will be handled by the negative range checks in makemap during runtime. - if hint.Type.IsKind(TIDEAL) || maxintval[hint.Type.Etype].Cmp(maxintval[TUINT]) <= 0 { - fnname = "makemap" - argtype = types.Types[TINT] - } - - fn := syslook(fnname) - fn = substArgTypes(fn, hmapType, t.Key(), t.Elem()) - n = mkcall1(fn, n.Type, init, typename(n.Type), conv(hint, argtype), h) - } - - case OMAKESLICE: - l := n.Left - r := n.Right - if r == nil { - r = safeexpr(l, init) - l = r - } - t := n.Type - if t.Elem().NotInHeap() { - yyerror("%v can't be allocated in Go; it is incomplete (or unallocatable)", t.Elem()) - } - if n.Esc == EscNone { - if why := heapAllocReason(n); why != "" { - Fatalf("%v has EscNone, but %v", n, why) - } - // var arr [r]T - // n = arr[:l] - i := indexconst(r) - if i < 0 { - Fatalf("walkexpr: invalid index %v", r) - } - - // cap is constrained to [0,2^31) or [0,2^63) depending on whether - // we're in 32-bit or 64-bit systems. So it's safe to do: - // - // if uint64(len) > cap { - // if len < 0 { panicmakeslicelen() } - // panicmakeslicecap() - // } - nif := nod(OIF, nod(OGT, conv(l, types.Types[TUINT64]), nodintconst(i)), nil) - niflen := nod(OIF, nod(OLT, l, nodintconst(0)), nil) - niflen.Nbody.Set1(mkcall("panicmakeslicelen", nil, init)) - nif.Nbody.Append(niflen, mkcall("panicmakeslicecap", nil, init)) - nif = typecheck(nif, ctxStmt) - init.Append(nif) - - t = types.NewArray(t.Elem(), i) // [r]T - var_ := temp(t) - a := nod(OAS, var_, nil) // zero temp - a = typecheck(a, ctxStmt) - init.Append(a) - r := nod(OSLICE, var_, nil) // arr[:l] - r.SetSliceBounds(nil, l, nil) - r = conv(r, n.Type) // in case n.Type is named. - r = typecheck(r, ctxExpr) - r = walkexpr(r, init) - n = r - } else { - // n escapes; set up a call to makeslice. - // When len and cap can fit into int, use makeslice instead of - // makeslice64, which is faster and shorter on 32 bit platforms. - - len, cap := l, r - - fnname := "makeslice64" - argtype := types.Types[TINT64] - - // Type checking guarantees that TIDEAL len/cap are positive and fit in an int. - // The case of len or cap overflow when converting TUINT or TUINTPTR to TINT - // will be handled by the negative range checks in makeslice during runtime. - if (len.Type.IsKind(TIDEAL) || maxintval[len.Type.Etype].Cmp(maxintval[TUINT]) <= 0) && - (cap.Type.IsKind(TIDEAL) || maxintval[cap.Type.Etype].Cmp(maxintval[TUINT]) <= 0) { - fnname = "makeslice" - argtype = types.Types[TINT] - } - - m := nod(OSLICEHEADER, nil, nil) - m.Type = t - - fn := syslook(fnname) - m.Left = mkcall1(fn, types.Types[TUNSAFEPTR], init, typename(t.Elem()), conv(len, argtype), conv(cap, argtype)) - m.Left.MarkNonNil() - m.List.Set2(conv(len, types.Types[TINT]), conv(cap, types.Types[TINT])) - - m = typecheck(m, ctxExpr) - m = walkexpr(m, init) - n = m - } - - case OMAKESLICECOPY: - if n.Esc == EscNone { - Fatalf("OMAKESLICECOPY with EscNone: %v", n) - } - - t := n.Type - if t.Elem().NotInHeap() { - yyerror("%v can't be allocated in Go; it is incomplete (or unallocatable)", t.Elem()) - } - - length := conv(n.Left, types.Types[TINT]) - copylen := nod(OLEN, n.Right, nil) - copyptr := nod(OSPTR, n.Right, nil) - - if !t.Elem().HasPointers() && n.Bounded() { - // When len(to)==len(from) and elements have no pointers: - // replace make+copy with runtime.mallocgc+runtime.memmove. - - // We do not check for overflow of len(to)*elem.Width here - // since len(from) is an existing checked slice capacity - // with same elem.Width for the from slice. - size := nod(OMUL, conv(length, types.Types[TUINTPTR]), conv(nodintconst(t.Elem().Width), types.Types[TUINTPTR])) - - // instantiate mallocgc(size uintptr, typ *byte, needszero bool) unsafe.Pointer - fn := syslook("mallocgc") - sh := nod(OSLICEHEADER, nil, nil) - sh.Left = mkcall1(fn, types.Types[TUNSAFEPTR], init, size, nodnil(), nodbool(false)) - sh.Left.MarkNonNil() - sh.List.Set2(length, length) - sh.Type = t - - s := temp(t) - r := typecheck(nod(OAS, s, sh), ctxStmt) - r = walkexpr(r, init) - init.Append(r) - - // instantiate memmove(to *any, frm *any, size uintptr) - fn = syslook("memmove") - fn = substArgTypes(fn, t.Elem(), t.Elem()) - ncopy := mkcall1(fn, nil, init, nod(OSPTR, s, nil), copyptr, size) - ncopy = typecheck(ncopy, ctxStmt) - ncopy = walkexpr(ncopy, init) - init.Append(ncopy) - - n = s - } else { // Replace make+copy with runtime.makeslicecopy. - // instantiate makeslicecopy(typ *byte, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer - fn := syslook("makeslicecopy") - s := nod(OSLICEHEADER, nil, nil) - s.Left = mkcall1(fn, types.Types[TUNSAFEPTR], init, typename(t.Elem()), length, copylen, conv(copyptr, types.Types[TUNSAFEPTR])) - s.Left.MarkNonNil() - s.List.Set2(length, length) - s.Type = t - n = typecheck(s, ctxExpr) - n = walkexpr(n, init) - } - - case ORUNESTR: - a := nodnil() - if n.Esc == EscNone { - t := types.NewArray(types.Types[TUINT8], 4) - a = nod(OADDR, temp(t), nil) - } - // intstring(*[4]byte, rune) - n = mkcall("intstring", n.Type, init, a, conv(n.Left, types.Types[TINT64])) - - case OBYTES2STR, ORUNES2STR: - a := nodnil() - if n.Esc == EscNone { - // Create temporary buffer for string on stack. - t := types.NewArray(types.Types[TUINT8], tmpstringbufsize) - a = nod(OADDR, temp(t), nil) - } - if n.Op == ORUNES2STR { - // slicerunetostring(*[32]byte, []rune) string - n = mkcall("slicerunetostring", n.Type, init, a, n.Left) - } else { - // slicebytetostring(*[32]byte, ptr *byte, n int) string - n.Left = cheapexpr(n.Left, init) - ptr, len := n.Left.backingArrayPtrLen() - n = mkcall("slicebytetostring", n.Type, init, a, ptr, len) - } - - case OBYTES2STRTMP: - n.Left = walkexpr(n.Left, init) - if !instrumenting { - // Let the backend handle OBYTES2STRTMP directly - // to avoid a function call to slicebytetostringtmp. - break - } - // slicebytetostringtmp(ptr *byte, n int) string - n.Left = cheapexpr(n.Left, init) - ptr, len := n.Left.backingArrayPtrLen() - n = mkcall("slicebytetostringtmp", n.Type, init, ptr, len) - - case OSTR2BYTES: - s := n.Left - if Isconst(s, CTSTR) { - sc := s.StringVal() - - // Allocate a [n]byte of the right size. - t := types.NewArray(types.Types[TUINT8], int64(len(sc))) - var a *Node - if n.Esc == EscNone && len(sc) <= int(maxImplicitStackVarSize) { - a = nod(OADDR, temp(t), nil) - } else { - a = callnew(t) - } - p := temp(t.PtrTo()) // *[n]byte - init.Append(typecheck(nod(OAS, p, a), ctxStmt)) - - // Copy from the static string data to the [n]byte. - if len(sc) > 0 { - as := nod(OAS, - nod(ODEREF, p, nil), - nod(ODEREF, convnop(nod(OSPTR, s, nil), t.PtrTo()), nil)) - as = typecheck(as, ctxStmt) - as = walkstmt(as) - init.Append(as) - } - - // Slice the [n]byte to a []byte. - n.Op = OSLICEARR - n.Left = p - n = walkexpr(n, init) - break - } - - a := nodnil() - if n.Esc == EscNone { - // Create temporary buffer for slice on stack. - t := types.NewArray(types.Types[TUINT8], tmpstringbufsize) - a = nod(OADDR, temp(t), nil) - } - // stringtoslicebyte(*32[byte], string) []byte - n = mkcall("stringtoslicebyte", n.Type, init, a, conv(s, types.Types[TSTRING])) - - case OSTR2BYTESTMP: - // []byte(string) conversion that creates a slice - // referring to the actual string bytes. - // This conversion is handled later by the backend and - // is only for use by internal compiler optimizations - // that know that the slice won't be mutated. - // The only such case today is: - // for i, c := range []byte(string) - n.Left = walkexpr(n.Left, init) - - case OSTR2RUNES: - a := nodnil() - if n.Esc == EscNone { - // Create temporary buffer for slice on stack. - t := types.NewArray(types.Types[TINT32], tmpstringbufsize) - a = nod(OADDR, temp(t), nil) - } - // stringtoslicerune(*[32]rune, string) []rune - n = mkcall("stringtoslicerune", n.Type, init, a, conv(n.Left, types.Types[TSTRING])) - - case OARRAYLIT, OSLICELIT, OMAPLIT, OSTRUCTLIT, OPTRLIT: - if isStaticCompositeLiteral(n) && !canSSAType(n.Type) { - // n can be directly represented in the read-only data section. - // Make direct reference to the static data. See issue 12841. - vstat := readonlystaticname(n.Type) - fixedlit(inInitFunction, initKindStatic, n, vstat, init) - n = vstat - n = typecheck(n, ctxExpr) - break - } - var_ := temp(n.Type) - anylit(n, var_, init) - n = var_ - - case OSEND: - n1 := n.Right - n1 = assignconv(n1, n.Left.Type.Elem(), "chan send") - n1 = walkexpr(n1, init) - n1 = nod(OADDR, n1, nil) - n = mkcall1(chanfn("chansend1", 2, n.Left.Type), nil, init, n.Left, n1) - - case OCLOSURE: - n = walkclosure(n, init) - - case OCALLPART: - n = walkpartialcall(n, init) - } - - // Expressions that are constant at run time but not - // considered const by the language spec are not turned into - // constants until walk. For example, if n is y%1 == 0, the - // walk of y%1 may have replaced it by 0. - // Check whether n with its updated args is itself now a constant. - t := n.Type - evconst(n) - if n.Type != t { - Fatalf("evconst changed Type: %v had type %v, now %v", n, t, n.Type) - } - if n.Op == OLITERAL { - n = typecheck(n, ctxExpr) - // Emit string symbol now to avoid emitting - // any concurrently during the backend. - if s, ok := n.Val().U.(string); ok { - _ = stringsym(n.Pos, s) - } - } - - updateHasCall(n) - - if Debug.w != 0 && n != nil { - Dump("after walk expr", n) - } - - lineno = lno - return n -} - -// markTypeUsedInInterface marks that type t is converted to an interface. -// This information is used in the linker in dead method elimination. -func markTypeUsedInInterface(t *types.Type, from *obj.LSym) { - tsym := typenamesym(t).Linksym() - // Emit a marker relocation. The linker will know the type is converted - // to an interface if "from" is reachable. - r := obj.Addrel(from) - r.Sym = tsym - r.Type = objabi.R_USEIFACE -} - -// markUsedIfaceMethod marks that an interface method is used in the current -// function. n is OCALLINTER node. -func markUsedIfaceMethod(n *Node) { - ityp := n.Left.Left.Type - tsym := typenamesym(ityp).Linksym() - r := obj.Addrel(Curfn.Func.lsym) - r.Sym = tsym - // n.Left.Xoffset is the method index * Widthptr (the offset of code pointer - // in itab). - midx := n.Left.Xoffset / int64(Widthptr) - r.Add = ifaceMethodOffset(ityp, midx) - r.Type = objabi.R_USEIFACEMETHOD -} - -// rtconvfn returns the parameter and result types that will be used by a -// runtime function to convert from type src to type dst. The runtime function -// name can be derived from the names of the returned types. -// -// If no such function is necessary, it returns (Txxx, Txxx). -func rtconvfn(src, dst *types.Type) (param, result types.EType) { - if thearch.SoftFloat { - return Txxx, Txxx - } - - switch thearch.LinkArch.Family { - case sys.ARM, sys.MIPS: - if src.IsFloat() { - switch dst.Etype { - case TINT64, TUINT64: - return TFLOAT64, dst.Etype - } - } - if dst.IsFloat() { - switch src.Etype { - case TINT64, TUINT64: - return src.Etype, TFLOAT64 - } - } - - case sys.I386: - if src.IsFloat() { - switch dst.Etype { - case TINT64, TUINT64: - return TFLOAT64, dst.Etype - case TUINT32, TUINT, TUINTPTR: - return TFLOAT64, TUINT32 - } - } - if dst.IsFloat() { - switch src.Etype { - case TINT64, TUINT64: - return src.Etype, TFLOAT64 - case TUINT32, TUINT, TUINTPTR: - return TUINT32, TFLOAT64 - } - } - } - return Txxx, Txxx -} - -// TODO(josharian): combine this with its caller and simplify -func reduceSlice(n *Node) *Node { - low, high, max := n.SliceBounds() - if high != nil && high.Op == OLEN && samesafeexpr(n.Left, high.Left) { - // Reduce x[i:len(x)] to x[i:]. - high = nil - } - n.SetSliceBounds(low, high, max) - if (n.Op == OSLICE || n.Op == OSLICESTR) && low == nil && high == nil { - // Reduce x[:] to x. - if Debug_slice > 0 { - Warn("slice: omit slice operation") - } - return n.Left - } - return n -} - -func ascompatee1(l *Node, r *Node, init *Nodes) *Node { - // convas will turn map assigns into function calls, - // making it impossible for reorder3 to work. - n := nod(OAS, l, r) - - if l.Op == OINDEXMAP { - return n - } - - return convas(n, init) -} - -func ascompatee(op Op, nl, nr []*Node, init *Nodes) []*Node { - // check assign expression list to - // an expression list. called in - // expr-list = expr-list - - // ensure order of evaluation for function calls - for i := range nl { - nl[i] = safeexpr(nl[i], init) - } - for i1 := range nr { - nr[i1] = safeexpr(nr[i1], init) - } - - var nn []*Node - i := 0 - for ; i < len(nl); i++ { - if i >= len(nr) { - break - } - // Do not generate 'x = x' during return. See issue 4014. - if op == ORETURN && samesafeexpr(nl[i], nr[i]) { - continue - } - nn = append(nn, ascompatee1(nl[i], nr[i], init)) - } - - // cannot happen: caller checked that lists had same length - if i < len(nl) || i < len(nr) { - var nln, nrn Nodes - nln.Set(nl) - nrn.Set(nr) - Fatalf("error in shape across %+v %v %+v / %d %d [%s]", nln, op, nrn, len(nl), len(nr), Curfn.funcname()) - } - return nn -} - -// fncall reports whether assigning an rvalue of type rt to an lvalue l might involve a function call. -func fncall(l *Node, rt *types.Type) bool { - if l.HasCall() || l.Op == OINDEXMAP { - return true - } - if types.Identical(l.Type, rt) { - return false - } - // There might be a conversion required, which might involve a runtime call. - return true -} - -// check assign type list to -// an expression list. called in -// expr-list = func() -func ascompatet(nl Nodes, nr *types.Type) []*Node { - if nl.Len() != nr.NumFields() { - Fatalf("ascompatet: assignment count mismatch: %d = %d", nl.Len(), nr.NumFields()) - } - - var nn, mm Nodes - for i, l := range nl.Slice() { - if l.isBlank() { - continue - } - r := nr.Field(i) - - // Any assignment to an lvalue that might cause a function call must be - // deferred until all the returned values have been read. - if fncall(l, r.Type) { - tmp := temp(r.Type) - tmp = typecheck(tmp, ctxExpr) - a := nod(OAS, l, tmp) - a = convas(a, &mm) - mm.Append(a) - l = tmp - } - - res := nod(ORESULT, nil, nil) - res.Xoffset = Ctxt.FixedFrameSize() + r.Offset - res.Type = r.Type - res.SetTypecheck(1) - - a := nod(OAS, l, res) - a = convas(a, &nn) - updateHasCall(a) - if a.HasCall() { - Dump("ascompatet ucount", a) - Fatalf("ascompatet: too many function calls evaluating parameters") - } - - nn.Append(a) - } - return append(nn.Slice(), mm.Slice()...) -} - -// package all the arguments that match a ... T parameter into a []T. -func mkdotargslice(typ *types.Type, args []*Node) *Node { - var n *Node - if len(args) == 0 { - n = nodnil() - n.Type = typ - } else { - n = nod(OCOMPLIT, nil, typenod(typ)) - n.List.Append(args...) - n.SetImplicit(true) - } - - n = typecheck(n, ctxExpr) - if n.Type == nil { - Fatalf("mkdotargslice: typecheck failed") - } - return n -} - -// fixVariadicCall rewrites calls to variadic functions to use an -// explicit ... argument if one is not already present. -func fixVariadicCall(call *Node) { - fntype := call.Left.Type - if !fntype.IsVariadic() || call.IsDDD() { - return - } - - vi := fntype.NumParams() - 1 - vt := fntype.Params().Field(vi).Type - - args := call.List.Slice() - extra := args[vi:] - slice := mkdotargslice(vt, extra) - for i := range extra { - extra[i] = nil // allow GC - } - - call.List.Set(append(args[:vi], slice)) - call.SetIsDDD(true) -} - -func walkCall(n *Node, init *Nodes) { - if n.Rlist.Len() != 0 { - return // already walked - } - - params := n.Left.Type.Params() - args := n.List.Slice() - - n.Left = walkexpr(n.Left, init) - walkexprlist(args, init) - - // If this is a method call, add the receiver at the beginning of the args. - if n.Op == OCALLMETH { - withRecv := make([]*Node, len(args)+1) - withRecv[0] = n.Left.Left - n.Left.Left = nil - copy(withRecv[1:], args) - args = withRecv - } - - // For any argument whose evaluation might require a function call, - // store that argument into a temporary variable, - // to prevent that calls from clobbering arguments already on the stack. - // When instrumenting, all arguments might require function calls. - var tempAssigns []*Node - for i, arg := range args { - updateHasCall(arg) - // Determine param type. - var t *types.Type - if n.Op == OCALLMETH { - if i == 0 { - t = n.Left.Type.Recv().Type - } else { - t = params.Field(i - 1).Type - } - } else { - t = params.Field(i).Type - } - if instrumenting || fncall(arg, t) { - // make assignment of fncall to tempAt - tmp := temp(t) - a := nod(OAS, tmp, arg) - a = convas(a, init) - tempAssigns = append(tempAssigns, a) - // replace arg with temp - args[i] = tmp - } - } - - n.List.Set(tempAssigns) - n.Rlist.Set(args) -} - -// generate code for print -func walkprint(nn *Node, init *Nodes) *Node { - // Hoist all the argument evaluation up before the lock. - walkexprlistcheap(nn.List.Slice(), init) - - // For println, add " " between elements and "\n" at the end. - if nn.Op == OPRINTN { - s := nn.List.Slice() - t := make([]*Node, 0, len(s)*2) - for i, n := range s { - if i != 0 { - t = append(t, nodstr(" ")) - } - t = append(t, n) - } - t = append(t, nodstr("\n")) - nn.List.Set(t) - } - - // Collapse runs of constant strings. - s := nn.List.Slice() - t := make([]*Node, 0, len(s)) - for i := 0; i < len(s); { - var strs []string - for i < len(s) && Isconst(s[i], CTSTR) { - strs = append(strs, s[i].StringVal()) - i++ - } - if len(strs) > 0 { - t = append(t, nodstr(strings.Join(strs, ""))) - } - if i < len(s) { - t = append(t, s[i]) - i++ - } - } - nn.List.Set(t) - - calls := []*Node{mkcall("printlock", nil, init)} - for i, n := range nn.List.Slice() { - if n.Op == OLITERAL { - switch n.Val().Ctype() { - case CTRUNE: - n = defaultlit(n, types.Runetype) - - case CTINT: - n = defaultlit(n, types.Types[TINT64]) - - case CTFLT: - n = defaultlit(n, types.Types[TFLOAT64]) - } - } - - if n.Op != OLITERAL && n.Type != nil && n.Type.Etype == TIDEAL { - n = defaultlit(n, types.Types[TINT64]) - } - n = defaultlit(n, nil) - nn.List.SetIndex(i, n) - if n.Type == nil || n.Type.Etype == TFORW { - continue - } - - var on *Node - switch n.Type.Etype { - case TINTER: - if n.Type.IsEmptyInterface() { - on = syslook("printeface") - } else { - on = syslook("printiface") - } - on = substArgTypes(on, n.Type) // any-1 - case TPTR: - if n.Type.Elem().NotInHeap() { - on = syslook("printuintptr") - n = nod(OCONV, n, nil) - n.Type = types.Types[TUNSAFEPTR] - n = nod(OCONV, n, nil) - n.Type = types.Types[TUINTPTR] - break - } - fallthrough - case TCHAN, TMAP, TFUNC, TUNSAFEPTR: - on = syslook("printpointer") - on = substArgTypes(on, n.Type) // any-1 - case TSLICE: - on = syslook("printslice") - on = substArgTypes(on, n.Type) // any-1 - case TUINT, TUINT8, TUINT16, TUINT32, TUINT64, TUINTPTR: - if isRuntimePkg(n.Type.Sym.Pkg) && n.Type.Sym.Name == "hex" { - on = syslook("printhex") - } else { - on = syslook("printuint") - } - case TINT, TINT8, TINT16, TINT32, TINT64: - on = syslook("printint") - case TFLOAT32, TFLOAT64: - on = syslook("printfloat") - case TCOMPLEX64, TCOMPLEX128: - on = syslook("printcomplex") - case TBOOL: - on = syslook("printbool") - case TSTRING: - cs := "" - if Isconst(n, CTSTR) { - cs = n.StringVal() - } - switch cs { - case " ": - on = syslook("printsp") - case "\n": - on = syslook("printnl") - default: - on = syslook("printstring") - } - default: - badtype(OPRINT, n.Type, nil) - continue - } - - r := nod(OCALL, on, nil) - if params := on.Type.Params().FieldSlice(); len(params) > 0 { - t := params[0].Type - if !types.Identical(t, n.Type) { - n = nod(OCONV, n, nil) - n.Type = t - } - r.List.Append(n) - } - calls = append(calls, r) - } - - calls = append(calls, mkcall("printunlock", nil, init)) - - typecheckslice(calls, ctxStmt) - walkexprlist(calls, init) - - r := nod(OEMPTY, nil, nil) - r = typecheck(r, ctxStmt) - r = walkexpr(r, init) - r.Ninit.Set(calls) - return r -} - -func callnew(t *types.Type) *Node { - dowidth(t) - n := nod(ONEWOBJ, typename(t), nil) - n.Type = types.NewPtr(t) - n.SetTypecheck(1) - n.MarkNonNil() - return n -} - -// isReflectHeaderDataField reports whether l is an expression p.Data -// where p has type reflect.SliceHeader or reflect.StringHeader. -func isReflectHeaderDataField(l *Node) bool { - if l.Type != types.Types[TUINTPTR] { - return false - } - - var tsym *types.Sym - switch l.Op { - case ODOT: - tsym = l.Left.Type.Sym - case ODOTPTR: - tsym = l.Left.Type.Elem().Sym - default: - return false - } - - if tsym == nil || l.Sym.Name != "Data" || tsym.Pkg.Path != "reflect" { - return false - } - return tsym.Name == "SliceHeader" || tsym.Name == "StringHeader" -} - -func convas(n *Node, init *Nodes) *Node { - if n.Op != OAS { - Fatalf("convas: not OAS %v", n.Op) - } - defer updateHasCall(n) - - n.SetTypecheck(1) - - if n.Left == nil || n.Right == nil { - return n - } - - lt := n.Left.Type - rt := n.Right.Type - if lt == nil || rt == nil { - return n - } - - if n.Left.isBlank() { - n.Right = defaultlit(n.Right, nil) - return n - } - - if !types.Identical(lt, rt) { - n.Right = assignconv(n.Right, lt, "assignment") - n.Right = walkexpr(n.Right, init) - } - dowidth(n.Right.Type) - - return n -} - -// from ascompat[ee] -// a,b = c,d -// simultaneous assignment. there cannot -// be later use of an earlier lvalue. -// -// function calls have been removed. -func reorder3(all []*Node) []*Node { - // If a needed expression may be affected by an - // earlier assignment, make an early copy of that - // expression and use the copy instead. - var early []*Node - - var mapinit Nodes - for i, n := range all { - l := n.Left - - // Save subexpressions needed on left side. - // Drill through non-dereferences. - for { - if l.Op == ODOT || l.Op == OPAREN { - l = l.Left - continue - } - - if l.Op == OINDEX && l.Left.Type.IsArray() { - l.Right = reorder3save(l.Right, all, i, &early) - l = l.Left - continue - } - - break - } - - switch l.Op { - default: - Fatalf("reorder3 unexpected lvalue %#v", l.Op) - - case ONAME: - break - - case OINDEX, OINDEXMAP: - l.Left = reorder3save(l.Left, all, i, &early) - l.Right = reorder3save(l.Right, all, i, &early) - if l.Op == OINDEXMAP { - all[i] = convas(all[i], &mapinit) - } - - case ODEREF, ODOTPTR: - l.Left = reorder3save(l.Left, all, i, &early) - } - - // Save expression on right side. - all[i].Right = reorder3save(all[i].Right, all, i, &early) - } - - early = append(mapinit.Slice(), early...) - return append(early, all...) -} - -// if the evaluation of *np would be affected by the -// assignments in all up to but not including the ith assignment, -// copy into a temporary during *early and -// replace *np with that temp. -// The result of reorder3save MUST be assigned back to n, e.g. -// n.Left = reorder3save(n.Left, all, i, early) -func reorder3save(n *Node, all []*Node, i int, early *[]*Node) *Node { - if !aliased(n, all[:i]) { - return n - } - - q := temp(n.Type) - q = nod(OAS, q, n) - q = typecheck(q, ctxStmt) - *early = append(*early, q) - return q.Left -} - -// what's the outer value that a write to n affects? -// outer value means containing struct or array. -func outervalue(n *Node) *Node { - for { - switch n.Op { - case OXDOT: - Fatalf("OXDOT in walk") - case ODOT, OPAREN, OCONVNOP: - n = n.Left - continue - case OINDEX: - if n.Left.Type != nil && n.Left.Type.IsArray() { - n = n.Left - continue - } - } - - return n - } -} - -// Is it possible that the computation of r might be -// affected by assignments in all? -func aliased(r *Node, all []*Node) bool { - if r == nil { - return false - } - - // Treat all fields of a struct as referring to the whole struct. - // We could do better but we would have to keep track of the fields. - for r.Op == ODOT { - r = r.Left - } - - // Look for obvious aliasing: a variable being assigned - // during the all list and appearing in n. - // Also record whether there are any writes to addressable - // memory (either main memory or variables whose addresses - // have been taken). - memwrite := false - for _, as := range all { - // We can ignore assignments to blank. - if as.Left.isBlank() { - continue - } - - l := outervalue(as.Left) - if l.Op != ONAME { - memwrite = true - continue - } - - switch l.Class() { - default: - Fatalf("unexpected class: %v, %v", l, l.Class()) - - case PAUTOHEAP, PEXTERN: - memwrite = true - continue - - case PPARAMOUT: - // Assignments to a result parameter in a function with defers - // becomes visible early if evaluation of any later expression - // panics (#43835). - if Curfn.Func.HasDefer() { - return true - } - fallthrough - case PAUTO, PPARAM: - if l.Name.Addrtaken() { - memwrite = true - continue - } - - if vmatch2(l, r) { - // Direct hit: l appears in r. - return true - } - } - } - - // The variables being written do not appear in r. - // However, r might refer to computed addresses - // that are being written. - - // If no computed addresses are affected by the writes, no aliasing. - if !memwrite { - return false - } - - // If r does not refer to computed addresses - // (that is, if r only refers to variables whose addresses - // have not been taken), no aliasing. - if varexpr(r) { - return false - } - - // Otherwise, both the writes and r refer to computed memory addresses. - // Assume that they might conflict. - return true -} - -// does the evaluation of n only refer to variables -// whose addresses have not been taken? -// (and no other memory) -func varexpr(n *Node) bool { - if n == nil { - return true - } - - switch n.Op { - case OLITERAL: - return true - - case ONAME: - switch n.Class() { - case PAUTO, PPARAM, PPARAMOUT: - if !n.Name.Addrtaken() { - return true - } - } - - return false - - case OADD, - OSUB, - OOR, - OXOR, - OMUL, - ODIV, - OMOD, - OLSH, - ORSH, - OAND, - OANDNOT, - OPLUS, - ONEG, - OBITNOT, - OPAREN, - OANDAND, - OOROR, - OCONV, - OCONVNOP, - OCONVIFACE, - ODOTTYPE: - return varexpr(n.Left) && varexpr(n.Right) - - case ODOT: // but not ODOTPTR - // Should have been handled in aliased. - Fatalf("varexpr unexpected ODOT") - } - - // Be conservative. - return false -} - -// is the name l mentioned in r? -func vmatch2(l *Node, r *Node) bool { - if r == nil { - return false - } - switch r.Op { - // match each right given left - case ONAME: - return l == r - - case OLITERAL: - return false - } - - if vmatch2(l, r.Left) { - return true - } - if vmatch2(l, r.Right) { - return true - } - for _, n := range r.List.Slice() { - if vmatch2(l, n) { - return true - } - } - return false -} - -// is any name mentioned in l also mentioned in r? -// called by sinit.go -func vmatch1(l *Node, r *Node) bool { - // isolate all left sides - if l == nil || r == nil { - return false - } - switch l.Op { - case ONAME: - switch l.Class() { - case PPARAM, PAUTO: - break - - default: - // assignment to non-stack variable must be - // delayed if right has function calls. - if r.HasCall() { - return true - } - } - - return vmatch2(l, r) - - case OLITERAL: - return false - } - - if vmatch1(l.Left, r) { - return true - } - if vmatch1(l.Right, r) { - return true - } - for _, n := range l.List.Slice() { - if vmatch1(n, r) { - return true - } - } - return false -} - -// paramstoheap returns code to allocate memory for heap-escaped parameters -// and to copy non-result parameters' values from the stack. -func paramstoheap(params *types.Type) []*Node { - var nn []*Node - for _, t := range params.Fields().Slice() { - v := asNode(t.Nname) - if v != nil && v.Sym != nil && strings.HasPrefix(v.Sym.Name, "~r") { // unnamed result - v = nil - } - if v == nil { - continue - } - - if stackcopy := v.Name.Param.Stackcopy; stackcopy != nil { - nn = append(nn, walkstmt(nod(ODCL, v, nil))) - if stackcopy.Class() == PPARAM { - nn = append(nn, walkstmt(typecheck(nod(OAS, v, stackcopy), ctxStmt))) - } - } - } - - return nn -} - -// zeroResults zeros the return values at the start of the function. -// We need to do this very early in the function. Defer might stop a -// panic and show the return values as they exist at the time of -// panic. For precise stacks, the garbage collector assumes results -// are always live, so we need to zero them before any allocations, -// even allocations to move params/results to the heap. -// The generated code is added to Curfn's Enter list. -func zeroResults() { - for _, f := range Curfn.Type.Results().Fields().Slice() { - v := asNode(f.Nname) - if v != nil && v.Name.Param.Heapaddr != nil { - // The local which points to the return value is the - // thing that needs zeroing. This is already handled - // by a Needzero annotation in plive.go:livenessepilogue. - continue - } - if v.isParamHeapCopy() { - // TODO(josharian/khr): Investigate whether we can switch to "continue" here, - // and document more in either case. - // In the review of CL 114797, Keith wrote (roughly): - // I don't think the zeroing below matters. - // The stack return value will never be marked as live anywhere in the function. - // It is not written to until deferreturn returns. - v = v.Name.Param.Stackcopy - } - // Zero the stack location containing f. - Curfn.Func.Enter.Append(nodl(Curfn.Pos, OAS, v, nil)) - } -} - -// returnsfromheap returns code to copy values for heap-escaped parameters -// back to the stack. -func returnsfromheap(params *types.Type) []*Node { - var nn []*Node - for _, t := range params.Fields().Slice() { - v := asNode(t.Nname) - if v == nil { - continue - } - if stackcopy := v.Name.Param.Stackcopy; stackcopy != nil && stackcopy.Class() == PPARAMOUT { - nn = append(nn, walkstmt(typecheck(nod(OAS, stackcopy, v), ctxStmt))) - } - } - - return nn -} - -// heapmoves generates code to handle migrating heap-escaped parameters -// between the stack and the heap. The generated code is added to Curfn's -// Enter and Exit lists. -func heapmoves() { - lno := lineno - lineno = Curfn.Pos - nn := paramstoheap(Curfn.Type.Recvs()) - nn = append(nn, paramstoheap(Curfn.Type.Params())...) - nn = append(nn, paramstoheap(Curfn.Type.Results())...) - Curfn.Func.Enter.Append(nn...) - lineno = Curfn.Func.Endlineno - Curfn.Func.Exit.Append(returnsfromheap(Curfn.Type.Results())...) - lineno = lno -} - -func vmkcall(fn *Node, t *types.Type, init *Nodes, va []*Node) *Node { - if fn.Type == nil || fn.Type.Etype != TFUNC { - Fatalf("mkcall %v %v", fn, fn.Type) - } - - n := fn.Type.NumParams() - if n != len(va) { - Fatalf("vmkcall %v needs %v args got %v", fn, n, len(va)) - } - - r := nod(OCALL, fn, nil) - r.List.Set(va) - if fn.Type.NumResults() > 0 { - r = typecheck(r, ctxExpr|ctxMultiOK) - } else { - r = typecheck(r, ctxStmt) - } - r = walkexpr(r, init) - r.Type = t - return r -} - -func mkcall(name string, t *types.Type, init *Nodes, args ...*Node) *Node { - return vmkcall(syslook(name), t, init, args) -} - -func mkcall1(fn *Node, t *types.Type, init *Nodes, args ...*Node) *Node { - return vmkcall(fn, t, init, args) -} - -func conv(n *Node, t *types.Type) *Node { - if types.Identical(n.Type, t) { - return n - } - n = nod(OCONV, n, nil) - n.Type = t - n = typecheck(n, ctxExpr) - return n -} - -// convnop converts node n to type t using the OCONVNOP op -// and typechecks the result with ctxExpr. -func convnop(n *Node, t *types.Type) *Node { - if types.Identical(n.Type, t) { - return n - } - n = nod(OCONVNOP, n, nil) - n.Type = t - n = typecheck(n, ctxExpr) - return n -} - -// byteindex converts n, which is byte-sized, to an int used to index into an array. -// We cannot use conv, because we allow converting bool to int here, -// which is forbidden in user code. -func byteindex(n *Node) *Node { - // We cannot convert from bool to int directly. - // While converting from int8 to int is possible, it would yield - // the wrong result for negative values. - // Reinterpreting the value as an unsigned byte solves both cases. - if !types.Identical(n.Type, types.Types[TUINT8]) { - n = nod(OCONV, n, nil) - n.Type = types.Types[TUINT8] - n.SetTypecheck(1) - } - n = nod(OCONV, n, nil) - n.Type = types.Types[TINT] - n.SetTypecheck(1) - return n -} - -func chanfn(name string, n int, t *types.Type) *Node { - if !t.IsChan() { - Fatalf("chanfn %v", t) - } - fn := syslook(name) - switch n { - default: - Fatalf("chanfn %d", n) - case 1: - fn = substArgTypes(fn, t.Elem()) - case 2: - fn = substArgTypes(fn, t.Elem(), t.Elem()) - } - return fn -} - -func mapfn(name string, t *types.Type) *Node { - if !t.IsMap() { - Fatalf("mapfn %v", t) - } - fn := syslook(name) - fn = substArgTypes(fn, t.Key(), t.Elem(), t.Key(), t.Elem()) - return fn -} - -func mapfndel(name string, t *types.Type) *Node { - if !t.IsMap() { - Fatalf("mapfn %v", t) - } - fn := syslook(name) - fn = substArgTypes(fn, t.Key(), t.Elem(), t.Key()) - return fn -} - -const ( - mapslow = iota - mapfast32 - mapfast32ptr - mapfast64 - mapfast64ptr - mapfaststr - nmapfast -) - -type mapnames [nmapfast]string - -func mkmapnames(base string, ptr string) mapnames { - return mapnames{base, base + "_fast32", base + "_fast32" + ptr, base + "_fast64", base + "_fast64" + ptr, base + "_faststr"} -} - -var mapaccess1 = mkmapnames("mapaccess1", "") -var mapaccess2 = mkmapnames("mapaccess2", "") -var mapassign = mkmapnames("mapassign", "ptr") -var mapdelete = mkmapnames("mapdelete", "") - -func mapfast(t *types.Type) int { - // Check runtime/map.go:maxElemSize before changing. - if t.Elem().Width > 128 { - return mapslow - } - switch algtype(t.Key()) { - case AMEM32: - if !t.Key().HasPointers() { - return mapfast32 - } - if Widthptr == 4 { - return mapfast32ptr - } - Fatalf("small pointer %v", t.Key()) - case AMEM64: - if !t.Key().HasPointers() { - return mapfast64 - } - if Widthptr == 8 { - return mapfast64ptr - } - // Two-word object, at least one of which is a pointer. - // Use the slow path. - case ASTRING: - return mapfaststr - } - return mapslow -} - -func writebarrierfn(name string, l *types.Type, r *types.Type) *Node { - fn := syslook(name) - fn = substArgTypes(fn, l, r) - return fn -} - -func addstr(n *Node, init *Nodes) *Node { - // order.expr rewrote OADDSTR to have a list of strings. - c := n.List.Len() - - if c < 2 { - Fatalf("addstr count %d too small", c) - } - - buf := nodnil() - if n.Esc == EscNone { - sz := int64(0) - for _, n1 := range n.List.Slice() { - if n1.Op == OLITERAL { - sz += int64(len(n1.StringVal())) - } - } - - // Don't allocate the buffer if the result won't fit. - if sz < tmpstringbufsize { - // Create temporary buffer for result string on stack. - t := types.NewArray(types.Types[TUINT8], tmpstringbufsize) - buf = nod(OADDR, temp(t), nil) - } - } - - // build list of string arguments - args := []*Node{buf} - for _, n2 := range n.List.Slice() { - args = append(args, conv(n2, types.Types[TSTRING])) - } - - var fn string - if c <= 5 { - // small numbers of strings use direct runtime helpers. - // note: order.expr knows this cutoff too. - fn = fmt.Sprintf("concatstring%d", c) - } else { - // large numbers of strings are passed to the runtime as a slice. - fn = "concatstrings" - - t := types.NewSlice(types.Types[TSTRING]) - slice := nod(OCOMPLIT, nil, typenod(t)) - if prealloc[n] != nil { - prealloc[slice] = prealloc[n] - } - slice.List.Set(args[1:]) // skip buf arg - args = []*Node{buf, slice} - slice.Esc = EscNone - } - - cat := syslook(fn) - r := nod(OCALL, cat, nil) - r.List.Set(args) - r = typecheck(r, ctxExpr) - r = walkexpr(r, init) - r.Type = n.Type - - return r -} - -func walkAppendArgs(n *Node, init *Nodes) { - walkexprlistsafe(n.List.Slice(), init) - - // walkexprlistsafe will leave OINDEX (s[n]) alone if both s - // and n are name or literal, but those may index the slice we're - // modifying here. Fix explicitly. - ls := n.List.Slice() - for i1, n1 := range ls { - ls[i1] = cheapexpr(n1, init) - } -} - -// expand append(l1, l2...) to -// init { -// s := l1 -// n := len(s) + len(l2) -// // Compare as uint so growslice can panic on overflow. -// if uint(n) > uint(cap(s)) { -// s = growslice(s, n) -// } -// s = s[:n] -// memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T)) -// } -// s -// -// l2 is allowed to be a string. -func appendslice(n *Node, init *Nodes) *Node { - walkAppendArgs(n, init) - - l1 := n.List.First() - l2 := n.List.Second() - l2 = cheapexpr(l2, init) - n.List.SetSecond(l2) - - var nodes Nodes - - // var s []T - s := temp(l1.Type) - nodes.Append(nod(OAS, s, l1)) // s = l1 - - elemtype := s.Type.Elem() - - // n := len(s) + len(l2) - nn := temp(types.Types[TINT]) - nodes.Append(nod(OAS, nn, nod(OADD, nod(OLEN, s, nil), nod(OLEN, l2, nil)))) - - // if uint(n) > uint(cap(s)) - nif := nod(OIF, nil, nil) - nuint := conv(nn, types.Types[TUINT]) - scapuint := conv(nod(OCAP, s, nil), types.Types[TUINT]) - nif.Left = nod(OGT, nuint, scapuint) - - // instantiate growslice(typ *type, []any, int) []any - fn := syslook("growslice") - fn = substArgTypes(fn, elemtype, elemtype) - - // s = growslice(T, s, n) - nif.Nbody.Set1(nod(OAS, s, mkcall1(fn, s.Type, &nif.Ninit, typename(elemtype), s, nn))) - nodes.Append(nif) - - // s = s[:n] - nt := nod(OSLICE, s, nil) - nt.SetSliceBounds(nil, nn, nil) - nt.SetBounded(true) - nodes.Append(nod(OAS, s, nt)) - - var ncopy *Node - if elemtype.HasPointers() { - // copy(s[len(l1):], l2) - nptr1 := nod(OSLICE, s, nil) - nptr1.Type = s.Type - nptr1.SetSliceBounds(nod(OLEN, l1, nil), nil, nil) - nptr1 = cheapexpr(nptr1, &nodes) - - nptr2 := l2 - - Curfn.Func.setWBPos(n.Pos) - - // instantiate typedslicecopy(typ *type, dstPtr *any, dstLen int, srcPtr *any, srcLen int) int - fn := syslook("typedslicecopy") - fn = substArgTypes(fn, l1.Type.Elem(), l2.Type.Elem()) - ptr1, len1 := nptr1.backingArrayPtrLen() - ptr2, len2 := nptr2.backingArrayPtrLen() - ncopy = mkcall1(fn, types.Types[TINT], &nodes, typename(elemtype), ptr1, len1, ptr2, len2) - } else if instrumenting && !compiling_runtime { - // rely on runtime to instrument: - // copy(s[len(l1):], l2) - // l2 can be a slice or string. - nptr1 := nod(OSLICE, s, nil) - nptr1.Type = s.Type - nptr1.SetSliceBounds(nod(OLEN, l1, nil), nil, nil) - nptr1 = cheapexpr(nptr1, &nodes) - nptr2 := l2 - - ptr1, len1 := nptr1.backingArrayPtrLen() - ptr2, len2 := nptr2.backingArrayPtrLen() - - fn := syslook("slicecopy") - fn = substArgTypes(fn, ptr1.Type.Elem(), ptr2.Type.Elem()) - ncopy = mkcall1(fn, types.Types[TINT], &nodes, ptr1, len1, ptr2, len2, nodintconst(elemtype.Width)) - } else { - // memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T)) - nptr1 := nod(OINDEX, s, nod(OLEN, l1, nil)) - nptr1.SetBounded(true) - nptr1 = nod(OADDR, nptr1, nil) - - nptr2 := nod(OSPTR, l2, nil) - - nwid := cheapexpr(conv(nod(OLEN, l2, nil), types.Types[TUINTPTR]), &nodes) - nwid = nod(OMUL, nwid, nodintconst(elemtype.Width)) - - // instantiate func memmove(to *any, frm *any, length uintptr) - fn := syslook("memmove") - fn = substArgTypes(fn, elemtype, elemtype) - ncopy = mkcall1(fn, nil, &nodes, nptr1, nptr2, nwid) - } - ln := append(nodes.Slice(), ncopy) - - typecheckslice(ln, ctxStmt) - walkstmtlist(ln) - init.Append(ln...) - return s -} - -// isAppendOfMake reports whether n is of the form append(x , make([]T, y)...). -// isAppendOfMake assumes n has already been typechecked. -func isAppendOfMake(n *Node) bool { - if Debug.N != 0 || instrumenting { - return false - } - - if n.Typecheck() == 0 { - Fatalf("missing typecheck: %+v", n) - } - - if n.Op != OAPPEND || !n.IsDDD() || n.List.Len() != 2 { - return false - } - - second := n.List.Second() - if second.Op != OMAKESLICE || second.Right != nil { - return false - } - - // y must be either an integer constant or the largest possible positive value - // of variable y needs to fit into an uint. - - // typecheck made sure that constant arguments to make are not negative and fit into an int. - - // The care of overflow of the len argument to make will be handled by an explicit check of int(len) < 0 during runtime. - y := second.Left - if !Isconst(y, CTINT) && maxintval[y.Type.Etype].Cmp(maxintval[TUINT]) > 0 { - return false - } - - return true -} - -// extendslice rewrites append(l1, make([]T, l2)...) to -// init { -// if l2 >= 0 { // Empty if block here for more meaningful node.SetLikely(true) -// } else { -// panicmakeslicelen() -// } -// s := l1 -// n := len(s) + l2 -// // Compare n and s as uint so growslice can panic on overflow of len(s) + l2. -// // cap is a positive int and n can become negative when len(s) + l2 -// // overflows int. Interpreting n when negative as uint makes it larger -// // than cap(s). growslice will check the int n arg and panic if n is -// // negative. This prevents the overflow from being undetected. -// if uint(n) > uint(cap(s)) { -// s = growslice(T, s, n) -// } -// s = s[:n] -// lptr := &l1[0] -// sptr := &s[0] -// if lptr == sptr || !T.HasPointers() { -// // growslice did not clear the whole underlying array (or did not get called) -// hp := &s[len(l1)] -// hn := l2 * sizeof(T) -// memclr(hp, hn) -// } -// } -// s -func extendslice(n *Node, init *Nodes) *Node { - // isAppendOfMake made sure all possible positive values of l2 fit into an uint. - // The case of l2 overflow when converting from e.g. uint to int is handled by an explicit - // check of l2 < 0 at runtime which is generated below. - l2 := conv(n.List.Second().Left, types.Types[TINT]) - l2 = typecheck(l2, ctxExpr) - n.List.SetSecond(l2) // walkAppendArgs expects l2 in n.List.Second(). - - walkAppendArgs(n, init) - - l1 := n.List.First() - l2 = n.List.Second() // re-read l2, as it may have been updated by walkAppendArgs - - var nodes []*Node - - // if l2 >= 0 (likely happens), do nothing - nifneg := nod(OIF, nod(OGE, l2, nodintconst(0)), nil) - nifneg.SetLikely(true) - - // else panicmakeslicelen() - nifneg.Rlist.Set1(mkcall("panicmakeslicelen", nil, init)) - nodes = append(nodes, nifneg) - - // s := l1 - s := temp(l1.Type) - nodes = append(nodes, nod(OAS, s, l1)) - - elemtype := s.Type.Elem() - - // n := len(s) + l2 - nn := temp(types.Types[TINT]) - nodes = append(nodes, nod(OAS, nn, nod(OADD, nod(OLEN, s, nil), l2))) - - // if uint(n) > uint(cap(s)) - nuint := conv(nn, types.Types[TUINT]) - capuint := conv(nod(OCAP, s, nil), types.Types[TUINT]) - nif := nod(OIF, nod(OGT, nuint, capuint), nil) - - // instantiate growslice(typ *type, old []any, newcap int) []any - fn := syslook("growslice") - fn = substArgTypes(fn, elemtype, elemtype) - - // s = growslice(T, s, n) - nif.Nbody.Set1(nod(OAS, s, mkcall1(fn, s.Type, &nif.Ninit, typename(elemtype), s, nn))) - nodes = append(nodes, nif) - - // s = s[:n] - nt := nod(OSLICE, s, nil) - nt.SetSliceBounds(nil, nn, nil) - nt.SetBounded(true) - nodes = append(nodes, nod(OAS, s, nt)) - - // lptr := &l1[0] - l1ptr := temp(l1.Type.Elem().PtrTo()) - tmp := nod(OSPTR, l1, nil) - nodes = append(nodes, nod(OAS, l1ptr, tmp)) - - // sptr := &s[0] - sptr := temp(elemtype.PtrTo()) - tmp = nod(OSPTR, s, nil) - nodes = append(nodes, nod(OAS, sptr, tmp)) - - // hp := &s[len(l1)] - hp := nod(OINDEX, s, nod(OLEN, l1, nil)) - hp.SetBounded(true) - hp = nod(OADDR, hp, nil) - hp = convnop(hp, types.Types[TUNSAFEPTR]) - - // hn := l2 * sizeof(elem(s)) - hn := nod(OMUL, l2, nodintconst(elemtype.Width)) - hn = conv(hn, types.Types[TUINTPTR]) - - clrname := "memclrNoHeapPointers" - hasPointers := elemtype.HasPointers() - if hasPointers { - clrname = "memclrHasPointers" - Curfn.Func.setWBPos(n.Pos) - } - - var clr Nodes - clrfn := mkcall(clrname, nil, &clr, hp, hn) - clr.Append(clrfn) - - if hasPointers { - // if l1ptr == sptr - nifclr := nod(OIF, nod(OEQ, l1ptr, sptr), nil) - nifclr.Nbody = clr - nodes = append(nodes, nifclr) - } else { - nodes = append(nodes, clr.Slice()...) - } - - typecheckslice(nodes, ctxStmt) - walkstmtlist(nodes) - init.Append(nodes...) - return s -} - -// Rewrite append(src, x, y, z) so that any side effects in -// x, y, z (including runtime panics) are evaluated in -// initialization statements before the append. -// For normal code generation, stop there and leave the -// rest to cgen_append. -// -// For race detector, expand append(src, a [, b]* ) to -// -// init { -// s := src -// const argc = len(args) - 1 -// if cap(s) - len(s) < argc { -// s = growslice(s, len(s)+argc) -// } -// n := len(s) -// s = s[:n+argc] -// s[n] = a -// s[n+1] = b -// ... -// } -// s -func walkappend(n *Node, init *Nodes, dst *Node) *Node { - if !samesafeexpr(dst, n.List.First()) { - n.List.SetFirst(safeexpr(n.List.First(), init)) - n.List.SetFirst(walkexpr(n.List.First(), init)) - } - walkexprlistsafe(n.List.Slice()[1:], init) - - nsrc := n.List.First() - - // walkexprlistsafe will leave OINDEX (s[n]) alone if both s - // and n are name or literal, but those may index the slice we're - // modifying here. Fix explicitly. - // Using cheapexpr also makes sure that the evaluation - // of all arguments (and especially any panics) happen - // before we begin to modify the slice in a visible way. - ls := n.List.Slice()[1:] - for i, n := range ls { - n = cheapexpr(n, init) - if !types.Identical(n.Type, nsrc.Type.Elem()) { - n = assignconv(n, nsrc.Type.Elem(), "append") - n = walkexpr(n, init) - } - ls[i] = n - } - - argc := n.List.Len() - 1 - if argc < 1 { - return nsrc - } - - // General case, with no function calls left as arguments. - // Leave for gen, except that instrumentation requires old form. - if !instrumenting || compiling_runtime { - return n - } - - var l []*Node - - ns := temp(nsrc.Type) - l = append(l, nod(OAS, ns, nsrc)) // s = src - - na := nodintconst(int64(argc)) // const argc - nx := nod(OIF, nil, nil) // if cap(s) - len(s) < argc - nx.Left = nod(OLT, nod(OSUB, nod(OCAP, ns, nil), nod(OLEN, ns, nil)), na) - - fn := syslook("growslice") // growslice(, old []T, mincap int) (ret []T) - fn = substArgTypes(fn, ns.Type.Elem(), ns.Type.Elem()) - - nx.Nbody.Set1(nod(OAS, ns, - mkcall1(fn, ns.Type, &nx.Ninit, typename(ns.Type.Elem()), ns, - nod(OADD, nod(OLEN, ns, nil), na)))) - - l = append(l, nx) - - nn := temp(types.Types[TINT]) - l = append(l, nod(OAS, nn, nod(OLEN, ns, nil))) // n = len(s) - - nx = nod(OSLICE, ns, nil) // ...s[:n+argc] - nx.SetSliceBounds(nil, nod(OADD, nn, na), nil) - nx.SetBounded(true) - l = append(l, nod(OAS, ns, nx)) // s = s[:n+argc] - - ls = n.List.Slice()[1:] - for i, n := range ls { - nx = nod(OINDEX, ns, nn) // s[n] ... - nx.SetBounded(true) - l = append(l, nod(OAS, nx, n)) // s[n] = arg - if i+1 < len(ls) { - l = append(l, nod(OAS, nn, nod(OADD, nn, nodintconst(1)))) // n = n + 1 - } - } - - typecheckslice(l, ctxStmt) - walkstmtlist(l) - init.Append(l...) - return ns -} - -// Lower copy(a, b) to a memmove call or a runtime call. -// -// init { -// n := len(a) -// if n > len(b) { n = len(b) } -// if a.ptr != b.ptr { memmove(a.ptr, b.ptr, n*sizeof(elem(a))) } -// } -// n; -// -// Also works if b is a string. -// -func copyany(n *Node, init *Nodes, runtimecall bool) *Node { - if n.Left.Type.Elem().HasPointers() { - Curfn.Func.setWBPos(n.Pos) - fn := writebarrierfn("typedslicecopy", n.Left.Type.Elem(), n.Right.Type.Elem()) - n.Left = cheapexpr(n.Left, init) - ptrL, lenL := n.Left.backingArrayPtrLen() - n.Right = cheapexpr(n.Right, init) - ptrR, lenR := n.Right.backingArrayPtrLen() - return mkcall1(fn, n.Type, init, typename(n.Left.Type.Elem()), ptrL, lenL, ptrR, lenR) - } - - if runtimecall { - // rely on runtime to instrument: - // copy(n.Left, n.Right) - // n.Right can be a slice or string. - - n.Left = cheapexpr(n.Left, init) - ptrL, lenL := n.Left.backingArrayPtrLen() - n.Right = cheapexpr(n.Right, init) - ptrR, lenR := n.Right.backingArrayPtrLen() - - fn := syslook("slicecopy") - fn = substArgTypes(fn, ptrL.Type.Elem(), ptrR.Type.Elem()) - - return mkcall1(fn, n.Type, init, ptrL, lenL, ptrR, lenR, nodintconst(n.Left.Type.Elem().Width)) - } - - n.Left = walkexpr(n.Left, init) - n.Right = walkexpr(n.Right, init) - nl := temp(n.Left.Type) - nr := temp(n.Right.Type) - var l []*Node - l = append(l, nod(OAS, nl, n.Left)) - l = append(l, nod(OAS, nr, n.Right)) - - nfrm := nod(OSPTR, nr, nil) - nto := nod(OSPTR, nl, nil) - - nlen := temp(types.Types[TINT]) - - // n = len(to) - l = append(l, nod(OAS, nlen, nod(OLEN, nl, nil))) - - // if n > len(frm) { n = len(frm) } - nif := nod(OIF, nil, nil) - - nif.Left = nod(OGT, nlen, nod(OLEN, nr, nil)) - nif.Nbody.Append(nod(OAS, nlen, nod(OLEN, nr, nil))) - l = append(l, nif) - - // if to.ptr != frm.ptr { memmove( ... ) } - ne := nod(OIF, nod(ONE, nto, nfrm), nil) - ne.SetLikely(true) - l = append(l, ne) - - fn := syslook("memmove") - fn = substArgTypes(fn, nl.Type.Elem(), nl.Type.Elem()) - nwid := temp(types.Types[TUINTPTR]) - setwid := nod(OAS, nwid, conv(nlen, types.Types[TUINTPTR])) - ne.Nbody.Append(setwid) - nwid = nod(OMUL, nwid, nodintconst(nl.Type.Elem().Width)) - call := mkcall1(fn, nil, init, nto, nfrm, nwid) - ne.Nbody.Append(call) - - typecheckslice(l, ctxStmt) - walkstmtlist(l) - init.Append(l...) - return nlen -} - -func eqfor(t *types.Type) (n *Node, needsize bool) { - // Should only arrive here with large memory or - // a struct/array containing a non-memory field/element. - // Small memory is handled inline, and single non-memory - // is handled by walkcompare. - switch a, _ := algtype1(t); a { - case AMEM: - n := syslook("memequal") - n = substArgTypes(n, t, t) - return n, true - case ASPECIAL: - sym := typesymprefix(".eq", t) - n := newname(sym) - setNodeNameFunc(n) - n.Type = functype(nil, []*Node{ - anonfield(types.NewPtr(t)), - anonfield(types.NewPtr(t)), - }, []*Node{ - anonfield(types.Types[TBOOL]), - }) - return n, false - } - Fatalf("eqfor %v", t) - return nil, false -} - -// The result of walkcompare MUST be assigned back to n, e.g. -// n.Left = walkcompare(n.Left, init) -func walkcompare(n *Node, init *Nodes) *Node { - if n.Left.Type.IsInterface() && n.Right.Type.IsInterface() && n.Left.Op != OLITERAL && n.Right.Op != OLITERAL { - return walkcompareInterface(n, init) - } - - if n.Left.Type.IsString() && n.Right.Type.IsString() { - return walkcompareString(n, init) - } - - n.Left = walkexpr(n.Left, init) - n.Right = walkexpr(n.Right, init) - - // Given mixed interface/concrete comparison, - // rewrite into types-equal && data-equal. - // This is efficient, avoids allocations, and avoids runtime calls. - if n.Left.Type.IsInterface() != n.Right.Type.IsInterface() { - // Preserve side-effects in case of short-circuiting; see #32187. - l := cheapexpr(n.Left, init) - r := cheapexpr(n.Right, init) - // Swap so that l is the interface value and r is the concrete value. - if n.Right.Type.IsInterface() { - l, r = r, l - } - - // Handle both == and !=. - eq := n.Op - andor := OOROR - if eq == OEQ { - andor = OANDAND - } - // Check for types equal. - // For empty interface, this is: - // l.tab == type(r) - // For non-empty interface, this is: - // l.tab != nil && l.tab._type == type(r) - var eqtype *Node - tab := nod(OITAB, l, nil) - rtyp := typename(r.Type) - if l.Type.IsEmptyInterface() { - tab.Type = types.NewPtr(types.Types[TUINT8]) - tab.SetTypecheck(1) - eqtype = nod(eq, tab, rtyp) - } else { - nonnil := nod(brcom(eq), nodnil(), tab) - match := nod(eq, itabType(tab), rtyp) - eqtype = nod(andor, nonnil, match) - } - // Check for data equal. - eqdata := nod(eq, ifaceData(n.Pos, l, r.Type), r) - // Put it all together. - expr := nod(andor, eqtype, eqdata) - n = finishcompare(n, expr, init) - return n - } - - // Must be comparison of array or struct. - // Otherwise back end handles it. - // While we're here, decide whether to - // inline or call an eq alg. - t := n.Left.Type - var inline bool - - maxcmpsize := int64(4) - unalignedLoad := canMergeLoads() - if unalignedLoad { - // Keep this low enough to generate less code than a function call. - maxcmpsize = 2 * int64(thearch.LinkArch.RegSize) - } - - switch t.Etype { - default: - if Debug_libfuzzer != 0 && t.IsInteger() { - n.Left = cheapexpr(n.Left, init) - n.Right = cheapexpr(n.Right, init) - - // If exactly one comparison operand is - // constant, invoke the constcmp functions - // instead, and arrange for the constant - // operand to be the first argument. - l, r := n.Left, n.Right - if r.Op == OLITERAL { - l, r = r, l - } - constcmp := l.Op == OLITERAL && r.Op != OLITERAL - - var fn string - var paramType *types.Type - switch t.Size() { - case 1: - fn = "libfuzzerTraceCmp1" - if constcmp { - fn = "libfuzzerTraceConstCmp1" - } - paramType = types.Types[TUINT8] - case 2: - fn = "libfuzzerTraceCmp2" - if constcmp { - fn = "libfuzzerTraceConstCmp2" - } - paramType = types.Types[TUINT16] - case 4: - fn = "libfuzzerTraceCmp4" - if constcmp { - fn = "libfuzzerTraceConstCmp4" - } - paramType = types.Types[TUINT32] - case 8: - fn = "libfuzzerTraceCmp8" - if constcmp { - fn = "libfuzzerTraceConstCmp8" - } - paramType = types.Types[TUINT64] - default: - Fatalf("unexpected integer size %d for %v", t.Size(), t) - } - init.Append(mkcall(fn, nil, init, tracecmpArg(l, paramType, init), tracecmpArg(r, paramType, init))) - } - return n - case TARRAY: - // We can compare several elements at once with 2/4/8 byte integer compares - inline = t.NumElem() <= 1 || (issimple[t.Elem().Etype] && (t.NumElem() <= 4 || t.Elem().Width*t.NumElem() <= maxcmpsize)) - case TSTRUCT: - inline = t.NumComponents(types.IgnoreBlankFields) <= 4 - } - - cmpl := n.Left - for cmpl != nil && cmpl.Op == OCONVNOP { - cmpl = cmpl.Left - } - cmpr := n.Right - for cmpr != nil && cmpr.Op == OCONVNOP { - cmpr = cmpr.Left - } - - // Chose not to inline. Call equality function directly. - if !inline { - // eq algs take pointers; cmpl and cmpr must be addressable - if !islvalue(cmpl) || !islvalue(cmpr) { - Fatalf("arguments of comparison must be lvalues - %v %v", cmpl, cmpr) - } - - fn, needsize := eqfor(t) - call := nod(OCALL, fn, nil) - call.List.Append(nod(OADDR, cmpl, nil)) - call.List.Append(nod(OADDR, cmpr, nil)) - if needsize { - call.List.Append(nodintconst(t.Width)) - } - res := call - if n.Op != OEQ { - res = nod(ONOT, res, nil) - } - n = finishcompare(n, res, init) - return n - } - - // inline: build boolean expression comparing element by element - andor := OANDAND - if n.Op == ONE { - andor = OOROR - } - var expr *Node - compare := func(el, er *Node) { - a := nod(n.Op, el, er) - if expr == nil { - expr = a - } else { - expr = nod(andor, expr, a) - } - } - cmpl = safeexpr(cmpl, init) - cmpr = safeexpr(cmpr, init) - if t.IsStruct() { - for _, f := range t.Fields().Slice() { - sym := f.Sym - if sym.IsBlank() { - continue - } - compare( - nodSym(OXDOT, cmpl, sym), - nodSym(OXDOT, cmpr, sym), - ) - } - } else { - step := int64(1) - remains := t.NumElem() * t.Elem().Width - combine64bit := unalignedLoad && Widthreg == 8 && t.Elem().Width <= 4 && t.Elem().IsInteger() - combine32bit := unalignedLoad && t.Elem().Width <= 2 && t.Elem().IsInteger() - combine16bit := unalignedLoad && t.Elem().Width == 1 && t.Elem().IsInteger() - for i := int64(0); remains > 0; { - var convType *types.Type - switch { - case remains >= 8 && combine64bit: - convType = types.Types[TINT64] - step = 8 / t.Elem().Width - case remains >= 4 && combine32bit: - convType = types.Types[TUINT32] - step = 4 / t.Elem().Width - case remains >= 2 && combine16bit: - convType = types.Types[TUINT16] - step = 2 / t.Elem().Width - default: - step = 1 - } - if step == 1 { - compare( - nod(OINDEX, cmpl, nodintconst(i)), - nod(OINDEX, cmpr, nodintconst(i)), - ) - i++ - remains -= t.Elem().Width - } else { - elemType := t.Elem().ToUnsigned() - cmplw := nod(OINDEX, cmpl, nodintconst(i)) - cmplw = conv(cmplw, elemType) // convert to unsigned - cmplw = conv(cmplw, convType) // widen - cmprw := nod(OINDEX, cmpr, nodintconst(i)) - cmprw = conv(cmprw, elemType) - cmprw = conv(cmprw, convType) - // For code like this: uint32(s[0]) | uint32(s[1])<<8 | uint32(s[2])<<16 ... - // ssa will generate a single large load. - for offset := int64(1); offset < step; offset++ { - lb := nod(OINDEX, cmpl, nodintconst(i+offset)) - lb = conv(lb, elemType) - lb = conv(lb, convType) - lb = nod(OLSH, lb, nodintconst(8*t.Elem().Width*offset)) - cmplw = nod(OOR, cmplw, lb) - rb := nod(OINDEX, cmpr, nodintconst(i+offset)) - rb = conv(rb, elemType) - rb = conv(rb, convType) - rb = nod(OLSH, rb, nodintconst(8*t.Elem().Width*offset)) - cmprw = nod(OOR, cmprw, rb) - } - compare(cmplw, cmprw) - i += step - remains -= step * t.Elem().Width - } - } - } - if expr == nil { - expr = nodbool(n.Op == OEQ) - // We still need to use cmpl and cmpr, in case they contain - // an expression which might panic. See issue 23837. - t := temp(cmpl.Type) - a1 := nod(OAS, t, cmpl) - a1 = typecheck(a1, ctxStmt) - a2 := nod(OAS, t, cmpr) - a2 = typecheck(a2, ctxStmt) - init.Append(a1, a2) - } - n = finishcompare(n, expr, init) - return n -} - -func tracecmpArg(n *Node, t *types.Type, init *Nodes) *Node { - // Ugly hack to avoid "constant -1 overflows uintptr" errors, etc. - if n.Op == OLITERAL && n.Type.IsSigned() && n.Int64Val() < 0 { - n = copyexpr(n, n.Type, init) - } - - return conv(n, t) -} - -func walkcompareInterface(n *Node, init *Nodes) *Node { - n.Right = cheapexpr(n.Right, init) - n.Left = cheapexpr(n.Left, init) - eqtab, eqdata := eqinterface(n.Left, n.Right) - var cmp *Node - if n.Op == OEQ { - cmp = nod(OANDAND, eqtab, eqdata) - } else { - eqtab.Op = ONE - cmp = nod(OOROR, eqtab, nod(ONOT, eqdata, nil)) - } - return finishcompare(n, cmp, init) -} - -func walkcompareString(n *Node, init *Nodes) *Node { - // Rewrite comparisons to short constant strings as length+byte-wise comparisons. - var cs, ncs *Node // const string, non-const string - switch { - case Isconst(n.Left, CTSTR) && Isconst(n.Right, CTSTR): - // ignore; will be constant evaluated - case Isconst(n.Left, CTSTR): - cs = n.Left - ncs = n.Right - case Isconst(n.Right, CTSTR): - cs = n.Right - ncs = n.Left - } - if cs != nil { - cmp := n.Op - // Our comparison below assumes that the non-constant string - // is on the left hand side, so rewrite "" cmp x to x cmp "". - // See issue 24817. - if Isconst(n.Left, CTSTR) { - cmp = brrev(cmp) - } - - // maxRewriteLen was chosen empirically. - // It is the value that minimizes cmd/go file size - // across most architectures. - // See the commit description for CL 26758 for details. - maxRewriteLen := 6 - // Some architectures can load unaligned byte sequence as 1 word. - // So we can cover longer strings with the same amount of code. - canCombineLoads := canMergeLoads() - combine64bit := false - if canCombineLoads { - // Keep this low enough to generate less code than a function call. - maxRewriteLen = 2 * thearch.LinkArch.RegSize - combine64bit = thearch.LinkArch.RegSize >= 8 - } - - var and Op - switch cmp { - case OEQ: - and = OANDAND - case ONE: - and = OOROR - default: - // Don't do byte-wise comparisons for <, <=, etc. - // They're fairly complicated. - // Length-only checks are ok, though. - maxRewriteLen = 0 - } - if s := cs.StringVal(); len(s) <= maxRewriteLen { - if len(s) > 0 { - ncs = safeexpr(ncs, init) - } - r := nod(cmp, nod(OLEN, ncs, nil), nodintconst(int64(len(s)))) - remains := len(s) - for i := 0; remains > 0; { - if remains == 1 || !canCombineLoads { - cb := nodintconst(int64(s[i])) - ncb := nod(OINDEX, ncs, nodintconst(int64(i))) - r = nod(and, r, nod(cmp, ncb, cb)) - remains-- - i++ - continue - } - var step int - var convType *types.Type - switch { - case remains >= 8 && combine64bit: - convType = types.Types[TINT64] - step = 8 - case remains >= 4: - convType = types.Types[TUINT32] - step = 4 - case remains >= 2: - convType = types.Types[TUINT16] - step = 2 - } - ncsubstr := nod(OINDEX, ncs, nodintconst(int64(i))) - ncsubstr = conv(ncsubstr, convType) - csubstr := int64(s[i]) - // Calculate large constant from bytes as sequence of shifts and ors. - // Like this: uint32(s[0]) | uint32(s[1])<<8 | uint32(s[2])<<16 ... - // ssa will combine this into a single large load. - for offset := 1; offset < step; offset++ { - b := nod(OINDEX, ncs, nodintconst(int64(i+offset))) - b = conv(b, convType) - b = nod(OLSH, b, nodintconst(int64(8*offset))) - ncsubstr = nod(OOR, ncsubstr, b) - csubstr |= int64(s[i+offset]) << uint8(8*offset) - } - csubstrPart := nodintconst(csubstr) - // Compare "step" bytes as once - r = nod(and, r, nod(cmp, csubstrPart, ncsubstr)) - remains -= step - i += step - } - return finishcompare(n, r, init) - } - } - - var r *Node - if n.Op == OEQ || n.Op == ONE { - // prepare for rewrite below - n.Left = cheapexpr(n.Left, init) - n.Right = cheapexpr(n.Right, init) - eqlen, eqmem := eqstring(n.Left, n.Right) - // quick check of len before full compare for == or !=. - // memequal then tests equality up to length len. - if n.Op == OEQ { - // len(left) == len(right) && memequal(left, right, len) - r = nod(OANDAND, eqlen, eqmem) - } else { - // len(left) != len(right) || !memequal(left, right, len) - eqlen.Op = ONE - r = nod(OOROR, eqlen, nod(ONOT, eqmem, nil)) - } - } else { - // sys_cmpstring(s1, s2) :: 0 - r = mkcall("cmpstring", types.Types[TINT], init, conv(n.Left, types.Types[TSTRING]), conv(n.Right, types.Types[TSTRING])) - r = nod(n.Op, r, nodintconst(0)) - } - - return finishcompare(n, r, init) -} - -// The result of finishcompare MUST be assigned back to n, e.g. -// n.Left = finishcompare(n.Left, x, r, init) -func finishcompare(n, r *Node, init *Nodes) *Node { - r = typecheck(r, ctxExpr) - r = conv(r, n.Type) - r = walkexpr(r, init) - return r -} - -// return 1 if integer n must be in range [0, max), 0 otherwise -func bounded(n *Node, max int64) bool { - if n.Type == nil || !n.Type.IsInteger() { - return false - } - - sign := n.Type.IsSigned() - bits := int32(8 * n.Type.Width) - - if smallintconst(n) { - v := n.Int64Val() - return 0 <= v && v < max - } - - switch n.Op { - case OAND, OANDNOT: - v := int64(-1) - switch { - case smallintconst(n.Left): - v = n.Left.Int64Val() - case smallintconst(n.Right): - v = n.Right.Int64Val() - if n.Op == OANDNOT { - v = ^v - if !sign { - v &= 1< 0 && v >= 2 { - bits-- - v >>= 1 - } - } - - case ORSH: - if !sign && smallintconst(n.Right) { - v := n.Right.Int64Val() - if v > int64(bits) { - return true - } - bits -= int32(v) - } - } - - if !sign && bits <= 62 && 1< 0 { - Fatalf("substArgTypes: too many argument types") - } - return n -} - -// canMergeLoads reports whether the backend optimization passes for -// the current architecture can combine adjacent loads into a single -// larger, possibly unaligned, load. Note that currently the -// optimizations must be able to handle little endian byte order. -func canMergeLoads() bool { - switch thearch.LinkArch.Family { - case sys.ARM64, sys.AMD64, sys.I386, sys.S390X: - return true - case sys.PPC64: - // Load combining only supported on ppc64le. - return thearch.LinkArch.ByteOrder == binary.LittleEndian - } - return false -} - -// isRuneCount reports whether n is of the form len([]rune(string)). -// These are optimized into a call to runtime.countrunes. -func isRuneCount(n *Node) bool { - return Debug.N == 0 && !instrumenting && n.Op == OLEN && n.Left.Op == OSTR2RUNES -} - -func walkCheckPtrAlignment(n *Node, init *Nodes, count *Node) *Node { - if !n.Type.IsPtr() { - Fatalf("expected pointer type: %v", n.Type) - } - elem := n.Type.Elem() - if count != nil { - if !elem.IsArray() { - Fatalf("expected array type: %v", elem) - } - elem = elem.Elem() - } - - size := elem.Size() - if elem.Alignment() == 1 && (size == 0 || size == 1 && count == nil) { - return n - } - - if count == nil { - count = nodintconst(1) - } - - n.Left = cheapexpr(n.Left, init) - init.Append(mkcall("checkptrAlignment", nil, init, convnop(n.Left, types.Types[TUNSAFEPTR]), typename(elem), conv(count, types.Types[TUINTPTR]))) - return n -} - -var walkCheckPtrArithmeticMarker byte - -func walkCheckPtrArithmetic(n *Node, init *Nodes) *Node { - // Calling cheapexpr(n, init) below leads to a recursive call - // to walkexpr, which leads us back here again. Use n.Opt to - // prevent infinite loops. - if opt := n.Opt(); opt == &walkCheckPtrArithmeticMarker { - return n - } else if opt != nil { - // We use n.Opt() here because today it's not used for OCONVNOP. If that changes, - // there's no guarantee that temporarily replacing it is safe, so just hard fail here. - Fatalf("unexpected Opt: %v", opt) - } - n.SetOpt(&walkCheckPtrArithmeticMarker) - defer n.SetOpt(nil) - - // TODO(mdempsky): Make stricter. We only need to exempt - // reflect.Value.Pointer and reflect.Value.UnsafeAddr. - switch n.Left.Op { - case OCALLFUNC, OCALLMETH, OCALLINTER: - return n - } - - if n.Left.Op == ODOTPTR && isReflectHeaderDataField(n.Left) { - return n - } - - // Find original unsafe.Pointer operands involved in this - // arithmetic expression. - // - // "It is valid both to add and to subtract offsets from a - // pointer in this way. It is also valid to use &^ to round - // pointers, usually for alignment." - var originals []*Node - var walk func(n *Node) - walk = func(n *Node) { - switch n.Op { - case OADD: - walk(n.Left) - walk(n.Right) - case OSUB, OANDNOT: - walk(n.Left) - case OCONVNOP: - if n.Left.Type.IsUnsafePtr() { - n.Left = cheapexpr(n.Left, init) - originals = append(originals, convnop(n.Left, types.Types[TUNSAFEPTR])) - } - } - } - walk(n.Left) - - n = cheapexpr(n, init) - - slice := mkdotargslice(types.NewSlice(types.Types[TUNSAFEPTR]), originals) - slice.Esc = EscNone - - init.Append(mkcall("checkptrArithmetic", nil, init, convnop(n, types.Types[TUNSAFEPTR]), slice)) - // TODO(khr): Mark backing store of slice as dead. This will allow us to reuse - // the backing store for multiple calls to checkptrArithmetic. - - return n -} - -// checkPtr reports whether pointer checking should be enabled for -// function fn at a given level. See debugHelpFooter for defined -// levels. -func checkPtr(fn *Node, level int) bool { - return Debug_checkptr >= level && fn.Func.Pragma&NoCheckPtr == 0 -} diff --git a/src/cmd/compile/internal/gc/zerorange_test.go b/src/cmd/compile/internal/gc/zerorange_test.go deleted file mode 100644 index 89f4cb9bcf6e436ae6981cb1809927bd9c799676..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/gc/zerorange_test.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "testing" -) - -var glob = 3 -var globp *int64 - -// Testing compilation of arch.ZeroRange of various sizes. - -// By storing a pointer to an int64 output param in a global, the compiler must -// ensure that output param is allocated on the heap. Also, since there is a -// defer, the pointer to each output param must be zeroed in the prologue (see -// plive.go:epilogue()). So, we will get a block of one or more stack slots that -// need to be zeroed. Hence, we are testing compilation completes successfully when -// zerorange calls of various sizes (8-136 bytes) are generated. We are not -// testing runtime correctness (which is hard to do for the current uses of -// ZeroRange). - -func TestZeroRange(t *testing.T) { - testZeroRange8(t) - testZeroRange16(t) - testZeroRange32(t) - testZeroRange64(t) - testZeroRange136(t) -} - -func testZeroRange8(t *testing.T) (r int64) { - defer func() { - glob = 4 - }() - globp = &r - return -} - -func testZeroRange16(t *testing.T) (r, s int64) { - defer func() { - glob = 4 - }() - globp = &r - globp = &s - return -} - -func testZeroRange32(t *testing.T) (r, s, t2, u int64) { - defer func() { - glob = 4 - }() - globp = &r - globp = &s - globp = &t2 - globp = &u - return -} - -func testZeroRange64(t *testing.T) (r, s, t2, u, v, w, x, y int64) { - defer func() { - glob = 4 - }() - globp = &r - globp = &s - globp = &t2 - globp = &u - globp = &v - globp = &w - globp = &x - globp = &y - return -} - -func testZeroRange136(t *testing.T) (r, s, t2, u, v, w, x, y, r1, s1, t1, u1, v1, w1, x1, y1, z1 int64) { - defer func() { - glob = 4 - }() - globp = &r - globp = &s - globp = &t2 - globp = &u - globp = &v - globp = &w - globp = &x - globp = &y - globp = &r1 - globp = &s1 - globp = &t1 - globp = &u1 - globp = &v1 - globp = &w1 - globp = &x1 - globp = &y1 - globp = &z1 - return -} diff --git a/src/cmd/compile/internal/importer/exportdata.go b/src/cmd/compile/internal/importer/exportdata.go new file mode 100644 index 0000000000000000000000000000000000000000..3925a64314ea0d8d160afcc5fbc3719dbae58338 --- /dev/null +++ b/src/cmd/compile/internal/importer/exportdata.go @@ -0,0 +1,92 @@ +// UNREVIEWED +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements FindExportData. + +package importer + +import ( + "bufio" + "fmt" + "io" + "strconv" + "strings" +) + +func readGopackHeader(r *bufio.Reader) (name string, size int, err error) { + // See $GOROOT/include/ar.h. + hdr := make([]byte, 16+12+6+6+8+10+2) + _, err = io.ReadFull(r, hdr) + if err != nil { + return + } + // leave for debugging + if false { + fmt.Printf("header: %s", hdr) + } + s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10])) + size, err = strconv.Atoi(s) + if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' { + err = fmt.Errorf("invalid archive header") + return + } + name = strings.TrimSpace(string(hdr[:16])) + return +} + +// FindExportData positions the reader r at the beginning of the +// export data section of an underlying GC-created object/archive +// file by reading from it. The reader must be positioned at the +// start of the file before calling this function. The hdr result +// is the string before the export data, either "$$" or "$$B". +// +func FindExportData(r *bufio.Reader) (hdr string, err error) { + // Read first line to make sure this is an object file. + line, err := r.ReadSlice('\n') + if err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + + if string(line) == "!\n" { + // Archive file. Scan to __.PKGDEF. + var name string + if name, _, err = readGopackHeader(r); err != nil { + return + } + + // First entry should be __.PKGDEF. + if name != "__.PKGDEF" { + err = fmt.Errorf("go archive is missing __.PKGDEF") + return + } + + // Read first line of __.PKGDEF data, so that line + // is once again the first line of the input. + if line, err = r.ReadSlice('\n'); err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + } + + // Now at __.PKGDEF in archive or still at beginning of file. + // Either way, line should begin with "go object ". + if !strings.HasPrefix(string(line), "go object ") { + err = fmt.Errorf("not a Go object file") + return + } + + // Skip over object header to export data. + // Begins after first line starting with $$. + for line[0] != '$' { + if line, err = r.ReadSlice('\n'); err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + } + hdr = string(line) + + return +} diff --git a/src/cmd/compile/internal/importer/gcimporter.go b/src/cmd/compile/internal/importer/gcimporter.go new file mode 100644 index 0000000000000000000000000000000000000000..feb18cf2c9ad78c50f2287d723246991e678e8bd --- /dev/null +++ b/src/cmd/compile/internal/importer/gcimporter.go @@ -0,0 +1,175 @@ +// UNREVIEWED +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// package importer implements Import for gc-generated object files. +package importer + +import ( + "bufio" + "cmd/compile/internal/types2" + "fmt" + "go/build" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +// debugging/development support +const debug = false + +var pkgExts = [...]string{".a", ".o"} + +// FindPkg returns the filename and unique package id for an import +// path based on package information provided by build.Import (using +// the build.Default build.Context). A relative srcDir is interpreted +// relative to the current working directory. +// If no file was found, an empty filename is returned. +// +func FindPkg(path, srcDir string) (filename, id string) { + if path == "" { + return + } + + var noext string + switch { + default: + // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" + // Don't require the source files to be present. + if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282 + srcDir = abs + } + bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) + if bp.PkgObj == "" { + id = path // make sure we have an id to print in error message + return + } + noext = strings.TrimSuffix(bp.PkgObj, ".a") + id = bp.ImportPath + + case build.IsLocalImport(path): + // "./x" -> "/this/directory/x.ext", "/this/directory/x" + noext = filepath.Join(srcDir, path) + id = noext + + case filepath.IsAbs(path): + // for completeness only - go/build.Import + // does not support absolute imports + // "/x" -> "/x.ext", "/x" + noext = path + id = path + } + + if false { // for debugging + if path != id { + fmt.Printf("%s -> %s\n", path, id) + } + } + + // try extensions + for _, ext := range pkgExts { + filename = noext + ext + if f, err := os.Stat(filename); err == nil && !f.IsDir() { + return + } + } + + filename = "" // not found + return +} + +// Import imports a gc-generated package given its import path and srcDir, adds +// the corresponding package object to the packages map, and returns the object. +// The packages map must contain all packages already imported. +// +func Import(packages map[string]*types2.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types2.Package, err error) { + var rc io.ReadCloser + var id string + if lookup != nil { + // With custom lookup specified, assume that caller has + // converted path to a canonical import path for use in the map. + if path == "unsafe" { + return types2.Unsafe, nil + } + id = path + + // No need to re-import if the package was imported completely before. + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + f, err := lookup(path) + if err != nil { + return nil, err + } + rc = f + } else { + var filename string + filename, id = FindPkg(path, srcDir) + if filename == "" { + if path == "unsafe" { + return types2.Unsafe, nil + } + return nil, fmt.Errorf("can't find import: %q", id) + } + + // no need to re-import if the package was imported completely before + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + + // open file + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + // add file name to error + err = fmt.Errorf("%s: %v", filename, err) + } + }() + rc = f + } + defer rc.Close() + + var hdr string + buf := bufio.NewReader(rc) + if hdr, err = FindExportData(buf); err != nil { + return + } + + switch hdr { + case "$$\n": + err = fmt.Errorf("import %q: old textual export format no longer supported (recompile library)", path) + + case "$$B\n": + var data []byte + data, err = ioutil.ReadAll(buf) + if err != nil { + break + } + + // The indexed export format starts with an 'i'; the older + // binary export format starts with a 'c', 'd', or 'v' + // (from "version"). Select appropriate importer. + if len(data) > 0 && data[0] == 'i' { + _, pkg, err = iImportData(packages, data[1:], id) + } else { + err = fmt.Errorf("import %q: old binary export format no longer supported (recompile library)", path) + } + + default: + err = fmt.Errorf("import %q: unknown export data header: %q", path, hdr) + } + + return +} + +type byPath []*types2.Package + +func (a byPath) Len() int { return len(a) } +func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() } diff --git a/src/cmd/compile/internal/importer/gcimporter_test.go b/src/cmd/compile/internal/importer/gcimporter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7fb8fed59cf9be1cd00471a1d0854e89d4ed1148 --- /dev/null +++ b/src/cmd/compile/internal/importer/gcimporter_test.go @@ -0,0 +1,611 @@ +// UNREVIEWED +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package importer + +import ( + "bytes" + "cmd/compile/internal/types2" + "fmt" + "internal/testenv" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" +) + +// skipSpecialPlatforms causes the test to be skipped for platforms where +// builders (build.golang.org) don't have access to compiled packages for +// import. +func skipSpecialPlatforms(t *testing.T) { + switch platform := runtime.GOOS + "-" + runtime.GOARCH; platform { + case "darwin-arm64": + t.Skipf("no compiled packages available for import on %s", platform) + } +} + +// compile runs the compiler on filename, with dirname as the working directory, +// and writes the output file to outdirname. +func compile(t *testing.T, dirname, filename, outdirname string) string { + // filename must end with ".go" + if !strings.HasSuffix(filename, ".go") { + t.Fatalf("filename doesn't end in .go: %s", filename) + } + basename := filepath.Base(filename) + outname := filepath.Join(outdirname, basename[:len(basename)-2]+"o") + cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-o", outname, filename) + cmd.Dir = dirname + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("go tool compile %s failed: %s", filename, err) + } + return outname +} + +func testPath(t *testing.T, path, srcDir string) *types2.Package { + t0 := time.Now() + pkg, err := Import(make(map[string]*types2.Package), path, srcDir, nil) + if err != nil { + t.Errorf("testPath(%s): %s", path, err) + return nil + } + t.Logf("testPath(%s): %v", path, time.Since(t0)) + return pkg +} + +const maxTime = 30 * time.Second + +func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) { + dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir) + list, err := ioutil.ReadDir(dirname) + if err != nil { + t.Fatalf("testDir(%s): %s", dirname, err) + } + for _, f := range list { + if time.Now().After(endTime) { + t.Log("testing time used up") + return + } + switch { + case !f.IsDir(): + // try extensions + for _, ext := range pkgExts { + if strings.HasSuffix(f.Name(), ext) { + name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension + if testPath(t, filepath.Join(dir, name), dir) != nil { + nimports++ + } + } + } + case f.IsDir(): + nimports += testDir(t, filepath.Join(dir, f.Name()), endTime) + } + } + return +} + +func mktmpdir(t *testing.T) string { + tmpdir, err := ioutil.TempDir("", "gcimporter_test") + if err != nil { + t.Fatal("mktmpdir:", err) + } + if err := os.Mkdir(filepath.Join(tmpdir, "testdata"), 0700); err != nil { + os.RemoveAll(tmpdir) + t.Fatal("mktmpdir:", err) + } + return tmpdir +} + +func TestImportTestdata(t *testing.T) { + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + + compile(t, "testdata", "exports.go", filepath.Join(tmpdir, "testdata")) + + if pkg := testPath(t, "./testdata/exports", tmpdir); pkg != nil { + // The package's Imports list must include all packages + // explicitly imported by exports.go, plus all packages + // referenced indirectly via exported objects in exports.go. + // With the textual export format, the list may also include + // additional packages that are not strictly required for + // import processing alone (they are exported to err "on + // the safe side"). + // TODO(gri) update the want list to be precise, now that + // the textual export data is gone. + got := fmt.Sprint(pkg.Imports()) + for _, want := range []string{"go/ast", "go/token"} { + if !strings.Contains(got, want) { + t.Errorf(`Package("exports").Imports() = %s, does not contain %s`, got, want) + } + } + } +} + +func TestVersionHandling(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + const dir = "./testdata/versions" + list, err := ioutil.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + corruptdir := filepath.Join(tmpdir, "testdata", "versions") + if err := os.Mkdir(corruptdir, 0700); err != nil { + t.Fatal(err) + } + + for _, f := range list { + name := f.Name() + if !strings.HasSuffix(name, ".a") { + continue // not a package file + } + if strings.Contains(name, "corrupted") { + continue // don't process a leftover corrupted file + } + pkgpath := "./" + name[:len(name)-2] + + if testing.Verbose() { + t.Logf("importing %s", name) + } + + // test that export data can be imported + _, err := Import(make(map[string]*types2.Package), pkgpath, dir, nil) + if err != nil { + // ok to fail if it fails with a no longer supported error for select files + if strings.Contains(err.Error(), "no longer supported") { + switch name { + case "test_go1.7_0.a", "test_go1.7_1.a", + "test_go1.8_4.a", "test_go1.8_5.a", + "test_go1.11_6b.a", "test_go1.11_999b.a": + continue + } + // fall through + } + // ok to fail if it fails with a newer version error for select files + if strings.Contains(err.Error(), "newer version") { + switch name { + case "test_go1.11_999i.a": + continue + } + // fall through + } + t.Errorf("import %q failed: %v", pkgpath, err) + continue + } + + // create file with corrupted export data + // 1) read file + data, err := ioutil.ReadFile(filepath.Join(dir, name)) + if err != nil { + t.Fatal(err) + } + // 2) find export data + i := bytes.Index(data, []byte("\n$$B\n")) + 5 + j := bytes.Index(data[i:], []byte("\n$$\n")) + i + if i < 0 || j < 0 || i > j { + t.Fatalf("export data section not found (i = %d, j = %d)", i, j) + } + // 3) corrupt the data (increment every 7th byte) + for k := j - 13; k >= i; k -= 7 { + data[k]++ + } + // 4) write the file + pkgpath += "_corrupted" + filename := filepath.Join(corruptdir, pkgpath) + ".a" + ioutil.WriteFile(filename, data, 0666) + + // test that importing the corrupted file results in an error + _, err = Import(make(map[string]*types2.Package), pkgpath, corruptdir, nil) + if err == nil { + t.Errorf("import corrupted %q succeeded", pkgpath) + } else if msg := err.Error(); !strings.Contains(msg, "version skew") { + t.Errorf("import %q error incorrect (%s)", pkgpath, msg) + } + } +} + +func TestImportStdLib(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + dt := maxTime + if testing.Short() && testenv.Builder() == "" { + dt = 10 * time.Millisecond + } + nimports := testDir(t, "", time.Now().Add(dt)) // installed packages + t.Logf("tested %d imports", nimports) +} + +var importedObjectTests = []struct { + name string + want string +}{ + // non-interfaces + {"crypto.Hash", "type Hash uint"}, + {"go/ast.ObjKind", "type ObjKind int"}, + {"go/types.Qualifier", "type Qualifier func(*Package) string"}, + {"go/types.Comparable", "func Comparable(T Type) bool"}, + {"math.Pi", "const Pi untyped float"}, + {"math.Sin", "func Sin(x float64) float64"}, + {"go/ast.NotNilFilter", "func NotNilFilter(_ string, v reflect.Value) bool"}, + {"go/internal/gcimporter.FindPkg", "func FindPkg(path string, srcDir string) (filename string, id string)"}, + + // interfaces + {"context.Context", "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key interface{}) interface{}}"}, + {"crypto.Decrypter", "type Decrypter interface{Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error); Public() PublicKey}"}, + {"encoding.BinaryMarshaler", "type BinaryMarshaler interface{MarshalBinary() (data []byte, err error)}"}, + {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"}, + {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"}, + {"go/ast.Node", "type Node interface{End() go/token.Pos; Pos() go/token.Pos}"}, + // go/types.Type has grown much larger - excluded for now + // {"go/types.Type", "type Type interface{String() string; Underlying() Type}"}, +} + +func TestImportedTypes(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + for _, test := range importedObjectTests { + s := strings.Split(test.name, ".") + if len(s) != 2 { + t.Fatal("inconsistent test data") + } + importPath := s[0] + objName := s[1] + + pkg, err := Import(make(map[string]*types2.Package), importPath, ".", nil) + if err != nil { + t.Error(err) + continue + } + + obj := pkg.Scope().Lookup(objName) + if obj == nil { + t.Errorf("%s: object not found", test.name) + continue + } + + got := types2.ObjectString(obj, types2.RelativeTo(pkg)) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + + if named, _ := obj.Type().(*types2.Named); named != nil { + verifyInterfaceMethodRecvs(t, named, 0) + } + } +} + +// verifyInterfaceMethodRecvs verifies that method receiver types +// are named if the methods belong to a named interface type. +func verifyInterfaceMethodRecvs(t *testing.T, named *types2.Named, level int) { + // avoid endless recursion in case of an embedding bug that lead to a cycle + if level > 10 { + t.Errorf("%s: embeds itself", named) + return + } + + iface, _ := named.Underlying().(*types2.Interface) + if iface == nil { + return // not an interface + } + + // check explicitly declared methods + for i := 0; i < iface.NumExplicitMethods(); i++ { + m := iface.ExplicitMethod(i) + recv := m.Type().(*types2.Signature).Recv() + if recv == nil { + t.Errorf("%s: missing receiver type", m) + continue + } + if recv.Type() != named { + t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named) + } + } + + // check embedded interfaces (if they are named, too) + for i := 0; i < iface.NumEmbeddeds(); i++ { + // embedding of interfaces cannot have cycles; recursion will terminate + if etype, _ := iface.EmbeddedType(i).(*types2.Named); etype != nil { + verifyInterfaceMethodRecvs(t, etype, level+1) + } + } +} + +func TestIssue5815(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + pkg := importPkg(t, "strings", ".") + + scope := pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + if obj.Pkg() == nil { + t.Errorf("no pkg for %s", obj) + } + if tname, _ := obj.(*types2.TypeName); tname != nil { + named := tname.Type().(*types2.Named) + for i := 0; i < named.NumMethods(); i++ { + m := named.Method(i) + if m.Pkg() == nil { + t.Errorf("no pkg for %s", m) + } + } + } + } +} + +// Smoke test to ensure that imported methods get the correct package. +func TestCorrectMethodPackage(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + imports := make(map[string]*types2.Package) + _, err := Import(imports, "net/http", ".", nil) + if err != nil { + t.Fatal(err) + } + + mutex := imports["sync"].Scope().Lookup("Mutex").(*types2.TypeName).Type() + obj, _, _ := types2.LookupFieldOrMethod(types2.NewPointer(mutex), false, nil, "Lock") + lock := obj.(*types2.Func) + if got, want := lock.Pkg().Path(), "sync"; got != want { + t.Errorf("got package path %q; want %q", got, want) + } +} + +func TestIssue13566(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + testoutdir := filepath.Join(tmpdir, "testdata") + + // b.go needs to be compiled from the output directory so that the compiler can + // find the compiled package a. We pass the full path to compile() so that we + // don't have to copy the file to that directory. + bpath, err := filepath.Abs(filepath.Join("testdata", "b.go")) + if err != nil { + t.Fatal(err) + } + compile(t, "testdata", "a.go", testoutdir) + compile(t, testoutdir, bpath, testoutdir) + + // import must succeed (test for issue at hand) + pkg := importPkg(t, "./testdata/b", tmpdir) + + // make sure all indirectly imported packages have names + for _, imp := range pkg.Imports() { + if imp.Name() == "" { + t.Errorf("no name for %s package", imp.Path()) + } + } +} + +func TestIssue13898(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // import go/internal/gcimporter which imports go/types partially + imports := make(map[string]*types2.Package) + _, err := Import(imports, "go/internal/gcimporter", ".", nil) + if err != nil { + t.Fatal(err) + } + + // look for go/types package + var goTypesPkg *types2.Package + for path, pkg := range imports { + if path == "go/types" { + goTypesPkg = pkg + break + } + } + if goTypesPkg == nil { + t.Fatal("go/types not found") + } + + // look for go/types2.Object type + obj := lookupObj(t, goTypesPkg.Scope(), "Object") + typ, ok := obj.Type().(*types2.Named) + if !ok { + t.Fatalf("go/types2.Object type is %v; wanted named type", typ) + } + + // lookup go/types2.Object.Pkg method + m, index, indirect := types2.LookupFieldOrMethod(typ, false, nil, "Pkg") + if m == nil { + t.Fatalf("go/types2.Object.Pkg not found (index = %v, indirect = %v)", index, indirect) + } + + // the method must belong to go/types + if m.Pkg().Path() != "go/types" { + t.Fatalf("found %v; want go/types", m.Pkg()) + } +} + +func TestIssue15517(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + + compile(t, "testdata", "p.go", filepath.Join(tmpdir, "testdata")) + + // Multiple imports of p must succeed without redeclaration errors. + // We use an import path that's not cleaned up so that the eventual + // file path for the package is different from the package path; this + // will expose the error if it is present. + // + // (Issue: Both the textual and the binary importer used the file path + // of the package to be imported as key into the shared packages map. + // However, the binary importer then used the package path to identify + // the imported package to mark it as complete; effectively marking the + // wrong package as complete. By using an "unclean" package path, the + // file and package path are different, exposing the problem if present. + // The same issue occurs with vendoring.) + imports := make(map[string]*types2.Package) + for i := 0; i < 3; i++ { + if _, err := Import(imports, "./././testdata/p", tmpdir, nil); err != nil { + t.Fatal(err) + } + } +} + +func TestIssue15920(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue15920") +} + +func TestIssue20046(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + // "./issue20046".V.M must exist + pkg := compileAndImportPkg(t, "issue20046") + obj := lookupObj(t, pkg.Scope(), "V") + if m, index, indirect := types2.LookupFieldOrMethod(obj.Type(), false, nil, "M"); m == nil { + t.Fatalf("V.M not found (index = %v, indirect = %v)", index, indirect) + } +} +func TestIssue25301(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue25301") +} + +func TestIssue25596(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue25596") +} + +func importPkg(t *testing.T, path, srcDir string) *types2.Package { + pkg, err := Import(make(map[string]*types2.Package), path, srcDir, nil) + if err != nil { + t.Fatal(err) + } + return pkg +} + +func compileAndImportPkg(t *testing.T, name string) *types2.Package { + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + compile(t, "testdata", name+".go", filepath.Join(tmpdir, "testdata")) + return importPkg(t, "./testdata/"+name, tmpdir) +} + +func lookupObj(t *testing.T, scope *types2.Scope, name string) types2.Object { + if obj := scope.Lookup(name); obj != nil { + return obj + } + t.Fatalf("%s not found", name) + return nil +} diff --git a/src/cmd/compile/internal/importer/iimport.go b/src/cmd/compile/internal/importer/iimport.go new file mode 100644 index 0000000000000000000000000000000000000000..8ab0b7b98961fdefe1e4dc605a65a029979c79b1 --- /dev/null +++ b/src/cmd/compile/internal/importer/iimport.go @@ -0,0 +1,612 @@ +// UNREVIEWED +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Indexed package import. +// See cmd/compile/internal/typecheck/iexport.go for the export data format. + +package importer + +import ( + "bytes" + "cmd/compile/internal/syntax" + "cmd/compile/internal/types2" + "encoding/binary" + "fmt" + "go/constant" + "go/token" + "io" + "math/big" + "sort" +) + +type intReader struct { + *bytes.Reader + path string +} + +func (r *intReader) int64() int64 { + i, err := binary.ReadVarint(r.Reader) + if err != nil { + errorf("import %q: read varint error: %v", r.path, err) + } + return i +} + +func (r *intReader) uint64() uint64 { + i, err := binary.ReadUvarint(r.Reader) + if err != nil { + errorf("import %q: read varint error: %v", r.path, err) + } + return i +} + +const predeclReserved = 32 + +type itag uint64 + +const ( + // Types + definedType itag = iota + pointerType + sliceType + arrayType + chanType + mapType + signatureType + structType + interfaceType +) + +const io_SeekCurrent = 1 // io.SeekCurrent (not defined in Go 1.4) + +// iImportData imports a package from the serialized package data +// and returns the number of bytes consumed and a reference to the package. +// If the export data version is not recognized or the format is otherwise +// compromised, an error is returned. +func iImportData(imports map[string]*types2.Package, data []byte, path string) (_ int, pkg *types2.Package, err error) { + const currentVersion = 1 + version := int64(-1) + defer func() { + if e := recover(); e != nil { + if version > currentVersion { + err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e) + } else { + err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e) + } + } + }() + + r := &intReader{bytes.NewReader(data), path} + + version = int64(r.uint64()) + switch version { + case currentVersion, 0: + default: + errorf("unknown iexport format version %d", version) + } + + sLen := int64(r.uint64()) + dLen := int64(r.uint64()) + + whence, _ := r.Seek(0, io_SeekCurrent) + stringData := data[whence : whence+sLen] + declData := data[whence+sLen : whence+sLen+dLen] + r.Seek(sLen+dLen, io_SeekCurrent) + + p := iimporter{ + ipath: path, + version: int(version), + + stringData: stringData, + stringCache: make(map[uint64]string), + pkgCache: make(map[uint64]*types2.Package), + + declData: declData, + pkgIndex: make(map[*types2.Package]map[string]uint64), + typCache: make(map[uint64]types2.Type), + } + + for i, pt := range predeclared { + p.typCache[uint64(i)] = pt + } + + pkgList := make([]*types2.Package, r.uint64()) + for i := range pkgList { + pkgPathOff := r.uint64() + pkgPath := p.stringAt(pkgPathOff) + pkgName := p.stringAt(r.uint64()) + _ = r.uint64() // package height; unused by go/types + + if pkgPath == "" { + pkgPath = path + } + pkg := imports[pkgPath] + if pkg == nil { + pkg = types2.NewPackage(pkgPath, pkgName) + imports[pkgPath] = pkg + } else if pkg.Name() != pkgName { + errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path) + } + + p.pkgCache[pkgPathOff] = pkg + + nameIndex := make(map[string]uint64) + for nSyms := r.uint64(); nSyms > 0; nSyms-- { + name := p.stringAt(r.uint64()) + nameIndex[name] = r.uint64() + } + + p.pkgIndex[pkg] = nameIndex + pkgList[i] = pkg + } + + localpkg := pkgList[0] + + names := make([]string, 0, len(p.pkgIndex[localpkg])) + for name := range p.pkgIndex[localpkg] { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + p.doDecl(localpkg, name) + } + + for _, typ := range p.interfaceList { + typ.Complete() + } + + // record all referenced packages as imports + list := append(([]*types2.Package)(nil), pkgList[1:]...) + sort.Sort(byPath(list)) + localpkg.SetImports(list) + + // package was imported completely and without errors + localpkg.MarkComplete() + + consumed, _ := r.Seek(0, io_SeekCurrent) + return int(consumed), localpkg, nil +} + +type iimporter struct { + ipath string + version int + + stringData []byte + stringCache map[uint64]string + pkgCache map[uint64]*types2.Package + + declData []byte + pkgIndex map[*types2.Package]map[string]uint64 + typCache map[uint64]types2.Type + + interfaceList []*types2.Interface +} + +func (p *iimporter) doDecl(pkg *types2.Package, name string) { + // See if we've already imported this declaration. + if obj := pkg.Scope().Lookup(name); obj != nil { + return + } + + off, ok := p.pkgIndex[pkg][name] + if !ok { + errorf("%v.%v not in index", pkg, name) + } + + r := &importReader{p: p, currPkg: pkg} + // Reader.Reset is not available in Go 1.4. + // Use bytes.NewReader for now. + // r.declReader.Reset(p.declData[off:]) + r.declReader = *bytes.NewReader(p.declData[off:]) + + r.obj(name) +} + +func (p *iimporter) stringAt(off uint64) string { + if s, ok := p.stringCache[off]; ok { + return s + } + + slen, n := binary.Uvarint(p.stringData[off:]) + if n <= 0 { + errorf("varint failed") + } + spos := off + uint64(n) + s := string(p.stringData[spos : spos+slen]) + p.stringCache[off] = s + return s +} + +func (p *iimporter) pkgAt(off uint64) *types2.Package { + if pkg, ok := p.pkgCache[off]; ok { + return pkg + } + path := p.stringAt(off) + errorf("missing package %q in %q", path, p.ipath) + return nil +} + +func (p *iimporter) typAt(off uint64, base *types2.Named) types2.Type { + if t, ok := p.typCache[off]; ok && (base == nil || !isInterface(t)) { + return t + } + + if off < predeclReserved { + errorf("predeclared type missing from cache: %v", off) + } + + r := &importReader{p: p} + // Reader.Reset is not available in Go 1.4. + // Use bytes.NewReader for now. + // r.declReader.Reset(p.declData[off-predeclReserved:]) + r.declReader = *bytes.NewReader(p.declData[off-predeclReserved:]) + t := r.doType(base) + + if base == nil || !isInterface(t) { + p.typCache[off] = t + } + return t +} + +type importReader struct { + p *iimporter + declReader bytes.Reader + currPkg *types2.Package + prevFile string + prevLine int64 + prevColumn int64 +} + +func (r *importReader) obj(name string) { + tag := r.byte() + pos := r.pos() + + switch tag { + case 'A': + typ := r.typ() + + r.declare(types2.NewTypeName(pos, r.currPkg, name, typ)) + + case 'C': + typ, val := r.value() + + r.declare(types2.NewConst(pos, r.currPkg, name, typ, val)) + + case 'F': + sig := r.signature(nil) + + r.declare(types2.NewFunc(pos, r.currPkg, name, sig)) + + case 'T': + // Types can be recursive. We need to setup a stub + // declaration before recursing. + obj := types2.NewTypeName(pos, r.currPkg, name, nil) + named := types2.NewNamed(obj, nil, nil) + r.declare(obj) + + underlying := r.p.typAt(r.uint64(), named).Underlying() + named.SetUnderlying(underlying) + + if !isInterface(underlying) { + for n := r.uint64(); n > 0; n-- { + mpos := r.pos() + mname := r.ident() + recv := r.param() + msig := r.signature(recv) + + named.AddMethod(types2.NewFunc(mpos, r.currPkg, mname, msig)) + } + } + + case 'V': + typ := r.typ() + + r.declare(types2.NewVar(pos, r.currPkg, name, typ)) + + default: + errorf("unexpected tag: %v", tag) + } +} + +func (r *importReader) declare(obj types2.Object) { + obj.Pkg().Scope().Insert(obj) +} + +func (r *importReader) value() (typ types2.Type, val constant.Value) { + typ = r.typ() + + switch b := typ.Underlying().(*types2.Basic); b.Info() & types2.IsConstType { + case types2.IsBoolean: + val = constant.MakeBool(r.bool()) + + case types2.IsString: + val = constant.MakeString(r.string()) + + case types2.IsInteger: + var x big.Int + r.mpint(&x, b) + val = constant.Make(&x) + + case types2.IsFloat: + val = r.mpfloat(b) + + case types2.IsComplex: + re := r.mpfloat(b) + im := r.mpfloat(b) + val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + + default: + errorf("unexpected type %v", typ) // panics + panic("unreachable") + } + + return +} + +func intSize(b *types2.Basic) (signed bool, maxBytes uint) { + if (b.Info() & types2.IsUntyped) != 0 { + return true, 64 + } + + switch b.Kind() { + case types2.Float32, types2.Complex64: + return true, 3 + case types2.Float64, types2.Complex128: + return true, 7 + } + + signed = (b.Info() & types2.IsUnsigned) == 0 + switch b.Kind() { + case types2.Int8, types2.Uint8: + maxBytes = 1 + case types2.Int16, types2.Uint16: + maxBytes = 2 + case types2.Int32, types2.Uint32: + maxBytes = 4 + default: + maxBytes = 8 + } + + return +} + +func (r *importReader) mpint(x *big.Int, typ *types2.Basic) { + signed, maxBytes := intSize(typ) + + maxSmall := 256 - maxBytes + if signed { + maxSmall = 256 - 2*maxBytes + } + if maxBytes == 1 { + maxSmall = 256 + } + + n, _ := r.declReader.ReadByte() + if uint(n) < maxSmall { + v := int64(n) + if signed { + v >>= 1 + if n&1 != 0 { + v = ^v + } + } + x.SetInt64(v) + return + } + + v := -n + if signed { + v = -(n &^ 1) >> 1 + } + if v < 1 || uint(v) > maxBytes { + errorf("weird decoding: %v, %v => %v", n, signed, v) + } + b := make([]byte, v) + io.ReadFull(&r.declReader, b) + x.SetBytes(b) + if signed && n&1 != 0 { + x.Neg(x) + } +} + +func (r *importReader) mpfloat(typ *types2.Basic) constant.Value { + var mant big.Int + r.mpint(&mant, typ) + var f big.Float + f.SetInt(&mant) + if f.Sign() != 0 { + f.SetMantExp(&f, int(r.int64())) + } + return constant.Make(&f) +} + +func (r *importReader) ident() string { + return r.string() +} + +func (r *importReader) qualifiedIdent() (*types2.Package, string) { + name := r.string() + pkg := r.pkg() + return pkg, name +} + +func (r *importReader) pos() syntax.Pos { + if r.p.version >= 1 { + r.posv1() + } else { + r.posv0() + } + + if r.prevFile == "" && r.prevLine == 0 && r.prevColumn == 0 { + return syntax.Pos{} + } + // TODO(gri) fix this + // return r.p.fake.pos(r.prevFile, int(r.prevLine), int(r.prevColumn)) + return syntax.Pos{} +} + +func (r *importReader) posv0() { + delta := r.int64() + if delta != deltaNewFile { + r.prevLine += delta + } else if l := r.int64(); l == -1 { + r.prevLine += deltaNewFile + } else { + r.prevFile = r.string() + r.prevLine = l + } +} + +func (r *importReader) posv1() { + delta := r.int64() + r.prevColumn += delta >> 1 + if delta&1 != 0 { + delta = r.int64() + r.prevLine += delta >> 1 + if delta&1 != 0 { + r.prevFile = r.string() + } + } +} + +func (r *importReader) typ() types2.Type { + return r.p.typAt(r.uint64(), nil) +} + +func isInterface(t types2.Type) bool { + _, ok := t.(*types2.Interface) + return ok +} + +func (r *importReader) pkg() *types2.Package { return r.p.pkgAt(r.uint64()) } +func (r *importReader) string() string { return r.p.stringAt(r.uint64()) } + +func (r *importReader) doType(base *types2.Named) types2.Type { + switch k := r.kind(); k { + default: + errorf("unexpected kind tag in %q: %v", r.p.ipath, k) + return nil + + case definedType: + pkg, name := r.qualifiedIdent() + r.p.doDecl(pkg, name) + return pkg.Scope().Lookup(name).(*types2.TypeName).Type() + case pointerType: + return types2.NewPointer(r.typ()) + case sliceType: + return types2.NewSlice(r.typ()) + case arrayType: + n := r.uint64() + return types2.NewArray(r.typ(), int64(n)) + case chanType: + dir := chanDir(int(r.uint64())) + return types2.NewChan(dir, r.typ()) + case mapType: + return types2.NewMap(r.typ(), r.typ()) + case signatureType: + r.currPkg = r.pkg() + return r.signature(nil) + + case structType: + r.currPkg = r.pkg() + + fields := make([]*types2.Var, r.uint64()) + tags := make([]string, len(fields)) + for i := range fields { + fpos := r.pos() + fname := r.ident() + ftyp := r.typ() + emb := r.bool() + tag := r.string() + + fields[i] = types2.NewField(fpos, r.currPkg, fname, ftyp, emb) + tags[i] = tag + } + return types2.NewStruct(fields, tags) + + case interfaceType: + r.currPkg = r.pkg() + + embeddeds := make([]types2.Type, r.uint64()) + for i := range embeddeds { + _ = r.pos() + embeddeds[i] = r.typ() + } + + methods := make([]*types2.Func, r.uint64()) + for i := range methods { + mpos := r.pos() + mname := r.ident() + + // TODO(mdempsky): Matches bimport.go, but I + // don't agree with this. + var recv *types2.Var + if base != nil { + recv = types2.NewVar(syntax.Pos{}, r.currPkg, "", base) + } + + msig := r.signature(recv) + methods[i] = types2.NewFunc(mpos, r.currPkg, mname, msig) + } + + typ := types2.NewInterfaceType(methods, embeddeds) + r.p.interfaceList = append(r.p.interfaceList, typ) + return typ + } +} + +func (r *importReader) kind() itag { + return itag(r.uint64()) +} + +func (r *importReader) signature(recv *types2.Var) *types2.Signature { + params := r.paramList() + results := r.paramList() + variadic := params.Len() > 0 && r.bool() + return types2.NewSignature(recv, params, results, variadic) +} + +func (r *importReader) paramList() *types2.Tuple { + xs := make([]*types2.Var, r.uint64()) + for i := range xs { + xs[i] = r.param() + } + return types2.NewTuple(xs...) +} + +func (r *importReader) param() *types2.Var { + pos := r.pos() + name := r.ident() + typ := r.typ() + return types2.NewParam(pos, r.currPkg, name, typ) +} + +func (r *importReader) bool() bool { + return r.uint64() != 0 +} + +func (r *importReader) int64() int64 { + n, err := binary.ReadVarint(&r.declReader) + if err != nil { + errorf("readVarint: %v", err) + } + return n +} + +func (r *importReader) uint64() uint64 { + n, err := binary.ReadUvarint(&r.declReader) + if err != nil { + errorf("readUvarint: %v", err) + } + return n +} + +func (r *importReader) byte() byte { + x, err := r.declReader.ReadByte() + if err != nil { + errorf("declReader.ReadByte: %v", err) + } + return x +} diff --git a/src/cmd/compile/internal/importer/support.go b/src/cmd/compile/internal/importer/support.go new file mode 100644 index 0000000000000000000000000000000000000000..40b9c7c9583fbff277c7d5a2237bdc82596384de --- /dev/null +++ b/src/cmd/compile/internal/importer/support.go @@ -0,0 +1,128 @@ +// UNREVIEWED +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements support functionality for iimport.go. + +package importer + +import ( + "cmd/compile/internal/types2" + "fmt" + "go/token" + "sync" +) + +func errorf(format string, args ...interface{}) { + panic(fmt.Sprintf(format, args...)) +} + +const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go + +// Synthesize a token.Pos +type fakeFileSet struct { + fset *token.FileSet + files map[string]*token.File +} + +func (s *fakeFileSet) pos(file string, line, column int) token.Pos { + // TODO(mdempsky): Make use of column. + + // Since we don't know the set of needed file positions, we + // reserve maxlines positions per file. + const maxlines = 64 * 1024 + f := s.files[file] + if f == nil { + f = s.fset.AddFile(file, -1, maxlines) + s.files[file] = f + // Allocate the fake linebreak indices on first use. + // TODO(adonovan): opt: save ~512KB using a more complex scheme? + fakeLinesOnce.Do(func() { + fakeLines = make([]int, maxlines) + for i := range fakeLines { + fakeLines[i] = i + } + }) + f.SetLines(fakeLines) + } + + if line > maxlines { + line = 1 + } + + // Treat the file as if it contained only newlines + // and column=1: use the line number as the offset. + return f.Pos(line - 1) +} + +var ( + fakeLines []int + fakeLinesOnce sync.Once +) + +func chanDir(d int) types2.ChanDir { + // tag values must match the constants in cmd/compile/internal/gc/go.go + switch d { + case 1 /* Crecv */ : + return types2.RecvOnly + case 2 /* Csend */ : + return types2.SendOnly + case 3 /* Cboth */ : + return types2.SendRecv + default: + errorf("unexpected channel dir %d", d) + return 0 + } +} + +var predeclared = []types2.Type{ + // basic types + types2.Typ[types2.Bool], + types2.Typ[types2.Int], + types2.Typ[types2.Int8], + types2.Typ[types2.Int16], + types2.Typ[types2.Int32], + types2.Typ[types2.Int64], + types2.Typ[types2.Uint], + types2.Typ[types2.Uint8], + types2.Typ[types2.Uint16], + types2.Typ[types2.Uint32], + types2.Typ[types2.Uint64], + types2.Typ[types2.Uintptr], + types2.Typ[types2.Float32], + types2.Typ[types2.Float64], + types2.Typ[types2.Complex64], + types2.Typ[types2.Complex128], + types2.Typ[types2.String], + + // basic type aliases + types2.Universe.Lookup("byte").Type(), + types2.Universe.Lookup("rune").Type(), + + // error + types2.Universe.Lookup("error").Type(), + + // untyped types + types2.Typ[types2.UntypedBool], + types2.Typ[types2.UntypedInt], + types2.Typ[types2.UntypedRune], + types2.Typ[types2.UntypedFloat], + types2.Typ[types2.UntypedComplex], + types2.Typ[types2.UntypedString], + types2.Typ[types2.UntypedNil], + + // package unsafe + types2.Typ[types2.UnsafePointer], + + // invalid type + types2.Typ[types2.Invalid], // only appears in packages with errors + + // used internally by gc; never used by this package or in .a files + anyType{}, +} + +type anyType struct{} + +func (t anyType) Underlying() types2.Type { return t } +func (t anyType) String() string { return "any" } diff --git a/src/cmd/compile/internal/importer/testdata/a.go b/src/cmd/compile/internal/importer/testdata/a.go new file mode 100644 index 0000000000000000000000000000000000000000..06dafee98c30f82e459be13f0c0ced7e535d57af --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/a.go @@ -0,0 +1,15 @@ +// UNREVIEWED +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Input for TestIssue13566 + +package a + +import "encoding/json" + +type A struct { + a *A + json json.RawMessage +} diff --git a/src/cmd/compile/internal/importer/testdata/b.go b/src/cmd/compile/internal/importer/testdata/b.go new file mode 100644 index 0000000000000000000000000000000000000000..a601dbccc5a0e9b76ac8bfd752e2fb7e5b6397cd --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/b.go @@ -0,0 +1,12 @@ +// UNREVIEWED +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Input for TestIssue13566 + +package b + +import "./a" + +type A a.A diff --git a/src/cmd/compile/internal/importer/testdata/exports.go b/src/cmd/compile/internal/importer/testdata/exports.go new file mode 100644 index 0000000000000000000000000000000000000000..2a720fd2c172541bbbe32976791534c06a2ecbdb --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/exports.go @@ -0,0 +1,89 @@ +// UNREVIEWED +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is used to generate an object file which +// serves as test file for gcimporter_test.go. + +package exports + +import "go/ast" + +// Issue 3682: Correctly read dotted identifiers from export data. +const init1 = 0 + +func init() {} + +const ( + C0 int = 0 + C1 = 3.14159265 + C2 = 2.718281828i + C3 = -123.456e-789 + C4 = +123.456e+789 + C5 = 1234i + C6 = "foo\n" + C7 = `bar\n` +) + +type ( + T1 int + T2 [10]int + T3 []int + T4 *int + T5 chan int + T6a chan<- int + T6b chan (<-chan int) + T6c chan<- (chan int) + T7 <-chan *ast.File + T8 struct{} + T9 struct { + a int + b, c float32 + d []string `go:"tag"` + } + T10 struct { + T8 + T9 + _ *T10 + } + T11 map[int]string + T12 interface{} + T13 interface { + m1() + m2(int) float32 + } + T14 interface { + T12 + T13 + m3(x ...struct{}) []T9 + } + T15 func() + T16 func(int) + T17 func(x int) + T18 func() float32 + T19 func() (x float32) + T20 func(...interface{}) + T21 struct{ next *T21 } + T22 struct{ link *T23 } + T23 struct{ link *T22 } + T24 *T24 + T25 *T26 + T26 *T27 + T27 *T25 + T28 func(T28) T28 +) + +var ( + V0 int + V1 = -991.0 + V2 float32 = 1.2 +) + +func F1() {} +func F2(x int) {} +func F3() int { return 0 } +func F4() float32 { return 0 } +func F5(a, b, c int, u, v, w struct{ x, y T1 }, more ...interface{}) (p, q, r chan<- T10) + +func (p *T1) M1() diff --git a/src/cmd/compile/internal/importer/testdata/issue15920.go b/src/cmd/compile/internal/importer/testdata/issue15920.go new file mode 100644 index 0000000000000000000000000000000000000000..b40202616275836a6d5e43eec0f8d5cca29d1f74 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/issue15920.go @@ -0,0 +1,12 @@ +// UNREVIEWED +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// The underlying type of Error is the underlying type of error. +// Make sure we can import this again without problems. +type Error error + +func F() Error { return nil } diff --git a/src/cmd/compile/internal/importer/testdata/issue20046.go b/src/cmd/compile/internal/importer/testdata/issue20046.go new file mode 100644 index 0000000000000000000000000000000000000000..e412f353ad2e6d7cbca580ba24adb67acfe4c51e --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/issue20046.go @@ -0,0 +1,10 @@ +// UNREVIEWED +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +var V interface { + M() +} diff --git a/src/cmd/compile/internal/importer/testdata/issue25301.go b/src/cmd/compile/internal/importer/testdata/issue25301.go new file mode 100644 index 0000000000000000000000000000000000000000..a9dc1d7f083b2eae0353e51fff547b2776fc4476 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/issue25301.go @@ -0,0 +1,18 @@ +// UNREVIEWED +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package issue25301 + +type ( + A = interface { + M() + } + T interface { + A + } + S struct{} +) + +func (S) M() { println("m") } diff --git a/src/cmd/compile/internal/importer/testdata/issue25596.go b/src/cmd/compile/internal/importer/testdata/issue25596.go new file mode 100644 index 0000000000000000000000000000000000000000..95bef42280e9b4283bcec8bb32993ffc4999da87 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/issue25596.go @@ -0,0 +1,14 @@ +// UNREVIEWED +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package issue25596 + +type E interface { + M() T +} + +type T interface { + E +} diff --git a/src/cmd/compile/internal/importer/testdata/p.go b/src/cmd/compile/internal/importer/testdata/p.go new file mode 100644 index 0000000000000000000000000000000000000000..34a20eaa1451eca59573898adcad40c3a6a58e26 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/p.go @@ -0,0 +1,14 @@ +// UNREVIEWED +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Input for TestIssue15517 + +package p + +const C = 0 + +var V int + +func F() {} diff --git a/src/cmd/compile/internal/importer/testdata/versions/test.go b/src/cmd/compile/internal/importer/testdata/versions/test.go new file mode 100644 index 0000000000000000000000000000000000000000..2f8eb5ced047c4ae88aadad7a34b688a2ad838a8 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/versions/test.go @@ -0,0 +1,29 @@ +// UNREVIEWED +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// To create a test case for a new export format version, +// build this package with the latest compiler and store +// the resulting .a file appropriately named in the versions +// directory. The VersionHandling test will pick it up. +// +// In the testdata/versions: +// +// go build -o test_go1.$X_$Y.a test.go +// +// with $X = Go version and $Y = export format version +// (add 'b' or 'i' to distinguish between binary and +// indexed format starting with 1.11 as long as both +// formats are supported). +// +// Make sure this source is extended such that it exercises +// whatever export format change has taken place. + +package test + +// Any release before and including Go 1.7 didn't encode +// the package for a blank struct field. +type BlankField struct { + _ int +} diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go new file mode 100644 index 0000000000000000000000000000000000000000..d6b4ced4e157cd46a2dbbcc814635453f32e198e --- /dev/null +++ b/src/cmd/compile/internal/inline/inl.go @@ -0,0 +1,1506 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// The inlining facility makes 2 passes: first caninl determines which +// functions are suitable for inlining, and for those that are it +// saves a copy of the body. Then InlineCalls walks each function body to +// expand calls to inlinable functions. +// +// The Debug.l flag controls the aggressiveness. Note that main() swaps level 0 and 1, +// making 1 the default and -l disable. Additional levels (beyond -l) may be buggy and +// are not supported. +// 0: disabled +// 1: 80-nodes leaf functions, oneliners, panic, lazy typechecking (default) +// 2: (unassigned) +// 3: (unassigned) +// 4: allow non-leaf functions +// +// At some point this may get another default and become switch-offable with -N. +// +// The -d typcheckinl flag enables early typechecking of all imported bodies, +// which is useful to flush out bugs. +// +// The Debug.m flag enables diagnostic output. a single -m is useful for verifying +// which calls get inlined or not, more is for debugging, and may go away at any point. + +package inline + +import ( + "fmt" + "go/constant" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/logopt" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/src" +) + +// Inlining budget parameters, gathered in one place +const ( + inlineMaxBudget = 80 + inlineExtraAppendCost = 0 + // default is to inline if there's at most one call. -l=4 overrides this by using 1 instead. + inlineExtraCallCost = 57 // 57 was benchmarked to provided most benefit with no bad surprises; see https://github.com/golang/go/issues/19348#issuecomment-439370742 + inlineExtraPanicCost = 1 // do not penalize inlining panics. + inlineExtraThrowCost = inlineMaxBudget // with current (2018-05/1.11) code, inlining runtime.throw does not help. + + inlineBigFunctionNodes = 5000 // Functions with this many nodes are considered "big". + inlineBigFunctionMaxCost = 20 // Max cost of inlinee when inlining into a "big" function. +) + +// InlinePackage finds functions that can be inlined and clones them before walk expands them. +func InlinePackage() { + ir.VisitFuncsBottomUp(typecheck.Target.Decls, func(list []*ir.Func, recursive bool) { + numfns := numNonClosures(list) + for _, n := range list { + if !recursive || numfns > 1 { + // We allow inlining if there is no + // recursion, or the recursion cycle is + // across more than one function. + CanInline(n) + } else { + if base.Flag.LowerM > 1 { + fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(n), n.Nname) + } + } + InlineCalls(n) + } + }) +} + +// CanInline determines whether fn is inlineable. +// If so, CanInline saves copies of fn.Body and fn.Dcl in fn.Inl. +// fn and fn.Body will already have been typechecked. +func CanInline(fn *ir.Func) { + if fn.Nname == nil { + base.Fatalf("CanInline no nname %+v", fn) + } + + var reason string // reason, if any, that the function was not inlined + if base.Flag.LowerM > 1 || logopt.Enabled() { + defer func() { + if reason != "" { + if base.Flag.LowerM > 1 { + fmt.Printf("%v: cannot inline %v: %s\n", ir.Line(fn), fn.Nname, reason) + } + if logopt.Enabled() { + logopt.LogOpt(fn.Pos(), "cannotInlineFunction", "inline", ir.FuncName(fn), reason) + } + } + }() + } + + // If marked "go:noinline", don't inline + if fn.Pragma&ir.Noinline != 0 { + reason = "marked go:noinline" + return + } + + // If marked "go:norace" and -race compilation, don't inline. + if base.Flag.Race && fn.Pragma&ir.Norace != 0 { + reason = "marked go:norace with -race compilation" + return + } + + // If marked "go:nocheckptr" and -d checkptr compilation, don't inline. + if base.Debug.Checkptr != 0 && fn.Pragma&ir.NoCheckPtr != 0 { + reason = "marked go:nocheckptr" + return + } + + // If marked "go:cgo_unsafe_args", don't inline, since the + // function makes assumptions about its argument frame layout. + if fn.Pragma&ir.CgoUnsafeArgs != 0 { + reason = "marked go:cgo_unsafe_args" + return + } + + // If marked as "go:uintptrescapes", don't inline, since the + // escape information is lost during inlining. + if fn.Pragma&ir.UintptrEscapes != 0 { + reason = "marked as having an escaping uintptr argument" + return + } + + // The nowritebarrierrec checker currently works at function + // granularity, so inlining yeswritebarrierrec functions can + // confuse it (#22342). As a workaround, disallow inlining + // them for now. + if fn.Pragma&ir.Yeswritebarrierrec != 0 { + reason = "marked go:yeswritebarrierrec" + return + } + + // If fn has no body (is defined outside of Go), cannot inline it. + if len(fn.Body) == 0 { + reason = "no function body" + return + } + + if fn.Typecheck() == 0 { + base.Fatalf("CanInline on non-typechecked function %v", fn) + } + + n := fn.Nname + if n.Func.InlinabilityChecked() { + return + } + defer n.Func.SetInlinabilityChecked(true) + + cc := int32(inlineExtraCallCost) + if base.Flag.LowerL == 4 { + cc = 1 // this appears to yield better performance than 0. + } + + // At this point in the game the function we're looking at may + // have "stale" autos, vars that still appear in the Dcl list, but + // which no longer have any uses in the function body (due to + // elimination by deadcode). We'd like to exclude these dead vars + // when creating the "Inline.Dcl" field below; to accomplish this, + // the hairyVisitor below builds up a map of used/referenced + // locals, and we use this map to produce a pruned Inline.Dcl + // list. See issue 25249 for more context. + + visitor := hairyVisitor{ + budget: inlineMaxBudget, + extraCallCost: cc, + } + if visitor.tooHairy(fn) { + reason = visitor.reason + return + } + + n.Func.Inl = &ir.Inline{ + Cost: inlineMaxBudget - visitor.budget, + Dcl: pruneUnusedAutos(n.Defn.(*ir.Func).Dcl, &visitor), + Body: inlcopylist(fn.Body), + } + + if base.Flag.LowerM > 1 { + fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n, inlineMaxBudget-visitor.budget, fn.Type(), ir.Nodes(n.Func.Inl.Body)) + } else if base.Flag.LowerM != 0 { + fmt.Printf("%v: can inline %v\n", ir.Line(fn), n) + } + if logopt.Enabled() { + logopt.LogOpt(fn.Pos(), "canInlineFunction", "inline", ir.FuncName(fn), fmt.Sprintf("cost: %d", inlineMaxBudget-visitor.budget)) + } +} + +// Inline_Flood marks n's inline body for export and recursively ensures +// all called functions are marked too. +func Inline_Flood(n *ir.Name, exportsym func(*ir.Name)) { + if n == nil { + return + } + if n.Op() != ir.ONAME || n.Class != ir.PFUNC { + base.Fatalf("Inline_Flood: unexpected %v, %v, %v", n, n.Op(), n.Class) + } + fn := n.Func + if fn == nil { + base.Fatalf("Inline_Flood: missing Func on %v", n) + } + if fn.Inl == nil { + return + } + + if fn.ExportInline() { + return + } + fn.SetExportInline(true) + + typecheck.ImportedBody(fn) + + var doFlood func(n ir.Node) + doFlood = func(n ir.Node) { + switch n.Op() { + case ir.OMETHEXPR, ir.ODOTMETH: + Inline_Flood(ir.MethodExprName(n), exportsym) + + case ir.ONAME: + n := n.(*ir.Name) + switch n.Class { + case ir.PFUNC: + Inline_Flood(n, exportsym) + exportsym(n) + case ir.PEXTERN: + exportsym(n) + } + + case ir.OCALLPART: + // Okay, because we don't yet inline indirect + // calls to method values. + case ir.OCLOSURE: + // VisitList doesn't visit closure bodies, so force a + // recursive call to VisitList on the body of the closure. + ir.VisitList(n.(*ir.ClosureExpr).Func.Body, doFlood) + } + } + + // Recursively identify all referenced functions for + // reexport. We want to include even non-called functions, + // because after inlining they might be callable. + ir.VisitList(ir.Nodes(fn.Inl.Body), doFlood) +} + +// hairyVisitor visits a function body to determine its inlining +// hairiness and whether or not it can be inlined. +type hairyVisitor struct { + budget int32 + reason string + extraCallCost int32 + usedLocals ir.NameSet + do func(ir.Node) bool +} + +func (v *hairyVisitor) tooHairy(fn *ir.Func) bool { + v.do = v.doNode // cache closure + if ir.DoChildren(fn, v.do) { + return true + } + if v.budget < 0 { + v.reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-v.budget, inlineMaxBudget) + return true + } + return false +} + +func (v *hairyVisitor) doNode(n ir.Node) bool { + if n == nil { + return false + } + switch n.Op() { + // Call is okay if inlinable and we have the budget for the body. + case ir.OCALLFUNC: + n := n.(*ir.CallExpr) + // Functions that call runtime.getcaller{pc,sp} can not be inlined + // because getcaller{pc,sp} expect a pointer to the caller's first argument. + // + // runtime.throw is a "cheap call" like panic in normal code. + if n.X.Op() == ir.ONAME { + name := n.X.(*ir.Name) + if name.Class == ir.PFUNC && types.IsRuntimePkg(name.Sym().Pkg) { + fn := name.Sym().Name + if fn == "getcallerpc" || fn == "getcallersp" { + v.reason = "call to " + fn + return true + } + if fn == "throw" { + v.budget -= inlineExtraThrowCost + break + } + } + } + + if ir.IsIntrinsicCall(n) { + // Treat like any other node. + break + } + + if fn := inlCallee(n.X); fn != nil && fn.Inl != nil { + v.budget -= fn.Inl.Cost + break + } + + // Call cost for non-leaf inlining. + v.budget -= v.extraCallCost + + // Call is okay if inlinable and we have the budget for the body. + case ir.OCALLMETH: + n := n.(*ir.CallExpr) + t := n.X.Type() + if t == nil { + base.Fatalf("no function type for [%p] %+v\n", n.X, n.X) + } + fn := ir.MethodExprName(n.X).Func + if types.IsRuntimePkg(fn.Sym().Pkg) && fn.Sym().Name == "heapBits.nextArena" { + // Special case: explicitly allow + // mid-stack inlining of + // runtime.heapBits.next even though + // it calls slow-path + // runtime.heapBits.nextArena. + break + } + if fn.Inl != nil { + v.budget -= fn.Inl.Cost + break + } + // Call cost for non-leaf inlining. + v.budget -= v.extraCallCost + + // Things that are too hairy, irrespective of the budget + case ir.OCALL, ir.OCALLINTER: + // Call cost for non-leaf inlining. + v.budget -= v.extraCallCost + + case ir.OPANIC: + n := n.(*ir.UnaryExpr) + if n.X.Op() == ir.OCONVIFACE && n.X.(*ir.ConvExpr).Implicit() { + // Hack to keep reflect.flag.mustBe inlinable for TestIntendedInlining. + // Before CL 284412, these conversions were introduced later in the + // compiler, so they didn't count against inlining budget. + v.budget++ + } + v.budget -= inlineExtraPanicCost + + case ir.ORECOVER: + // recover matches the argument frame pointer to find + // the right panic value, so it needs an argument frame. + v.reason = "call to recover" + return true + + case ir.OCLOSURE: + if base.Debug.InlFuncsWithClosures == 0 { + v.reason = "not inlining functions with closures" + return true + } + + // TODO(danscales): Maybe make budget proportional to number of closure + // variables, e.g.: + //v.budget -= int32(len(n.(*ir.ClosureExpr).Func.ClosureVars) * 3) + v.budget -= 15 + // Scan body of closure (which DoChildren doesn't automatically + // do) to check for disallowed ops in the body and include the + // body in the budget. + if doList(n.(*ir.ClosureExpr).Func.Body, v.do) { + return true + } + + case ir.ORANGE, + ir.OSELECT, + ir.OGO, + ir.ODEFER, + ir.ODCLTYPE, // can't print yet + ir.OTAILCALL: + v.reason = "unhandled op " + n.Op().String() + return true + + case ir.OAPPEND: + v.budget -= inlineExtraAppendCost + + case ir.ODEREF: + // *(*X)(unsafe.Pointer(&x)) is low-cost + n := n.(*ir.StarExpr) + + ptr := n.X + for ptr.Op() == ir.OCONVNOP { + ptr = ptr.(*ir.ConvExpr).X + } + if ptr.Op() == ir.OADDR { + v.budget += 1 // undo half of default cost of ir.ODEREF+ir.OADDR + } + + case ir.OCONVNOP: + // This doesn't produce code, but the children might. + v.budget++ // undo default cost + + case ir.ODCLCONST, ir.OFALL: + // These nodes don't produce code; omit from inlining budget. + return false + + case ir.OFOR, ir.OFORUNTIL: + n := n.(*ir.ForStmt) + if n.Label != nil { + v.reason = "labeled control" + return true + } + case ir.OSWITCH: + n := n.(*ir.SwitchStmt) + if n.Label != nil { + v.reason = "labeled control" + return true + } + // case ir.ORANGE, ir.OSELECT in "unhandled" above + + case ir.OBREAK, ir.OCONTINUE: + n := n.(*ir.BranchStmt) + if n.Label != nil { + // Should have short-circuited due to labeled control error above. + base.Fatalf("unexpected labeled break/continue: %v", n) + } + + case ir.OIF: + n := n.(*ir.IfStmt) + if ir.IsConst(n.Cond, constant.Bool) { + // This if and the condition cost nothing. + // TODO(rsc): It seems strange that we visit the dead branch. + return doList(n.Init(), v.do) || + doList(n.Body, v.do) || + doList(n.Else, v.do) + } + + case ir.ONAME: + n := n.(*ir.Name) + if n.Class == ir.PAUTO { + v.usedLocals.Add(n) + } + + case ir.OBLOCK: + // The only OBLOCK we should see at this point is an empty one. + // In any event, let the visitList(n.List()) below take care of the statements, + // and don't charge for the OBLOCK itself. The ++ undoes the -- below. + v.budget++ + + case ir.OCALLPART, ir.OSLICELIT: + v.budget-- // Hack for toolstash -cmp. + + case ir.OMETHEXPR: + v.budget++ // Hack for toolstash -cmp. + } + + v.budget-- + + // When debugging, don't stop early, to get full cost of inlining this function + if v.budget < 0 && base.Flag.LowerM < 2 && !logopt.Enabled() { + v.reason = "too expensive" + return true + } + + return ir.DoChildren(n, v.do) +} + +func isBigFunc(fn *ir.Func) bool { + budget := inlineBigFunctionNodes + return ir.Any(fn, func(n ir.Node) bool { + budget-- + return budget <= 0 + }) +} + +// inlcopylist (together with inlcopy) recursively copies a list of nodes, except +// that it keeps the same ONAME, OTYPE, and OLITERAL nodes. It is used for copying +// the body and dcls of an inlineable function. +func inlcopylist(ll []ir.Node) []ir.Node { + s := make([]ir.Node, len(ll)) + for i, n := range ll { + s[i] = inlcopy(n) + } + return s +} + +// inlcopy is like DeepCopy(), but does extra work to copy closures. +func inlcopy(n ir.Node) ir.Node { + var edit func(ir.Node) ir.Node + edit = func(x ir.Node) ir.Node { + switch x.Op() { + case ir.ONAME, ir.OTYPE, ir.OLITERAL, ir.ONIL: + return x + } + m := ir.Copy(x) + ir.EditChildren(m, edit) + if x.Op() == ir.OCLOSURE { + x := x.(*ir.ClosureExpr) + // Need to save/duplicate x.Func.Nname, + // x.Func.Nname.Ntype, x.Func.Dcl, x.Func.ClosureVars, and + // x.Func.Body for iexport and local inlining. + oldfn := x.Func + newfn := ir.NewFunc(oldfn.Pos()) + if oldfn.ClosureCalled() { + newfn.SetClosureCalled(true) + } + m.(*ir.ClosureExpr).Func = newfn + newfn.Nname = ir.NewNameAt(oldfn.Nname.Pos(), oldfn.Nname.Sym()) + // XXX OK to share fn.Type() ?? + newfn.Nname.SetType(oldfn.Nname.Type()) + // Ntype can be nil for -G=3 mode. + if oldfn.Nname.Ntype != nil { + newfn.Nname.Ntype = inlcopy(oldfn.Nname.Ntype).(ir.Ntype) + } + newfn.Body = inlcopylist(oldfn.Body) + // Make shallow copy of the Dcl and ClosureVar slices + newfn.Dcl = append([]*ir.Name(nil), oldfn.Dcl...) + newfn.ClosureVars = append([]*ir.Name(nil), oldfn.ClosureVars...) + } + return m + } + return edit(n) +} + +// InlineCalls/inlnode walks fn's statements and expressions and substitutes any +// calls made to inlineable functions. This is the external entry point. +func InlineCalls(fn *ir.Func) { + savefn := ir.CurFunc + ir.CurFunc = fn + maxCost := int32(inlineMaxBudget) + if isBigFunc(fn) { + maxCost = inlineBigFunctionMaxCost + } + // Map to keep track of functions that have been inlined at a particular + // call site, in order to stop inlining when we reach the beginning of a + // recursion cycle again. We don't inline immediately recursive functions, + // but allow inlining if there is a recursion cycle of many functions. + // Most likely, the inlining will stop before we even hit the beginning of + // the cycle again, but the map catches the unusual case. + inlMap := make(map[*ir.Func]bool) + var edit func(ir.Node) ir.Node + edit = func(n ir.Node) ir.Node { + return inlnode(n, maxCost, inlMap, edit) + } + ir.EditChildren(fn, edit) + ir.CurFunc = savefn +} + +// Turn an OINLCALL into a statement. +func inlconv2stmt(inlcall *ir.InlinedCallExpr) ir.Node { + n := ir.NewBlockStmt(inlcall.Pos(), nil) + n.List = inlcall.Init() + n.List.Append(inlcall.Body.Take()...) + return n +} + +// Turn an OINLCALL into a single valued expression. +// The result of inlconv2expr MUST be assigned back to n, e.g. +// n.Left = inlconv2expr(n.Left) +func inlconv2expr(n *ir.InlinedCallExpr) ir.Node { + r := n.ReturnVars[0] + return ir.InitExpr(append(n.Init(), n.Body...), r) +} + +// Turn the rlist (with the return values) of the OINLCALL in +// n into an expression list lumping the ninit and body +// containing the inlined statements on the first list element so +// order will be preserved. Used in return, oas2func and call +// statements. +func inlconv2list(n *ir.InlinedCallExpr) []ir.Node { + if n.Op() != ir.OINLCALL || len(n.ReturnVars) == 0 { + base.Fatalf("inlconv2list %+v\n", n) + } + + s := n.ReturnVars + s[0] = ir.InitExpr(append(n.Init(), n.Body...), s[0]) + return s +} + +// inlnode recurses over the tree to find inlineable calls, which will +// be turned into OINLCALLs by mkinlcall. When the recursion comes +// back up will examine left, right, list, rlist, ninit, ntest, nincr, +// nbody and nelse and use one of the 4 inlconv/glue functions above +// to turn the OINLCALL into an expression, a statement, or patch it +// in to this nodes list or rlist as appropriate. +// NOTE it makes no sense to pass the glue functions down the +// recursion to the level where the OINLCALL gets created because they +// have to edit /this/ n, so you'd have to push that one down as well, +// but then you may as well do it here. so this is cleaner and +// shorter and less complicated. +// The result of inlnode MUST be assigned back to n, e.g. +// n.Left = inlnode(n.Left) +func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node { + if n == nil { + return n + } + + switch n.Op() { + case ir.ODEFER, ir.OGO: + n := n.(*ir.GoDeferStmt) + switch call := n.Call; call.Op() { + case ir.OCALLFUNC, ir.OCALLMETH: + call := call.(*ir.CallExpr) + call.NoInline = true + } + + // TODO do them here (or earlier), + // so escape analysis can avoid more heapmoves. + case ir.OCLOSURE: + return n + case ir.OCALLMETH: + // Prevent inlining some reflect.Value methods when using checkptr, + // even when package reflect was compiled without it (#35073). + n := n.(*ir.CallExpr) + if s := ir.MethodExprName(n.X).Sym(); base.Debug.Checkptr != 0 && types.IsReflectPkg(s.Pkg) && (s.Name == "Value.UnsafeAddr" || s.Name == "Value.Pointer") { + return n + } + } + + lno := ir.SetPos(n) + + ir.EditChildren(n, edit) + + if as := n; as.Op() == ir.OAS2FUNC { + as := as.(*ir.AssignListStmt) + if as.Rhs[0].Op() == ir.OINLCALL { + as.Rhs = inlconv2list(as.Rhs[0].(*ir.InlinedCallExpr)) + as.SetOp(ir.OAS2) + as.SetTypecheck(0) + n = typecheck.Stmt(as) + } + } + + // with all the branches out of the way, it is now time to + // transmogrify this node itself unless inhibited by the + // switch at the top of this function. + switch n.Op() { + case ir.OCALLFUNC, ir.OCALLMETH: + n := n.(*ir.CallExpr) + if n.NoInline { + return n + } + } + + var call *ir.CallExpr + switch n.Op() { + case ir.OCALLFUNC: + call = n.(*ir.CallExpr) + if base.Flag.LowerM > 3 { + fmt.Printf("%v:call to func %+v\n", ir.Line(n), call.X) + } + if ir.IsIntrinsicCall(call) { + break + } + if fn := inlCallee(call.X); fn != nil && fn.Inl != nil { + n = mkinlcall(call, fn, maxCost, inlMap, edit) + } + + case ir.OCALLMETH: + call = n.(*ir.CallExpr) + if base.Flag.LowerM > 3 { + fmt.Printf("%v:call to meth %v\n", ir.Line(n), call.X.(*ir.SelectorExpr).Sel) + } + + // typecheck should have resolved ODOTMETH->type, whose nname points to the actual function. + if call.X.Type() == nil { + base.Fatalf("no function type for [%p] %+v\n", call.X, call.X) + } + + n = mkinlcall(call, ir.MethodExprName(call.X).Func, maxCost, inlMap, edit) + } + + base.Pos = lno + + if n.Op() == ir.OINLCALL { + ic := n.(*ir.InlinedCallExpr) + switch call.Use { + default: + ir.Dump("call", call) + base.Fatalf("call missing use") + case ir.CallUseExpr: + n = inlconv2expr(ic) + case ir.CallUseStmt: + n = inlconv2stmt(ic) + case ir.CallUseList: + // leave for caller to convert + } + } + + return n +} + +// inlCallee takes a function-typed expression and returns the underlying function ONAME +// that it refers to if statically known. Otherwise, it returns nil. +func inlCallee(fn ir.Node) *ir.Func { + fn = ir.StaticValue(fn) + switch fn.Op() { + case ir.OMETHEXPR: + fn := fn.(*ir.SelectorExpr) + n := ir.MethodExprName(fn) + // Check that receiver type matches fn.X. + // TODO(mdempsky): Handle implicit dereference + // of pointer receiver argument? + if n == nil || !types.Identical(n.Type().Recv().Type, fn.X.Type()) { + return nil + } + return n.Func + case ir.ONAME: + fn := fn.(*ir.Name) + if fn.Class == ir.PFUNC { + return fn.Func + } + case ir.OCLOSURE: + fn := fn.(*ir.ClosureExpr) + c := fn.Func + CanInline(c) + return c + } + return nil +} + +func inlParam(t *types.Field, as ir.InitNode, inlvars map[*ir.Name]*ir.Name) ir.Node { + if t.Nname == nil { + return ir.BlankNode + } + n := t.Nname.(*ir.Name) + if ir.IsBlank(n) { + return ir.BlankNode + } + inlvar := inlvars[n] + if inlvar == nil { + base.Fatalf("missing inlvar for %v", n) + } + as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, inlvar)) + inlvar.Name().Defn = as + return inlvar +} + +var inlgen int + +// SSADumpInline gives the SSA back end a chance to dump the function +// when producing output for debugging the compiler itself. +var SSADumpInline = func(*ir.Func) {} + +// If n is a call node (OCALLFUNC or OCALLMETH), and fn is an ONAME node for a +// function with an inlinable body, return an OINLCALL node that can replace n. +// The returned node's Ninit has the parameter assignments, the Nbody is the +// inlined function body, and (List, Rlist) contain the (input, output) +// parameters. +// The result of mkinlcall MUST be assigned back to n, e.g. +// n.Left = mkinlcall(n.Left, fn, isddd) +func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node { + if fn.Inl == nil { + if logopt.Enabled() { + logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(ir.CurFunc), + fmt.Sprintf("%s cannot be inlined", ir.PkgFuncName(fn))) + } + return n + } + if fn.Inl.Cost > maxCost { + // The inlined function body is too big. Typically we use this check to restrict + // inlining into very big functions. See issue 26546 and 17566. + if logopt.Enabled() { + logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(ir.CurFunc), + fmt.Sprintf("cost %d of %s exceeds max large caller cost %d", fn.Inl.Cost, ir.PkgFuncName(fn), maxCost)) + } + return n + } + + if fn == ir.CurFunc { + // Can't recursively inline a function into itself. + if logopt.Enabled() { + logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", ir.FuncName(ir.CurFunc))) + } + return n + } + + if base.Flag.Cfg.Instrumenting && types.IsRuntimePkg(fn.Sym().Pkg) { + // Runtime package must not be instrumented. + // Instrument skips runtime package. However, some runtime code can be + // inlined into other packages and instrumented there. To avoid this, + // we disable inlining of runtime functions when instrumenting. + // The example that we observed is inlining of LockOSThread, + // which lead to false race reports on m contents. + return n + } + + if inlMap[fn] { + if base.Flag.LowerM > 1 { + fmt.Printf("%v: cannot inline %v into %v: repeated recursive cycle\n", ir.Line(n), fn, ir.FuncName(ir.CurFunc)) + } + return n + } + inlMap[fn] = true + defer func() { + inlMap[fn] = false + }() + if base.Debug.TypecheckInl == 0 { + typecheck.ImportedBody(fn) + } + + // We have a function node, and it has an inlineable body. + if base.Flag.LowerM > 1 { + fmt.Printf("%v: inlining call to %v %v { %v }\n", ir.Line(n), fn.Sym(), fn.Type(), ir.Nodes(fn.Inl.Body)) + } else if base.Flag.LowerM != 0 { + fmt.Printf("%v: inlining call to %v\n", ir.Line(n), fn) + } + if base.Flag.LowerM > 2 { + fmt.Printf("%v: Before inlining: %+v\n", ir.Line(n), n) + } + + SSADumpInline(fn) + + ninit := n.Init() + + // For normal function calls, the function callee expression + // may contain side effects (e.g., added by addinit during + // inlconv2expr or inlconv2list). Make sure to preserve these, + // if necessary (#42703). + if n.Op() == ir.OCALLFUNC { + callee := n.X + for callee.Op() == ir.OCONVNOP { + conv := callee.(*ir.ConvExpr) + ninit.Append(ir.TakeInit(conv)...) + callee = conv.X + } + if callee.Op() != ir.ONAME && callee.Op() != ir.OCLOSURE && callee.Op() != ir.OMETHEXPR { + base.Fatalf("unexpected callee expression: %v", callee) + } + } + + // Make temp names to use instead of the originals. + inlvars := make(map[*ir.Name]*ir.Name) + + // record formals/locals for later post-processing + var inlfvars []*ir.Name + + for _, ln := range fn.Inl.Dcl { + if ln.Op() != ir.ONAME { + continue + } + if ln.Class == ir.PPARAMOUT { // return values handled below. + continue + } + inlf := typecheck.Expr(inlvar(ln)).(*ir.Name) + inlvars[ln] = inlf + if base.Flag.GenDwarfInl > 0 { + if ln.Class == ir.PPARAM { + inlf.Name().SetInlFormal(true) + } else { + inlf.Name().SetInlLocal(true) + } + inlf.SetPos(ln.Pos()) + inlfvars = append(inlfvars, inlf) + } + } + + // We can delay declaring+initializing result parameters if: + // (1) there's exactly one "return" statement in the inlined function; + // (2) it's not an empty return statement (#44355); and + // (3) the result parameters aren't named. + delayretvars := true + + nreturns := 0 + ir.VisitList(ir.Nodes(fn.Inl.Body), func(n ir.Node) { + if n, ok := n.(*ir.ReturnStmt); ok { + nreturns++ + if len(n.Results) == 0 { + delayretvars = false // empty return statement (case 2) + } + } + }) + + if nreturns != 1 { + delayretvars = false // not exactly one return statement (case 1) + } + + // temporaries for return values. + var retvars []ir.Node + for i, t := range fn.Type().Results().Fields().Slice() { + var m *ir.Name + if nn := t.Nname; nn != nil && !ir.IsBlank(nn.(*ir.Name)) && !strings.HasPrefix(nn.Sym().Name, "~r") { + n := nn.(*ir.Name) + m = inlvar(n) + m = typecheck.Expr(m).(*ir.Name) + inlvars[n] = m + delayretvars = false // found a named result parameter (case 3) + } else { + // anonymous return values, synthesize names for use in assignment that replaces return + m = retvar(t, i) + } + + if base.Flag.GenDwarfInl > 0 { + // Don't update the src.Pos on a return variable if it + // was manufactured by the inliner (e.g. "~R2"); such vars + // were not part of the original callee. + if !strings.HasPrefix(m.Sym().Name, "~R") { + m.Name().SetInlFormal(true) + m.SetPos(t.Pos) + inlfvars = append(inlfvars, m) + } + } + + retvars = append(retvars, m) + } + + // Assign arguments to the parameters' temp names. + as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil) + as.Def = true + if n.Op() == ir.OCALLMETH { + sel := n.X.(*ir.SelectorExpr) + if sel.X == nil { + base.Fatalf("method call without receiver: %+v", n) + } + as.Rhs.Append(sel.X) + } + as.Rhs.Append(n.Args...) + + // For non-dotted calls to variadic functions, we assign the + // variadic parameter's temp name separately. + var vas *ir.AssignStmt + + if recv := fn.Type().Recv(); recv != nil { + as.Lhs.Append(inlParam(recv, as, inlvars)) + } + for _, param := range fn.Type().Params().Fields().Slice() { + // For ordinary parameters or variadic parameters in + // dotted calls, just add the variable to the + // assignment list, and we're done. + if !param.IsDDD() || n.IsDDD { + as.Lhs.Append(inlParam(param, as, inlvars)) + continue + } + + // Otherwise, we need to collect the remaining values + // to pass as a slice. + + x := len(as.Lhs) + for len(as.Lhs) < len(as.Rhs) { + as.Lhs.Append(argvar(param.Type, len(as.Lhs))) + } + varargs := as.Lhs[x:] + + vas = ir.NewAssignStmt(base.Pos, nil, nil) + vas.X = inlParam(param, vas, inlvars) + if len(varargs) == 0 { + vas.Y = typecheck.NodNil() + vas.Y.SetType(param.Type) + } else { + lit := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(param.Type), nil) + lit.List = varargs + vas.Y = lit + } + } + + if len(as.Rhs) != 0 { + ninit.Append(typecheck.Stmt(as)) + } + + if vas != nil { + ninit.Append(typecheck.Stmt(vas)) + } + + if !delayretvars { + // Zero the return parameters. + for _, n := range retvars { + ninit.Append(ir.NewDecl(base.Pos, ir.ODCL, n.(*ir.Name))) + ras := ir.NewAssignStmt(base.Pos, n, nil) + ninit.Append(typecheck.Stmt(ras)) + } + } + + retlabel := typecheck.AutoLabel(".i") + + inlgen++ + + parent := -1 + if b := base.Ctxt.PosTable.Pos(n.Pos()).Base(); b != nil { + parent = b.InliningIndex() + } + + sym := fn.Linksym() + newIndex := base.Ctxt.InlTree.Add(parent, n.Pos(), sym) + + // Add an inline mark just before the inlined body. + // This mark is inline in the code so that it's a reasonable spot + // to put a breakpoint. Not sure if that's really necessary or not + // (in which case it could go at the end of the function instead). + // Note issue 28603. + inlMark := ir.NewInlineMarkStmt(base.Pos, types.BADWIDTH) + inlMark.SetPos(n.Pos().WithIsStmt()) + inlMark.Index = int64(newIndex) + ninit.Append(inlMark) + + if base.Flag.GenDwarfInl > 0 { + if !sym.WasInlined() { + base.Ctxt.DwFixups.SetPrecursorFunc(sym, fn) + sym.Set(obj.AttrWasInlined, true) + } + } + + subst := inlsubst{ + retlabel: retlabel, + retvars: retvars, + delayretvars: delayretvars, + inlvars: inlvars, + defnMarker: ir.NilExpr{}, + bases: make(map[*src.PosBase]*src.PosBase), + newInlIndex: newIndex, + fn: fn, + } + subst.edit = subst.node + + body := subst.list(ir.Nodes(fn.Inl.Body)) + + lab := ir.NewLabelStmt(base.Pos, retlabel) + body = append(body, lab) + + if !typecheck.Go117ExportTypes { + typecheck.Stmts(body) + } + + if base.Flag.GenDwarfInl > 0 { + for _, v := range inlfvars { + v.SetPos(subst.updatedPos(v.Pos())) + } + } + + //dumplist("ninit post", ninit); + + call := ir.NewInlinedCallExpr(base.Pos, nil, nil) + *call.PtrInit() = ninit + call.Body = body + call.ReturnVars = retvars + call.SetType(n.Type()) + call.SetTypecheck(1) + + // transitive inlining + // might be nice to do this before exporting the body, + // but can't emit the body with inlining expanded. + // instead we emit the things that the body needs + // and each use must redo the inlining. + // luckily these are small. + ir.EditChildren(call, edit) + + if base.Flag.LowerM > 2 { + fmt.Printf("%v: After inlining %+v\n\n", ir.Line(call), call) + } + + return call +} + +// Every time we expand a function we generate a new set of tmpnames, +// PAUTO's in the calling functions, and link them off of the +// PPARAM's, PAUTOS and PPARAMOUTs of the called function. +func inlvar(var_ *ir.Name) *ir.Name { + if base.Flag.LowerM > 3 { + fmt.Printf("inlvar %+v\n", var_) + } + + n := typecheck.NewName(var_.Sym()) + n.SetType(var_.Type()) + n.Class = ir.PAUTO + n.SetUsed(true) + n.Curfn = ir.CurFunc // the calling function, not the called one + n.SetAddrtaken(var_.Addrtaken()) + + ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, n) + return n +} + +// Synthesize a variable to store the inlined function's results in. +func retvar(t *types.Field, i int) *ir.Name { + n := typecheck.NewName(typecheck.LookupNum("~R", i)) + n.SetType(t.Type) + n.Class = ir.PAUTO + n.SetUsed(true) + n.Curfn = ir.CurFunc // the calling function, not the called one + ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, n) + return n +} + +// Synthesize a variable to store the inlined function's arguments +// when they come from a multiple return call. +func argvar(t *types.Type, i int) ir.Node { + n := typecheck.NewName(typecheck.LookupNum("~arg", i)) + n.SetType(t.Elem()) + n.Class = ir.PAUTO + n.SetUsed(true) + n.Curfn = ir.CurFunc // the calling function, not the called one + ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, n) + return n +} + +// The inlsubst type implements the actual inlining of a single +// function call. +type inlsubst struct { + // Target of the goto substituted in place of a return. + retlabel *types.Sym + + // Temporary result variables. + retvars []ir.Node + + // Whether result variables should be initialized at the + // "return" statement. + delayretvars bool + + inlvars map[*ir.Name]*ir.Name + // defnMarker is used to mark a Node for reassignment. + // inlsubst.clovar set this during creating new ONAME. + // inlsubst.node will set the correct Defn for inlvar. + defnMarker ir.NilExpr + + // bases maps from original PosBase to PosBase with an extra + // inlined call frame. + bases map[*src.PosBase]*src.PosBase + + // newInlIndex is the index of the inlined call frame to + // insert for inlined nodes. + newInlIndex int + + edit func(ir.Node) ir.Node // cached copy of subst.node method value closure + + // If non-nil, we are inside a closure inside the inlined function, and + // newclofn is the Func of the new inlined closure. + newclofn *ir.Func + + fn *ir.Func // For debug -- the func that is being inlined + + // If true, then don't update source positions during substitution + // (retain old source positions). + noPosUpdate bool +} + +// list inlines a list of nodes. +func (subst *inlsubst) list(ll ir.Nodes) []ir.Node { + s := make([]ir.Node, 0, len(ll)) + for _, n := range ll { + s = append(s, subst.node(n)) + } + return s +} + +// fields returns a list of the fields of a struct type representing receiver, +// params, or results, after duplicating the field nodes and substituting the +// Nname nodes inside the field nodes. +func (subst *inlsubst) fields(oldt *types.Type) []*types.Field { + oldfields := oldt.FieldSlice() + newfields := make([]*types.Field, len(oldfields)) + for i := range oldfields { + newfields[i] = oldfields[i].Copy() + if oldfields[i].Nname != nil { + newfields[i].Nname = subst.node(oldfields[i].Nname.(*ir.Name)) + } + } + return newfields +} + +// clovar creates a new ONAME node for a local variable or param of a closure +// inside a function being inlined. +func (subst *inlsubst) clovar(n *ir.Name) *ir.Name { + // TODO(danscales): want to get rid of this shallow copy, with code like the + // following, but it is hard to copy all the necessary flags in a maintainable way. + // m := ir.NewNameAt(n.Pos(), n.Sym()) + // m.Class = n.Class + // m.SetType(n.Type()) + // m.SetTypecheck(1) + //if n.IsClosureVar() { + // m.SetIsClosureVar(true) + //} + m := &ir.Name{} + *m = *n + m.Curfn = subst.newclofn + + switch defn := n.Defn.(type) { + case nil: + // ok + case *ir.Name: + if !n.IsClosureVar() { + base.FatalfAt(n.Pos(), "want closure variable, got: %+v", n) + } + if n.Sym().Pkg != types.LocalPkg { + // If the closure came from inlining a function from + // another package, must change package of captured + // variable to localpkg, so that the fields of the closure + // struct are local package and can be accessed even if + // name is not exported. If you disable this code, you can + // reproduce the problem by running 'go test + // go/internal/srcimporter'. TODO(mdempsky) - maybe change + // how we create closure structs? + m.SetSym(types.LocalPkg.Lookup(n.Sym().Name)) + } + // Make sure any inlvar which is the Defn + // of an ONAME closure var is rewritten + // during inlining. Don't substitute + // if Defn node is outside inlined function. + if subst.inlvars[n.Defn.(*ir.Name)] != nil { + m.Defn = subst.node(n.Defn) + } + case *ir.AssignStmt, *ir.AssignListStmt: + // Mark node for reassignment at the end of inlsubst.node. + m.Defn = &subst.defnMarker + case *ir.TypeSwitchGuard: + // TODO(mdempsky): Set m.Defn properly. See discussion on #45743. + default: + base.FatalfAt(n.Pos(), "unexpected Defn: %+v", defn) + } + + if n.Outer != nil { + // Either the outer variable is defined in function being inlined, + // and we will replace it with the substituted variable, or it is + // defined outside the function being inlined, and we should just + // skip the outer variable (the closure variable of the function + // being inlined). + s := subst.node(n.Outer).(*ir.Name) + if s == n.Outer { + s = n.Outer.Outer + } + m.Outer = s + } + return m +} + +// closure does the necessary substitions for a ClosureExpr n and returns the new +// closure node. +func (subst *inlsubst) closure(n *ir.ClosureExpr) ir.Node { + m := ir.Copy(n) + + // Prior to the subst edit, set a flag in the inlsubst to + // indicated that we don't want to update the source positions in + // the new closure. If we do this, it will appear that the closure + // itself has things inlined into it, which is not the case. See + // issue #46234 for more details. + defer func(prev bool) { subst.noPosUpdate = prev }(subst.noPosUpdate) + subst.noPosUpdate = true + ir.EditChildren(m, subst.edit) + + //fmt.Printf("Inlining func %v with closure into %v\n", subst.fn, ir.FuncName(ir.CurFunc)) + + // The following is similar to funcLit + oldfn := n.Func + newfn := ir.NewFunc(oldfn.Pos()) + // These three lines are not strictly necessary, but just to be clear + // that new function needs to redo typechecking and inlinability. + newfn.SetTypecheck(0) + newfn.SetInlinabilityChecked(false) + newfn.Inl = nil + newfn.SetIsHiddenClosure(true) + newfn.Nname = ir.NewNameAt(n.Pos(), ir.BlankNode.Sym()) + newfn.Nname.Func = newfn + // Ntype can be nil for -G=3 mode. + if oldfn.Nname.Ntype != nil { + newfn.Nname.Ntype = subst.node(oldfn.Nname.Ntype).(ir.Ntype) + } + newfn.Nname.Defn = newfn + + m.(*ir.ClosureExpr).Func = newfn + newfn.OClosure = m.(*ir.ClosureExpr) + + if subst.newclofn != nil { + //fmt.Printf("Inlining a closure with a nested closure\n") + } + prevxfunc := subst.newclofn + + // Mark that we are now substituting within a closure (within the + // inlined function), and create new nodes for all the local + // vars/params inside this closure. + subst.newclofn = newfn + newfn.Dcl = nil + newfn.ClosureVars = nil + for _, oldv := range oldfn.Dcl { + newv := subst.clovar(oldv) + subst.inlvars[oldv] = newv + newfn.Dcl = append(newfn.Dcl, newv) + } + for _, oldv := range oldfn.ClosureVars { + newv := subst.clovar(oldv) + subst.inlvars[oldv] = newv + newfn.ClosureVars = append(newfn.ClosureVars, newv) + } + + // Need to replace ONAME nodes in + // newfn.Type().FuncType().Receiver/Params/Results.FieldSlice().Nname + oldt := oldfn.Type() + newrecvs := subst.fields(oldt.Recvs()) + var newrecv *types.Field + if len(newrecvs) > 0 { + newrecv = newrecvs[0] + } + newt := types.NewSignature(oldt.Pkg(), newrecv, + nil, subst.fields(oldt.Params()), subst.fields(oldt.Results())) + + newfn.Nname.SetType(newt) + newfn.Body = subst.list(oldfn.Body) + + // Remove the nodes for the current closure from subst.inlvars + for _, oldv := range oldfn.Dcl { + delete(subst.inlvars, oldv) + } + for _, oldv := range oldfn.ClosureVars { + delete(subst.inlvars, oldv) + } + // Go back to previous closure func + subst.newclofn = prevxfunc + + // Actually create the named function for the closure, now that + // the closure is inlined in a specific function. + m.SetTypecheck(0) + if oldfn.ClosureCalled() { + typecheck.Callee(m) + } else { + typecheck.Expr(m) + } + return m +} + +// node recursively copies a node from the saved pristine body of the +// inlined function, substituting references to input/output +// parameters with ones to the tmpnames, and substituting returns with +// assignments to the output. +func (subst *inlsubst) node(n ir.Node) ir.Node { + if n == nil { + return nil + } + + switch n.Op() { + case ir.ONAME: + n := n.(*ir.Name) + + // Handle captured variables when inlining closures. + if n.IsClosureVar() && subst.newclofn == nil { + o := n.Outer + + // Deal with case where sequence of closures are inlined. + // TODO(danscales) - write test case to see if we need to + // go up multiple levels. + if o.Curfn != ir.CurFunc { + o = o.Outer + } + + // make sure the outer param matches the inlining location + if o == nil || o.Curfn != ir.CurFunc { + base.Fatalf("%v: unresolvable capture %v\n", ir.Line(n), n) + } + + if base.Flag.LowerM > 2 { + fmt.Printf("substituting captured name %+v -> %+v\n", n, o) + } + return o + } + + if inlvar := subst.inlvars[n]; inlvar != nil { // These will be set during inlnode + if base.Flag.LowerM > 2 { + fmt.Printf("substituting name %+v -> %+v\n", n, inlvar) + } + return inlvar + } + + if base.Flag.LowerM > 2 { + fmt.Printf("not substituting name %+v\n", n) + } + return n + + case ir.OMETHEXPR: + n := n.(*ir.SelectorExpr) + return n + + case ir.OLITERAL, ir.ONIL, ir.OTYPE: + // If n is a named constant or type, we can continue + // using it in the inline copy. Otherwise, make a copy + // so we can update the line number. + if n.Sym() != nil { + return n + } + + case ir.ORETURN: + if subst.newclofn != nil { + // Don't do special substitutions if inside a closure + break + } + // Since we don't handle bodies with closures, + // this return is guaranteed to belong to the current inlined function. + n := n.(*ir.ReturnStmt) + init := subst.list(n.Init()) + if len(subst.retvars) != 0 && len(n.Results) != 0 { + as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil) + + // Make a shallow copy of retvars. + // Otherwise OINLCALL.Rlist will be the same list, + // and later walk and typecheck may clobber it. + for _, n := range subst.retvars { + as.Lhs.Append(n) + } + as.Rhs = subst.list(n.Results) + + if subst.delayretvars { + for _, n := range as.Lhs { + as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, n.(*ir.Name))) + n.Name().Defn = as + } + } + + init = append(init, typecheck.Stmt(as)) + } + init = append(init, ir.NewBranchStmt(base.Pos, ir.OGOTO, subst.retlabel)) + typecheck.Stmts(init) + return ir.NewBlockStmt(base.Pos, init) + + case ir.OGOTO: + if subst.newclofn != nil { + // Don't do special substitutions if inside a closure + break + } + n := n.(*ir.BranchStmt) + m := ir.Copy(n).(*ir.BranchStmt) + m.SetPos(subst.updatedPos(m.Pos())) + *m.PtrInit() = nil + p := fmt.Sprintf("%s·%d", n.Label.Name, inlgen) + m.Label = typecheck.Lookup(p) + return m + + case ir.OLABEL: + if subst.newclofn != nil { + // Don't do special substitutions if inside a closure + break + } + n := n.(*ir.LabelStmt) + m := ir.Copy(n).(*ir.LabelStmt) + m.SetPos(subst.updatedPos(m.Pos())) + *m.PtrInit() = nil + p := fmt.Sprintf("%s·%d", n.Label.Name, inlgen) + m.Label = typecheck.Lookup(p) + return m + + case ir.OCLOSURE: + return subst.closure(n.(*ir.ClosureExpr)) + + } + + m := ir.Copy(n) + m.SetPos(subst.updatedPos(m.Pos())) + ir.EditChildren(m, subst.edit) + + switch m := m.(type) { + case *ir.AssignStmt: + if lhs, ok := m.X.(*ir.Name); ok && lhs.Defn == &subst.defnMarker { + lhs.Defn = m + } + case *ir.AssignListStmt: + for _, lhs := range m.Lhs { + if lhs, ok := lhs.(*ir.Name); ok && lhs.Defn == &subst.defnMarker { + lhs.Defn = m + } + } + } + + return m +} + +func (subst *inlsubst) updatedPos(xpos src.XPos) src.XPos { + if subst.noPosUpdate { + return xpos + } + pos := base.Ctxt.PosTable.Pos(xpos) + oldbase := pos.Base() // can be nil + newbase := subst.bases[oldbase] + if newbase == nil { + newbase = src.NewInliningBase(oldbase, subst.newInlIndex) + subst.bases[oldbase] = newbase + } + pos.SetBase(newbase) + return base.Ctxt.PosTable.XPos(pos) +} + +func pruneUnusedAutos(ll []*ir.Name, vis *hairyVisitor) []*ir.Name { + s := make([]*ir.Name, 0, len(ll)) + for _, n := range ll { + if n.Class == ir.PAUTO { + if !vis.usedLocals.Has(n) { + continue + } + } + s = append(s, n) + } + return s +} + +// numNonClosures returns the number of functions in list which are not closures. +func numNonClosures(list []*ir.Func) int { + count := 0 + for _, fn := range list { + if fn.OClosure == nil { + count++ + } + } + return count +} + +func doList(list []ir.Node, do func(ir.Node) bool) bool { + for _, x := range list { + if x != nil { + if do(x) { + return true + } + } + } + return false +} diff --git a/src/cmd/compile/internal/gc/bitset.go b/src/cmd/compile/internal/ir/bitset.go similarity index 79% rename from src/cmd/compile/internal/gc/bitset.go rename to src/cmd/compile/internal/ir/bitset.go index ed5eea0a11be9a559b2b237ba9c0c8db88706804..0c7bd542f60bbcdf31949dd53a4fe88d6fd21e42 100644 --- a/src/cmd/compile/internal/gc/bitset.go +++ b/src/cmd/compile/internal/ir/bitset.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package ir type bitset8 uint8 @@ -14,6 +14,18 @@ func (f *bitset8) set(mask uint8, b bool) { } } +func (f bitset8) get2(shift uint8) uint8 { + return uint8(f>>shift) & 3 +} + +// set2 sets two bits in f using the bottom two bits of b. +func (f *bitset8) set2(shift uint8, b uint8) { + // Clear old bits. + *(*uint8)(f) &^= 3 << shift + // Set new bits. + *(*uint8)(f) |= uint8(b&3) << shift +} + type bitset16 uint16 func (f *bitset16) set(mask uint16, b bool) { diff --git a/src/cmd/compile/internal/ir/cfg.go b/src/cmd/compile/internal/ir/cfg.go new file mode 100644 index 0000000000000000000000000000000000000000..d986ac3a1e974e93ff0adad2218de94dd11bf222 --- /dev/null +++ b/src/cmd/compile/internal/ir/cfg.go @@ -0,0 +1,26 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +var ( + // maximum size variable which we will allocate on the stack. + // This limit is for explicit variable declarations like "var x T" or "x := ...". + // Note: the flag smallframes can update this value. + MaxStackVarSize = int64(10 * 1024 * 1024) + + // maximum size of implicit variables that we will allocate on the stack. + // p := new(T) allocating T on the stack + // p := &T{} allocating T on the stack + // s := make([]T, n) allocating [n]T on the stack + // s := []byte("...") allocating [n]byte on the stack + // Note: the flag smallframes can update this value. + MaxImplicitStackVarSize = int64(64 * 1024) + + // MaxSmallArraySize is the maximum size of an array which is considered small. + // Small arrays will be initialized directly with a sequence of constant stores. + // Large arrays will be initialized by copying from a static temp. + // 256 bytes was chosen to minimize generated code + statictmp size. + MaxSmallArraySize = int64(256) +) diff --git a/src/cmd/compile/internal/gc/class_string.go b/src/cmd/compile/internal/ir/class_string.go similarity index 66% rename from src/cmd/compile/internal/gc/class_string.go rename to src/cmd/compile/internal/ir/class_string.go index a4084a7535209505093583c7b2745520c68772fd..11a94c004701ba4f6238217b145b2972abbea1a7 100644 --- a/src/cmd/compile/internal/gc/class_string.go +++ b/src/cmd/compile/internal/ir/class_string.go @@ -1,6 +1,6 @@ -// Code generated by "stringer -type=Class"; DO NOT EDIT. +// Code generated by "stringer -type=Class name.go"; DO NOT EDIT. -package gc +package ir import "strconv" @@ -14,12 +14,13 @@ func _() { _ = x[PAUTOHEAP-3] _ = x[PPARAM-4] _ = x[PPARAMOUT-5] - _ = x[PFUNC-6] + _ = x[PTYPEPARAM-6] + _ = x[PFUNC-7] } -const _Class_name = "PxxxPEXTERNPAUTOPAUTOHEAPPPARAMPPARAMOUTPFUNC" +const _Class_name = "PxxxPEXTERNPAUTOPAUTOHEAPPPARAMPPARAMOUTPTYPEPARAMPFUNC" -var _Class_index = [...]uint8{0, 4, 11, 16, 25, 31, 40, 45} +var _Class_index = [...]uint8{0, 4, 11, 16, 25, 31, 40, 50, 55} func (i Class) String() string { if i >= Class(len(_Class_index)-1) { diff --git a/src/cmd/compile/internal/ir/const.go b/src/cmd/compile/internal/ir/const.go new file mode 100644 index 0000000000000000000000000000000000000000..eaa4d5b6b15ca79534832f76cd4d81d0013ef13b --- /dev/null +++ b/src/cmd/compile/internal/ir/const.go @@ -0,0 +1,99 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "go/constant" + "math" + "math/big" + + "cmd/compile/internal/base" + "cmd/compile/internal/types" +) + +func NewBool(b bool) Node { + return NewLiteral(constant.MakeBool(b)) +} + +func NewInt(v int64) Node { + return NewLiteral(constant.MakeInt64(v)) +} + +func NewString(s string) Node { + return NewLiteral(constant.MakeString(s)) +} + +const ( + // Maximum size in bits for big.Ints before signalling + // overflow and also mantissa precision for big.Floats. + ConstPrec = 512 +) + +func BigFloat(v constant.Value) *big.Float { + f := new(big.Float) + f.SetPrec(ConstPrec) + switch u := constant.Val(v).(type) { + case int64: + f.SetInt64(u) + case *big.Int: + f.SetInt(u) + case *big.Float: + f.Set(u) + case *big.Rat: + f.SetRat(u) + default: + base.Fatalf("unexpected: %v", u) + } + return f +} + +// ConstOverflow reports whether constant value v is too large +// to represent with type t. +func ConstOverflow(v constant.Value, t *types.Type) bool { + switch { + case t.IsInteger(): + bits := uint(8 * t.Size()) + if t.IsUnsigned() { + x, ok := constant.Uint64Val(v) + return !ok || x>>bits != 0 + } + x, ok := constant.Int64Val(v) + if x < 0 { + x = ^x + } + return !ok || x>>(bits-1) != 0 + case t.IsFloat(): + switch t.Size() { + case 4: + f, _ := constant.Float32Val(v) + return math.IsInf(float64(f), 0) + case 8: + f, _ := constant.Float64Val(v) + return math.IsInf(f, 0) + } + case t.IsComplex(): + ft := types.FloatForComplex(t) + return ConstOverflow(constant.Real(v), ft) || ConstOverflow(constant.Imag(v), ft) + } + base.Fatalf("ConstOverflow: %v, %v", v, t) + panic("unreachable") +} + +// IsConstNode reports whether n is a Go language constant (as opposed to a +// compile-time constant). +// +// Expressions derived from nil, like string([]byte(nil)), while they +// may be known at compile time, are not Go language constants. +func IsConstNode(n Node) bool { + return n.Op() == OLITERAL +} + +func IsSmallIntConst(n Node) bool { + if n.Op() == OLITERAL { + v, ok := constant.Int64Val(n.Val()) + return ok && int64(int32(v)) == v + } + return false +} diff --git a/src/cmd/compile/internal/ir/copy.go b/src/cmd/compile/internal/ir/copy.go new file mode 100644 index 0000000000000000000000000000000000000000..7da9b24940ff9d215d2c51e90b92a5595121da00 --- /dev/null +++ b/src/cmd/compile/internal/ir/copy.go @@ -0,0 +1,102 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "cmd/compile/internal/base" + "cmd/internal/src" +) + +// A Node may implement the Orig and SetOrig method to +// maintain a pointer to the "unrewritten" form of a Node. +// If a Node does not implement OrigNode, it is its own Orig. +// +// Note that both SepCopy and Copy have definitions compatible +// with a Node that does not implement OrigNode: such a Node +// is its own Orig, and in that case, that's what both want to return +// anyway (SepCopy unconditionally, and Copy only when the input +// is its own Orig as well, but if the output does not implement +// OrigNode, then neither does the input, making the condition true). +type OrigNode interface { + Node + Orig() Node + SetOrig(Node) +} + +// origNode may be embedded into a Node to make it implement OrigNode. +type origNode struct { + orig Node `mknode:"-"` +} + +func (n *origNode) Orig() Node { return n.orig } +func (n *origNode) SetOrig(o Node) { n.orig = o } + +// Orig returns the “original” node for n. +// If n implements OrigNode, Orig returns n.Orig(). +// Otherwise Orig returns n itself. +func Orig(n Node) Node { + if n, ok := n.(OrigNode); ok { + o := n.Orig() + if o == nil { + Dump("Orig nil", n) + base.Fatalf("Orig returned nil") + } + return o + } + return n +} + +// SepCopy returns a separate shallow copy of n, +// breaking any Orig link to any other nodes. +func SepCopy(n Node) Node { + n = n.copy() + if n, ok := n.(OrigNode); ok { + n.SetOrig(n) + } + return n +} + +// Copy returns a shallow copy of n. +// If Orig(n) == n, then Orig(Copy(n)) == the copy. +// Otherwise the Orig link is preserved as well. +// +// The specific semantics surrounding Orig are subtle but right for most uses. +// See issues #26855 and #27765 for pitfalls. +func Copy(n Node) Node { + c := n.copy() + if n, ok := n.(OrigNode); ok && n.Orig() == n { + c.(OrigNode).SetOrig(c) + } + return c +} + +// DeepCopy returns a “deep” copy of n, with its entire structure copied +// (except for shared nodes like ONAME, ONONAME, OLITERAL, and OTYPE). +// If pos.IsKnown(), it sets the source position of newly allocated Nodes to pos. +func DeepCopy(pos src.XPos, n Node) Node { + var edit func(Node) Node + edit = func(x Node) Node { + switch x.Op() { + case OPACK, ONAME, ONONAME, OLITERAL, ONIL, OTYPE: + return x + } + x = Copy(x) + if pos.IsKnown() { + x.SetPos(pos) + } + EditChildren(x, edit) + return x + } + return edit(n) +} + +// DeepCopyList returns a list of deep copies (using DeepCopy) of the nodes in list. +func DeepCopyList(pos src.XPos, list []Node) []Node { + var out []Node + for _, n := range list { + out = append(out, DeepCopy(pos, n)) + } + return out +} diff --git a/src/cmd/compile/internal/gc/dump.go b/src/cmd/compile/internal/ir/dump.go similarity index 89% rename from src/cmd/compile/internal/gc/dump.go rename to src/cmd/compile/internal/ir/dump.go index 29eb1c1e48b6cf9f7b165c2ce6331b74352ace4e..59914baa5cc04cfe367fcfb51328805604e91e08 100644 --- a/src/cmd/compile/internal/gc/dump.go +++ b/src/cmd/compile/internal/ir/dump.go @@ -6,24 +6,26 @@ // for debugging purposes. The code is customized for Node graphs // and may be used for an alternative view of the node structure. -package gc +package ir import ( - "cmd/compile/internal/types" - "cmd/internal/src" "fmt" "io" "os" "reflect" "regexp" + + "cmd/compile/internal/base" + "cmd/compile/internal/types" + "cmd/internal/src" ) -// dump is like fdump but prints to stderr. -func dump(root interface{}, filter string, depth int) { - fdump(os.Stderr, root, filter, depth) +// DumpAny is like FDumpAny but prints to stderr. +func DumpAny(root interface{}, filter string, depth int) { + FDumpAny(os.Stderr, root, filter, depth) } -// fdump prints the structure of a rooted data structure +// FDumpAny prints the structure of a rooted data structure // to w by depth-first traversal of the data structure. // // The filter parameter is a regular expression. If it is @@ -40,7 +42,7 @@ func dump(root interface{}, filter string, depth int) { // rather than their type; struct fields with zero values or // non-matching field names are omitted, and "…" means recursion // depth has been reached or struct fields have been omitted. -func fdump(w io.Writer, root interface{}, filter string, depth int) { +func FDumpAny(w io.Writer, root interface{}, filter string, depth int) { if root == nil { fmt.Fprintln(w, "nil") return @@ -138,19 +140,9 @@ func (p *dumper) dump(x reflect.Value, depth int) { return } - // special cases - switch v := x.Interface().(type) { - case Nodes: - // unpack Nodes since reflect cannot look inside - // due to the unexported field in its struct - x = reflect.ValueOf(v.Slice()) - - case src.XPos: - p.printf("%s", linestr(v)) + if pos, ok := x.Interface().(src.XPos); ok { + p.printf("%s", base.FmtPos(pos)) return - - case *types.Node: - x = reflect.ValueOf(asNode(v)) } switch x.Kind() { @@ -203,7 +195,7 @@ func (p *dumper) dump(x reflect.Value, depth int) { isNode := false if n, ok := x.Interface().(Node); ok { isNode = true - p.printf("%s %s {", n.Op.String(), p.addr(x)) + p.printf("%s %s {", n.Op().String(), p.addr(x)) } else { p.printf("%s {", typ) } @@ -230,7 +222,7 @@ func (p *dumper) dump(x reflect.Value, depth int) { omitted = true continue // exclude zero-valued fields } - if n, ok := x.Interface().(Nodes); ok && n.Len() == 0 { + if n, ok := x.Interface().(Nodes); ok && len(n) == 0 { omitted = true continue // exclude empty Nodes slices } diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go new file mode 100644 index 0000000000000000000000000000000000000000..f70645f07913f8c309023d0fc84208d8e09b85b3 --- /dev/null +++ b/src/cmd/compile/internal/ir/expr.go @@ -0,0 +1,1079 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "bytes" + "cmd/compile/internal/base" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/src" + "fmt" + "go/constant" + "go/token" +) + +// An Expr is a Node that can appear as an expression. +type Expr interface { + Node + isExpr() +} + +// A miniExpr is a miniNode with extra fields common to expressions. +// TODO(rsc): Once we are sure about the contents, compact the bools +// into a bit field and leave extra bits available for implementations +// embedding miniExpr. Right now there are ~60 unused bits sitting here. +type miniExpr struct { + miniNode + typ *types.Type + init Nodes // TODO(rsc): Don't require every Node to have an init + flags bitset8 +} + +const ( + miniExprNonNil = 1 << iota + miniExprTransient + miniExprBounded + miniExprImplicit // for use by implementations; not supported by every Expr + miniExprCheckPtr +) + +func (*miniExpr) isExpr() {} + +func (n *miniExpr) Type() *types.Type { return n.typ } +func (n *miniExpr) SetType(x *types.Type) { n.typ = x } +func (n *miniExpr) NonNil() bool { return n.flags&miniExprNonNil != 0 } +func (n *miniExpr) MarkNonNil() { n.flags |= miniExprNonNil } +func (n *miniExpr) Transient() bool { return n.flags&miniExprTransient != 0 } +func (n *miniExpr) SetTransient(b bool) { n.flags.set(miniExprTransient, b) } +func (n *miniExpr) Bounded() bool { return n.flags&miniExprBounded != 0 } +func (n *miniExpr) SetBounded(b bool) { n.flags.set(miniExprBounded, b) } +func (n *miniExpr) Init() Nodes { return n.init } +func (n *miniExpr) PtrInit() *Nodes { return &n.init } +func (n *miniExpr) SetInit(x Nodes) { n.init = x } + +// An AddStringExpr is a string concatenation Expr[0] + Exprs[1] + ... + Expr[len(Expr)-1]. +type AddStringExpr struct { + miniExpr + List Nodes + Prealloc *Name +} + +func NewAddStringExpr(pos src.XPos, list []Node) *AddStringExpr { + n := &AddStringExpr{} + n.pos = pos + n.op = OADDSTR + n.List = list + return n +} + +// An AddrExpr is an address-of expression &X. +// It may end up being a normal address-of or an allocation of a composite literal. +type AddrExpr struct { + miniExpr + X Node + Prealloc *Name // preallocated storage if any +} + +func NewAddrExpr(pos src.XPos, x Node) *AddrExpr { + n := &AddrExpr{X: x} + n.op = OADDR + n.pos = pos + return n +} + +func (n *AddrExpr) Implicit() bool { return n.flags&miniExprImplicit != 0 } +func (n *AddrExpr) SetImplicit(b bool) { n.flags.set(miniExprImplicit, b) } + +func (n *AddrExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OADDR, OPTRLIT: + n.op = op + } +} + +// A BasicLit is a literal of basic type. +type BasicLit struct { + miniExpr + val constant.Value +} + +func NewBasicLit(pos src.XPos, val constant.Value) Node { + n := &BasicLit{val: val} + n.op = OLITERAL + n.pos = pos + if k := val.Kind(); k != constant.Unknown { + n.SetType(idealType(k)) + } + return n +} + +func (n *BasicLit) Val() constant.Value { return n.val } +func (n *BasicLit) SetVal(val constant.Value) { n.val = val } + +// A BinaryExpr is a binary expression X Op Y, +// or Op(X, Y) for builtin functions that do not become calls. +type BinaryExpr struct { + miniExpr + X Node + Y Node +} + +func NewBinaryExpr(pos src.XPos, op Op, x, y Node) *BinaryExpr { + n := &BinaryExpr{X: x, Y: y} + n.pos = pos + n.SetOp(op) + return n +} + +func (n *BinaryExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OADD, OADDSTR, OAND, OANDNOT, ODIV, OEQ, OGE, OGT, OLE, + OLSH, OLT, OMOD, OMUL, ONE, OOR, ORSH, OSUB, OXOR, + OCOPY, OCOMPLEX, OUNSAFEADD, OUNSAFESLICE, + OEFACE: + n.op = op + } +} + +// A CallUse records how the result of the call is used: +type CallUse byte + +const ( + _ CallUse = iota + + CallUseExpr // single expression result is used + CallUseList // list of results are used + CallUseStmt // results not used - call is a statement +) + +// A CallExpr is a function call X(Args). +type CallExpr struct { + miniExpr + origNode + X Node + Args Nodes + KeepAlive []*Name // vars to be kept alive until call returns + IsDDD bool + Use CallUse + NoInline bool + PreserveClosure bool // disable directClosureCall for this call +} + +func NewCallExpr(pos src.XPos, op Op, fun Node, args []Node) *CallExpr { + n := &CallExpr{X: fun} + n.pos = pos + n.orig = n + n.SetOp(op) + n.Args = args + return n +} + +func (*CallExpr) isStmt() {} + +func (n *CallExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OCALL, OCALLFUNC, OCALLINTER, OCALLMETH, + OAPPEND, ODELETE, OGETG, OMAKE, OPRINT, OPRINTN, ORECOVER: + n.op = op + } +} + +// A ClosureExpr is a function literal expression. +type ClosureExpr struct { + miniExpr + Func *Func `mknode:"-"` + Prealloc *Name +} + +func NewClosureExpr(pos src.XPos, fn *Func) *ClosureExpr { + n := &ClosureExpr{Func: fn} + n.op = OCLOSURE + n.pos = pos + return n +} + +// A CompLitExpr is a composite literal Type{Vals}. +// Before type-checking, the type is Ntype. +type CompLitExpr struct { + miniExpr + origNode + Ntype Ntype + List Nodes // initialized values + Prealloc *Name + Len int64 // backing array length for OSLICELIT +} + +func NewCompLitExpr(pos src.XPos, op Op, typ Ntype, list []Node) *CompLitExpr { + n := &CompLitExpr{Ntype: typ} + n.pos = pos + n.SetOp(op) + n.List = list + n.orig = n + return n +} + +func (n *CompLitExpr) Implicit() bool { return n.flags&miniExprImplicit != 0 } +func (n *CompLitExpr) SetImplicit(b bool) { n.flags.set(miniExprImplicit, b) } + +func (n *CompLitExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OARRAYLIT, OCOMPLIT, OMAPLIT, OSTRUCTLIT, OSLICELIT: + n.op = op + } +} + +type ConstExpr struct { + miniExpr + origNode + val constant.Value +} + +func NewConstExpr(val constant.Value, orig Node) Node { + n := &ConstExpr{val: val} + n.op = OLITERAL + n.pos = orig.Pos() + n.orig = orig + n.SetType(orig.Type()) + n.SetTypecheck(orig.Typecheck()) + n.SetDiag(orig.Diag()) + return n +} + +func (n *ConstExpr) Sym() *types.Sym { return n.orig.Sym() } +func (n *ConstExpr) Val() constant.Value { return n.val } + +// A ConvExpr is a conversion Type(X). +// It may end up being a value or a type. +type ConvExpr struct { + miniExpr + X Node +} + +func NewConvExpr(pos src.XPos, op Op, typ *types.Type, x Node) *ConvExpr { + n := &ConvExpr{X: x} + n.pos = pos + n.typ = typ + n.SetOp(op) + return n +} + +func (n *ConvExpr) Implicit() bool { return n.flags&miniExprImplicit != 0 } +func (n *ConvExpr) SetImplicit(b bool) { n.flags.set(miniExprImplicit, b) } +func (n *ConvExpr) CheckPtr() bool { return n.flags&miniExprCheckPtr != 0 } +func (n *ConvExpr) SetCheckPtr(b bool) { n.flags.set(miniExprCheckPtr, b) } + +func (n *ConvExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OCONV, OCONVIFACE, OCONVNOP, OBYTES2STR, OBYTES2STRTMP, ORUNES2STR, OSTR2BYTES, OSTR2BYTESTMP, OSTR2RUNES, ORUNESTR, OSLICE2ARRPTR: + n.op = op + } +} + +// An IndexExpr is an index expression X[Y]. +type IndexExpr struct { + miniExpr + X Node + Index Node + Assigned bool +} + +func NewIndexExpr(pos src.XPos, x, index Node) *IndexExpr { + n := &IndexExpr{X: x, Index: index} + n.pos = pos + n.op = OINDEX + return n +} + +func (n *IndexExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OINDEX, OINDEXMAP: + n.op = op + } +} + +// A KeyExpr is a Key: Value composite literal key. +type KeyExpr struct { + miniExpr + Key Node + Value Node +} + +func NewKeyExpr(pos src.XPos, key, value Node) *KeyExpr { + n := &KeyExpr{Key: key, Value: value} + n.pos = pos + n.op = OKEY + return n +} + +// A StructKeyExpr is an Field: Value composite literal key. +type StructKeyExpr struct { + miniExpr + Field *types.Sym + Value Node + Offset int64 +} + +func NewStructKeyExpr(pos src.XPos, field *types.Sym, value Node) *StructKeyExpr { + n := &StructKeyExpr{Field: field, Value: value} + n.pos = pos + n.op = OSTRUCTKEY + n.Offset = types.BADWIDTH + return n +} + +func (n *StructKeyExpr) Sym() *types.Sym { return n.Field } + +// An InlinedCallExpr is an inlined function call. +type InlinedCallExpr struct { + miniExpr + Body Nodes + ReturnVars Nodes +} + +func NewInlinedCallExpr(pos src.XPos, body, retvars []Node) *InlinedCallExpr { + n := &InlinedCallExpr{} + n.pos = pos + n.op = OINLCALL + n.Body = body + n.ReturnVars = retvars + return n +} + +// A LogicalExpr is a expression X Op Y where Op is && or ||. +// It is separate from BinaryExpr to make room for statements +// that must be executed before Y but after X. +type LogicalExpr struct { + miniExpr + X Node + Y Node +} + +func NewLogicalExpr(pos src.XPos, op Op, x, y Node) *LogicalExpr { + n := &LogicalExpr{X: x, Y: y} + n.pos = pos + n.SetOp(op) + return n +} + +func (n *LogicalExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OANDAND, OOROR: + n.op = op + } +} + +// A MakeExpr is a make expression: make(Type[, Len[, Cap]]). +// Op is OMAKECHAN, OMAKEMAP, OMAKESLICE, or OMAKESLICECOPY, +// but *not* OMAKE (that's a pre-typechecking CallExpr). +type MakeExpr struct { + miniExpr + Len Node + Cap Node +} + +func NewMakeExpr(pos src.XPos, op Op, len, cap Node) *MakeExpr { + n := &MakeExpr{Len: len, Cap: cap} + n.pos = pos + n.SetOp(op) + return n +} + +func (n *MakeExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OMAKECHAN, OMAKEMAP, OMAKESLICE, OMAKESLICECOPY: + n.op = op + } +} + +// A NilExpr represents the predefined untyped constant nil. +// (It may be copied and assigned a type, though.) +type NilExpr struct { + miniExpr + Sym_ *types.Sym // TODO: Remove +} + +func NewNilExpr(pos src.XPos) *NilExpr { + n := &NilExpr{} + n.pos = pos + n.op = ONIL + return n +} + +func (n *NilExpr) Sym() *types.Sym { return n.Sym_ } +func (n *NilExpr) SetSym(x *types.Sym) { n.Sym_ = x } + +// A ParenExpr is a parenthesized expression (X). +// It may end up being a value or a type. +type ParenExpr struct { + miniExpr + X Node +} + +func NewParenExpr(pos src.XPos, x Node) *ParenExpr { + n := &ParenExpr{X: x} + n.op = OPAREN + n.pos = pos + return n +} + +func (n *ParenExpr) Implicit() bool { return n.flags&miniExprImplicit != 0 } +func (n *ParenExpr) SetImplicit(b bool) { n.flags.set(miniExprImplicit, b) } + +func (*ParenExpr) CanBeNtype() {} + +// SetOTYPE changes n to be an OTYPE node returning t, +// like all the type nodes in type.go. +func (n *ParenExpr) SetOTYPE(t *types.Type) { + n.op = OTYPE + n.typ = t + t.SetNod(n) +} + +// A ResultExpr represents a direct access to a result. +type ResultExpr struct { + miniExpr + Index int64 // index of the result expr. +} + +func NewResultExpr(pos src.XPos, typ *types.Type, index int64) *ResultExpr { + n := &ResultExpr{Index: index} + n.pos = pos + n.op = ORESULT + n.typ = typ + return n +} + +// A LinksymOffsetExpr refers to an offset within a global variable. +// It is like a SelectorExpr but without the field name. +type LinksymOffsetExpr struct { + miniExpr + Linksym *obj.LSym + Offset_ int64 +} + +func NewLinksymOffsetExpr(pos src.XPos, lsym *obj.LSym, offset int64, typ *types.Type) *LinksymOffsetExpr { + n := &LinksymOffsetExpr{Linksym: lsym, Offset_: offset} + n.typ = typ + n.op = OLINKSYMOFFSET + return n +} + +// NewLinksymExpr is NewLinksymOffsetExpr, but with offset fixed at 0. +func NewLinksymExpr(pos src.XPos, lsym *obj.LSym, typ *types.Type) *LinksymOffsetExpr { + return NewLinksymOffsetExpr(pos, lsym, 0, typ) +} + +// NewNameOffsetExpr is NewLinksymOffsetExpr, but taking a *Name +// representing a global variable instead of an *obj.LSym directly. +func NewNameOffsetExpr(pos src.XPos, name *Name, offset int64, typ *types.Type) *LinksymOffsetExpr { + if name == nil || IsBlank(name) || !(name.Op() == ONAME && name.Class == PEXTERN) { + base.FatalfAt(pos, "cannot take offset of nil, blank name or non-global variable: %v", name) + } + return NewLinksymOffsetExpr(pos, name.Linksym(), offset, typ) +} + +// A SelectorExpr is a selector expression X.Sel. +type SelectorExpr struct { + miniExpr + X Node + Sel *types.Sym + Selection *types.Field + Prealloc *Name // preallocated storage for OCALLPART, if any +} + +func NewSelectorExpr(pos src.XPos, op Op, x Node, sel *types.Sym) *SelectorExpr { + n := &SelectorExpr{X: x, Sel: sel} + n.pos = pos + n.SetOp(op) + return n +} + +func (n *SelectorExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OXDOT, ODOT, ODOTPTR, ODOTMETH, ODOTINTER, OCALLPART, OMETHEXPR: + n.op = op + } +} + +func (n *SelectorExpr) Sym() *types.Sym { return n.Sel } +func (n *SelectorExpr) Implicit() bool { return n.flags&miniExprImplicit != 0 } +func (n *SelectorExpr) SetImplicit(b bool) { n.flags.set(miniExprImplicit, b) } +func (n *SelectorExpr) Offset() int64 { return n.Selection.Offset } + +func (n *SelectorExpr) FuncName() *Name { + if n.Op() != OMETHEXPR { + panic(n.no("FuncName")) + } + fn := NewNameAt(n.Selection.Pos, MethodSym(n.X.Type(), n.Sel)) + fn.Class = PFUNC + fn.SetType(n.Type()) + if n.Selection.Nname != nil { + // TODO(austin): Nname is nil for interface method + // expressions (I.M), so we can't attach a Func to + // those here. reflectdata.methodWrapper generates the + // Func. + fn.Func = n.Selection.Nname.(*Name).Func + } + return fn +} + +// Before type-checking, bytes.Buffer is a SelectorExpr. +// After type-checking it becomes a Name. +func (*SelectorExpr) CanBeNtype() {} + +// A SliceExpr is a slice expression X[Low:High] or X[Low:High:Max]. +type SliceExpr struct { + miniExpr + X Node + Low Node + High Node + Max Node +} + +func NewSliceExpr(pos src.XPos, op Op, x, low, high, max Node) *SliceExpr { + n := &SliceExpr{X: x, Low: low, High: high, Max: max} + n.pos = pos + n.op = op + return n +} + +func (n *SliceExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OSLICE, OSLICEARR, OSLICESTR, OSLICE3, OSLICE3ARR: + n.op = op + } +} + +// IsSlice3 reports whether o is a slice3 op (OSLICE3, OSLICE3ARR). +// o must be a slicing op. +func (o Op) IsSlice3() bool { + switch o { + case OSLICE, OSLICEARR, OSLICESTR: + return false + case OSLICE3, OSLICE3ARR: + return true + } + base.Fatalf("IsSlice3 op %v", o) + return false +} + +// A SliceHeader expression constructs a slice header from its parts. +type SliceHeaderExpr struct { + miniExpr + Ptr Node + Len Node + Cap Node +} + +func NewSliceHeaderExpr(pos src.XPos, typ *types.Type, ptr, len, cap Node) *SliceHeaderExpr { + n := &SliceHeaderExpr{Ptr: ptr, Len: len, Cap: cap} + n.pos = pos + n.op = OSLICEHEADER + n.typ = typ + return n +} + +// A StarExpr is a dereference expression *X. +// It may end up being a value or a type. +type StarExpr struct { + miniExpr + X Node +} + +func NewStarExpr(pos src.XPos, x Node) *StarExpr { + n := &StarExpr{X: x} + n.op = ODEREF + n.pos = pos + return n +} + +func (n *StarExpr) Implicit() bool { return n.flags&miniExprImplicit != 0 } +func (n *StarExpr) SetImplicit(b bool) { n.flags.set(miniExprImplicit, b) } + +func (*StarExpr) CanBeNtype() {} + +// SetOTYPE changes n to be an OTYPE node returning t, +// like all the type nodes in type.go. +func (n *StarExpr) SetOTYPE(t *types.Type) { + n.op = OTYPE + n.X = nil + n.typ = t + t.SetNod(n) +} + +// A TypeAssertionExpr is a selector expression X.(Type). +// Before type-checking, the type is Ntype. +type TypeAssertExpr struct { + miniExpr + X Node + Ntype Ntype + + // Runtime type information provided by walkDotType for + // assertions from non-empty interface to concrete type. + Itab *AddrExpr `mknode:"-"` // *runtime.itab for Type implementing X's type +} + +func NewTypeAssertExpr(pos src.XPos, x Node, typ Ntype) *TypeAssertExpr { + n := &TypeAssertExpr{X: x, Ntype: typ} + n.pos = pos + n.op = ODOTTYPE + return n +} + +func (n *TypeAssertExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case ODOTTYPE, ODOTTYPE2: + n.op = op + } +} + +// A UnaryExpr is a unary expression Op X, +// or Op(X) for a builtin function that does not end up being a call. +type UnaryExpr struct { + miniExpr + X Node +} + +func NewUnaryExpr(pos src.XPos, op Op, x Node) *UnaryExpr { + n := &UnaryExpr{X: x} + n.pos = pos + n.SetOp(op) + return n +} + +func (n *UnaryExpr) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OBITNOT, ONEG, ONOT, OPLUS, ORECV, + OALIGNOF, OCAP, OCLOSE, OIMAG, OLEN, ONEW, + OOFFSETOF, OPANIC, OREAL, OSIZEOF, + OCHECKNIL, OCFUNC, OIDATA, OITAB, OSPTR, OVARDEF, OVARKILL, OVARLIVE: + n.op = op + } +} + +// An InstExpr is a generic function or type instantiation. +type InstExpr struct { + miniExpr + X Node + Targs []Node +} + +func NewInstExpr(pos src.XPos, op Op, x Node, targs []Node) *InstExpr { + n := &InstExpr{X: x, Targs: targs} + n.pos = pos + n.op = op + return n +} + +func IsZero(n Node) bool { + switch n.Op() { + case ONIL: + return true + + case OLITERAL: + switch u := n.Val(); u.Kind() { + case constant.String: + return constant.StringVal(u) == "" + case constant.Bool: + return !constant.BoolVal(u) + default: + return constant.Sign(u) == 0 + } + + case OARRAYLIT: + n := n.(*CompLitExpr) + for _, n1 := range n.List { + if n1.Op() == OKEY { + n1 = n1.(*KeyExpr).Value + } + if !IsZero(n1) { + return false + } + } + return true + + case OSTRUCTLIT: + n := n.(*CompLitExpr) + for _, n1 := range n.List { + n1 := n1.(*StructKeyExpr) + if !IsZero(n1.Value) { + return false + } + } + return true + } + + return false +} + +// lvalue etc +func IsAddressable(n Node) bool { + switch n.Op() { + case OINDEX: + n := n.(*IndexExpr) + if n.X.Type() != nil && n.X.Type().IsArray() { + return IsAddressable(n.X) + } + if n.X.Type() != nil && n.X.Type().IsString() { + return false + } + fallthrough + case ODEREF, ODOTPTR: + return true + + case ODOT: + n := n.(*SelectorExpr) + return IsAddressable(n.X) + + case ONAME: + n := n.(*Name) + if n.Class == PFUNC { + return false + } + return true + + case OLINKSYMOFFSET: + return true + } + + return false +} + +func StaticValue(n Node) Node { + for { + if n.Op() == OCONVNOP { + n = n.(*ConvExpr).X + continue + } + + n1 := staticValue1(n) + if n1 == nil { + return n + } + n = n1 + } +} + +// staticValue1 implements a simple SSA-like optimization. If n is a local variable +// that is initialized and never reassigned, staticValue1 returns the initializer +// expression. Otherwise, it returns nil. +func staticValue1(nn Node) Node { + if nn.Op() != ONAME { + return nil + } + n := nn.(*Name) + if n.Class != PAUTO { + return nil + } + + defn := n.Defn + if defn == nil { + return nil + } + + var rhs Node +FindRHS: + switch defn.Op() { + case OAS: + defn := defn.(*AssignStmt) + rhs = defn.Y + case OAS2: + defn := defn.(*AssignListStmt) + for i, lhs := range defn.Lhs { + if lhs == n { + rhs = defn.Rhs[i] + break FindRHS + } + } + base.Fatalf("%v missing from LHS of %v", n, defn) + default: + return nil + } + if rhs == nil { + base.Fatalf("RHS is nil: %v", defn) + } + + if reassigned(n) { + return nil + } + + return rhs +} + +// reassigned takes an ONAME node, walks the function in which it is defined, and returns a boolean +// indicating whether the name has any assignments other than its declaration. +// The second return value is the first such assignment encountered in the walk, if any. It is mostly +// useful for -m output documenting the reason for inhibited optimizations. +// NB: global variables are always considered to be re-assigned. +// TODO: handle initial declaration not including an assignment and followed by a single assignment? +func reassigned(name *Name) bool { + if name.Op() != ONAME { + base.Fatalf("reassigned %v", name) + } + // no way to reliably check for no-reassignment of globals, assume it can be + if name.Curfn == nil { + return true + } + + // TODO(mdempsky): This is inefficient and becoming increasingly + // unwieldy. Figure out a way to generalize escape analysis's + // reassignment detection for use by inlining and devirtualization. + + // isName reports whether n is a reference to name. + isName := func(x Node) bool { + n, ok := x.(*Name) + return ok && n.Canonical() == name + } + + var do func(n Node) bool + do = func(n Node) bool { + switch n.Op() { + case OAS: + n := n.(*AssignStmt) + if isName(n.X) && n != name.Defn { + return true + } + case OAS2, OAS2FUNC, OAS2MAPR, OAS2DOTTYPE, OAS2RECV, OSELRECV2: + n := n.(*AssignListStmt) + for _, p := range n.Lhs { + if isName(p) && n != name.Defn { + return true + } + } + case OADDR: + n := n.(*AddrExpr) + if isName(OuterValue(n.X)) { + return true + } + case OCLOSURE: + n := n.(*ClosureExpr) + if Any(n.Func, do) { + return true + } + } + return false + } + return Any(name.Curfn, do) +} + +// IsIntrinsicCall reports whether the compiler back end will treat the call as an intrinsic operation. +var IsIntrinsicCall = func(*CallExpr) bool { return false } + +// SameSafeExpr checks whether it is safe to reuse one of l and r +// instead of computing both. SameSafeExpr assumes that l and r are +// used in the same statement or expression. In order for it to be +// safe to reuse l or r, they must: +// * be the same expression +// * not have side-effects (no function calls, no channel ops); +// however, panics are ok +// * not cause inappropriate aliasing; e.g. two string to []byte +// conversions, must result in two distinct slices +// +// The handling of OINDEXMAP is subtle. OINDEXMAP can occur both +// as an lvalue (map assignment) and an rvalue (map access). This is +// currently OK, since the only place SameSafeExpr gets used on an +// lvalue expression is for OSLICE and OAPPEND optimizations, and it +// is correct in those settings. +func SameSafeExpr(l Node, r Node) bool { + if l.Op() != r.Op() || !types.Identical(l.Type(), r.Type()) { + return false + } + + switch l.Op() { + case ONAME: + return l == r + + case ODOT, ODOTPTR: + l := l.(*SelectorExpr) + r := r.(*SelectorExpr) + return l.Sel != nil && r.Sel != nil && l.Sel == r.Sel && SameSafeExpr(l.X, r.X) + + case ODEREF: + l := l.(*StarExpr) + r := r.(*StarExpr) + return SameSafeExpr(l.X, r.X) + + case ONOT, OBITNOT, OPLUS, ONEG: + l := l.(*UnaryExpr) + r := r.(*UnaryExpr) + return SameSafeExpr(l.X, r.X) + + case OCONVNOP: + l := l.(*ConvExpr) + r := r.(*ConvExpr) + return SameSafeExpr(l.X, r.X) + + case OCONV: + l := l.(*ConvExpr) + r := r.(*ConvExpr) + // Some conversions can't be reused, such as []byte(str). + // Allow only numeric-ish types. This is a bit conservative. + return types.IsSimple[l.Type().Kind()] && SameSafeExpr(l.X, r.X) + + case OINDEX, OINDEXMAP: + l := l.(*IndexExpr) + r := r.(*IndexExpr) + return SameSafeExpr(l.X, r.X) && SameSafeExpr(l.Index, r.Index) + + case OADD, OSUB, OOR, OXOR, OMUL, OLSH, ORSH, OAND, OANDNOT, ODIV, OMOD: + l := l.(*BinaryExpr) + r := r.(*BinaryExpr) + return SameSafeExpr(l.X, r.X) && SameSafeExpr(l.Y, r.Y) + + case OLITERAL: + return constant.Compare(l.Val(), token.EQL, r.Val()) + + case ONIL: + return true + } + + return false +} + +// ShouldCheckPtr reports whether pointer checking should be enabled for +// function fn at a given level. See debugHelpFooter for defined +// levels. +func ShouldCheckPtr(fn *Func, level int) bool { + return base.Debug.Checkptr >= level && fn.Pragma&NoCheckPtr == 0 +} + +// IsReflectHeaderDataField reports whether l is an expression p.Data +// where p has type reflect.SliceHeader or reflect.StringHeader. +func IsReflectHeaderDataField(l Node) bool { + if l.Type() != types.Types[types.TUINTPTR] { + return false + } + + var tsym *types.Sym + switch l.Op() { + case ODOT: + l := l.(*SelectorExpr) + tsym = l.X.Type().Sym() + case ODOTPTR: + l := l.(*SelectorExpr) + tsym = l.X.Type().Elem().Sym() + default: + return false + } + + if tsym == nil || l.Sym().Name != "Data" || tsym.Pkg.Path != "reflect" { + return false + } + return tsym.Name == "SliceHeader" || tsym.Name == "StringHeader" +} + +func ParamNames(ft *types.Type) []Node { + args := make([]Node, ft.NumParams()) + for i, f := range ft.Params().FieldSlice() { + args[i] = AsNode(f.Nname) + } + return args +} + +// MethodSym returns the method symbol representing a method name +// associated with a specific receiver type. +// +// Method symbols can be used to distinguish the same method appearing +// in different method sets. For example, T.M and (*T).M have distinct +// method symbols. +// +// The returned symbol will be marked as a function. +func MethodSym(recv *types.Type, msym *types.Sym) *types.Sym { + sym := MethodSymSuffix(recv, msym, "") + sym.SetFunc(true) + return sym +} + +// MethodSymSuffix is like methodsym, but allows attaching a +// distinguisher suffix. To avoid collisions, the suffix must not +// start with a letter, number, or period. +func MethodSymSuffix(recv *types.Type, msym *types.Sym, suffix string) *types.Sym { + if msym.IsBlank() { + base.Fatalf("blank method name") + } + + rsym := recv.Sym() + if recv.IsPtr() { + if rsym != nil { + base.Fatalf("declared pointer receiver type: %v", recv) + } + rsym = recv.Elem().Sym() + } + + // Find the package the receiver type appeared in. For + // anonymous receiver types (i.e., anonymous structs with + // embedded fields), use the "go" pseudo-package instead. + rpkg := Pkgs.Go + if rsym != nil { + rpkg = rsym.Pkg + } + + var b bytes.Buffer + if recv.IsPtr() { + // The parentheses aren't really necessary, but + // they're pretty traditional at this point. + fmt.Fprintf(&b, "(%-S)", recv) + } else { + fmt.Fprintf(&b, "%-S", recv) + } + + // A particular receiver type may have multiple non-exported + // methods with the same name. To disambiguate them, include a + // package qualifier for names that came from a different + // package than the receiver type. + if !types.IsExported(msym.Name) && msym.Pkg != rpkg { + b.WriteString(".") + b.WriteString(msym.Pkg.Prefix) + } + + b.WriteString(".") + b.WriteString(msym.Name) + b.WriteString(suffix) + + return rpkg.LookupBytes(b.Bytes()) +} + +// MethodExprName returns the ONAME representing the method +// referenced by expression n, which must be a method selector, +// method expression, or method value. +func MethodExprName(n Node) *Name { + name, _ := MethodExprFunc(n).Nname.(*Name) + return name +} + +// MethodExprFunc is like MethodExprName, but returns the types.Field instead. +func MethodExprFunc(n Node) *types.Field { + switch n.Op() { + case ODOTMETH, OMETHEXPR, OCALLPART: + return n.(*SelectorExpr).Selection + } + base.Fatalf("unexpected node: %v (%v)", n, n.Op()) + panic("unreachable") +} diff --git a/src/cmd/compile/internal/ir/fmt.go b/src/cmd/compile/internal/ir/fmt.go new file mode 100644 index 0000000000000000000000000000000000000000..f2ae0f7606ee7ba04d8b219f1330de8ecf18a121 --- /dev/null +++ b/src/cmd/compile/internal/ir/fmt.go @@ -0,0 +1,1337 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "bytes" + "fmt" + "go/constant" + "io" + "math" + "os" + "path/filepath" + "reflect" + "strings" + + "unicode/utf8" + + "cmd/compile/internal/base" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +// Op + +var OpNames = []string{ + OADDR: "&", + OADD: "+", + OADDSTR: "+", + OALIGNOF: "unsafe.Alignof", + OANDAND: "&&", + OANDNOT: "&^", + OAND: "&", + OAPPEND: "append", + OAS: "=", + OAS2: "=", + OBREAK: "break", + OCALL: "function call", // not actual syntax + OCAP: "cap", + OCASE: "case", + OCLOSE: "close", + OCOMPLEX: "complex", + OBITNOT: "^", + OCONTINUE: "continue", + OCOPY: "copy", + ODELETE: "delete", + ODEFER: "defer", + ODIV: "/", + OEQ: "==", + OFALL: "fallthrough", + OFOR: "for", + OFORUNTIL: "foruntil", // not actual syntax; used to avoid off-end pointer live on backedge.892 + OGE: ">=", + OGOTO: "goto", + OGT: ">", + OIF: "if", + OIMAG: "imag", + OINLMARK: "inlmark", + ODEREF: "*", + OLEN: "len", + OLE: "<=", + OLSH: "<<", + OLT: "<", + OMAKE: "make", + ONEG: "-", + OMOD: "%", + OMUL: "*", + ONEW: "new", + ONE: "!=", + ONOT: "!", + OOFFSETOF: "unsafe.Offsetof", + OOROR: "||", + OOR: "|", + OPANIC: "panic", + OPLUS: "+", + OPRINTN: "println", + OPRINT: "print", + ORANGE: "range", + OREAL: "real", + ORECV: "<-", + ORECOVER: "recover", + ORETURN: "return", + ORSH: ">>", + OSELECT: "select", + OSEND: "<-", + OSIZEOF: "unsafe.Sizeof", + OSUB: "-", + OSWITCH: "switch", + OUNSAFEADD: "unsafe.Add", + OUNSAFESLICE: "unsafe.Slice", + OXOR: "^", +} + +// GoString returns the Go syntax for the Op, or else its name. +func (o Op) GoString() string { + if int(o) < len(OpNames) && OpNames[o] != "" { + return OpNames[o] + } + return o.String() +} + +// Format implements formatting for an Op. +// The valid formats are: +// +// %v Go syntax ("+", "<-", "print") +// %+v Debug syntax ("ADD", "RECV", "PRINT") +// +func (o Op) Format(s fmt.State, verb rune) { + switch verb { + default: + fmt.Fprintf(s, "%%!%c(Op=%d)", verb, int(o)) + case 'v': + if s.Flag('+') { + // %+v is OMUL instead of "*" + io.WriteString(s, o.String()) + return + } + io.WriteString(s, o.GoString()) + } +} + +// Node + +// FmtNode implements formatting for a Node n. +// Every Node implementation must define a Format method that calls FmtNode. +// The valid formats are: +// +// %v Go syntax +// %L Go syntax followed by " (type T)" if type is known. +// %+v Debug syntax, as in Dump. +// +func fmtNode(n Node, s fmt.State, verb rune) { + // %+v prints Dump. + // Otherwise we print Go syntax. + if s.Flag('+') && verb == 'v' { + dumpNode(s, n, 1) + return + } + + if verb != 'v' && verb != 'S' && verb != 'L' { + fmt.Fprintf(s, "%%!%c(*Node=%p)", verb, n) + return + } + + if n == nil { + fmt.Fprint(s, "") + return + } + + t := n.Type() + if verb == 'L' && t != nil { + if t.Kind() == types.TNIL { + fmt.Fprint(s, "nil") + } else if n.Op() == ONAME && n.Name().AutoTemp() { + fmt.Fprintf(s, "%v value", t) + } else { + fmt.Fprintf(s, "%v (type %v)", n, t) + } + return + } + + // TODO inlining produces expressions with ninits. we can't print these yet. + + if OpPrec[n.Op()] < 0 { + stmtFmt(n, s) + return + } + + exprFmt(n, s, 0) +} + +var OpPrec = []int{ + OALIGNOF: 8, + OAPPEND: 8, + OBYTES2STR: 8, + OARRAYLIT: 8, + OSLICELIT: 8, + ORUNES2STR: 8, + OCALLFUNC: 8, + OCALLINTER: 8, + OCALLMETH: 8, + OCALL: 8, + OCAP: 8, + OCLOSE: 8, + OCOMPLIT: 8, + OCONVIFACE: 8, + OCONVNOP: 8, + OCONV: 8, + OCOPY: 8, + ODELETE: 8, + OGETG: 8, + OLEN: 8, + OLITERAL: 8, + OMAKESLICE: 8, + OMAKESLICECOPY: 8, + OMAKE: 8, + OMAPLIT: 8, + ONAME: 8, + ONEW: 8, + ONIL: 8, + ONONAME: 8, + OOFFSETOF: 8, + OPACK: 8, + OPANIC: 8, + OPAREN: 8, + OPRINTN: 8, + OPRINT: 8, + ORUNESTR: 8, + OSIZEOF: 8, + OSLICE2ARRPTR: 8, + OSTR2BYTES: 8, + OSTR2RUNES: 8, + OSTRUCTLIT: 8, + OTARRAY: 8, + OTSLICE: 8, + OTCHAN: 8, + OTFUNC: 8, + OTINTER: 8, + OTMAP: 8, + OTSTRUCT: 8, + OTYPE: 8, + OUNSAFEADD: 8, + OUNSAFESLICE: 8, + OINDEXMAP: 8, + OINDEX: 8, + OSLICE: 8, + OSLICESTR: 8, + OSLICEARR: 8, + OSLICE3: 8, + OSLICE3ARR: 8, + OSLICEHEADER: 8, + ODOTINTER: 8, + ODOTMETH: 8, + ODOTPTR: 8, + ODOTTYPE2: 8, + ODOTTYPE: 8, + ODOT: 8, + OXDOT: 8, + OCALLPART: 8, + OMETHEXPR: 8, + OPLUS: 7, + ONOT: 7, + OBITNOT: 7, + ONEG: 7, + OADDR: 7, + ODEREF: 7, + ORECV: 7, + OMUL: 6, + ODIV: 6, + OMOD: 6, + OLSH: 6, + ORSH: 6, + OAND: 6, + OANDNOT: 6, + OADD: 5, + OSUB: 5, + OOR: 5, + OXOR: 5, + OEQ: 4, + OLT: 4, + OLE: 4, + OGE: 4, + OGT: 4, + ONE: 4, + OSEND: 3, + OANDAND: 2, + OOROR: 1, + + // Statements handled by stmtfmt + OAS: -1, + OAS2: -1, + OAS2DOTTYPE: -1, + OAS2FUNC: -1, + OAS2MAPR: -1, + OAS2RECV: -1, + OASOP: -1, + OBLOCK: -1, + OBREAK: -1, + OCASE: -1, + OCONTINUE: -1, + ODCL: -1, + ODEFER: -1, + OFALL: -1, + OFOR: -1, + OFORUNTIL: -1, + OGOTO: -1, + OIF: -1, + OLABEL: -1, + OGO: -1, + ORANGE: -1, + ORETURN: -1, + OSELECT: -1, + OSWITCH: -1, + + OEND: 0, +} + +// StmtWithInit reports whether op is a statement with an explicit init list. +func StmtWithInit(op Op) bool { + switch op { + case OIF, OFOR, OFORUNTIL, OSWITCH: + return true + } + return false +} + +func stmtFmt(n Node, s fmt.State) { + // NOTE(rsc): This code used to support the text-based + // which was more aggressive about printing full Go syntax + // (for example, an actual loop instead of "for loop"). + // The code is preserved for now in case we want to expand + // any of those shortenings later. Or maybe we will delete + // the code. But for now, keep it. + const exportFormat = false + + // some statements allow for an init, but at most one, + // but we may have an arbitrary number added, eg by typecheck + // and inlining. If it doesn't fit the syntax, emit an enclosing + // block starting with the init statements. + + // if we can just say "for" n->ninit; ... then do so + simpleinit := len(n.Init()) == 1 && len(n.Init()[0].Init()) == 0 && StmtWithInit(n.Op()) + + // otherwise, print the inits as separate statements + complexinit := len(n.Init()) != 0 && !simpleinit && exportFormat + + // but if it was for if/for/switch, put in an extra surrounding block to limit the scope + extrablock := complexinit && StmtWithInit(n.Op()) + + if extrablock { + fmt.Fprint(s, "{") + } + + if complexinit { + fmt.Fprintf(s, " %v; ", n.Init()) + } + + switch n.Op() { + case ODCL: + n := n.(*Decl) + fmt.Fprintf(s, "var %v %v", n.X.Sym(), n.X.Type()) + + // Don't export "v = " initializing statements, hope they're always + // preceded by the DCL which will be re-parsed and typechecked to reproduce + // the "v = " again. + case OAS: + n := n.(*AssignStmt) + if n.Def && !complexinit { + fmt.Fprintf(s, "%v := %v", n.X, n.Y) + } else { + fmt.Fprintf(s, "%v = %v", n.X, n.Y) + } + + case OASOP: + n := n.(*AssignOpStmt) + if n.IncDec { + if n.AsOp == OADD { + fmt.Fprintf(s, "%v++", n.X) + } else { + fmt.Fprintf(s, "%v--", n.X) + } + break + } + + fmt.Fprintf(s, "%v %v= %v", n.X, n.AsOp, n.Y) + + case OAS2, OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: + n := n.(*AssignListStmt) + if n.Def && !complexinit { + fmt.Fprintf(s, "%.v := %.v", n.Lhs, n.Rhs) + } else { + fmt.Fprintf(s, "%.v = %.v", n.Lhs, n.Rhs) + } + + case OBLOCK: + n := n.(*BlockStmt) + if len(n.List) != 0 { + fmt.Fprintf(s, "%v", n.List) + } + + case ORETURN: + n := n.(*ReturnStmt) + fmt.Fprintf(s, "return %.v", n.Results) + + case OTAILCALL: + n := n.(*TailCallStmt) + fmt.Fprintf(s, "tailcall %v", n.Target) + + case OINLMARK: + n := n.(*InlineMarkStmt) + fmt.Fprintf(s, "inlmark %d", n.Index) + + case OGO: + n := n.(*GoDeferStmt) + fmt.Fprintf(s, "go %v", n.Call) + + case ODEFER: + n := n.(*GoDeferStmt) + fmt.Fprintf(s, "defer %v", n.Call) + + case OIF: + n := n.(*IfStmt) + if simpleinit { + fmt.Fprintf(s, "if %v; %v { %v }", n.Init()[0], n.Cond, n.Body) + } else { + fmt.Fprintf(s, "if %v { %v }", n.Cond, n.Body) + } + if len(n.Else) != 0 { + fmt.Fprintf(s, " else { %v }", n.Else) + } + + case OFOR, OFORUNTIL: + n := n.(*ForStmt) + opname := "for" + if n.Op() == OFORUNTIL { + opname = "foruntil" + } + if !exportFormat { // TODO maybe only if FmtShort, same below + fmt.Fprintf(s, "%s loop", opname) + break + } + + fmt.Fprint(s, opname) + if simpleinit { + fmt.Fprintf(s, " %v;", n.Init()[0]) + } else if n.Post != nil { + fmt.Fprint(s, " ;") + } + + if n.Cond != nil { + fmt.Fprintf(s, " %v", n.Cond) + } + + if n.Post != nil { + fmt.Fprintf(s, "; %v", n.Post) + } else if simpleinit { + fmt.Fprint(s, ";") + } + + if n.Op() == OFORUNTIL && len(n.Late) != 0 { + fmt.Fprintf(s, "; %v", n.Late) + } + + fmt.Fprintf(s, " { %v }", n.Body) + + case ORANGE: + n := n.(*RangeStmt) + if !exportFormat { + fmt.Fprint(s, "for loop") + break + } + + fmt.Fprint(s, "for") + if n.Key != nil { + fmt.Fprintf(s, " %v", n.Key) + if n.Value != nil { + fmt.Fprintf(s, ", %v", n.Value) + } + fmt.Fprint(s, " =") + } + fmt.Fprintf(s, " range %v { %v }", n.X, n.Body) + + case OSELECT: + n := n.(*SelectStmt) + if !exportFormat { + fmt.Fprintf(s, "%v statement", n.Op()) + break + } + fmt.Fprintf(s, "select { %v }", n.Cases) + + case OSWITCH: + n := n.(*SwitchStmt) + if !exportFormat { + fmt.Fprintf(s, "%v statement", n.Op()) + break + } + fmt.Fprintf(s, "switch") + if simpleinit { + fmt.Fprintf(s, " %v;", n.Init()[0]) + } + if n.Tag != nil { + fmt.Fprintf(s, " %v ", n.Tag) + } + fmt.Fprintf(s, " { %v }", n.Cases) + + case OCASE: + n := n.(*CaseClause) + if len(n.List) != 0 { + fmt.Fprintf(s, "case %.v", n.List) + } else { + fmt.Fprint(s, "default") + } + fmt.Fprintf(s, ": %v", n.Body) + + case OBREAK, OCONTINUE, OGOTO, OFALL: + n := n.(*BranchStmt) + if n.Label != nil { + fmt.Fprintf(s, "%v %v", n.Op(), n.Label) + } else { + fmt.Fprintf(s, "%v", n.Op()) + } + + case OLABEL: + n := n.(*LabelStmt) + fmt.Fprintf(s, "%v: ", n.Label) + } + + if extrablock { + fmt.Fprint(s, "}") + } +} + +func exprFmt(n Node, s fmt.State, prec int) { + // NOTE(rsc): This code used to support the text-based + // which was more aggressive about printing full Go syntax + // (for example, an actual loop instead of "for loop"). + // The code is preserved for now in case we want to expand + // any of those shortenings later. Or maybe we will delete + // the code. But for now, keep it. + const exportFormat = false + + for { + if n == nil { + fmt.Fprint(s, "") + return + } + + // We always want the original, if any. + if o := Orig(n); o != n { + n = o + continue + } + + // Skip implicit operations introduced during typechecking. + switch nn := n; nn.Op() { + case OADDR: + nn := nn.(*AddrExpr) + if nn.Implicit() { + n = nn.X + continue + } + case ODEREF: + nn := nn.(*StarExpr) + if nn.Implicit() { + n = nn.X + continue + } + case OCONV, OCONVNOP, OCONVIFACE: + nn := nn.(*ConvExpr) + if nn.Implicit() { + n = nn.X + continue + } + } + + break + } + + nprec := OpPrec[n.Op()] + if n.Op() == OTYPE && n.Type().IsPtr() { + nprec = OpPrec[ODEREF] + } + + if prec > nprec { + fmt.Fprintf(s, "(%v)", n) + return + } + + switch n.Op() { + case OPAREN: + n := n.(*ParenExpr) + fmt.Fprintf(s, "(%v)", n.X) + + case ONIL: + fmt.Fprint(s, "nil") + + case OLITERAL: // this is a bit of a mess + if !exportFormat && n.Sym() != nil { + fmt.Fprint(s, n.Sym()) + return + } + + needUnparen := false + if n.Type() != nil && !n.Type().IsUntyped() { + // Need parens when type begins with what might + // be misinterpreted as a unary operator: * or <-. + if n.Type().IsPtr() || (n.Type().IsChan() && n.Type().ChanDir() == types.Crecv) { + fmt.Fprintf(s, "(%v)(", n.Type()) + } else { + fmt.Fprintf(s, "%v(", n.Type()) + } + needUnparen = true + } + + if n.Type() == types.UntypedRune { + switch x, ok := constant.Uint64Val(n.Val()); { + case !ok: + fallthrough + default: + fmt.Fprintf(s, "('\\x00' + %v)", n.Val()) + + case x < utf8.RuneSelf: + fmt.Fprintf(s, "%q", x) + + case x < 1<<16: + fmt.Fprintf(s, "'\\u%04x'", x) + + case x <= utf8.MaxRune: + fmt.Fprintf(s, "'\\U%08x'", x) + } + } else { + fmt.Fprint(s, types.FmtConst(n.Val(), s.Flag('#'))) + } + + if needUnparen { + fmt.Fprintf(s, ")") + } + + case ODCLFUNC: + n := n.(*Func) + if sym := n.Sym(); sym != nil { + fmt.Fprint(s, sym) + return + } + fmt.Fprintf(s, "") + + case ONAME: + n := n.(*Name) + // Special case: name used as local variable in export. + // _ becomes ~b%d internally; print as _ for export + if !exportFormat && n.Sym() != nil && n.Sym().Name[0] == '~' && n.Sym().Name[1] == 'b' { + fmt.Fprint(s, "_") + return + } + fallthrough + case OPACK, ONONAME: + fmt.Fprint(s, n.Sym()) + + case OLINKSYMOFFSET: + n := n.(*LinksymOffsetExpr) + fmt.Fprintf(s, "(%v)(%s@%d)", n.Type(), n.Linksym.Name, n.Offset_) + + case OTYPE: + if n.Type() == nil && n.Sym() != nil { + fmt.Fprint(s, n.Sym()) + return + } + fmt.Fprintf(s, "%v", n.Type()) + + case OTSLICE: + n := n.(*SliceType) + if n.DDD { + fmt.Fprintf(s, "...%v", n.Elem) + } else { + fmt.Fprintf(s, "[]%v", n.Elem) // happens before typecheck + } + + case OTARRAY: + n := n.(*ArrayType) + if n.Len == nil { + fmt.Fprintf(s, "[...]%v", n.Elem) + } else { + fmt.Fprintf(s, "[%v]%v", n.Len, n.Elem) + } + + case OTMAP: + n := n.(*MapType) + fmt.Fprintf(s, "map[%v]%v", n.Key, n.Elem) + + case OTCHAN: + n := n.(*ChanType) + switch n.Dir { + case types.Crecv: + fmt.Fprintf(s, "<-chan %v", n.Elem) + + case types.Csend: + fmt.Fprintf(s, "chan<- %v", n.Elem) + + default: + if n.Elem != nil && n.Elem.Op() == OTCHAN && n.Elem.(*ChanType).Dir == types.Crecv { + fmt.Fprintf(s, "chan (%v)", n.Elem) + } else { + fmt.Fprintf(s, "chan %v", n.Elem) + } + } + + case OTSTRUCT: + fmt.Fprint(s, "") + + case OTINTER: + fmt.Fprint(s, "") + + case OTFUNC: + fmt.Fprint(s, "") + + case OCLOSURE: + n := n.(*ClosureExpr) + if !exportFormat { + fmt.Fprint(s, "func literal") + return + } + fmt.Fprintf(s, "%v { %v }", n.Type(), n.Func.Body) + + case OCOMPLIT: + n := n.(*CompLitExpr) + if !exportFormat { + if n.Implicit() { + fmt.Fprintf(s, "... argument") + return + } + if n.Ntype != nil { + fmt.Fprintf(s, "%v{%s}", n.Ntype, ellipsisIf(len(n.List) != 0)) + return + } + + fmt.Fprint(s, "composite literal") + return + } + fmt.Fprintf(s, "(%v{ %.v })", n.Ntype, n.List) + + case OPTRLIT: + n := n.(*AddrExpr) + fmt.Fprintf(s, "&%v", n.X) + + case OSTRUCTLIT, OARRAYLIT, OSLICELIT, OMAPLIT: + n := n.(*CompLitExpr) + if !exportFormat { + fmt.Fprintf(s, "%v{%s}", n.Type(), ellipsisIf(len(n.List) != 0)) + return + } + fmt.Fprintf(s, "(%v{ %.v })", n.Type(), n.List) + + case OKEY: + n := n.(*KeyExpr) + if n.Key != nil && n.Value != nil { + fmt.Fprintf(s, "%v:%v", n.Key, n.Value) + return + } + + if n.Key == nil && n.Value != nil { + fmt.Fprintf(s, ":%v", n.Value) + return + } + if n.Key != nil && n.Value == nil { + fmt.Fprintf(s, "%v:", n.Key) + return + } + fmt.Fprint(s, ":") + + case OSTRUCTKEY: + n := n.(*StructKeyExpr) + fmt.Fprintf(s, "%v:%v", n.Field, n.Value) + + case OXDOT, ODOT, ODOTPTR, ODOTINTER, ODOTMETH, OCALLPART, OMETHEXPR: + n := n.(*SelectorExpr) + exprFmt(n.X, s, nprec) + if n.Sel == nil { + fmt.Fprint(s, ".") + return + } + fmt.Fprintf(s, ".%s", n.Sel.Name) + + case ODOTTYPE, ODOTTYPE2: + n := n.(*TypeAssertExpr) + exprFmt(n.X, s, nprec) + if n.Ntype != nil { + fmt.Fprintf(s, ".(%v)", n.Ntype) + return + } + fmt.Fprintf(s, ".(%v)", n.Type()) + + case OINDEX, OINDEXMAP: + n := n.(*IndexExpr) + exprFmt(n.X, s, nprec) + fmt.Fprintf(s, "[%v]", n.Index) + + case OSLICE, OSLICESTR, OSLICEARR, OSLICE3, OSLICE3ARR: + n := n.(*SliceExpr) + exprFmt(n.X, s, nprec) + fmt.Fprint(s, "[") + if n.Low != nil { + fmt.Fprint(s, n.Low) + } + fmt.Fprint(s, ":") + if n.High != nil { + fmt.Fprint(s, n.High) + } + if n.Op().IsSlice3() { + fmt.Fprint(s, ":") + if n.Max != nil { + fmt.Fprint(s, n.Max) + } + } + fmt.Fprint(s, "]") + + case OSLICEHEADER: + n := n.(*SliceHeaderExpr) + fmt.Fprintf(s, "sliceheader{%v,%v,%v}", n.Ptr, n.Len, n.Cap) + + case OCOMPLEX, OCOPY, OUNSAFEADD, OUNSAFESLICE: + n := n.(*BinaryExpr) + fmt.Fprintf(s, "%v(%v, %v)", n.Op(), n.X, n.Y) + + case OCONV, + OCONVIFACE, + OCONVNOP, + OBYTES2STR, + ORUNES2STR, + OSTR2BYTES, + OSTR2RUNES, + ORUNESTR, + OSLICE2ARRPTR: + n := n.(*ConvExpr) + if n.Type() == nil || n.Type().Sym() == nil { + fmt.Fprintf(s, "(%v)", n.Type()) + } else { + fmt.Fprintf(s, "%v", n.Type()) + } + fmt.Fprintf(s, "(%v)", n.X) + + case OREAL, + OIMAG, + OCAP, + OCLOSE, + OLEN, + ONEW, + OPANIC, + OALIGNOF, + OOFFSETOF, + OSIZEOF: + n := n.(*UnaryExpr) + fmt.Fprintf(s, "%v(%v)", n.Op(), n.X) + + case OAPPEND, + ODELETE, + OMAKE, + ORECOVER, + OPRINT, + OPRINTN: + n := n.(*CallExpr) + if n.IsDDD { + fmt.Fprintf(s, "%v(%.v...)", n.Op(), n.Args) + return + } + fmt.Fprintf(s, "%v(%.v)", n.Op(), n.Args) + + case OCALL, OCALLFUNC, OCALLINTER, OCALLMETH, OGETG: + n := n.(*CallExpr) + exprFmt(n.X, s, nprec) + if n.IsDDD { + fmt.Fprintf(s, "(%.v...)", n.Args) + return + } + fmt.Fprintf(s, "(%.v)", n.Args) + + case OMAKEMAP, OMAKECHAN, OMAKESLICE: + n := n.(*MakeExpr) + if n.Cap != nil { + fmt.Fprintf(s, "make(%v, %v, %v)", n.Type(), n.Len, n.Cap) + return + } + if n.Len != nil && (n.Op() == OMAKESLICE || !n.Len.Type().IsUntyped()) { + fmt.Fprintf(s, "make(%v, %v)", n.Type(), n.Len) + return + } + fmt.Fprintf(s, "make(%v)", n.Type()) + + case OMAKESLICECOPY: + n := n.(*MakeExpr) + fmt.Fprintf(s, "makeslicecopy(%v, %v, %v)", n.Type(), n.Len, n.Cap) + + case OPLUS, ONEG, OBITNOT, ONOT, ORECV: + // Unary + n := n.(*UnaryExpr) + fmt.Fprintf(s, "%v", n.Op()) + if n.X != nil && n.X.Op() == n.Op() { + fmt.Fprint(s, " ") + } + exprFmt(n.X, s, nprec+1) + + case OADDR: + n := n.(*AddrExpr) + fmt.Fprintf(s, "%v", n.Op()) + if n.X != nil && n.X.Op() == n.Op() { + fmt.Fprint(s, " ") + } + exprFmt(n.X, s, nprec+1) + + case ODEREF: + n := n.(*StarExpr) + fmt.Fprintf(s, "%v", n.Op()) + exprFmt(n.X, s, nprec+1) + + // Binary + case OADD, + OAND, + OANDNOT, + ODIV, + OEQ, + OGE, + OGT, + OLE, + OLT, + OLSH, + OMOD, + OMUL, + ONE, + OOR, + ORSH, + OSUB, + OXOR: + n := n.(*BinaryExpr) + exprFmt(n.X, s, nprec) + fmt.Fprintf(s, " %v ", n.Op()) + exprFmt(n.Y, s, nprec+1) + + case OANDAND, + OOROR: + n := n.(*LogicalExpr) + exprFmt(n.X, s, nprec) + fmt.Fprintf(s, " %v ", n.Op()) + exprFmt(n.Y, s, nprec+1) + + case OSEND: + n := n.(*SendStmt) + exprFmt(n.Chan, s, nprec) + fmt.Fprintf(s, " <- ") + exprFmt(n.Value, s, nprec+1) + + case OADDSTR: + n := n.(*AddStringExpr) + for i, n1 := range n.List { + if i != 0 { + fmt.Fprint(s, " + ") + } + exprFmt(n1, s, nprec) + } + default: + fmt.Fprintf(s, "", n.Op()) + } +} + +func ellipsisIf(b bool) string { + if b { + return "..." + } + return "" +} + +// Nodes + +// Format implements formatting for a Nodes. +// The valid formats are: +// +// %v Go syntax, semicolon-separated +// %.v Go syntax, comma-separated +// %+v Debug syntax, as in DumpList. +// +func (l Nodes) Format(s fmt.State, verb rune) { + if s.Flag('+') && verb == 'v' { + // %+v is DumpList output + dumpNodes(s, l, 1) + return + } + + if verb != 'v' { + fmt.Fprintf(s, "%%!%c(Nodes)", verb) + return + } + + sep := "; " + if _, ok := s.Precision(); ok { // %.v is expr list + sep = ", " + } + + for i, n := range l { + fmt.Fprint(s, n) + if i+1 < len(l) { + fmt.Fprint(s, sep) + } + } +} + +// Dump + +// Dump prints the message s followed by a debug dump of n. +func Dump(s string, n Node) { + fmt.Printf("%s [%p]%+v\n", s, n, n) +} + +// DumpList prints the message s followed by a debug dump of each node in the list. +func DumpList(s string, list Nodes) { + var buf bytes.Buffer + FDumpList(&buf, s, list) + os.Stdout.Write(buf.Bytes()) +} + +// FDumpList prints to w the message s followed by a debug dump of each node in the list. +func FDumpList(w io.Writer, s string, list Nodes) { + io.WriteString(w, s) + dumpNodes(w, list, 1) + io.WriteString(w, "\n") +} + +// indent prints indentation to w. +func indent(w io.Writer, depth int) { + fmt.Fprint(w, "\n") + for i := 0; i < depth; i++ { + fmt.Fprint(w, ". ") + } +} + +// EscFmt is set by the escape analysis code to add escape analysis details to the node print. +var EscFmt func(n Node) string + +// dumpNodeHeader prints the debug-format node header line to w. +func dumpNodeHeader(w io.Writer, n Node) { + // Useful to see which nodes in an AST printout are actually identical + if base.Debug.DumpPtrs != 0 { + fmt.Fprintf(w, " p(%p)", n) + } + + if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Defn != nil { + // Useful to see where Defn is set and what node it points to + fmt.Fprintf(w, " defn(%p)", n.Name().Defn) + } + + if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Curfn != nil { + // Useful to see where Defn is set and what node it points to + fmt.Fprintf(w, " curfn(%p)", n.Name().Curfn) + } + if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Outer != nil { + // Useful to see where Defn is set and what node it points to + fmt.Fprintf(w, " outer(%p)", n.Name().Outer) + } + + if EscFmt != nil { + if esc := EscFmt(n); esc != "" { + fmt.Fprintf(w, " %s", esc) + } + } + + if n.Typecheck() != 0 { + fmt.Fprintf(w, " tc(%d)", n.Typecheck()) + } + + // Print Node-specific fields of basic type in header line. + v := reflect.ValueOf(n).Elem() + t := v.Type() + nf := t.NumField() + for i := 0; i < nf; i++ { + tf := t.Field(i) + if tf.PkgPath != "" { + // skip unexported field - Interface will fail + continue + } + k := tf.Type.Kind() + if reflect.Bool <= k && k <= reflect.Complex128 { + name := strings.TrimSuffix(tf.Name, "_") + vf := v.Field(i) + vfi := vf.Interface() + if name == "Offset" && vfi == types.BADWIDTH || name != "Offset" && isZero(vf) { + continue + } + if vfi == true { + fmt.Fprintf(w, " %s", name) + } else { + fmt.Fprintf(w, " %s:%+v", name, vf.Interface()) + } + } + } + + // Print Node-specific booleans by looking for methods. + // Different v, t from above - want *Struct not Struct, for methods. + v = reflect.ValueOf(n) + t = v.Type() + nm := t.NumMethod() + for i := 0; i < nm; i++ { + tm := t.Method(i) + if tm.PkgPath != "" { + // skip unexported method - call will fail + continue + } + m := v.Method(i) + mt := m.Type() + if mt.NumIn() == 0 && mt.NumOut() == 1 && mt.Out(0).Kind() == reflect.Bool { + // TODO(rsc): Remove the func/defer/recover wrapping, + // which is guarding against panics in miniExpr, + // once we get down to the simpler state in which + // nodes have no getter methods that aren't allowed to be called. + func() { + defer func() { recover() }() + if m.Call(nil)[0].Bool() { + name := strings.TrimSuffix(tm.Name, "_") + fmt.Fprintf(w, " %s", name) + } + }() + } + } + + if n.Op() == OCLOSURE { + n := n.(*ClosureExpr) + if fn := n.Func; fn != nil && fn.Nname.Sym() != nil { + fmt.Fprintf(w, " fnName(%+v)", fn.Nname.Sym()) + } + } + + if n.Type() != nil { + if n.Op() == OTYPE { + fmt.Fprintf(w, " type") + } + fmt.Fprintf(w, " %+v", n.Type()) + } + + if n.Pos().IsKnown() { + pfx := "" + switch n.Pos().IsStmt() { + case src.PosNotStmt: + pfx = "_" // "-" would be confusing + case src.PosIsStmt: + pfx = "+" + } + pos := base.Ctxt.PosTable.Pos(n.Pos()) + file := filepath.Base(pos.Filename()) + fmt.Fprintf(w, " # %s%s:%d", pfx, file, pos.Line()) + } +} + +func dumpNode(w io.Writer, n Node, depth int) { + indent(w, depth) + if depth > 40 { + fmt.Fprint(w, "...") + return + } + + if n == nil { + fmt.Fprint(w, "NilIrNode") + return + } + + if len(n.Init()) != 0 { + fmt.Fprintf(w, "%+v-init", n.Op()) + dumpNodes(w, n.Init(), depth+1) + indent(w, depth) + } + + switch n.Op() { + default: + fmt.Fprintf(w, "%+v", n.Op()) + dumpNodeHeader(w, n) + + case OLITERAL: + fmt.Fprintf(w, "%+v-%v", n.Op(), n.Val()) + dumpNodeHeader(w, n) + return + + case ONAME, ONONAME: + if n.Sym() != nil { + fmt.Fprintf(w, "%+v-%+v", n.Op(), n.Sym()) + } else { + fmt.Fprintf(w, "%+v", n.Op()) + } + dumpNodeHeader(w, n) + if n.Type() == nil && n.Name() != nil && n.Name().Ntype != nil { + indent(w, depth) + fmt.Fprintf(w, "%+v-ntype", n.Op()) + dumpNode(w, n.Name().Ntype, depth+1) + } + return + + case OASOP: + n := n.(*AssignOpStmt) + fmt.Fprintf(w, "%+v-%+v", n.Op(), n.AsOp) + dumpNodeHeader(w, n) + + case OTYPE: + fmt.Fprintf(w, "%+v %+v", n.Op(), n.Sym()) + dumpNodeHeader(w, n) + if n.Type() == nil && n.Name() != nil && n.Name().Ntype != nil { + indent(w, depth) + fmt.Fprintf(w, "%+v-ntype", n.Op()) + dumpNode(w, n.Name().Ntype, depth+1) + } + return + + case OCLOSURE: + fmt.Fprintf(w, "%+v", n.Op()) + dumpNodeHeader(w, n) + + case ODCLFUNC: + // Func has many fields we don't want to print. + // Bypass reflection and just print what we want. + n := n.(*Func) + fmt.Fprintf(w, "%+v", n.Op()) + dumpNodeHeader(w, n) + fn := n + if len(fn.Dcl) > 0 { + indent(w, depth) + fmt.Fprintf(w, "%+v-Dcl", n.Op()) + for _, dcl := range n.Dcl { + dumpNode(w, dcl, depth+1) + } + } + if len(fn.ClosureVars) > 0 { + indent(w, depth) + fmt.Fprintf(w, "%+v-ClosureVars", n.Op()) + for _, cv := range fn.ClosureVars { + dumpNode(w, cv, depth+1) + } + } + if len(fn.Enter) > 0 { + indent(w, depth) + fmt.Fprintf(w, "%+v-Enter", n.Op()) + dumpNodes(w, fn.Enter, depth+1) + } + if len(fn.Body) > 0 { + indent(w, depth) + fmt.Fprintf(w, "%+v-body", n.Op()) + dumpNodes(w, fn.Body, depth+1) + } + return + } + + if n.Sym() != nil { + fmt.Fprintf(w, " %+v", n.Sym()) + } + if n.Type() != nil { + fmt.Fprintf(w, " %+v", n.Type()) + } + + v := reflect.ValueOf(n).Elem() + t := reflect.TypeOf(n).Elem() + nf := t.NumField() + for i := 0; i < nf; i++ { + tf := t.Field(i) + vf := v.Field(i) + if tf.PkgPath != "" { + // skip unexported field - Interface will fail + continue + } + switch tf.Type.Kind() { + case reflect.Interface, reflect.Ptr, reflect.Slice: + if vf.IsNil() { + continue + } + } + name := strings.TrimSuffix(tf.Name, "_") + // Do not bother with field name header lines for the + // most common positional arguments: unary, binary expr, + // index expr, send stmt, go and defer call expression. + switch name { + case "X", "Y", "Index", "Chan", "Value", "Call": + name = "" + } + switch val := vf.Interface().(type) { + case Node: + if name != "" { + indent(w, depth) + fmt.Fprintf(w, "%+v-%s", n.Op(), name) + } + dumpNode(w, val, depth+1) + case Nodes: + if len(val) == 0 { + continue + } + if name != "" { + indent(w, depth) + fmt.Fprintf(w, "%+v-%s", n.Op(), name) + } + dumpNodes(w, val, depth+1) + default: + if vf.Kind() == reflect.Slice && vf.Type().Elem().Implements(nodeType) { + if vf.Len() == 0 { + continue + } + if name != "" { + indent(w, depth) + fmt.Fprintf(w, "%+v-%s", n.Op(), name) + } + for i, n := 0, vf.Len(); i < n; i++ { + dumpNode(w, vf.Index(i).Interface().(Node), depth+1) + } + } + } + } +} + +var nodeType = reflect.TypeOf((*Node)(nil)).Elem() + +func dumpNodes(w io.Writer, list Nodes, depth int) { + if len(list) == 0 { + fmt.Fprintf(w, " ") + return + } + + for _, n := range list { + dumpNode(w, n, depth) + } +} + +// reflect.IsZero is not available in Go 1.4 (added in Go 1.13), so we use this copy instead. +func isZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return math.Float64bits(v.Float()) == 0 + case reflect.Complex64, reflect.Complex128: + c := v.Complex() + return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 + case reflect.Array: + for i := 0; i < v.Len(); i++ { + if !isZero(v.Index(i)) { + return false + } + } + return true + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: + return v.IsNil() + case reflect.String: + return v.Len() == 0 + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + if !isZero(v.Field(i)) { + return false + } + } + return true + default: + return false + } +} diff --git a/src/cmd/compile/internal/ir/func.go b/src/cmd/compile/internal/ir/func.go new file mode 100644 index 0000000000000000000000000000000000000000..20fe965711df33896e2f8025ea91b450a863dc4d --- /dev/null +++ b/src/cmd/compile/internal/ir/func.go @@ -0,0 +1,308 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/src" +) + +// A Func corresponds to a single function in a Go program +// (and vice versa: each function is denoted by exactly one *Func). +// +// There are multiple nodes that represent a Func in the IR. +// +// The ONAME node (Func.Nname) is used for plain references to it. +// The ODCLFUNC node (the Func itself) is used for its declaration code. +// The OCLOSURE node (Func.OClosure) is used for a reference to a +// function literal. +// +// An imported function will have an ONAME node which points to a Func +// with an empty body. +// A declared function or method has an ODCLFUNC (the Func itself) and an ONAME. +// A function literal is represented directly by an OCLOSURE, but it also +// has an ODCLFUNC (and a matching ONAME) representing the compiled +// underlying form of the closure, which accesses the captured variables +// using a special data structure passed in a register. +// +// A method declaration is represented like functions, except f.Sym +// will be the qualified method name (e.g., "T.m") and +// f.Func.Shortname is the bare method name (e.g., "m"). +// +// A method expression (T.M) is represented as an OMETHEXPR node, +// in which n.Left and n.Right point to the type and method, respectively. +// Each distinct mention of a method expression in the source code +// constructs a fresh node. +// +// A method value (t.M) is represented by ODOTMETH/ODOTINTER +// when it is called directly and by OCALLPART otherwise. +// These are like method expressions, except that for ODOTMETH/ODOTINTER, +// the method name is stored in Sym instead of Right. +// Each OCALLPART ends up being implemented as a new +// function, a bit like a closure, with its own ODCLFUNC. +// The OCALLPART uses n.Func to record the linkage to +// the generated ODCLFUNC, but there is no +// pointer from the Func back to the OCALLPART. +type Func struct { + miniNode + Body Nodes + Iota int64 + + Nname *Name // ONAME node + OClosure *ClosureExpr // OCLOSURE node + + Shortname *types.Sym + + // Extra entry code for the function. For example, allocate and initialize + // memory for escaping parameters. + Enter Nodes + Exit Nodes + + // ONAME nodes for all params/locals for this func/closure, does NOT + // include closurevars until transforming closures during walk. + // Names must be listed PPARAMs, PPARAMOUTs, then PAUTOs, + // with PPARAMs and PPARAMOUTs in order corresponding to the function signature. + // However, as anonymous or blank PPARAMs are not actually declared, + // they are omitted from Dcl. + // Anonymous and blank PPARAMOUTs are declared as ~rNN and ~bNN Names, respectively. + Dcl []*Name + + // ClosureVars lists the free variables that are used within a + // function literal, but formally declared in an enclosing + // function. The variables in this slice are the closure function's + // own copy of the variables, which are used within its function + // body. They will also each have IsClosureVar set, and will have + // Byval set if they're captured by value. + ClosureVars []*Name + + // Enclosed functions that need to be compiled. + // Populated during walk. + Closures []*Func + + // Parents records the parent scope of each scope within a + // function. The root scope (0) has no parent, so the i'th + // scope's parent is stored at Parents[i-1]. + Parents []ScopeID + + // Marks records scope boundary changes. + Marks []Mark + + FieldTrack map[*obj.LSym]struct{} + DebugInfo interface{} + LSym *obj.LSym // Linker object in this function's native ABI (Func.ABI) + + Inl *Inline + + // Closgen tracks how many closures have been generated within + // this function. Used by closurename for creating unique + // function names. + Closgen int32 + + Label int32 // largest auto-generated label in this function + + Endlineno src.XPos + WBPos src.XPos // position of first write barrier; see SetWBPos + + Pragma PragmaFlag // go:xxx function annotations + + flags bitset16 + + // ABI is a function's "definition" ABI. This is the ABI that + // this function's generated code is expecting to be called by. + // + // For most functions, this will be obj.ABIInternal. It may be + // a different ABI for functions defined in assembly or ABI wrappers. + // + // This is included in the export data and tracked across packages. + ABI obj.ABI + // ABIRefs is the set of ABIs by which this function is referenced. + // For ABIs other than this function's definition ABI, the + // compiler generates ABI wrapper functions. This is only tracked + // within a package. + ABIRefs obj.ABISet + + NumDefers int32 // number of defer calls in the function + NumReturns int32 // number of explicit returns in the function + + // nwbrCalls records the LSyms of functions called by this + // function for go:nowritebarrierrec analysis. Only filled in + // if nowritebarrierrecCheck != nil. + NWBRCalls *[]SymAndPos +} + +func NewFunc(pos src.XPos) *Func { + f := new(Func) + f.pos = pos + f.op = ODCLFUNC + f.Iota = -1 + // Most functions are ABIInternal. The importer or symabis + // pass may override this. + f.ABI = obj.ABIInternal + return f +} + +func (f *Func) isStmt() {} + +func (n *Func) copy() Node { panic(n.no("copy")) } +func (n *Func) doChildren(do func(Node) bool) bool { return doNodes(n.Body, do) } +func (n *Func) editChildren(edit func(Node) Node) { editNodes(n.Body, edit) } + +func (f *Func) Type() *types.Type { return f.Nname.Type() } +func (f *Func) Sym() *types.Sym { return f.Nname.Sym() } +func (f *Func) Linksym() *obj.LSym { return f.Nname.Linksym() } +func (f *Func) LinksymABI(abi obj.ABI) *obj.LSym { return f.Nname.LinksymABI(abi) } + +// An Inline holds fields used for function bodies that can be inlined. +type Inline struct { + Cost int32 // heuristic cost of inlining this function + + // Copies of Func.Dcl and Func.Body for use during inlining. Copies are + // needed because the function's dcl/body may be changed by later compiler + // transformations. These fields are also populated when a function from + // another package is imported. + Dcl []*Name + Body []Node +} + +// A Mark represents a scope boundary. +type Mark struct { + // Pos is the position of the token that marks the scope + // change. + Pos src.XPos + + // Scope identifies the innermost scope to the right of Pos. + Scope ScopeID +} + +// A ScopeID represents a lexical scope within a function. +type ScopeID int32 + +const ( + funcDupok = 1 << iota // duplicate definitions ok + funcWrapper // hide frame from users (elide in tracebacks, don't count as a frame for recover()) + funcABIWrapper // is an ABI wrapper (also set flagWrapper) + funcNeedctxt // function uses context register (has closure variables) + funcReflectMethod // function calls reflect.Type.Method or MethodByName + // true if closure inside a function; false if a simple function or a + // closure in a global variable initialization + funcIsHiddenClosure + funcHasDefer // contains a defer statement + funcNilCheckDisabled // disable nil checks when compiling this function + funcInlinabilityChecked // inliner has already determined whether the function is inlinable + funcExportInline // include inline body in export data + funcInstrumentBody // add race/msan instrumentation during SSA construction + funcOpenCodedDeferDisallowed // can't do open-coded defers + funcClosureCalled // closure is only immediately called +) + +type SymAndPos struct { + Sym *obj.LSym // LSym of callee + Pos src.XPos // line of call +} + +func (f *Func) Dupok() bool { return f.flags&funcDupok != 0 } +func (f *Func) Wrapper() bool { return f.flags&funcWrapper != 0 } +func (f *Func) ABIWrapper() bool { return f.flags&funcABIWrapper != 0 } +func (f *Func) Needctxt() bool { return f.flags&funcNeedctxt != 0 } +func (f *Func) ReflectMethod() bool { return f.flags&funcReflectMethod != 0 } +func (f *Func) IsHiddenClosure() bool { return f.flags&funcIsHiddenClosure != 0 } +func (f *Func) HasDefer() bool { return f.flags&funcHasDefer != 0 } +func (f *Func) NilCheckDisabled() bool { return f.flags&funcNilCheckDisabled != 0 } +func (f *Func) InlinabilityChecked() bool { return f.flags&funcInlinabilityChecked != 0 } +func (f *Func) ExportInline() bool { return f.flags&funcExportInline != 0 } +func (f *Func) InstrumentBody() bool { return f.flags&funcInstrumentBody != 0 } +func (f *Func) OpenCodedDeferDisallowed() bool { return f.flags&funcOpenCodedDeferDisallowed != 0 } +func (f *Func) ClosureCalled() bool { return f.flags&funcClosureCalled != 0 } + +func (f *Func) SetDupok(b bool) { f.flags.set(funcDupok, b) } +func (f *Func) SetWrapper(b bool) { f.flags.set(funcWrapper, b) } +func (f *Func) SetABIWrapper(b bool) { f.flags.set(funcABIWrapper, b) } +func (f *Func) SetNeedctxt(b bool) { f.flags.set(funcNeedctxt, b) } +func (f *Func) SetReflectMethod(b bool) { f.flags.set(funcReflectMethod, b) } +func (f *Func) SetIsHiddenClosure(b bool) { f.flags.set(funcIsHiddenClosure, b) } +func (f *Func) SetHasDefer(b bool) { f.flags.set(funcHasDefer, b) } +func (f *Func) SetNilCheckDisabled(b bool) { f.flags.set(funcNilCheckDisabled, b) } +func (f *Func) SetInlinabilityChecked(b bool) { f.flags.set(funcInlinabilityChecked, b) } +func (f *Func) SetExportInline(b bool) { f.flags.set(funcExportInline, b) } +func (f *Func) SetInstrumentBody(b bool) { f.flags.set(funcInstrumentBody, b) } +func (f *Func) SetOpenCodedDeferDisallowed(b bool) { f.flags.set(funcOpenCodedDeferDisallowed, b) } +func (f *Func) SetClosureCalled(b bool) { f.flags.set(funcClosureCalled, b) } + +func (f *Func) SetWBPos(pos src.XPos) { + if base.Debug.WB != 0 { + base.WarnfAt(pos, "write barrier") + } + if !f.WBPos.IsKnown() { + f.WBPos = pos + } +} + +// FuncName returns the name (without the package) of the function n. +func FuncName(f *Func) string { + if f == nil || f.Nname == nil { + return "" + } + return f.Sym().Name +} + +// PkgFuncName returns the name of the function referenced by n, with package prepended. +// This differs from the compiler's internal convention where local functions lack a package +// because the ultimate consumer of this is a human looking at an IDE; package is only empty +// if the compilation package is actually the empty string. +func PkgFuncName(f *Func) string { + if f == nil || f.Nname == nil { + return "" + } + s := f.Sym() + pkg := s.Pkg + + p := base.Ctxt.Pkgpath + if pkg != nil && pkg.Path != "" { + p = pkg.Path + } + if p == "" { + return s.Name + } + return p + "." + s.Name +} + +var CurFunc *Func + +func FuncSymName(s *types.Sym) string { + return s.Name + "·f" +} + +// MarkFunc marks a node as a function. +func MarkFunc(n *Name) { + if n.Op() != ONAME || n.Class != Pxxx { + base.Fatalf("expected ONAME/Pxxx node, got %v", n) + } + + n.Class = PFUNC + n.Sym().SetFunc(true) +} + +// ClosureDebugRuntimeCheck applies boilerplate checks for debug flags +// and compiling runtime +func ClosureDebugRuntimeCheck(clo *ClosureExpr) { + if base.Debug.Closure > 0 { + if clo.Esc() == EscHeap { + base.WarnfAt(clo.Pos(), "heap closure, captured vars = %v", clo.Func.ClosureVars) + } else { + base.WarnfAt(clo.Pos(), "stack closure, captured vars = %v", clo.Func.ClosureVars) + } + } + if base.Flag.CompilingRuntime && clo.Esc() == EscHeap { + base.ErrorfAt(clo.Pos(), "heap-allocated closure, not allowed in runtime") + } +} + +// IsTrivialClosure reports whether closure clo has an +// empty list of captured vars. +func IsTrivialClosure(clo *ClosureExpr) bool { + return len(clo.Func.ClosureVars) == 0 +} diff --git a/src/syscall/syscall_windows_amd64.go b/src/cmd/compile/internal/ir/ir.go similarity index 90% rename from src/syscall/syscall_windows_amd64.go rename to src/cmd/compile/internal/ir/ir.go index e82b540b4b64da1ef68d4cde3eb4beb73762befc..82224ca2ed8350660cb412acb7e483e27e073ff3 100644 --- a/src/syscall/syscall_windows_amd64.go +++ b/src/cmd/compile/internal/ir/ir.go @@ -2,4 +2,4 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package syscall +package ir diff --git a/src/cmd/compile/internal/ir/mini.go b/src/cmd/compile/internal/ir/mini.go new file mode 100644 index 0000000000000000000000000000000000000000..a7ff4ac9c77a1a122b8fd558dd69f6b83e4879cb --- /dev/null +++ b/src/cmd/compile/internal/ir/mini.go @@ -0,0 +1,92 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run -mod=mod mknode.go + +package ir + +import ( + "cmd/compile/internal/types" + "cmd/internal/src" + "fmt" + "go/constant" +) + +// A miniNode is a minimal node implementation, +// meant to be embedded as the first field in a larger node implementation, +// at a cost of 8 bytes. +// +// A miniNode is NOT a valid Node by itself: the embedding struct +// must at the least provide: +// +// func (n *MyNode) String() string { return fmt.Sprint(n) } +// func (n *MyNode) rawCopy() Node { c := *n; return &c } +// func (n *MyNode) Format(s fmt.State, verb rune) { FmtNode(n, s, verb) } +// +// The embedding struct should also fill in n.op in its constructor, +// for more useful panic messages when invalid methods are called, +// instead of implementing Op itself. +// +type miniNode struct { + pos src.XPos // uint32 + op Op // uint8 + bits bitset8 + esc uint16 +} + +// posOr returns pos if known, or else n.pos. +// For use in DeepCopy. +func (n *miniNode) posOr(pos src.XPos) src.XPos { + if pos.IsKnown() { + return pos + } + return n.pos +} + +// op can be read, but not written. +// An embedding implementation can provide a SetOp if desired. +// (The panicking SetOp is with the other panics below.) +func (n *miniNode) Op() Op { return n.op } +func (n *miniNode) Pos() src.XPos { return n.pos } +func (n *miniNode) SetPos(x src.XPos) { n.pos = x } +func (n *miniNode) Esc() uint16 { return n.esc } +func (n *miniNode) SetEsc(x uint16) { n.esc = x } + +const ( + miniWalkdefShift = 0 // TODO(mdempsky): Move to Name.flags. + miniTypecheckShift = 2 + miniDiag = 1 << 4 + miniWalked = 1 << 5 // to prevent/catch re-walking +) + +func (n *miniNode) Typecheck() uint8 { return n.bits.get2(miniTypecheckShift) } +func (n *miniNode) SetTypecheck(x uint8) { + if x > 3 { + panic(fmt.Sprintf("cannot SetTypecheck %d", x)) + } + n.bits.set2(miniTypecheckShift, x) +} + +func (n *miniNode) Diag() bool { return n.bits&miniDiag != 0 } +func (n *miniNode) SetDiag(x bool) { n.bits.set(miniDiag, x) } + +func (n *miniNode) Walked() bool { return n.bits&miniWalked != 0 } +func (n *miniNode) SetWalked(x bool) { n.bits.set(miniWalked, x) } + +// Empty, immutable graph structure. + +func (n *miniNode) Init() Nodes { return Nodes{} } + +// Additional functionality unavailable. + +func (n *miniNode) no(name string) string { return "cannot " + name + " on " + n.op.String() } + +func (n *miniNode) Type() *types.Type { return nil } +func (n *miniNode) SetType(*types.Type) { panic(n.no("SetType")) } +func (n *miniNode) Name() *Name { return nil } +func (n *miniNode) Sym() *types.Sym { return nil } +func (n *miniNode) Val() constant.Value { panic(n.no("Val")) } +func (n *miniNode) SetVal(v constant.Value) { panic(n.no("SetVal")) } +func (n *miniNode) NonNil() bool { return false } +func (n *miniNode) MarkNonNil() { panic(n.no("MarkNonNil")) } diff --git a/src/cmd/compile/internal/ir/mknode.go b/src/cmd/compile/internal/ir/mknode.go new file mode 100644 index 0000000000000000000000000000000000000000..5a0aaadf16fa66ed864cf1903b8688620293d6e5 --- /dev/null +++ b/src/cmd/compile/internal/ir/mknode.go @@ -0,0 +1,229 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore +// +build ignore + +package main + +import ( + "bytes" + "fmt" + "go/format" + "go/types" + "io/ioutil" + "log" + "reflect" + "sort" + "strings" + + "golang.org/x/tools/go/packages" +) + +var irPkg *types.Package +var buf bytes.Buffer + +func main() { + cfg := &packages.Config{ + Mode: packages.NeedSyntax | packages.NeedTypes, + } + pkgs, err := packages.Load(cfg, "cmd/compile/internal/ir") + if err != nil { + log.Fatal(err) + } + irPkg = pkgs[0].Types + + fmt.Fprintln(&buf, "// Code generated by mknode.go. DO NOT EDIT.") + fmt.Fprintln(&buf) + fmt.Fprintln(&buf, "package ir") + fmt.Fprintln(&buf) + fmt.Fprintln(&buf, `import "fmt"`) + + scope := irPkg.Scope() + for _, name := range scope.Names() { + if strings.HasPrefix(name, "mini") { + continue + } + + obj, ok := scope.Lookup(name).(*types.TypeName) + if !ok { + continue + } + typ := obj.Type().(*types.Named) + if !implementsNode(types.NewPointer(typ)) { + continue + } + + fmt.Fprintf(&buf, "\n") + fmt.Fprintf(&buf, "func (n *%s) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) }\n", name) + + switch name { + case "Name", "Func": + // Too specialized to automate. + continue + } + + forNodeFields(typ, + "func (n *%[1]s) copy() Node { c := *n\n", + "", + "c.%[1]s = copy%[2]s(c.%[1]s)", + "return &c }\n") + + forNodeFields(typ, + "func (n *%[1]s) doChildren(do func(Node) bool) bool {\n", + "if n.%[1]s != nil && do(n.%[1]s) { return true }", + "if do%[2]s(n.%[1]s, do) { return true }", + "return false }\n") + + forNodeFields(typ, + "func (n *%[1]s) editChildren(edit func(Node) Node) {\n", + "if n.%[1]s != nil { n.%[1]s = edit(n.%[1]s).(%[2]s) }", + "edit%[2]s(n.%[1]s, edit)", + "}\n") + } + + makeHelpers() + + out, err := format.Source(buf.Bytes()) + if err != nil { + // write out mangled source so we can see the bug. + out = buf.Bytes() + } + + err = ioutil.WriteFile("node_gen.go", out, 0666) + if err != nil { + log.Fatal(err) + } +} + +// needHelper maps needed slice helpers from their base name to their +// respective slice-element type. +var needHelper = map[string]string{} + +func makeHelpers() { + var names []string + for name := range needHelper { + names = append(names, name) + } + sort.Strings(names) + + for _, name := range names { + fmt.Fprintf(&buf, sliceHelperTmpl, name, needHelper[name]) + } +} + +const sliceHelperTmpl = ` +func copy%[1]s(list []%[2]s) []%[2]s { + if list == nil { + return nil + } + c := make([]%[2]s, len(list)) + copy(c, list) + return c +} +func do%[1]s(list []%[2]s, do func(Node) bool) bool { + for _, x := range list { + if x != nil && do(x) { + return true + } + } + return false +} +func edit%[1]s(list []%[2]s, edit func(Node) Node) { + for i, x := range list { + if x != nil { + list[i] = edit(x).(%[2]s) + } + } +} +` + +func forNodeFields(named *types.Named, prologue, singleTmpl, sliceTmpl, epilogue string) { + fmt.Fprintf(&buf, prologue, named.Obj().Name()) + + anyField(named.Underlying().(*types.Struct), func(f *types.Var) bool { + if f.Embedded() { + return false + } + name, typ := f.Name(), f.Type() + + slice, _ := typ.Underlying().(*types.Slice) + if slice != nil { + typ = slice.Elem() + } + + tmpl, what := singleTmpl, types.TypeString(typ, types.RelativeTo(irPkg)) + if implementsNode(typ) { + if slice != nil { + helper := strings.TrimPrefix(what, "*") + "s" + needHelper[helper] = what + tmpl, what = sliceTmpl, helper + } + } else if what == "*Field" { + // Special case for *Field. + tmpl = sliceTmpl + if slice != nil { + what = "Fields" + } else { + what = "Field" + } + } else { + return false + } + + if tmpl == "" { + return false + } + + // Allow template to not use all arguments without + // upsetting fmt.Printf. + s := fmt.Sprintf(tmpl+"\x00 %[1]s %[2]s", name, what) + fmt.Fprintln(&buf, s[:strings.LastIndex(s, "\x00")]) + return false + }) + + fmt.Fprintf(&buf, epilogue) +} + +func implementsNode(typ types.Type) bool { + if _, ok := typ.Underlying().(*types.Interface); ok { + // TODO(mdempsky): Check the interface implements Node. + // Worst case, node_gen.go will fail to compile if we're wrong. + return true + } + + if ptr, ok := typ.(*types.Pointer); ok { + if str, ok := ptr.Elem().Underlying().(*types.Struct); ok { + return anyField(str, func(f *types.Var) bool { + return f.Embedded() && f.Name() == "miniNode" + }) + } + } + + return false +} + +func anyField(typ *types.Struct, pred func(f *types.Var) bool) bool { + for i, n := 0, typ.NumFields(); i < n; i++ { + if value, ok := reflect.StructTag(typ.Tag(i)).Lookup("mknode"); ok { + if value != "-" { + panic(fmt.Sprintf("unexpected tag value: %q", value)) + } + continue + } + + f := typ.Field(i) + if pred(f) { + return true + } + if f.Embedded() { + if typ, ok := f.Type().Underlying().(*types.Struct); ok { + if anyField(typ, pred) { + return true + } + } + } + } + return false +} diff --git a/src/cmd/compile/internal/ir/name.go b/src/cmd/compile/internal/ir/name.go new file mode 100644 index 0000000000000000000000000000000000000000..b6c68bc5e01a4b33d8b590f29658886446584fbf --- /dev/null +++ b/src/cmd/compile/internal/ir/name.go @@ -0,0 +1,516 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/src" + "fmt" + + "go/constant" +) + +// An Ident is an identifier, possibly qualified. +type Ident struct { + miniExpr + sym *types.Sym +} + +func NewIdent(pos src.XPos, sym *types.Sym) *Ident { + n := new(Ident) + n.op = ONONAME + n.pos = pos + n.sym = sym + return n +} + +func (n *Ident) Sym() *types.Sym { return n.sym } + +func (*Ident) CanBeNtype() {} + +// Name holds Node fields used only by named nodes (ONAME, OTYPE, some OLITERAL). +type Name struct { + miniExpr + BuiltinOp Op // uint8 + Class Class // uint8 + pragma PragmaFlag // int16 + flags bitset16 + sym *types.Sym + Func *Func // TODO(austin): nil for I.M, eqFor, hashfor, and hashmem + Offset_ int64 + val constant.Value + Opt interface{} // for use by escape analysis + Embed *[]Embed // list of embedded files, for ONAME var + + PkgName *PkgName // real package for import . names + // For a local variable (not param) or extern, the initializing assignment (OAS or OAS2). + // For a closure var, the ONAME node of the outer captured variable. + // For the case-local variables of a type switch, the type switch guard (OTYPESW). + // For the name of a function, points to corresponding Func node. + Defn Node + + // The function, method, or closure in which local variable or param is declared. + Curfn *Func + + Ntype Ntype + Heapaddr *Name // temp holding heap address of param + + // ONAME closure linkage + // Consider: + // + // func f() { + // x := 1 // x1 + // func() { + // use(x) // x2 + // func() { + // use(x) // x3 + // --- parser is here --- + // }() + // }() + // } + // + // There is an original declaration of x and then a chain of mentions of x + // leading into the current function. Each time x is mentioned in a new closure, + // we create a variable representing x for use in that specific closure, + // since the way you get to x is different in each closure. + // + // Let's number the specific variables as shown in the code: + // x1 is the original x, x2 is when mentioned in the closure, + // and x3 is when mentioned in the closure in the closure. + // + // We keep these linked (assume N > 1): + // + // - x1.Defn = original declaration statement for x (like most variables) + // - x1.Innermost = current innermost closure x (in this case x3), or nil for none + // - x1.IsClosureVar() = false + // + // - xN.Defn = x1, N > 1 + // - xN.IsClosureVar() = true, N > 1 + // - x2.Outer = nil + // - xN.Outer = x(N-1), N > 2 + // + // + // When we look up x in the symbol table, we always get x1. + // Then we can use x1.Innermost (if not nil) to get the x + // for the innermost known closure function, + // but the first reference in a closure will find either no x1.Innermost + // or an x1.Innermost with .Funcdepth < Funcdepth. + // In that case, a new xN must be created, linked in with: + // + // xN.Defn = x1 + // xN.Outer = x1.Innermost + // x1.Innermost = xN + // + // When we finish the function, we'll process its closure variables + // and find xN and pop it off the list using: + // + // x1 := xN.Defn + // x1.Innermost = xN.Outer + // + // We leave x1.Innermost set so that we can still get to the original + // variable quickly. Not shown here, but once we're + // done parsing a function and no longer need xN.Outer for the + // lexical x reference links as described above, funcLit + // recomputes xN.Outer as the semantic x reference link tree, + // even filling in x in intermediate closures that might not + // have mentioned it along the way to inner closures that did. + // See funcLit for details. + // + // During the eventual compilation, then, for closure variables we have: + // + // xN.Defn = original variable + // xN.Outer = variable captured in next outward scope + // to make closure where xN appears + // + // Because of the sharding of pieces of the node, x.Defn means x.Name.Defn + // and x.Innermost/Outer means x.Name.Param.Innermost/Outer. + Innermost *Name + Outer *Name +} + +func (n *Name) isExpr() {} + +func (n *Name) copy() Node { panic(n.no("copy")) } +func (n *Name) doChildren(do func(Node) bool) bool { return false } +func (n *Name) editChildren(edit func(Node) Node) {} + +// TypeDefn returns the type definition for a named OTYPE. +// That is, given "type T Defn", it returns Defn. +// It is used by package types. +func (n *Name) TypeDefn() *types.Type { + return n.Ntype.Type() +} + +// RecordFrameOffset records the frame offset for the name. +// It is used by package types when laying out function arguments. +func (n *Name) RecordFrameOffset(offset int64) { + n.SetFrameOffset(offset) +} + +// NewNameAt returns a new ONAME Node associated with symbol s at position pos. +// The caller is responsible for setting Curfn. +func NewNameAt(pos src.XPos, sym *types.Sym) *Name { + if sym == nil { + base.Fatalf("NewNameAt nil") + } + return newNameAt(pos, ONAME, sym) +} + +// NewIota returns a new OIOTA Node. +func NewIota(pos src.XPos, sym *types.Sym) *Name { + if sym == nil { + base.Fatalf("NewIota nil") + } + return newNameAt(pos, OIOTA, sym) +} + +// NewDeclNameAt returns a new Name associated with symbol s at position pos. +// The caller is responsible for setting Curfn. +func NewDeclNameAt(pos src.XPos, op Op, sym *types.Sym) *Name { + if sym == nil { + base.Fatalf("NewDeclNameAt nil") + } + switch op { + case ONAME, OTYPE, OLITERAL: + // ok + default: + base.Fatalf("NewDeclNameAt op %v", op) + } + return newNameAt(pos, op, sym) +} + +// NewConstAt returns a new OLITERAL Node associated with symbol s at position pos. +func NewConstAt(pos src.XPos, sym *types.Sym, typ *types.Type, val constant.Value) *Name { + if sym == nil { + base.Fatalf("NewConstAt nil") + } + n := newNameAt(pos, OLITERAL, sym) + n.SetType(typ) + n.SetVal(val) + return n +} + +// newNameAt is like NewNameAt but allows sym == nil. +func newNameAt(pos src.XPos, op Op, sym *types.Sym) *Name { + n := new(Name) + n.op = op + n.pos = pos + n.sym = sym + return n +} + +func (n *Name) Name() *Name { return n } +func (n *Name) Sym() *types.Sym { return n.sym } +func (n *Name) SetSym(x *types.Sym) { n.sym = x } +func (n *Name) SubOp() Op { return n.BuiltinOp } +func (n *Name) SetSubOp(x Op) { n.BuiltinOp = x } +func (n *Name) SetFunc(x *Func) { n.Func = x } +func (n *Name) Offset() int64 { panic("Name.Offset") } +func (n *Name) SetOffset(x int64) { + if x != 0 { + panic("Name.SetOffset") + } +} +func (n *Name) FrameOffset() int64 { return n.Offset_ } +func (n *Name) SetFrameOffset(x int64) { n.Offset_ = x } +func (n *Name) Iota() int64 { return n.Offset_ } +func (n *Name) SetIota(x int64) { n.Offset_ = x } +func (n *Name) Walkdef() uint8 { return n.bits.get2(miniWalkdefShift) } +func (n *Name) SetWalkdef(x uint8) { + if x > 3 { + panic(fmt.Sprintf("cannot SetWalkdef %d", x)) + } + n.bits.set2(miniWalkdefShift, x) +} + +func (n *Name) Linksym() *obj.LSym { return n.sym.Linksym() } +func (n *Name) LinksymABI(abi obj.ABI) *obj.LSym { return n.sym.LinksymABI(abi) } + +func (*Name) CanBeNtype() {} +func (*Name) CanBeAnSSASym() {} +func (*Name) CanBeAnSSAAux() {} + +// Pragma returns the PragmaFlag for p, which must be for an OTYPE. +func (n *Name) Pragma() PragmaFlag { return n.pragma } + +// SetPragma sets the PragmaFlag for p, which must be for an OTYPE. +func (n *Name) SetPragma(flag PragmaFlag) { n.pragma = flag } + +// Alias reports whether p, which must be for an OTYPE, is a type alias. +func (n *Name) Alias() bool { return n.flags&nameAlias != 0 } + +// SetAlias sets whether p, which must be for an OTYPE, is a type alias. +func (n *Name) SetAlias(alias bool) { n.flags.set(nameAlias, alias) } + +const ( + nameReadonly = 1 << iota + nameByval // is the variable captured by value or by reference + nameNeedzero // if it contains pointers, needs to be zeroed on function entry + nameAutoTemp // is the variable a temporary (implies no dwarf info. reset if escapes to heap) + nameUsed // for variable declared and not used error + nameIsClosureVar // PAUTOHEAP closure pseudo-variable; original (if any) at n.Defn + nameIsOutputParamHeapAddr // pointer to a result parameter's heap copy + nameIsOutputParamInRegisters // output parameter in registers spills as an auto + nameAddrtaken // address taken, even if not moved to heap + nameInlFormal // PAUTO created by inliner, derived from callee formal + nameInlLocal // PAUTO created by inliner, derived from callee local + nameOpenDeferSlot // if temporary var storing info for open-coded defers + nameLibfuzzerExtraCounter // if PEXTERN should be assigned to __libfuzzer_extra_counters section + nameAlias // is type name an alias +) + +func (n *Name) Readonly() bool { return n.flags&nameReadonly != 0 } +func (n *Name) Needzero() bool { return n.flags&nameNeedzero != 0 } +func (n *Name) AutoTemp() bool { return n.flags&nameAutoTemp != 0 } +func (n *Name) Used() bool { return n.flags&nameUsed != 0 } +func (n *Name) IsClosureVar() bool { return n.flags&nameIsClosureVar != 0 } +func (n *Name) IsOutputParamHeapAddr() bool { return n.flags&nameIsOutputParamHeapAddr != 0 } +func (n *Name) IsOutputParamInRegisters() bool { return n.flags&nameIsOutputParamInRegisters != 0 } +func (n *Name) Addrtaken() bool { return n.flags&nameAddrtaken != 0 } +func (n *Name) InlFormal() bool { return n.flags&nameInlFormal != 0 } +func (n *Name) InlLocal() bool { return n.flags&nameInlLocal != 0 } +func (n *Name) OpenDeferSlot() bool { return n.flags&nameOpenDeferSlot != 0 } +func (n *Name) LibfuzzerExtraCounter() bool { return n.flags&nameLibfuzzerExtraCounter != 0 } + +func (n *Name) setReadonly(b bool) { n.flags.set(nameReadonly, b) } +func (n *Name) SetNeedzero(b bool) { n.flags.set(nameNeedzero, b) } +func (n *Name) SetAutoTemp(b bool) { n.flags.set(nameAutoTemp, b) } +func (n *Name) SetUsed(b bool) { n.flags.set(nameUsed, b) } +func (n *Name) SetIsClosureVar(b bool) { n.flags.set(nameIsClosureVar, b) } +func (n *Name) SetIsOutputParamHeapAddr(b bool) { n.flags.set(nameIsOutputParamHeapAddr, b) } +func (n *Name) SetIsOutputParamInRegisters(b bool) { n.flags.set(nameIsOutputParamInRegisters, b) } +func (n *Name) SetAddrtaken(b bool) { n.flags.set(nameAddrtaken, b) } +func (n *Name) SetInlFormal(b bool) { n.flags.set(nameInlFormal, b) } +func (n *Name) SetInlLocal(b bool) { n.flags.set(nameInlLocal, b) } +func (n *Name) SetOpenDeferSlot(b bool) { n.flags.set(nameOpenDeferSlot, b) } +func (n *Name) SetLibfuzzerExtraCounter(b bool) { n.flags.set(nameLibfuzzerExtraCounter, b) } + +// OnStack reports whether variable n may reside on the stack. +func (n *Name) OnStack() bool { + if n.Op() == ONAME { + switch n.Class { + case PPARAM, PPARAMOUT, PAUTO: + return n.Esc() != EscHeap + case PEXTERN, PAUTOHEAP: + return false + } + } + // Note: fmt.go:dumpNodeHeader calls all "func() bool"-typed + // methods, but it can only recover from panics, not Fatalf. + panic(fmt.Sprintf("%v: not a variable: %v", base.FmtPos(n.Pos()), n)) +} + +// MarkReadonly indicates that n is an ONAME with readonly contents. +func (n *Name) MarkReadonly() { + if n.Op() != ONAME { + base.Fatalf("Node.MarkReadonly %v", n.Op()) + } + n.setReadonly(true) + // Mark the linksym as readonly immediately + // so that the SSA backend can use this information. + // It will be overridden later during dumpglobls. + n.Linksym().Type = objabi.SRODATA +} + +// Val returns the constant.Value for the node. +func (n *Name) Val() constant.Value { + if n.val == nil { + return constant.MakeUnknown() + } + return n.val +} + +// SetVal sets the constant.Value for the node. +func (n *Name) SetVal(v constant.Value) { + if n.op != OLITERAL { + panic(n.no("SetVal")) + } + AssertValidTypeForConst(n.Type(), v) + n.val = v +} + +// Canonical returns the logical declaration that n represents. If n +// is a closure variable, then Canonical returns the original Name as +// it appears in the function that immediately contains the +// declaration. Otherwise, Canonical simply returns n itself. +func (n *Name) Canonical() *Name { + if n.IsClosureVar() && n.Defn != nil { + n = n.Defn.(*Name) + } + return n +} + +func (n *Name) SetByval(b bool) { + if n.Canonical() != n { + base.Fatalf("SetByval called on non-canonical variable: %v", n) + } + n.flags.set(nameByval, b) +} + +func (n *Name) Byval() bool { + // We require byval to be set on the canonical variable, but we + // allow it to be accessed from any instance. + return n.Canonical().flags&nameByval != 0 +} + +// CaptureName returns a Name suitable for referring to n from within function +// fn or from the package block if fn is nil. If n is a free variable declared +// within a function that encloses fn, then CaptureName returns a closure +// variable that refers to n and adds it to fn.ClosureVars. Otherwise, it simply +// returns n. +func CaptureName(pos src.XPos, fn *Func, n *Name) *Name { + if n.IsClosureVar() { + base.FatalfAt(pos, "misuse of CaptureName on closure variable: %v", n) + } + if n.Op() != ONAME || n.Curfn == nil || n.Curfn == fn { + return n // okay to use directly + } + if fn == nil { + base.FatalfAt(pos, "package-block reference to %v, declared in %v", n, n.Curfn) + } + + c := n.Innermost + if c != nil && c.Curfn == fn { + return c + } + + // Do not have a closure var for the active closure yet; make one. + c = NewNameAt(pos, n.Sym()) + c.Curfn = fn + c.Class = PAUTOHEAP + c.SetIsClosureVar(true) + c.Defn = n + + // Link into list of active closure variables. + // Popped from list in FinishCaptureNames. + c.Outer = n.Innermost + n.Innermost = c + fn.ClosureVars = append(fn.ClosureVars, c) + + return c +} + +// FinishCaptureNames handles any work leftover from calling CaptureName +// earlier. outerfn should be the function that immediately encloses fn. +func FinishCaptureNames(pos src.XPos, outerfn, fn *Func) { + // closure-specific variables are hanging off the + // ordinary ones; see CaptureName above. + // unhook them. + // make the list of pointers for the closure call. + for _, cv := range fn.ClosureVars { + // Unlink from n; see comment above on type Name for these fields. + n := cv.Defn.(*Name) + n.Innermost = cv.Outer + + // If the closure usage of n is not dense, we need to make it + // dense by recapturing n within the enclosing function. + // + // That is, suppose we just finished parsing the innermost + // closure f4 in this code: + // + // func f() { + // n := 1 + // func() { // f2 + // use(n) + // func() { // f3 + // func() { // f4 + // use(n) + // }() + // }() + // }() + // } + // + // At this point cv.Outer is f2's n; there is no n for f3. To + // construct the closure f4 from within f3, we need to use f3's + // n and in this case we need to create f3's n with CaptureName. + // + // We'll decide later in walk whether to use v directly or &v. + cv.Outer = CaptureName(pos, outerfn, n) + } +} + +// SameSource reports whether two nodes refer to the same source +// element. +// +// It exists to help incrementally migrate the compiler towards +// allowing the introduction of IdentExpr (#42990). Once we have +// IdentExpr, it will no longer be safe to directly compare Node +// values to tell if they refer to the same Name. Instead, code will +// need to explicitly get references to the underlying Name object(s), +// and compare those instead. +// +// It will still be safe to compare Nodes directly for checking if two +// nodes are syntactically the same. The SameSource function exists to +// indicate code that intentionally compares Nodes for syntactic +// equality as opposed to code that has yet to be updated in +// preparation for IdentExpr. +func SameSource(n1, n2 Node) bool { + return n1 == n2 +} + +// Uses reports whether expression x is a (direct) use of the given +// variable. +func Uses(x Node, v *Name) bool { + if v == nil || v.Op() != ONAME { + base.Fatalf("RefersTo bad Name: %v", v) + } + return x.Op() == ONAME && x.Name() == v +} + +// DeclaredBy reports whether expression x refers (directly) to a +// variable that was declared by the given statement. +func DeclaredBy(x, stmt Node) bool { + if stmt == nil { + base.Fatalf("DeclaredBy nil") + } + return x.Op() == ONAME && SameSource(x.Name().Defn, stmt) +} + +// The Class of a variable/function describes the "storage class" +// of a variable or function. During parsing, storage classes are +// called declaration contexts. +type Class uint8 + +//go:generate stringer -type=Class name.go +const ( + Pxxx Class = iota // no class; used during ssa conversion to indicate pseudo-variables + PEXTERN // global variables + PAUTO // local variables + PAUTOHEAP // local variables or parameters moved to heap + PPARAM // input arguments + PPARAMOUT // output results + PTYPEPARAM // type params + PFUNC // global functions + + // Careful: Class is stored in three bits in Node.flags. + _ = uint((1 << 3) - iota) // static assert for iota <= (1 << 3) +) + +type Embed struct { + Pos src.XPos + Patterns []string +} + +// A Pack is an identifier referring to an imported package. +type PkgName struct { + miniNode + sym *types.Sym + Pkg *types.Pkg + Used bool +} + +func (p *PkgName) Sym() *types.Sym { return p.sym } + +func (*PkgName) CanBeNtype() {} + +func NewPkgName(pos src.XPos, sym *types.Sym, pkg *types.Pkg) *PkgName { + p := &PkgName{sym: sym, Pkg: pkg} + p.op = OPACK + p.pos = pos + return p +} diff --git a/src/cmd/compile/internal/ir/node.go b/src/cmd/compile/internal/ir/node.go new file mode 100644 index 0000000000000000000000000000000000000000..af559cc0820cc3e74ebd3065630e558c008e3e53 --- /dev/null +++ b/src/cmd/compile/internal/ir/node.go @@ -0,0 +1,599 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// “Abstract” syntax representation. + +package ir + +import ( + "fmt" + "go/constant" + "sort" + + "cmd/compile/internal/base" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +// A Node is the abstract interface to an IR node. +type Node interface { + // Formatting + Format(s fmt.State, verb rune) + + // Source position. + Pos() src.XPos + SetPos(x src.XPos) + + // For making copies. For Copy and SepCopy. + copy() Node + + doChildren(func(Node) bool) bool + editChildren(func(Node) Node) + + // Abstract graph structure, for generic traversals. + Op() Op + Init() Nodes + + // Fields specific to certain Ops only. + Type() *types.Type + SetType(t *types.Type) + Name() *Name + Sym() *types.Sym + Val() constant.Value + SetVal(v constant.Value) + + // Storage for analysis passes. + Esc() uint16 + SetEsc(x uint16) + Diag() bool + SetDiag(x bool) + + // Typecheck values: + // 0 means the node is not typechecked + // 1 means the node is completely typechecked + // 2 means typechecking of the node is in progress + // 3 means the node has its type from types2, but may need transformation + Typecheck() uint8 + SetTypecheck(x uint8) + NonNil() bool + MarkNonNil() +} + +// Line returns n's position as a string. If n has been inlined, +// it uses the outermost position where n has been inlined. +func Line(n Node) string { + return base.FmtPos(n.Pos()) +} + +func IsSynthetic(n Node) bool { + name := n.Sym().Name + return name[0] == '.' || name[0] == '~' +} + +// IsAutoTmp indicates if n was created by the compiler as a temporary, +// based on the setting of the .AutoTemp flag in n's Name. +func IsAutoTmp(n Node) bool { + if n == nil || n.Op() != ONAME { + return false + } + return n.Name().AutoTemp() +} + +// MayBeShared reports whether n may occur in multiple places in the AST. +// Extra care must be taken when mutating such a node. +func MayBeShared(n Node) bool { + switch n.Op() { + case ONAME, OLITERAL, ONIL, OTYPE: + return true + } + return false +} + +type InitNode interface { + Node + PtrInit() *Nodes + SetInit(x Nodes) +} + +func TakeInit(n Node) Nodes { + init := n.Init() + if len(init) != 0 { + n.(InitNode).SetInit(nil) + } + return init +} + +//go:generate stringer -type=Op -trimprefix=O node.go + +type Op uint8 + +// Node ops. +const ( + OXXX Op = iota + + // names + ONAME // var or func name + // Unnamed arg or return value: f(int, string) (int, error) { etc } + // Also used for a qualified package identifier that hasn't been resolved yet. + ONONAME + OTYPE // type name + OPACK // import + OLITERAL // literal + ONIL // nil + + // expressions + OADD // X + Y + OSUB // X - Y + OOR // X | Y + OXOR // X ^ Y + OADDSTR // +{List} (string addition, list elements are strings) + OADDR // &X + OANDAND // X && Y + OAPPEND // append(Args); after walk, X may contain elem type descriptor + OBYTES2STR // Type(X) (Type is string, X is a []byte) + OBYTES2STRTMP // Type(X) (Type is string, X is a []byte, ephemeral) + ORUNES2STR // Type(X) (Type is string, X is a []rune) + OSTR2BYTES // Type(X) (Type is []byte, X is a string) + OSTR2BYTESTMP // Type(X) (Type is []byte, X is a string, ephemeral) + OSTR2RUNES // Type(X) (Type is []rune, X is a string) + OSLICE2ARRPTR // Type(X) (Type is *[N]T, X is a []T) + // X = Y or (if Def=true) X := Y + // If Def, then Init includes a DCL node for X. + OAS + // Lhs = Rhs (x, y, z = a, b, c) or (if Def=true) Lhs := Rhs + // If Def, then Init includes DCL nodes for Lhs + OAS2 + OAS2DOTTYPE // Lhs = Rhs (x, ok = I.(int)) + OAS2FUNC // Lhs = Rhs (x, y = f()) + OAS2MAPR // Lhs = Rhs (x, ok = m["foo"]) + OAS2RECV // Lhs = Rhs (x, ok = <-c) + OASOP // X AsOp= Y (x += y) + OCALL // X(Args) (function call, method call or type conversion) + + // OCALLFUNC, OCALLMETH, and OCALLINTER have the same structure. + // Prior to walk, they are: X(Args), where Args is all regular arguments. + // After walk, if any argument whose evaluation might requires temporary variable, + // that temporary variable will be pushed to Init, Args will contains an updated + // set of arguments. KeepAlive is all OVARLIVE nodes that are attached to OCALLxxx. + OCALLFUNC // X(Args) (function call f(args)) + OCALLMETH // X(Args) (direct method call x.Method(args)) + OCALLINTER // X(Args) (interface method call x.Method(args)) + OCALLPART // X.Sel (method expression x.Method, not called) + OCAP // cap(X) + OCLOSE // close(X) + OCLOSURE // func Type { Func.Closure.Body } (func literal) + OCOMPLIT // Type{List} (composite literal, not yet lowered to specific form) + OMAPLIT // Type{List} (composite literal, Type is map) + OSTRUCTLIT // Type{List} (composite literal, Type is struct) + OARRAYLIT // Type{List} (composite literal, Type is array) + OSLICELIT // Type{List} (composite literal, Type is slice), Len is slice length. + OPTRLIT // &X (X is composite literal) + OCONV // Type(X) (type conversion) + OCONVIFACE // Type(X) (type conversion, to interface) + OCONVNOP // Type(X) (type conversion, no effect) + OCOPY // copy(X, Y) + ODCL // var X (declares X of type X.Type) + + // Used during parsing but don't last. + ODCLFUNC // func f() or func (r) f() + ODCLCONST // const pi = 3.14 + ODCLTYPE // type Int int or type Int = int + + ODELETE // delete(Args) + ODOT // X.Sel (X is of struct type) + ODOTPTR // X.Sel (X is of pointer to struct type) + ODOTMETH // X.Sel (X is non-interface, Sel is method name) + ODOTINTER // X.Sel (X is interface, Sel is method name) + OXDOT // X.Sel (before rewrite to one of the preceding) + ODOTTYPE // X.Ntype or X.Type (.Ntype during parsing, .Type once resolved); after walk, Itab contains address of interface type descriptor and Itab.X contains address of concrete type descriptor + ODOTTYPE2 // X.Ntype or X.Type (.Ntype during parsing, .Type once resolved; on rhs of OAS2DOTTYPE); after walk, Itab contains address of interface type descriptor + OEQ // X == Y + ONE // X != Y + OLT // X < Y + OLE // X <= Y + OGE // X >= Y + OGT // X > Y + ODEREF // *X + OINDEX // X[Index] (index of array or slice) + OINDEXMAP // X[Index] (index of map) + OKEY // Key:Value (key:value in struct/array/map literal) + OSTRUCTKEY // Field:Value (key:value in struct literal, after type checking) + OLEN // len(X) + OMAKE // make(Args) (before type checking converts to one of the following) + OMAKECHAN // make(Type[, Len]) (type is chan) + OMAKEMAP // make(Type[, Len]) (type is map) + OMAKESLICE // make(Type[, Len[, Cap]]) (type is slice) + OMAKESLICECOPY // makeslicecopy(Type, Len, Cap) (type is slice; Len is length and Cap is the copied from slice) + // OMAKESLICECOPY is created by the order pass and corresponds to: + // s = make(Type, Len); copy(s, Cap) + // + // Bounded can be set on the node when Len == len(Cap) is known at compile time. + // + // This node is created so the walk pass can optimize this pattern which would + // otherwise be hard to detect after the order pass. + OMUL // X * Y + ODIV // X / Y + OMOD // X % Y + OLSH // X << Y + ORSH // X >> Y + OAND // X & Y + OANDNOT // X &^ Y + ONEW // new(X); corresponds to calls to new in source code + ONOT // !X + OBITNOT // ^X + OPLUS // +X + ONEG // -X + OOROR // X || Y + OPANIC // panic(X) + OPRINT // print(List) + OPRINTN // println(List) + OPAREN // (X) + OSEND // Chan <- Value + OSLICE // X[Low : High] (X is untypechecked or slice) + OSLICEARR // X[Low : High] (X is pointer to array) + OSLICESTR // X[Low : High] (X is string) + OSLICE3 // X[Low : High : Max] (X is untypedchecked or slice) + OSLICE3ARR // X[Low : High : Max] (X is pointer to array) + OSLICEHEADER // sliceheader{Ptr, Len, Cap} (Ptr is unsafe.Pointer, Len is length, Cap is capacity) + ORECOVER // recover() + ORECV // <-X + ORUNESTR // Type(X) (Type is string, X is rune) + OSELRECV2 // like OAS2: Lhs = Rhs where len(Lhs)=2, len(Rhs)=1, Rhs[0].Op = ORECV (appears as .Var of OCASE) + OIOTA // iota + OREAL // real(X) + OIMAG // imag(X) + OCOMPLEX // complex(X, Y) + OALIGNOF // unsafe.Alignof(X) + OOFFSETOF // unsafe.Offsetof(X) + OSIZEOF // unsafe.Sizeof(X) + OUNSAFEADD // unsafe.Add(X, Y) + OUNSAFESLICE // unsafe.Slice(X, Y) + OMETHEXPR // method expression + + // statements + OBLOCK // { List } (block of code) + OBREAK // break [Label] + // OCASE: case List: Body (List==nil means default) + // For OTYPESW, List is a OTYPE node for the specified type (or OLITERAL + // for nil), and, if a type-switch variable is specified, Rlist is an + // ONAME for the version of the type-switch variable with the specified + // type. + OCASE + OCONTINUE // continue [Label] + ODEFER // defer Call + OFALL // fallthrough + OFOR // for Init; Cond; Post { Body } + // OFORUNTIL is like OFOR, but the test (Cond) is applied after the body: + // Init + // top: { Body } // Execute the body at least once + // cont: Post + // if Cond { // And then test the loop condition + // List // Before looping to top, execute List + // goto top + // } + // OFORUNTIL is created by walk. There's no way to write this in Go code. + OFORUNTIL + OGOTO // goto Label + OIF // if Init; Cond { Then } else { Else } + OLABEL // Label: + OGO // go Call + ORANGE // for Key, Value = range X { Body } + ORETURN // return Results + OSELECT // select { Cases } + OSWITCH // switch Init; Expr { Cases } + // OTYPESW: X := Y.(type) (appears as .Tag of OSWITCH) + // X is nil if there is no type-switch variable + OTYPESW + OFUNCINST // instantiation of a generic function + + // types + OTCHAN // chan int + OTMAP // map[string]int + OTSTRUCT // struct{} + OTINTER // interface{} + // OTFUNC: func() - Recv is receiver field, Params is list of param fields, Results is + // list of result fields. + OTFUNC + OTARRAY // [8]int or [...]int + OTSLICE // []int + + // misc + // intermediate representation of an inlined call. Uses Init (assignments + // for the captured variables, parameters, retvars, & INLMARK op), + // Body (body of the inlined function), and ReturnVars (list of + // return values) + OINLCALL // intermediary representation of an inlined call. + OEFACE // itable and data words of an empty-interface value. + OITAB // itable word of an interface value. + OIDATA // data word of an interface value in X + OSPTR // base pointer of a slice or string. + OCFUNC // reference to c function pointer (not go func value) + OCHECKNIL // emit code to ensure pointer/interface not nil + OVARDEF // variable is about to be fully initialized + OVARKILL // variable is dead + OVARLIVE // variable is alive + ORESULT // result of a function call; Xoffset is stack offset + OINLMARK // start of an inlined body, with file/line of caller. Xoffset is an index into the inline tree. + OLINKSYMOFFSET // offset within a name + + // arch-specific opcodes + OTAILCALL // tail call to another function + OGETG // runtime.getg() (read g pointer) + + OEND +) + +// Nodes is a pointer to a slice of *Node. +// For fields that are not used in most nodes, this is used instead of +// a slice to save space. +type Nodes []Node + +// Append appends entries to Nodes. +func (n *Nodes) Append(a ...Node) { + if len(a) == 0 { + return + } + *n = append(*n, a...) +} + +// Prepend prepends entries to Nodes. +// If a slice is passed in, this will take ownership of it. +func (n *Nodes) Prepend(a ...Node) { + if len(a) == 0 { + return + } + *n = append(a, *n...) +} + +// Take clears n, returning its former contents. +func (n *Nodes) Take() []Node { + ret := *n + *n = nil + return ret +} + +// Copy returns a copy of the content of the slice. +func (n Nodes) Copy() Nodes { + if n == nil { + return nil + } + c := make(Nodes, len(n)) + copy(c, n) + return c +} + +// NameQueue is a FIFO queue of *Name. The zero value of NameQueue is +// a ready-to-use empty queue. +type NameQueue struct { + ring []*Name + head, tail int +} + +// Empty reports whether q contains no Names. +func (q *NameQueue) Empty() bool { + return q.head == q.tail +} + +// PushRight appends n to the right of the queue. +func (q *NameQueue) PushRight(n *Name) { + if len(q.ring) == 0 { + q.ring = make([]*Name, 16) + } else if q.head+len(q.ring) == q.tail { + // Grow the ring. + nring := make([]*Name, len(q.ring)*2) + // Copy the old elements. + part := q.ring[q.head%len(q.ring):] + if q.tail-q.head <= len(part) { + part = part[:q.tail-q.head] + copy(nring, part) + } else { + pos := copy(nring, part) + copy(nring[pos:], q.ring[:q.tail%len(q.ring)]) + } + q.ring, q.head, q.tail = nring, 0, q.tail-q.head + } + + q.ring[q.tail%len(q.ring)] = n + q.tail++ +} + +// PopLeft pops a Name from the left of the queue. It panics if q is +// empty. +func (q *NameQueue) PopLeft() *Name { + if q.Empty() { + panic("dequeue empty") + } + n := q.ring[q.head%len(q.ring)] + q.head++ + return n +} + +// NameSet is a set of Names. +type NameSet map[*Name]struct{} + +// Has reports whether s contains n. +func (s NameSet) Has(n *Name) bool { + _, isPresent := s[n] + return isPresent +} + +// Add adds n to s. +func (s *NameSet) Add(n *Name) { + if *s == nil { + *s = make(map[*Name]struct{}) + } + (*s)[n] = struct{}{} +} + +// Sorted returns s sorted according to less. +func (s NameSet) Sorted(less func(*Name, *Name) bool) []*Name { + var res []*Name + for n := range s { + res = append(res, n) + } + sort.Slice(res, func(i, j int) bool { return less(res[i], res[j]) }) + return res +} + +type PragmaFlag int16 + +const ( + // Func pragmas. + Nointerface PragmaFlag = 1 << iota + Noescape // func parameters don't escape + Norace // func must not have race detector annotations + Nosplit // func should not execute on separate stack + Noinline // func should not be inlined + NoCheckPtr // func should not be instrumented by checkptr + CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all + UintptrEscapes // pointers converted to uintptr escape + + // Runtime-only func pragmas. + // See ../../../../runtime/README.md for detailed descriptions. + Systemstack // func must run on system stack + Nowritebarrier // emit compiler error instead of write barrier + Nowritebarrierrec // error on write barrier in this or recursive callees + Yeswritebarrierrec // cancels Nowritebarrierrec in this function and callees + + // Runtime and cgo type pragmas + NotInHeap // values of this type must not be heap allocated + + // Go command pragmas + GoBuildPragma + + RegisterParams // TODO(register args) remove after register abi is working + +) + +func AsNode(n types.Object) Node { + if n == nil { + return nil + } + return n.(Node) +} + +var BlankNode Node + +func IsConst(n Node, ct constant.Kind) bool { + return ConstType(n) == ct +} + +// IsNil reports whether n represents the universal untyped zero value "nil". +func IsNil(n Node) bool { + // Check n.Orig because constant propagation may produce typed nil constants, + // which don't exist in the Go spec. + return n != nil && Orig(n).Op() == ONIL +} + +func IsBlank(n Node) bool { + if n == nil { + return false + } + return n.Sym().IsBlank() +} + +// IsMethod reports whether n is a method. +// n must be a function or a method. +func IsMethod(n Node) bool { + return n.Type().Recv() != nil +} + +func HasNamedResults(fn *Func) bool { + typ := fn.Type() + return typ.NumResults() > 0 && types.OrigSym(typ.Results().Field(0).Sym) != nil +} + +// HasUniquePos reports whether n has a unique position that can be +// used for reporting error messages. +// +// It's primarily used to distinguish references to named objects, +// whose Pos will point back to their declaration position rather than +// their usage position. +func HasUniquePos(n Node) bool { + switch n.Op() { + case ONAME, OPACK: + return false + case OLITERAL, ONIL, OTYPE: + if n.Sym() != nil { + return false + } + } + + if !n.Pos().IsKnown() { + if base.Flag.K != 0 { + base.Warn("setlineno: unknown position (line 0)") + } + return false + } + + return true +} + +func SetPos(n Node) src.XPos { + lno := base.Pos + if n != nil && HasUniquePos(n) { + base.Pos = n.Pos() + } + return lno +} + +// The result of InitExpr MUST be assigned back to n, e.g. +// n.X = InitExpr(init, n.X) +func InitExpr(init []Node, expr Node) Node { + if len(init) == 0 { + return expr + } + + n, ok := expr.(InitNode) + if !ok || MayBeShared(n) { + // Introduce OCONVNOP to hold init list. + n = NewConvExpr(base.Pos, OCONVNOP, nil, expr) + n.SetType(expr.Type()) + n.SetTypecheck(1) + } + + n.PtrInit().Prepend(init...) + return n +} + +// what's the outer value that a write to n affects? +// outer value means containing struct or array. +func OuterValue(n Node) Node { + for { + switch nn := n; nn.Op() { + case OXDOT: + base.Fatalf("OXDOT in walk") + case ODOT: + nn := nn.(*SelectorExpr) + n = nn.X + continue + case OPAREN: + nn := nn.(*ParenExpr) + n = nn.X + continue + case OCONVNOP: + nn := nn.(*ConvExpr) + n = nn.X + continue + case OINDEX: + nn := nn.(*IndexExpr) + if nn.X.Type() == nil { + base.Fatalf("OuterValue needs type for %v", nn.X) + } + if nn.X.Type().IsArray() { + n = nn.X + continue + } + } + + return n + } +} + +const ( + EscUnknown = iota + EscNone // Does not escape to heap, result, or parameters. + EscHeap // Reachable from the heap + EscNever // By construction will not escape. +) diff --git a/src/cmd/compile/internal/ir/node_gen.go b/src/cmd/compile/internal/ir/node_gen.go new file mode 100644 index 0000000000000000000000000000000000000000..22855d7163f44a6e9d51586c60cabc3c397dccff --- /dev/null +++ b/src/cmd/compile/internal/ir/node_gen.go @@ -0,0 +1,1452 @@ +// Code generated by mknode.go. DO NOT EDIT. + +package ir + +import "fmt" + +func (n *AddStringExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *AddStringExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.List = copyNodes(c.List) + return &c +} +func (n *AddStringExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if doNodes(n.List, do) { + return true + } + if n.Prealloc != nil && do(n.Prealloc) { + return true + } + return false +} +func (n *AddStringExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + editNodes(n.List, edit) + if n.Prealloc != nil { + n.Prealloc = edit(n.Prealloc).(*Name) + } +} + +func (n *AddrExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *AddrExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *AddrExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if n.Prealloc != nil && do(n.Prealloc) { + return true + } + return false +} +func (n *AddrExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + if n.Prealloc != nil { + n.Prealloc = edit(n.Prealloc).(*Name) + } +} + +func (n *ArrayType) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *ArrayType) copy() Node { + c := *n + return &c +} +func (n *ArrayType) doChildren(do func(Node) bool) bool { + if n.Len != nil && do(n.Len) { + return true + } + if n.Elem != nil && do(n.Elem) { + return true + } + return false +} +func (n *ArrayType) editChildren(edit func(Node) Node) { + if n.Len != nil { + n.Len = edit(n.Len).(Node) + } + if n.Elem != nil { + n.Elem = edit(n.Elem).(Ntype) + } +} + +func (n *AssignListStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *AssignListStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Lhs = copyNodes(c.Lhs) + c.Rhs = copyNodes(c.Rhs) + return &c +} +func (n *AssignListStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if doNodes(n.Lhs, do) { + return true + } + if doNodes(n.Rhs, do) { + return true + } + return false +} +func (n *AssignListStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + editNodes(n.Lhs, edit) + editNodes(n.Rhs, edit) +} + +func (n *AssignOpStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *AssignOpStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *AssignOpStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if n.Y != nil && do(n.Y) { + return true + } + return false +} +func (n *AssignOpStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + if n.Y != nil { + n.Y = edit(n.Y).(Node) + } +} + +func (n *AssignStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *AssignStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *AssignStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if n.Y != nil && do(n.Y) { + return true + } + return false +} +func (n *AssignStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + if n.Y != nil { + n.Y = edit(n.Y).(Node) + } +} + +func (n *BasicLit) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *BasicLit) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *BasicLit) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + return false +} +func (n *BasicLit) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) +} + +func (n *BinaryExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *BinaryExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *BinaryExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if n.Y != nil && do(n.Y) { + return true + } + return false +} +func (n *BinaryExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + if n.Y != nil { + n.Y = edit(n.Y).(Node) + } +} + +func (n *BlockStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *BlockStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.List = copyNodes(c.List) + return &c +} +func (n *BlockStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if doNodes(n.List, do) { + return true + } + return false +} +func (n *BlockStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + editNodes(n.List, edit) +} + +func (n *BranchStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *BranchStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *BranchStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + return false +} +func (n *BranchStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) +} + +func (n *CallExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *CallExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Args = copyNodes(c.Args) + c.KeepAlive = copyNames(c.KeepAlive) + return &c +} +func (n *CallExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if doNodes(n.Args, do) { + return true + } + if doNames(n.KeepAlive, do) { + return true + } + return false +} +func (n *CallExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + editNodes(n.Args, edit) + editNames(n.KeepAlive, edit) +} + +func (n *CaseClause) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *CaseClause) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.List = copyNodes(c.List) + c.Body = copyNodes(c.Body) + return &c +} +func (n *CaseClause) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Var != nil && do(n.Var) { + return true + } + if doNodes(n.List, do) { + return true + } + if doNodes(n.Body, do) { + return true + } + return false +} +func (n *CaseClause) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Var != nil { + n.Var = edit(n.Var).(*Name) + } + editNodes(n.List, edit) + editNodes(n.Body, edit) +} + +func (n *ChanType) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *ChanType) copy() Node { + c := *n + return &c +} +func (n *ChanType) doChildren(do func(Node) bool) bool { + if n.Elem != nil && do(n.Elem) { + return true + } + return false +} +func (n *ChanType) editChildren(edit func(Node) Node) { + if n.Elem != nil { + n.Elem = edit(n.Elem).(Ntype) + } +} + +func (n *ClosureExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *ClosureExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *ClosureExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Prealloc != nil && do(n.Prealloc) { + return true + } + return false +} +func (n *ClosureExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Prealloc != nil { + n.Prealloc = edit(n.Prealloc).(*Name) + } +} + +func (n *CommClause) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *CommClause) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Body = copyNodes(c.Body) + return &c +} +func (n *CommClause) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Comm != nil && do(n.Comm) { + return true + } + if doNodes(n.Body, do) { + return true + } + return false +} +func (n *CommClause) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Comm != nil { + n.Comm = edit(n.Comm).(Node) + } + editNodes(n.Body, edit) +} + +func (n *CompLitExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *CompLitExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.List = copyNodes(c.List) + return &c +} +func (n *CompLitExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Ntype != nil && do(n.Ntype) { + return true + } + if doNodes(n.List, do) { + return true + } + if n.Prealloc != nil && do(n.Prealloc) { + return true + } + return false +} +func (n *CompLitExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Ntype != nil { + n.Ntype = edit(n.Ntype).(Ntype) + } + editNodes(n.List, edit) + if n.Prealloc != nil { + n.Prealloc = edit(n.Prealloc).(*Name) + } +} + +func (n *ConstExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *ConstExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *ConstExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + return false +} +func (n *ConstExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) +} + +func (n *ConvExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *ConvExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *ConvExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + return false +} +func (n *ConvExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } +} + +func (n *Decl) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *Decl) copy() Node { + c := *n + return &c +} +func (n *Decl) doChildren(do func(Node) bool) bool { + if n.X != nil && do(n.X) { + return true + } + return false +} +func (n *Decl) editChildren(edit func(Node) Node) { + if n.X != nil { + n.X = edit(n.X).(*Name) + } +} + +func (n *ForStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *ForStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Late = copyNodes(c.Late) + c.Body = copyNodes(c.Body) + return &c +} +func (n *ForStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Cond != nil && do(n.Cond) { + return true + } + if doNodes(n.Late, do) { + return true + } + if n.Post != nil && do(n.Post) { + return true + } + if doNodes(n.Body, do) { + return true + } + return false +} +func (n *ForStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Cond != nil { + n.Cond = edit(n.Cond).(Node) + } + editNodes(n.Late, edit) + if n.Post != nil { + n.Post = edit(n.Post).(Node) + } + editNodes(n.Body, edit) +} + +func (n *Func) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } + +func (n *FuncType) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *FuncType) copy() Node { + c := *n + c.Recv = copyField(c.Recv) + c.Params = copyFields(c.Params) + c.Results = copyFields(c.Results) + return &c +} +func (n *FuncType) doChildren(do func(Node) bool) bool { + if doField(n.Recv, do) { + return true + } + if doFields(n.Params, do) { + return true + } + if doFields(n.Results, do) { + return true + } + return false +} +func (n *FuncType) editChildren(edit func(Node) Node) { + editField(n.Recv, edit) + editFields(n.Params, edit) + editFields(n.Results, edit) +} + +func (n *GoDeferStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *GoDeferStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *GoDeferStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Call != nil && do(n.Call) { + return true + } + return false +} +func (n *GoDeferStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Call != nil { + n.Call = edit(n.Call).(Node) + } +} + +func (n *Ident) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *Ident) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *Ident) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + return false +} +func (n *Ident) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) +} + +func (n *IfStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *IfStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Body = copyNodes(c.Body) + c.Else = copyNodes(c.Else) + return &c +} +func (n *IfStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Cond != nil && do(n.Cond) { + return true + } + if doNodes(n.Body, do) { + return true + } + if doNodes(n.Else, do) { + return true + } + return false +} +func (n *IfStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Cond != nil { + n.Cond = edit(n.Cond).(Node) + } + editNodes(n.Body, edit) + editNodes(n.Else, edit) +} + +func (n *IndexExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *IndexExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *IndexExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if n.Index != nil && do(n.Index) { + return true + } + return false +} +func (n *IndexExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + if n.Index != nil { + n.Index = edit(n.Index).(Node) + } +} + +func (n *InlineMarkStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *InlineMarkStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *InlineMarkStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + return false +} +func (n *InlineMarkStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) +} + +func (n *InlinedCallExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *InlinedCallExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Body = copyNodes(c.Body) + c.ReturnVars = copyNodes(c.ReturnVars) + return &c +} +func (n *InlinedCallExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if doNodes(n.Body, do) { + return true + } + if doNodes(n.ReturnVars, do) { + return true + } + return false +} +func (n *InlinedCallExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + editNodes(n.Body, edit) + editNodes(n.ReturnVars, edit) +} + +func (n *InstExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *InstExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Targs = copyNodes(c.Targs) + return &c +} +func (n *InstExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if doNodes(n.Targs, do) { + return true + } + return false +} +func (n *InstExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + editNodes(n.Targs, edit) +} + +func (n *InterfaceType) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *InterfaceType) copy() Node { + c := *n + c.Methods = copyFields(c.Methods) + return &c +} +func (n *InterfaceType) doChildren(do func(Node) bool) bool { + if doFields(n.Methods, do) { + return true + } + return false +} +func (n *InterfaceType) editChildren(edit func(Node) Node) { + editFields(n.Methods, edit) +} + +func (n *KeyExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *KeyExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *KeyExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Key != nil && do(n.Key) { + return true + } + if n.Value != nil && do(n.Value) { + return true + } + return false +} +func (n *KeyExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Key != nil { + n.Key = edit(n.Key).(Node) + } + if n.Value != nil { + n.Value = edit(n.Value).(Node) + } +} + +func (n *LabelStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *LabelStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *LabelStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + return false +} +func (n *LabelStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) +} + +func (n *LinksymOffsetExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *LinksymOffsetExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *LinksymOffsetExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + return false +} +func (n *LinksymOffsetExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) +} + +func (n *LogicalExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *LogicalExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *LogicalExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if n.Y != nil && do(n.Y) { + return true + } + return false +} +func (n *LogicalExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + if n.Y != nil { + n.Y = edit(n.Y).(Node) + } +} + +func (n *MakeExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *MakeExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *MakeExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Len != nil && do(n.Len) { + return true + } + if n.Cap != nil && do(n.Cap) { + return true + } + return false +} +func (n *MakeExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Len != nil { + n.Len = edit(n.Len).(Node) + } + if n.Cap != nil { + n.Cap = edit(n.Cap).(Node) + } +} + +func (n *MapType) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *MapType) copy() Node { + c := *n + return &c +} +func (n *MapType) doChildren(do func(Node) bool) bool { + if n.Key != nil && do(n.Key) { + return true + } + if n.Elem != nil && do(n.Elem) { + return true + } + return false +} +func (n *MapType) editChildren(edit func(Node) Node) { + if n.Key != nil { + n.Key = edit(n.Key).(Ntype) + } + if n.Elem != nil { + n.Elem = edit(n.Elem).(Ntype) + } +} + +func (n *Name) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } + +func (n *NilExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *NilExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *NilExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + return false +} +func (n *NilExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) +} + +func (n *ParenExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *ParenExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *ParenExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + return false +} +func (n *ParenExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } +} + +func (n *PkgName) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *PkgName) copy() Node { + c := *n + return &c +} +func (n *PkgName) doChildren(do func(Node) bool) bool { + return false +} +func (n *PkgName) editChildren(edit func(Node) Node) { +} + +func (n *RangeStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *RangeStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Body = copyNodes(c.Body) + return &c +} +func (n *RangeStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if n.Key != nil && do(n.Key) { + return true + } + if n.Value != nil && do(n.Value) { + return true + } + if doNodes(n.Body, do) { + return true + } + if n.Prealloc != nil && do(n.Prealloc) { + return true + } + return false +} +func (n *RangeStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + if n.Key != nil { + n.Key = edit(n.Key).(Node) + } + if n.Value != nil { + n.Value = edit(n.Value).(Node) + } + editNodes(n.Body, edit) + if n.Prealloc != nil { + n.Prealloc = edit(n.Prealloc).(*Name) + } +} + +func (n *ResultExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *ResultExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *ResultExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + return false +} +func (n *ResultExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) +} + +func (n *ReturnStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *ReturnStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Results = copyNodes(c.Results) + return &c +} +func (n *ReturnStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if doNodes(n.Results, do) { + return true + } + return false +} +func (n *ReturnStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + editNodes(n.Results, edit) +} + +func (n *SelectStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *SelectStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Cases = copyCommClauses(c.Cases) + c.Compiled = copyNodes(c.Compiled) + return &c +} +func (n *SelectStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if doCommClauses(n.Cases, do) { + return true + } + if doNodes(n.Compiled, do) { + return true + } + return false +} +func (n *SelectStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + editCommClauses(n.Cases, edit) + editNodes(n.Compiled, edit) +} + +func (n *SelectorExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *SelectorExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *SelectorExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if n.Prealloc != nil && do(n.Prealloc) { + return true + } + return false +} +func (n *SelectorExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + if n.Prealloc != nil { + n.Prealloc = edit(n.Prealloc).(*Name) + } +} + +func (n *SendStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *SendStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *SendStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Chan != nil && do(n.Chan) { + return true + } + if n.Value != nil && do(n.Value) { + return true + } + return false +} +func (n *SendStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Chan != nil { + n.Chan = edit(n.Chan).(Node) + } + if n.Value != nil { + n.Value = edit(n.Value).(Node) + } +} + +func (n *SliceExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *SliceExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *SliceExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if n.Low != nil && do(n.Low) { + return true + } + if n.High != nil && do(n.High) { + return true + } + if n.Max != nil && do(n.Max) { + return true + } + return false +} +func (n *SliceExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + if n.Low != nil { + n.Low = edit(n.Low).(Node) + } + if n.High != nil { + n.High = edit(n.High).(Node) + } + if n.Max != nil { + n.Max = edit(n.Max).(Node) + } +} + +func (n *SliceHeaderExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *SliceHeaderExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *SliceHeaderExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Ptr != nil && do(n.Ptr) { + return true + } + if n.Len != nil && do(n.Len) { + return true + } + if n.Cap != nil && do(n.Cap) { + return true + } + return false +} +func (n *SliceHeaderExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Ptr != nil { + n.Ptr = edit(n.Ptr).(Node) + } + if n.Len != nil { + n.Len = edit(n.Len).(Node) + } + if n.Cap != nil { + n.Cap = edit(n.Cap).(Node) + } +} + +func (n *SliceType) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *SliceType) copy() Node { + c := *n + return &c +} +func (n *SliceType) doChildren(do func(Node) bool) bool { + if n.Elem != nil && do(n.Elem) { + return true + } + return false +} +func (n *SliceType) editChildren(edit func(Node) Node) { + if n.Elem != nil { + n.Elem = edit(n.Elem).(Ntype) + } +} + +func (n *StarExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *StarExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *StarExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + return false +} +func (n *StarExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } +} + +func (n *StructKeyExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *StructKeyExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *StructKeyExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Value != nil && do(n.Value) { + return true + } + return false +} +func (n *StructKeyExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Value != nil { + n.Value = edit(n.Value).(Node) + } +} + +func (n *StructType) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *StructType) copy() Node { + c := *n + c.Fields = copyFields(c.Fields) + return &c +} +func (n *StructType) doChildren(do func(Node) bool) bool { + if doFields(n.Fields, do) { + return true + } + return false +} +func (n *StructType) editChildren(edit func(Node) Node) { + editFields(n.Fields, edit) +} + +func (n *SwitchStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *SwitchStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + c.Cases = copyCaseClauses(c.Cases) + c.Compiled = copyNodes(c.Compiled) + return &c +} +func (n *SwitchStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Tag != nil && do(n.Tag) { + return true + } + if doCaseClauses(n.Cases, do) { + return true + } + if doNodes(n.Compiled, do) { + return true + } + return false +} +func (n *SwitchStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Tag != nil { + n.Tag = edit(n.Tag).(Node) + } + editCaseClauses(n.Cases, edit) + editNodes(n.Compiled, edit) +} + +func (n *TailCallStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *TailCallStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *TailCallStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Target != nil && do(n.Target) { + return true + } + return false +} +func (n *TailCallStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Target != nil { + n.Target = edit(n.Target).(*Name) + } +} + +func (n *TypeAssertExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *TypeAssertExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *TypeAssertExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + if n.Ntype != nil && do(n.Ntype) { + return true + } + return false +} +func (n *TypeAssertExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } + if n.Ntype != nil { + n.Ntype = edit(n.Ntype).(Ntype) + } +} + +func (n *TypeSwitchGuard) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *TypeSwitchGuard) copy() Node { + c := *n + return &c +} +func (n *TypeSwitchGuard) doChildren(do func(Node) bool) bool { + if n.Tag != nil && do(n.Tag) { + return true + } + if n.X != nil && do(n.X) { + return true + } + return false +} +func (n *TypeSwitchGuard) editChildren(edit func(Node) Node) { + if n.Tag != nil { + n.Tag = edit(n.Tag).(*Ident) + } + if n.X != nil { + n.X = edit(n.X).(Node) + } +} + +func (n *UnaryExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *UnaryExpr) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *UnaryExpr) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.X != nil && do(n.X) { + return true + } + return false +} +func (n *UnaryExpr) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.X != nil { + n.X = edit(n.X).(Node) + } +} + +func (n *typeNode) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *typeNode) copy() Node { + c := *n + return &c +} +func (n *typeNode) doChildren(do func(Node) bool) bool { + return false +} +func (n *typeNode) editChildren(edit func(Node) Node) { +} + +func copyCaseClauses(list []*CaseClause) []*CaseClause { + if list == nil { + return nil + } + c := make([]*CaseClause, len(list)) + copy(c, list) + return c +} +func doCaseClauses(list []*CaseClause, do func(Node) bool) bool { + for _, x := range list { + if x != nil && do(x) { + return true + } + } + return false +} +func editCaseClauses(list []*CaseClause, edit func(Node) Node) { + for i, x := range list { + if x != nil { + list[i] = edit(x).(*CaseClause) + } + } +} + +func copyCommClauses(list []*CommClause) []*CommClause { + if list == nil { + return nil + } + c := make([]*CommClause, len(list)) + copy(c, list) + return c +} +func doCommClauses(list []*CommClause, do func(Node) bool) bool { + for _, x := range list { + if x != nil && do(x) { + return true + } + } + return false +} +func editCommClauses(list []*CommClause, edit func(Node) Node) { + for i, x := range list { + if x != nil { + list[i] = edit(x).(*CommClause) + } + } +} + +func copyNames(list []*Name) []*Name { + if list == nil { + return nil + } + c := make([]*Name, len(list)) + copy(c, list) + return c +} +func doNames(list []*Name, do func(Node) bool) bool { + for _, x := range list { + if x != nil && do(x) { + return true + } + } + return false +} +func editNames(list []*Name, edit func(Node) Node) { + for i, x := range list { + if x != nil { + list[i] = edit(x).(*Name) + } + } +} + +func copyNodes(list []Node) []Node { + if list == nil { + return nil + } + c := make([]Node, len(list)) + copy(c, list) + return c +} +func doNodes(list []Node, do func(Node) bool) bool { + for _, x := range list { + if x != nil && do(x) { + return true + } + } + return false +} +func editNodes(list []Node, edit func(Node) Node) { + for i, x := range list { + if x != nil { + list[i] = edit(x).(Node) + } + } +} diff --git a/src/cmd/compile/internal/ir/op_string.go b/src/cmd/compile/internal/ir/op_string.go new file mode 100644 index 0000000000000000000000000000000000000000..405a0c6b3c985851c4dbbf68c5fd8990a61e06cf --- /dev/null +++ b/src/cmd/compile/internal/ir/op_string.go @@ -0,0 +1,177 @@ +// Code generated by "stringer -type=Op -trimprefix=O node.go"; DO NOT EDIT. + +package ir + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[OXXX-0] + _ = x[ONAME-1] + _ = x[ONONAME-2] + _ = x[OTYPE-3] + _ = x[OPACK-4] + _ = x[OLITERAL-5] + _ = x[ONIL-6] + _ = x[OADD-7] + _ = x[OSUB-8] + _ = x[OOR-9] + _ = x[OXOR-10] + _ = x[OADDSTR-11] + _ = x[OADDR-12] + _ = x[OANDAND-13] + _ = x[OAPPEND-14] + _ = x[OBYTES2STR-15] + _ = x[OBYTES2STRTMP-16] + _ = x[ORUNES2STR-17] + _ = x[OSTR2BYTES-18] + _ = x[OSTR2BYTESTMP-19] + _ = x[OSTR2RUNES-20] + _ = x[OSLICE2ARRPTR-21] + _ = x[OAS-22] + _ = x[OAS2-23] + _ = x[OAS2DOTTYPE-24] + _ = x[OAS2FUNC-25] + _ = x[OAS2MAPR-26] + _ = x[OAS2RECV-27] + _ = x[OASOP-28] + _ = x[OCALL-29] + _ = x[OCALLFUNC-30] + _ = x[OCALLMETH-31] + _ = x[OCALLINTER-32] + _ = x[OCALLPART-33] + _ = x[OCAP-34] + _ = x[OCLOSE-35] + _ = x[OCLOSURE-36] + _ = x[OCOMPLIT-37] + _ = x[OMAPLIT-38] + _ = x[OSTRUCTLIT-39] + _ = x[OARRAYLIT-40] + _ = x[OSLICELIT-41] + _ = x[OPTRLIT-42] + _ = x[OCONV-43] + _ = x[OCONVIFACE-44] + _ = x[OCONVNOP-45] + _ = x[OCOPY-46] + _ = x[ODCL-47] + _ = x[ODCLFUNC-48] + _ = x[ODCLCONST-49] + _ = x[ODCLTYPE-50] + _ = x[ODELETE-51] + _ = x[ODOT-52] + _ = x[ODOTPTR-53] + _ = x[ODOTMETH-54] + _ = x[ODOTINTER-55] + _ = x[OXDOT-56] + _ = x[ODOTTYPE-57] + _ = x[ODOTTYPE2-58] + _ = x[OEQ-59] + _ = x[ONE-60] + _ = x[OLT-61] + _ = x[OLE-62] + _ = x[OGE-63] + _ = x[OGT-64] + _ = x[ODEREF-65] + _ = x[OINDEX-66] + _ = x[OINDEXMAP-67] + _ = x[OKEY-68] + _ = x[OSTRUCTKEY-69] + _ = x[OLEN-70] + _ = x[OMAKE-71] + _ = x[OMAKECHAN-72] + _ = x[OMAKEMAP-73] + _ = x[OMAKESLICE-74] + _ = x[OMAKESLICECOPY-75] + _ = x[OMUL-76] + _ = x[ODIV-77] + _ = x[OMOD-78] + _ = x[OLSH-79] + _ = x[ORSH-80] + _ = x[OAND-81] + _ = x[OANDNOT-82] + _ = x[ONEW-83] + _ = x[ONOT-84] + _ = x[OBITNOT-85] + _ = x[OPLUS-86] + _ = x[ONEG-87] + _ = x[OOROR-88] + _ = x[OPANIC-89] + _ = x[OPRINT-90] + _ = x[OPRINTN-91] + _ = x[OPAREN-92] + _ = x[OSEND-93] + _ = x[OSLICE-94] + _ = x[OSLICEARR-95] + _ = x[OSLICESTR-96] + _ = x[OSLICE3-97] + _ = x[OSLICE3ARR-98] + _ = x[OSLICEHEADER-99] + _ = x[ORECOVER-100] + _ = x[ORECV-101] + _ = x[ORUNESTR-102] + _ = x[OSELRECV2-103] + _ = x[OIOTA-104] + _ = x[OREAL-105] + _ = x[OIMAG-106] + _ = x[OCOMPLEX-107] + _ = x[OALIGNOF-108] + _ = x[OOFFSETOF-109] + _ = x[OSIZEOF-110] + _ = x[OUNSAFEADD-111] + _ = x[OUNSAFESLICE-112] + _ = x[OMETHEXPR-113] + _ = x[OBLOCK-114] + _ = x[OBREAK-115] + _ = x[OCASE-116] + _ = x[OCONTINUE-117] + _ = x[ODEFER-118] + _ = x[OFALL-119] + _ = x[OFOR-120] + _ = x[OFORUNTIL-121] + _ = x[OGOTO-122] + _ = x[OIF-123] + _ = x[OLABEL-124] + _ = x[OGO-125] + _ = x[ORANGE-126] + _ = x[ORETURN-127] + _ = x[OSELECT-128] + _ = x[OSWITCH-129] + _ = x[OTYPESW-130] + _ = x[OFUNCINST-131] + _ = x[OTCHAN-132] + _ = x[OTMAP-133] + _ = x[OTSTRUCT-134] + _ = x[OTINTER-135] + _ = x[OTFUNC-136] + _ = x[OTARRAY-137] + _ = x[OTSLICE-138] + _ = x[OINLCALL-139] + _ = x[OEFACE-140] + _ = x[OITAB-141] + _ = x[OIDATA-142] + _ = x[OSPTR-143] + _ = x[OCFUNC-144] + _ = x[OCHECKNIL-145] + _ = x[OVARDEF-146] + _ = x[OVARKILL-147] + _ = x[OVARLIVE-148] + _ = x[ORESULT-149] + _ = x[OINLMARK-150] + _ = x[OLINKSYMOFFSET-151] + _ = x[OTAILCALL-152] + _ = x[OGETG-153] + _ = x[OEND-154] +} + +const _Op_name = "XXXNAMENONAMETYPEPACKLITERALNILADDSUBORXORADDSTRADDRANDANDAPPENDBYTES2STRBYTES2STRTMPRUNES2STRSTR2BYTESSTR2BYTESTMPSTR2RUNESSLICE2ARRPTRASAS2AS2DOTTYPEAS2FUNCAS2MAPRAS2RECVASOPCALLCALLFUNCCALLMETHCALLINTERCALLPARTCAPCLOSECLOSURECOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVNOPCOPYDCLDCLFUNCDCLCONSTDCLTYPEDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTDEREFINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMAKESLICECOPYMULDIVMODLSHRSHANDANDNOTNEWNOTBITNOTPLUSNEGORORPANICPRINTPRINTNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRSLICEHEADERRECOVERRECVRUNESTRSELRECV2IOTAREALIMAGCOMPLEXALIGNOFOFFSETOFSIZEOFUNSAFEADDUNSAFESLICEMETHEXPRBLOCKBREAKCASECONTINUEDEFERFALLFORFORUNTILGOTOIFLABELGORANGERETURNSELECTSWITCHTYPESWFUNCINSTTCHANTMAPTSTRUCTTINTERTFUNCTARRAYTSLICEINLCALLEFACEITABIDATASPTRCFUNCCHECKNILVARDEFVARKILLVARLIVERESULTINLMARKLINKSYMOFFSETTAILCALLGETGEND" + +var _Op_index = [...]uint16{0, 3, 7, 13, 17, 21, 28, 31, 34, 37, 39, 42, 48, 52, 58, 64, 73, 85, 94, 103, 115, 124, 136, 138, 141, 151, 158, 165, 172, 176, 180, 188, 196, 205, 213, 216, 221, 228, 235, 241, 250, 258, 266, 272, 276, 285, 292, 296, 299, 306, 314, 321, 327, 330, 336, 343, 351, 355, 362, 370, 372, 374, 376, 378, 380, 382, 387, 392, 400, 403, 412, 415, 419, 427, 434, 443, 456, 459, 462, 465, 468, 471, 474, 480, 483, 486, 492, 496, 499, 503, 508, 513, 519, 524, 528, 533, 541, 549, 555, 564, 575, 582, 586, 593, 601, 605, 609, 613, 620, 627, 635, 641, 650, 661, 669, 674, 679, 683, 691, 696, 700, 703, 711, 715, 717, 722, 724, 729, 735, 741, 747, 753, 761, 766, 770, 777, 783, 788, 794, 800, 807, 812, 816, 821, 825, 830, 838, 844, 851, 858, 864, 871, 884, 892, 896, 899} + +func (i Op) String() string { + if i >= Op(len(_Op_index)-1) { + return "Op(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Op_name[_Op_index[i]:_Op_index[i+1]] +} diff --git a/src/cmd/compile/internal/ir/package.go b/src/cmd/compile/internal/ir/package.go new file mode 100644 index 0000000000000000000000000000000000000000..e4b93d113eda9c89b44512e2e5957c977df15a03 --- /dev/null +++ b/src/cmd/compile/internal/ir/package.go @@ -0,0 +1,38 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import "cmd/compile/internal/types" + +// A Package holds information about the package being compiled. +type Package struct { + // Imports, listed in source order. + // See golang.org/issue/31636. + Imports []*types.Pkg + + // Init functions, listed in source order. + Inits []*Func + + // Top-level declarations. + Decls []Node + + // Extern (package global) declarations. + Externs []Node + + // Assembly function declarations. + Asms []*Name + + // Cgo directives. + CgoPragmas [][]string + + // Variables with //go:embed lines. + Embeds []*Name + + // Exported (or re-exported) symbols. + Exports []*Name + + // Map from function names of stencils to already-created stencils. + Stencils map[*types.Sym]*Func +} diff --git a/src/cmd/compile/internal/gc/scc.go b/src/cmd/compile/internal/ir/scc.go similarity index 74% rename from src/cmd/compile/internal/gc/scc.go rename to src/cmd/compile/internal/ir/scc.go index 5c7935aa87674dd1348d23fbdb838d2112daf665..83c6074170b31370e0e6c6e71f35e56533204d2b 100644 --- a/src/cmd/compile/internal/gc/scc.go +++ b/src/cmd/compile/internal/ir/scc.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package ir // Strongly connected components. // @@ -30,13 +30,13 @@ package gc // when analyzing a set of mutually recursive functions. type bottomUpVisitor struct { - analyze func([]*Node, bool) + analyze func([]*Func, bool) visitgen uint32 - nodeID map[*Node]uint32 - stack []*Node + nodeID map[*Func]uint32 + stack []*Func } -// visitBottomUp invokes analyze on the ODCLFUNC nodes listed in list. +// VisitFuncsBottomUp invokes analyze on the ODCLFUNC nodes listed in list. // It calls analyze with successive groups of functions, working from // the bottom of the call graph upward. Each time analyze is called with // a list of functions, every function on that list only calls other functions @@ -49,18 +49,21 @@ type bottomUpVisitor struct { // If recursive is false, the list consists of only a single function and its closures. // If recursive is true, the list may still contain only a single function, // if that function is itself recursive. -func visitBottomUp(list []*Node, analyze func(list []*Node, recursive bool)) { +func VisitFuncsBottomUp(list []Node, analyze func(list []*Func, recursive bool)) { var v bottomUpVisitor v.analyze = analyze - v.nodeID = make(map[*Node]uint32) + v.nodeID = make(map[*Func]uint32) for _, n := range list { - if n.Op == ODCLFUNC && !n.Func.IsHiddenClosure() { - v.visit(n) + if n.Op() == ODCLFUNC { + n := n.(*Func) + if !n.IsHiddenClosure() { + v.visit(n) + } } } } -func (v *bottomUpVisitor) visit(n *Node) uint32 { +func (v *bottomUpVisitor) visit(n *Func) uint32 { if id := v.nodeID[n]; id > 0 { // already visited return id @@ -73,42 +76,31 @@ func (v *bottomUpVisitor) visit(n *Node) uint32 { min := v.visitgen v.stack = append(v.stack, n) - inspectList(n.Nbody, func(n *Node) bool { - switch n.Op { - case ONAME: - if n.Class() == PFUNC { - if n.isMethodExpression() { - n = asNode(n.Type.Nname()) - } - if n != nil && n.Name.Defn != nil { - if m := v.visit(n.Name.Defn); m < min { - min = m - } - } + do := func(defn Node) { + if defn != nil { + if m := v.visit(defn.(*Func)); m < min { + min = m } - case ODOTMETH: - fn := asNode(n.Type.Nname()) - if fn != nil && fn.Op == ONAME && fn.Class() == PFUNC && fn.Name.Defn != nil { - if m := v.visit(fn.Name.Defn); m < min { - min = m - } + } + } + + Visit(n, func(n Node) { + switch n.Op() { + case ONAME: + if n := n.(*Name); n.Class == PFUNC { + do(n.Defn) } - case OCALLPART: - fn := asNode(callpartMethod(n).Type.Nname()) - if fn != nil && fn.Op == ONAME && fn.Class() == PFUNC && fn.Name.Defn != nil { - if m := v.visit(fn.Name.Defn); m < min { - min = m - } + case ODOTMETH, OCALLPART, OMETHEXPR: + if fn := MethodExprName(n); fn != nil { + do(fn.Defn) } case OCLOSURE: - if m := v.visit(n.Func.Closure); m < min { - min = m - } + n := n.(*ClosureExpr) + do(n.Func) } - return true }) - if (min == id || min == id+1) && !n.Func.IsHiddenClosure() { + if (min == id || min == id+1) && !n.IsHiddenClosure() { // This node is the root of a strongly connected component. // The original min passed to visitcodelist was v.nodeID[n]+1. diff --git a/src/cmd/compile/internal/gc/sizeof_test.go b/src/cmd/compile/internal/ir/sizeof_test.go similarity index 88% rename from src/cmd/compile/internal/gc/sizeof_test.go rename to src/cmd/compile/internal/ir/sizeof_test.go index ce4a216c2e2daa1090910c62eb2dd7b4cda4e2d5..a4421fcf531503dbac132e906cc7c55f9a429d85 100644 --- a/src/cmd/compile/internal/gc/sizeof_test.go +++ b/src/cmd/compile/internal/ir/sizeof_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package ir import ( "reflect" @@ -20,10 +20,8 @@ func TestSizeof(t *testing.T) { _32bit uintptr // size on 32bit platforms _64bit uintptr // size on 64bit platforms }{ - {Func{}, 124, 224}, - {Name{}, 32, 56}, - {Param{}, 24, 48}, - {Node{}, 76, 128}, + {Func{}, 192, 328}, + {Name{}, 112, 200}, } for _, tt := range tests { diff --git a/src/cmd/compile/internal/ir/stmt.go b/src/cmd/compile/internal/ir/stmt.go new file mode 100644 index 0000000000000000000000000000000000000000..8115012f97852c030d59b0e74bdd0cad17825916 --- /dev/null +++ b/src/cmd/compile/internal/ir/stmt.go @@ -0,0 +1,414 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +// A Decl is a declaration of a const, type, or var. (A declared func is a Func.) +type Decl struct { + miniNode + X *Name // the thing being declared +} + +func NewDecl(pos src.XPos, op Op, x *Name) *Decl { + n := &Decl{X: x} + n.pos = pos + switch op { + default: + panic("invalid Decl op " + op.String()) + case ODCL, ODCLCONST, ODCLTYPE: + n.op = op + } + return n +} + +func (*Decl) isStmt() {} + +// A Stmt is a Node that can appear as a statement. +// This includes statement-like expressions such as f(). +// +// (It's possible it should include <-c, but that would require +// splitting ORECV out of UnaryExpr, which hasn't yet been +// necessary. Maybe instead we will introduce ExprStmt at +// some point.) +type Stmt interface { + Node + isStmt() +} + +// A miniStmt is a miniNode with extra fields common to statements. +type miniStmt struct { + miniNode + init Nodes +} + +func (*miniStmt) isStmt() {} + +func (n *miniStmt) Init() Nodes { return n.init } +func (n *miniStmt) SetInit(x Nodes) { n.init = x } +func (n *miniStmt) PtrInit() *Nodes { return &n.init } + +// An AssignListStmt is an assignment statement with +// more than one item on at least one side: Lhs = Rhs. +// If Def is true, the assignment is a :=. +type AssignListStmt struct { + miniStmt + Lhs Nodes + Def bool + Rhs Nodes +} + +func NewAssignListStmt(pos src.XPos, op Op, lhs, rhs []Node) *AssignListStmt { + n := &AssignListStmt{} + n.pos = pos + n.SetOp(op) + n.Lhs = lhs + n.Rhs = rhs + return n +} + +func (n *AssignListStmt) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OAS2, OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV, OSELRECV2: + n.op = op + } +} + +// An AssignStmt is a simple assignment statement: X = Y. +// If Def is true, the assignment is a :=. +type AssignStmt struct { + miniStmt + X Node + Def bool + Y Node +} + +func NewAssignStmt(pos src.XPos, x, y Node) *AssignStmt { + n := &AssignStmt{X: x, Y: y} + n.pos = pos + n.op = OAS + return n +} + +func (n *AssignStmt) SetOp(op Op) { + switch op { + default: + panic(n.no("SetOp " + op.String())) + case OAS: + n.op = op + } +} + +// An AssignOpStmt is an AsOp= assignment statement: X AsOp= Y. +type AssignOpStmt struct { + miniStmt + X Node + AsOp Op // OADD etc + Y Node + IncDec bool // actually ++ or -- +} + +func NewAssignOpStmt(pos src.XPos, asOp Op, x, y Node) *AssignOpStmt { + n := &AssignOpStmt{AsOp: asOp, X: x, Y: y} + n.pos = pos + n.op = OASOP + return n +} + +// A BlockStmt is a block: { List }. +type BlockStmt struct { + miniStmt + List Nodes +} + +func NewBlockStmt(pos src.XPos, list []Node) *BlockStmt { + n := &BlockStmt{} + n.pos = pos + if !pos.IsKnown() { + n.pos = base.Pos + if len(list) > 0 { + n.pos = list[0].Pos() + } + } + n.op = OBLOCK + n.List = list + return n +} + +// A BranchStmt is a break, continue, fallthrough, or goto statement. +type BranchStmt struct { + miniStmt + Label *types.Sym // label if present +} + +func NewBranchStmt(pos src.XPos, op Op, label *types.Sym) *BranchStmt { + switch op { + case OBREAK, OCONTINUE, OFALL, OGOTO: + // ok + default: + panic("NewBranch " + op.String()) + } + n := &BranchStmt{Label: label} + n.pos = pos + n.op = op + return n +} + +func (n *BranchStmt) Sym() *types.Sym { return n.Label } + +// A CaseClause is a case statement in a switch or select: case List: Body. +type CaseClause struct { + miniStmt + Var *Name // declared variable for this case in type switch + List Nodes // list of expressions for switch, early select + Body Nodes +} + +func NewCaseStmt(pos src.XPos, list, body []Node) *CaseClause { + n := &CaseClause{List: list, Body: body} + n.pos = pos + n.op = OCASE + return n +} + +type CommClause struct { + miniStmt + Comm Node // communication case + Body Nodes +} + +func NewCommStmt(pos src.XPos, comm Node, body []Node) *CommClause { + n := &CommClause{Comm: comm, Body: body} + n.pos = pos + n.op = OCASE + return n +} + +// A ForStmt is a non-range for loop: for Init; Cond; Post { Body } +// Op can be OFOR or OFORUNTIL (!Cond). +type ForStmt struct { + miniStmt + Label *types.Sym + Cond Node + Late Nodes + Post Node + Body Nodes + HasBreak bool +} + +func NewForStmt(pos src.XPos, init Node, cond, post Node, body []Node) *ForStmt { + n := &ForStmt{Cond: cond, Post: post} + n.pos = pos + n.op = OFOR + if init != nil { + n.init = []Node{init} + } + n.Body = body + return n +} + +func (n *ForStmt) SetOp(op Op) { + if op != OFOR && op != OFORUNTIL { + panic(n.no("SetOp " + op.String())) + } + n.op = op +} + +// A GoDeferStmt is a go or defer statement: go Call / defer Call. +// +// The two opcodes use a single syntax because the implementations +// are very similar: both are concerned with saving Call and running it +// in a different context (a separate goroutine or a later time). +type GoDeferStmt struct { + miniStmt + Call Node +} + +func NewGoDeferStmt(pos src.XPos, op Op, call Node) *GoDeferStmt { + n := &GoDeferStmt{Call: call} + n.pos = pos + switch op { + case ODEFER, OGO: + n.op = op + default: + panic("NewGoDeferStmt " + op.String()) + } + return n +} + +// A IfStmt is a return statement: if Init; Cond { Then } else { Else }. +type IfStmt struct { + miniStmt + Cond Node + Body Nodes + Else Nodes + Likely bool // code layout hint +} + +func NewIfStmt(pos src.XPos, cond Node, body, els []Node) *IfStmt { + n := &IfStmt{Cond: cond} + n.pos = pos + n.op = OIF + n.Body = body + n.Else = els + return n +} + +// An InlineMarkStmt is a marker placed just before an inlined body. +type InlineMarkStmt struct { + miniStmt + Index int64 +} + +func NewInlineMarkStmt(pos src.XPos, index int64) *InlineMarkStmt { + n := &InlineMarkStmt{Index: index} + n.pos = pos + n.op = OINLMARK + return n +} + +func (n *InlineMarkStmt) Offset() int64 { return n.Index } +func (n *InlineMarkStmt) SetOffset(x int64) { n.Index = x } + +// A LabelStmt is a label statement (just the label, not including the statement it labels). +type LabelStmt struct { + miniStmt + Label *types.Sym // "Label:" +} + +func NewLabelStmt(pos src.XPos, label *types.Sym) *LabelStmt { + n := &LabelStmt{Label: label} + n.pos = pos + n.op = OLABEL + return n +} + +func (n *LabelStmt) Sym() *types.Sym { return n.Label } + +// A RangeStmt is a range loop: for Key, Value = range X { Body } +type RangeStmt struct { + miniStmt + Label *types.Sym + Def bool + X Node + Key Node + Value Node + Body Nodes + HasBreak bool + Prealloc *Name +} + +func NewRangeStmt(pos src.XPos, key, value, x Node, body []Node) *RangeStmt { + n := &RangeStmt{X: x, Key: key, Value: value} + n.pos = pos + n.op = ORANGE + n.Body = body + return n +} + +// A ReturnStmt is a return statement. +type ReturnStmt struct { + miniStmt + origNode // for typecheckargs rewrite + Results Nodes // return list +} + +func NewReturnStmt(pos src.XPos, results []Node) *ReturnStmt { + n := &ReturnStmt{} + n.pos = pos + n.op = ORETURN + n.orig = n + n.Results = results + return n +} + +// A SelectStmt is a block: { Cases }. +type SelectStmt struct { + miniStmt + Label *types.Sym + Cases []*CommClause + HasBreak bool + + // TODO(rsc): Instead of recording here, replace with a block? + Compiled Nodes // compiled form, after walkSwitch +} + +func NewSelectStmt(pos src.XPos, cases []*CommClause) *SelectStmt { + n := &SelectStmt{Cases: cases} + n.pos = pos + n.op = OSELECT + return n +} + +// A SendStmt is a send statement: X <- Y. +type SendStmt struct { + miniStmt + Chan Node + Value Node +} + +func NewSendStmt(pos src.XPos, ch, value Node) *SendStmt { + n := &SendStmt{Chan: ch, Value: value} + n.pos = pos + n.op = OSEND + return n +} + +// A SwitchStmt is a switch statement: switch Init; Expr { Cases }. +type SwitchStmt struct { + miniStmt + Tag Node + Cases []*CaseClause + Label *types.Sym + HasBreak bool + + // TODO(rsc): Instead of recording here, replace with a block? + Compiled Nodes // compiled form, after walkSwitch +} + +func NewSwitchStmt(pos src.XPos, tag Node, cases []*CaseClause) *SwitchStmt { + n := &SwitchStmt{Tag: tag, Cases: cases} + n.pos = pos + n.op = OSWITCH + return n +} + +// A TailCallStmt is a tail call statement, which is used for back-end +// code generation to jump directly to another function entirely. +type TailCallStmt struct { + miniStmt + Target *Name +} + +func NewTailCallStmt(pos src.XPos, target *Name) *TailCallStmt { + if target.Op() != ONAME || target.Class != PFUNC { + base.FatalfAt(pos, "tail call to non-func %v", target) + } + n := &TailCallStmt{Target: target} + n.pos = pos + n.op = OTAILCALL + return n +} + +// A TypeSwitchGuard is the [Name :=] X.(type) in a type switch. +type TypeSwitchGuard struct { + miniNode + Tag *Ident + X Node + Used bool +} + +func NewTypeSwitchGuard(pos src.XPos, tag *Ident, x Node) *TypeSwitchGuard { + n := &TypeSwitchGuard{Tag: tag, X: x} + n.pos = pos + n.op = OTYPESW + return n +} diff --git a/src/cmd/compile/internal/ir/symtab.go b/src/cmd/compile/internal/ir/symtab.go new file mode 100644 index 0000000000000000000000000000000000000000..61727fb1c4b004d52e4942631e200ce837f791f4 --- /dev/null +++ b/src/cmd/compile/internal/ir/symtab.go @@ -0,0 +1,72 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "cmd/compile/internal/types" + "cmd/internal/obj" +) + +// Syms holds known symbols. +var Syms struct { + AssertE2I *obj.LSym + AssertE2I2 *obj.LSym + AssertI2I *obj.LSym + AssertI2I2 *obj.LSym + Deferproc *obj.LSym + DeferprocStack *obj.LSym + Deferreturn *obj.LSym + Duffcopy *obj.LSym + Duffzero *obj.LSym + GCWriteBarrier *obj.LSym + Goschedguarded *obj.LSym + Growslice *obj.LSym + Msanread *obj.LSym + Msanwrite *obj.LSym + Msanmove *obj.LSym + Newobject *obj.LSym + Newproc *obj.LSym + Panicdivide *obj.LSym + Panicshift *obj.LSym + PanicdottypeE *obj.LSym + PanicdottypeI *obj.LSym + Panicnildottype *obj.LSym + Panicoverflow *obj.LSym + Raceread *obj.LSym + Racereadrange *obj.LSym + Racewrite *obj.LSym + Racewriterange *obj.LSym + // Wasm + SigPanic *obj.LSym + Staticuint64s *obj.LSym + Typedmemclr *obj.LSym + Typedmemmove *obj.LSym + Udiv *obj.LSym + WriteBarrier *obj.LSym + Zerobase *obj.LSym + ARM64HasATOMICS *obj.LSym + ARMHasVFPv4 *obj.LSym + X86HasFMA *obj.LSym + X86HasPOPCNT *obj.LSym + X86HasSSE41 *obj.LSym + // Wasm + WasmDiv *obj.LSym + // Wasm + WasmMove *obj.LSym + // Wasm + WasmZero *obj.LSym + // Wasm + WasmTruncS *obj.LSym + // Wasm + WasmTruncU *obj.LSym +} + +// Pkgs holds known packages. +var Pkgs struct { + Go *types.Pkg + Itab *types.Pkg + Runtime *types.Pkg + Unsafe *types.Pkg +} diff --git a/src/cmd/compile/internal/ir/type.go b/src/cmd/compile/internal/ir/type.go new file mode 100644 index 0000000000000000000000000000000000000000..a903ea8cd45543faebe7c9bce30c428f318b6a1b --- /dev/null +++ b/src/cmd/compile/internal/ir/type.go @@ -0,0 +1,310 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/types" + "cmd/internal/src" + "fmt" +) + +// Nodes that represent the syntax of a type before type-checking. +// After type-checking, they serve only as shells around a *types.Type. +// Calling TypeNode converts a *types.Type to a Node shell. + +// An Ntype is a Node that syntactically looks like a type. +// It can be the raw syntax for a type before typechecking, +// or it can be an OTYPE with Type() set to a *types.Type. +// Note that syntax doesn't guarantee it's a type: an expression +// like *fmt is an Ntype (we don't know whether names are types yet), +// but at least 1+1 is not an Ntype. +type Ntype interface { + Node + CanBeNtype() +} + +// A miniType is a minimal type syntax Node implementation, +// to be embedded as the first field in a larger node implementation. +type miniType struct { + miniNode + typ *types.Type +} + +func (*miniType) CanBeNtype() {} + +func (n *miniType) Type() *types.Type { return n.typ } + +// setOTYPE changes n to be an OTYPE node returning t. +// Rewriting the node in place this way should not be strictly +// necessary (we should be able to update the uses with +// proper OTYPE nodes), but it's mostly harmless and easy +// to keep doing for now. +// +// setOTYPE also records t.Nod = self if t.Nod is not already set. +// (Some types are shared by multiple OTYPE nodes, so only +// the first such node is used as t.Nod.) +func (n *miniType) setOTYPE(t *types.Type, self Ntype) { + if n.typ != nil { + panic(n.op.String() + " SetType: type already set") + } + n.op = OTYPE + n.typ = t + t.SetNod(self) +} + +func (n *miniType) Sym() *types.Sym { return nil } // for Format OTYPE +func (n *miniType) Implicit() bool { return false } // for Format OTYPE + +// A ChanType represents a chan Elem syntax with the direction Dir. +type ChanType struct { + miniType + Elem Ntype + Dir types.ChanDir +} + +func NewChanType(pos src.XPos, elem Ntype, dir types.ChanDir) *ChanType { + n := &ChanType{Elem: elem, Dir: dir} + n.op = OTCHAN + n.pos = pos + return n +} + +func (n *ChanType) SetOTYPE(t *types.Type) { + n.setOTYPE(t, n) + n.Elem = nil +} + +// A MapType represents a map[Key]Value type syntax. +type MapType struct { + miniType + Key Ntype + Elem Ntype +} + +func NewMapType(pos src.XPos, key, elem Ntype) *MapType { + n := &MapType{Key: key, Elem: elem} + n.op = OTMAP + n.pos = pos + return n +} + +func (n *MapType) SetOTYPE(t *types.Type) { + n.setOTYPE(t, n) + n.Key = nil + n.Elem = nil +} + +// A StructType represents a struct { ... } type syntax. +type StructType struct { + miniType + Fields []*Field +} + +func NewStructType(pos src.XPos, fields []*Field) *StructType { + n := &StructType{Fields: fields} + n.op = OTSTRUCT + n.pos = pos + return n +} + +func (n *StructType) SetOTYPE(t *types.Type) { + n.setOTYPE(t, n) + n.Fields = nil +} + +// An InterfaceType represents a struct { ... } type syntax. +type InterfaceType struct { + miniType + Methods []*Field +} + +func NewInterfaceType(pos src.XPos, methods []*Field) *InterfaceType { + n := &InterfaceType{Methods: methods} + n.op = OTINTER + n.pos = pos + return n +} + +func (n *InterfaceType) SetOTYPE(t *types.Type) { + n.setOTYPE(t, n) + n.Methods = nil +} + +// A FuncType represents a func(Args) Results type syntax. +type FuncType struct { + miniType + Recv *Field + Params []*Field + Results []*Field +} + +func NewFuncType(pos src.XPos, rcvr *Field, args, results []*Field) *FuncType { + n := &FuncType{Recv: rcvr, Params: args, Results: results} + n.op = OTFUNC + n.pos = pos + return n +} + +func (n *FuncType) SetOTYPE(t *types.Type) { + n.setOTYPE(t, n) + n.Recv = nil + n.Params = nil + n.Results = nil +} + +// A Field is a declared struct field, interface method, or function argument. +// It is not a Node. +type Field struct { + Pos src.XPos + Sym *types.Sym + Ntype Ntype + Type *types.Type + Embedded bool + IsDDD bool + Note string + Decl *Name +} + +func NewField(pos src.XPos, sym *types.Sym, ntyp Ntype, typ *types.Type) *Field { + return &Field{Pos: pos, Sym: sym, Ntype: ntyp, Type: typ} +} + +func (f *Field) String() string { + var typ string + if f.Type != nil { + typ = fmt.Sprint(f.Type) + } else { + typ = fmt.Sprint(f.Ntype) + } + if f.Sym != nil { + return fmt.Sprintf("%v %v", f.Sym, typ) + } + return typ +} + +// TODO(mdempsky): Make Field a Node again so these can be generated? +// Fields are Nodes in go/ast and cmd/compile/internal/syntax. + +func copyField(f *Field) *Field { + if f == nil { + return nil + } + c := *f + return &c +} +func doField(f *Field, do func(Node) bool) bool { + if f == nil { + return false + } + if f.Decl != nil && do(f.Decl) { + return true + } + if f.Ntype != nil && do(f.Ntype) { + return true + } + return false +} +func editField(f *Field, edit func(Node) Node) { + if f == nil { + return + } + if f.Decl != nil { + f.Decl = edit(f.Decl).(*Name) + } + if f.Ntype != nil { + f.Ntype = edit(f.Ntype).(Ntype) + } +} + +func copyFields(list []*Field) []*Field { + out := make([]*Field, len(list)) + for i, f := range list { + out[i] = copyField(f) + } + return out +} +func doFields(list []*Field, do func(Node) bool) bool { + for _, x := range list { + if doField(x, do) { + return true + } + } + return false +} +func editFields(list []*Field, edit func(Node) Node) { + for _, f := range list { + editField(f, edit) + } +} + +// A SliceType represents a []Elem type syntax. +// If DDD is true, it's the ...Elem at the end of a function list. +type SliceType struct { + miniType + Elem Ntype + DDD bool +} + +func NewSliceType(pos src.XPos, elem Ntype) *SliceType { + n := &SliceType{Elem: elem} + n.op = OTSLICE + n.pos = pos + return n +} + +func (n *SliceType) SetOTYPE(t *types.Type) { + n.setOTYPE(t, n) + n.Elem = nil +} + +// An ArrayType represents a [Len]Elem type syntax. +// If Len is nil, the type is a [...]Elem in an array literal. +type ArrayType struct { + miniType + Len Node + Elem Ntype +} + +func NewArrayType(pos src.XPos, len Node, elem Ntype) *ArrayType { + n := &ArrayType{Len: len, Elem: elem} + n.op = OTARRAY + n.pos = pos + return n +} + +func (n *ArrayType) SetOTYPE(t *types.Type) { + n.setOTYPE(t, n) + n.Len = nil + n.Elem = nil +} + +// A typeNode is a Node wrapper for type t. +type typeNode struct { + miniNode + typ *types.Type +} + +func newTypeNode(pos src.XPos, typ *types.Type) *typeNode { + n := &typeNode{typ: typ} + n.pos = pos + n.op = OTYPE + return n +} + +func (n *typeNode) Type() *types.Type { return n.typ } +func (n *typeNode) Sym() *types.Sym { return n.typ.Sym() } +func (n *typeNode) CanBeNtype() {} + +// TypeNode returns the Node representing the type t. +func TypeNode(t *types.Type) Ntype { + if n := t.Obj(); n != nil { + if n.Type() != t { + base.Fatalf("type skew: %v has type %v, but expected %v", n, n.Type(), t) + } + return n.(Ntype) + } + return newTypeNode(src.NoXPos, t) +} diff --git a/src/cmd/compile/internal/ir/val.go b/src/cmd/compile/internal/ir/val.go new file mode 100644 index 0000000000000000000000000000000000000000..03c320e205dbfdf8b6b46eb7d3d9adcc8295c46d --- /dev/null +++ b/src/cmd/compile/internal/ir/val.go @@ -0,0 +1,171 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "go/constant" + "math" + + "cmd/compile/internal/base" + "cmd/compile/internal/types" +) + +func ConstType(n Node) constant.Kind { + if n == nil || n.Op() != OLITERAL { + return constant.Unknown + } + return n.Val().Kind() +} + +// ConstValue returns the constant value stored in n as an interface{}. +// It returns int64s for ints and runes, float64s for floats, +// and complex128s for complex values. +func ConstValue(n Node) interface{} { + switch v := n.Val(); v.Kind() { + default: + base.Fatalf("unexpected constant: %v", v) + panic("unreachable") + case constant.Bool: + return constant.BoolVal(v) + case constant.String: + return constant.StringVal(v) + case constant.Int: + return IntVal(n.Type(), v) + case constant.Float: + return Float64Val(v) + case constant.Complex: + return complex(Float64Val(constant.Real(v)), Float64Val(constant.Imag(v))) + } +} + +// IntVal returns v converted to int64. +// Note: if t is uint64, very large values will be converted to negative int64. +func IntVal(t *types.Type, v constant.Value) int64 { + if t.IsUnsigned() { + if x, ok := constant.Uint64Val(v); ok { + return int64(x) + } + } else { + if x, ok := constant.Int64Val(v); ok { + return x + } + } + base.Fatalf("%v out of range for %v", v, t) + panic("unreachable") +} + +func Float64Val(v constant.Value) float64 { + if x, _ := constant.Float64Val(v); !math.IsInf(x, 0) { + return x + 0 // avoid -0 (should not be needed, but be conservative) + } + base.Fatalf("bad float64 value: %v", v) + panic("unreachable") +} + +func AssertValidTypeForConst(t *types.Type, v constant.Value) { + if !ValidTypeForConst(t, v) { + base.Fatalf("%v does not represent %v", t, v) + } +} + +func ValidTypeForConst(t *types.Type, v constant.Value) bool { + switch v.Kind() { + case constant.Unknown: + return OKForConst[t.Kind()] + case constant.Bool: + return t.IsBoolean() + case constant.String: + return t.IsString() + case constant.Int: + return t.IsInteger() + case constant.Float: + return t.IsFloat() + case constant.Complex: + return t.IsComplex() + } + + base.Fatalf("unexpected constant kind: %v", v) + panic("unreachable") +} + +// NewLiteral returns a new untyped constant with value v. +func NewLiteral(v constant.Value) Node { + return NewBasicLit(base.Pos, v) +} + +func idealType(ct constant.Kind) *types.Type { + switch ct { + case constant.String: + return types.UntypedString + case constant.Bool: + return types.UntypedBool + case constant.Int: + return types.UntypedInt + case constant.Float: + return types.UntypedFloat + case constant.Complex: + return types.UntypedComplex + } + base.Fatalf("unexpected Ctype: %v", ct) + return nil +} + +var OKForConst [types.NTYPE]bool + +// CanInt64 reports whether it is safe to call Int64Val() on n. +func CanInt64(n Node) bool { + if !IsConst(n, constant.Int) { + return false + } + + // if the value inside n cannot be represented as an int64, the + // return value of Int64 is undefined + _, ok := constant.Int64Val(n.Val()) + return ok +} + +// Int64Val returns n as an int64. +// n must be an integer or rune constant. +func Int64Val(n Node) int64 { + if !IsConst(n, constant.Int) { + base.Fatalf("Int64Val(%v)", n) + } + x, ok := constant.Int64Val(n.Val()) + if !ok { + base.Fatalf("Int64Val(%v)", n) + } + return x +} + +// Uint64Val returns n as an uint64. +// n must be an integer or rune constant. +func Uint64Val(n Node) uint64 { + if !IsConst(n, constant.Int) { + base.Fatalf("Uint64Val(%v)", n) + } + x, ok := constant.Uint64Val(n.Val()) + if !ok { + base.Fatalf("Uint64Val(%v)", n) + } + return x +} + +// BoolVal returns n as a bool. +// n must be a boolean constant. +func BoolVal(n Node) bool { + if !IsConst(n, constant.Bool) { + base.Fatalf("BoolVal(%v)", n) + } + return constant.BoolVal(n.Val()) +} + +// StringVal returns the value of a literal string Node as a string. +// n must be a string constant. +func StringVal(n Node) string { + if !IsConst(n, constant.String) { + base.Fatalf("StringVal(%v)", n) + } + return constant.StringVal(n.Val()) +} diff --git a/src/cmd/compile/internal/ir/visit.go b/src/cmd/compile/internal/ir/visit.go new file mode 100644 index 0000000000000000000000000000000000000000..e4aeae352209a7c5b6fe31aa263fc4f598cf3334 --- /dev/null +++ b/src/cmd/compile/internal/ir/visit.go @@ -0,0 +1,186 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// IR visitors for walking the IR tree. +// +// The lowest level helpers are DoChildren and EditChildren, which +// nodes help implement and provide control over whether and when +// recursion happens during the walk of the IR. +// +// Although these are both useful directly, two simpler patterns +// are fairly common and also provided: Visit and Any. + +package ir + +// DoChildren calls do(x) on each of n's non-nil child nodes x. +// If any call returns true, DoChildren stops and returns true. +// Otherwise, DoChildren returns false. +// +// Note that DoChildren(n, do) only calls do(x) for n's immediate children. +// If x's children should be processed, then do(x) must call DoChildren(x, do). +// +// DoChildren allows constructing general traversals of the IR graph +// that can stop early if needed. The most general usage is: +// +// var do func(ir.Node) bool +// do = func(x ir.Node) bool { +// ... processing BEFORE visiting children ... +// if ... should visit children ... { +// ir.DoChildren(x, do) +// ... processing AFTER visiting children ... +// } +// if ... should stop parent DoChildren call from visiting siblings ... { +// return true +// } +// return false +// } +// do(root) +// +// Since DoChildren does not return true itself, if the do function +// never wants to stop the traversal, it can assume that DoChildren +// itself will always return false, simplifying to: +// +// var do func(ir.Node) bool +// do = func(x ir.Node) bool { +// ... processing BEFORE visiting children ... +// if ... should visit children ... { +// ir.DoChildren(x, do) +// } +// ... processing AFTER visiting children ... +// return false +// } +// do(root) +// +// The Visit function illustrates a further simplification of the pattern, +// only processing before visiting children and never stopping: +// +// func Visit(n ir.Node, visit func(ir.Node)) { +// if n == nil { +// return +// } +// var do func(ir.Node) bool +// do = func(x ir.Node) bool { +// visit(x) +// return ir.DoChildren(x, do) +// } +// do(n) +// } +// +// The Any function illustrates a different simplification of the pattern, +// visiting each node and then its children, recursively, until finding +// a node x for which cond(x) returns true, at which point the entire +// traversal stops and returns true. +// +// func Any(n ir.Node, cond(ir.Node) bool) bool { +// if n == nil { +// return false +// } +// var do func(ir.Node) bool +// do = func(x ir.Node) bool { +// return cond(x) || ir.DoChildren(x, do) +// } +// return do(n) +// } +// +// Visit and Any are presented above as examples of how to use +// DoChildren effectively, but of course, usage that fits within the +// simplifications captured by Visit or Any will be best served +// by directly calling the ones provided by this package. +func DoChildren(n Node, do func(Node) bool) bool { + if n == nil { + return false + } + return n.doChildren(do) +} + +// Visit visits each non-nil node x in the IR tree rooted at n +// in a depth-first preorder traversal, calling visit on each node visited. +func Visit(n Node, visit func(Node)) { + if n == nil { + return + } + var do func(Node) bool + do = func(x Node) bool { + visit(x) + return DoChildren(x, do) + } + do(n) +} + +// VisitList calls Visit(x, visit) for each node x in the list. +func VisitList(list Nodes, visit func(Node)) { + for _, x := range list { + Visit(x, visit) + } +} + +// Any looks for a non-nil node x in the IR tree rooted at n +// for which cond(x) returns true. +// Any considers nodes in a depth-first, preorder traversal. +// When Any finds a node x such that cond(x) is true, +// Any ends the traversal and returns true immediately. +// Otherwise Any returns false after completing the entire traversal. +func Any(n Node, cond func(Node) bool) bool { + if n == nil { + return false + } + var do func(Node) bool + do = func(x Node) bool { + return cond(x) || DoChildren(x, do) + } + return do(n) +} + +// AnyList calls Any(x, cond) for each node x in the list, in order. +// If any call returns true, AnyList stops and returns true. +// Otherwise, AnyList returns false after calling Any(x, cond) +// for every x in the list. +func AnyList(list Nodes, cond func(Node) bool) bool { + for _, x := range list { + if Any(x, cond) { + return true + } + } + return false +} + +// EditChildren edits the child nodes of n, replacing each child x with edit(x). +// +// Note that EditChildren(n, edit) only calls edit(x) for n's immediate children. +// If x's children should be processed, then edit(x) must call EditChildren(x, edit). +// +// EditChildren allows constructing general editing passes of the IR graph. +// The most general usage is: +// +// var edit func(ir.Node) ir.Node +// edit = func(x ir.Node) ir.Node { +// ... processing BEFORE editing children ... +// if ... should edit children ... { +// EditChildren(x, edit) +// ... processing AFTER editing children ... +// } +// ... return x ... +// } +// n = edit(n) +// +// EditChildren edits the node in place. To edit a copy, call Copy first. +// As an example, a simple deep copy implementation would be: +// +// func deepCopy(n ir.Node) ir.Node { +// var edit func(ir.Node) ir.Node +// edit = func(x ir.Node) ir.Node { +// x = ir.Copy(x) +// ir.EditChildren(x, edit) +// return x +// } +// return edit(n) +// } +// +// Of course, in this case it is better to call ir.DeepCopy than to build one anew. +func EditChildren(n Node, edit func(Node) Node) { + if n == nil { + return + } + n.editChildren(edit) +} diff --git a/src/cmd/compile/internal/liveness/bvset.go b/src/cmd/compile/internal/liveness/bvset.go new file mode 100644 index 0000000000000000000000000000000000000000..3431f54ede84e09bec45272d044247e115eb59d2 --- /dev/null +++ b/src/cmd/compile/internal/liveness/bvset.go @@ -0,0 +1,97 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package liveness + +import "cmd/compile/internal/bitvec" + +// FNV-1 hash function constants. +const ( + h0 = 2166136261 + hp = 16777619 +) + +// bvecSet is a set of bvecs, in initial insertion order. +type bvecSet struct { + index []int // hash -> uniq index. -1 indicates empty slot. + uniq []bitvec.BitVec // unique bvecs, in insertion order +} + +func (m *bvecSet) grow() { + // Allocate new index. + n := len(m.index) * 2 + if n == 0 { + n = 32 + } + newIndex := make([]int, n) + for i := range newIndex { + newIndex[i] = -1 + } + + // Rehash into newIndex. + for i, bv := range m.uniq { + h := hashbitmap(h0, bv) % uint32(len(newIndex)) + for { + j := newIndex[h] + if j < 0 { + newIndex[h] = i + break + } + h++ + if h == uint32(len(newIndex)) { + h = 0 + } + } + } + m.index = newIndex +} + +// add adds bv to the set and returns its index in m.extractUnique. +// The caller must not modify bv after this. +func (m *bvecSet) add(bv bitvec.BitVec) int { + if len(m.uniq)*4 >= len(m.index) { + m.grow() + } + + index := m.index + h := hashbitmap(h0, bv) % uint32(len(index)) + for { + j := index[h] + if j < 0 { + // New bvec. + index[h] = len(m.uniq) + m.uniq = append(m.uniq, bv) + return len(m.uniq) - 1 + } + jlive := m.uniq[j] + if bv.Eq(jlive) { + // Existing bvec. + return j + } + + h++ + if h == uint32(len(index)) { + h = 0 + } + } +} + +// extractUnique returns this slice of unique bit vectors in m, as +// indexed by the result of bvecSet.add. +func (m *bvecSet) extractUnique() []bitvec.BitVec { + return m.uniq +} + +func hashbitmap(h uint32, bv bitvec.BitVec) uint32 { + n := int((bv.N + 31) / 32) + for i := 0; i < n; i++ { + w := bv.B[i] + h = (h * hp) ^ (w & 0xff) + h = (h * hp) ^ ((w >> 8) & 0xff) + h = (h * hp) ^ ((w >> 16) & 0xff) + h = (h * hp) ^ ((w >> 24) & 0xff) + } + + return h +} diff --git a/src/cmd/compile/internal/gc/plive.go b/src/cmd/compile/internal/liveness/plive.go similarity index 60% rename from src/cmd/compile/internal/gc/plive.go rename to src/cmd/compile/internal/liveness/plive.go index a48173e0d65bfec00ce5bfdc78ae8de5a26ba775..f5c2ef7709e515c376cc181c8e338bd51a1b33d8 100644 --- a/src/cmd/compile/internal/gc/plive.go +++ b/src/cmd/compile/internal/liveness/plive.go @@ -12,16 +12,28 @@ // // Each level includes the earlier output as well. -package gc +package liveness import ( + "crypto/md5" + "crypto/sha1" + "fmt" + "os" + "sort" + "strings" + + "cmd/compile/internal/abi" + "cmd/compile/internal/base" + "cmd/compile/internal/bitvec" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/reflectdata" "cmd/compile/internal/ssa" + "cmd/compile/internal/typebits" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/objabi" - "crypto/md5" - "fmt" - "strings" + "cmd/internal/src" ) // OpVarDef is an annotation for the liveness analysis, marking a place @@ -79,117 +91,95 @@ import ( // so the compiler can allocate two temps to the same location. Here it's now // useless, since the implementation of stack objects. -// BlockEffects summarizes the liveness effects on an SSA block. -type BlockEffects struct { +// blockEffects summarizes the liveness effects on an SSA block. +type blockEffects struct { // Computed during Liveness.prologue using only the content of // individual blocks: // // uevar: upward exposed variables (used before set in block) // varkill: killed variables (set in block) - uevar bvec - varkill bvec + uevar bitvec.BitVec + varkill bitvec.BitVec // Computed during Liveness.solve using control flow information: // // livein: variables live at block entry // liveout: variables live at block exit - livein bvec - liveout bvec + livein bitvec.BitVec + liveout bitvec.BitVec } // A collection of global state used by liveness analysis. -type Liveness struct { - fn *Node +type liveness struct { + fn *ir.Func f *ssa.Func - vars []*Node - idx map[*Node]int32 + vars []*ir.Name + idx map[*ir.Name]int32 stkptrsize int64 - be []BlockEffects + be []blockEffects // allUnsafe indicates that all points in this function are // unsafe-points. allUnsafe bool // unsafePoints bit i is set if Value ID i is an unsafe-point // (preemption is not allowed). Only valid if !allUnsafe. - unsafePoints bvec + unsafePoints bitvec.BitVec // An array with a bit vector for each safe point in the - // current Block during Liveness.epilogue. Indexed in Value + // current Block during liveness.epilogue. Indexed in Value // order for that block. Additionally, for the entry block - // livevars[0] is the entry bitmap. Liveness.compact moves + // livevars[0] is the entry bitmap. liveness.compact moves // these to stackMaps. - livevars []bvec + livevars []bitvec.BitVec // livenessMap maps from safe points (i.e., CALLs) to their // liveness map indexes. - livenessMap LivenessMap + livenessMap Map stackMapSet bvecSet - stackMaps []bvec + stackMaps []bitvec.BitVec cache progeffectscache + + // partLiveArgs includes input arguments (PPARAM) that may + // be partially live. That is, it is considered live because + // a part of it is used, but we may not initialize all parts. + partLiveArgs map[*ir.Name]bool + + doClobber bool // Whether to clobber dead stack slots in this function. + noClobberArgs bool // Do not clobber function arguments } -// LivenessMap maps from *ssa.Value to LivenessIndex. -type LivenessMap struct { - vals map[ssa.ID]LivenessIndex - // The set of live, pointer-containing variables at the deferreturn +// Map maps from *ssa.Value to LivenessIndex. +type Map struct { + Vals map[ssa.ID]objw.LivenessIndex + // The set of live, pointer-containing variables at the DeferReturn // call (only set when open-coded defers are used). - deferreturn LivenessIndex + DeferReturn objw.LivenessIndex } -func (m *LivenessMap) reset() { - if m.vals == nil { - m.vals = make(map[ssa.ID]LivenessIndex) +func (m *Map) reset() { + if m.Vals == nil { + m.Vals = make(map[ssa.ID]objw.LivenessIndex) } else { - for k := range m.vals { - delete(m.vals, k) + for k := range m.Vals { + delete(m.Vals, k) } } - m.deferreturn = LivenessDontCare + m.DeferReturn = objw.LivenessDontCare } -func (m *LivenessMap) set(v *ssa.Value, i LivenessIndex) { - m.vals[v.ID] = i +func (m *Map) set(v *ssa.Value, i objw.LivenessIndex) { + m.Vals[v.ID] = i } -func (m LivenessMap) Get(v *ssa.Value) LivenessIndex { +func (m Map) Get(v *ssa.Value) objw.LivenessIndex { // If v isn't in the map, then it's a "don't care" and not an // unsafe-point. - if idx, ok := m.vals[v.ID]; ok { + if idx, ok := m.Vals[v.ID]; ok { return idx } - return LivenessIndex{StackMapDontCare, false} -} - -// LivenessIndex stores the liveness map information for a Value. -type LivenessIndex struct { - stackMapIndex int - - // isUnsafePoint indicates that this is an unsafe-point. - // - // Note that it's possible for a call Value to have a stack - // map while also being an unsafe-point. This means it cannot - // be preempted at this instruction, but that a preemption or - // stack growth may happen in the called function. - isUnsafePoint bool -} - -// LivenessDontCare indicates that the liveness information doesn't -// matter. Currently it is used in deferreturn liveness when we don't -// actually need it. It should never be emitted to the PCDATA stream. -var LivenessDontCare = LivenessIndex{StackMapDontCare, true} - -// StackMapDontCare indicates that the stack map index at a Value -// doesn't matter. -// -// This is a sentinel value that should never be emitted to the PCDATA -// stream. We use -1000 because that's obviously never a valid stack -// index (but -1 is). -const StackMapDontCare = -1000 - -func (idx LivenessIndex) StackMapValid() bool { - return idx.stackMapIndex != StackMapDontCare + return objw.LivenessIndex{StackMapIndex: objw.StackMapDontCare, IsUnsafePoint: false} } type progeffectscache struct { @@ -198,42 +188,42 @@ type progeffectscache struct { initialized bool } -// livenessShouldTrack reports whether the liveness analysis +// shouldTrack reports whether the liveness analysis // should track the variable n. // We don't care about variables that have no pointers, // nor do we care about non-local variables, // nor do we care about empty structs (handled by the pointer check), // nor do we care about the fake PAUTOHEAP variables. -func livenessShouldTrack(n *Node) bool { - return n.Op == ONAME && (n.Class() == PAUTO || n.Class() == PPARAM || n.Class() == PPARAMOUT) && n.Type.HasPointers() +func shouldTrack(n *ir.Name) bool { + return (n.Class == ir.PAUTO && n.Esc() != ir.EscHeap || n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT) && n.Type().HasPointers() } // getvariables returns the list of on-stack variables that we need to track // and a map for looking up indices by *Node. -func getvariables(fn *Node) ([]*Node, map[*Node]int32) { - var vars []*Node - for _, n := range fn.Func.Dcl { - if livenessShouldTrack(n) { +func getvariables(fn *ir.Func) ([]*ir.Name, map[*ir.Name]int32) { + var vars []*ir.Name + for _, n := range fn.Dcl { + if shouldTrack(n) { vars = append(vars, n) } } - idx := make(map[*Node]int32, len(vars)) + idx := make(map[*ir.Name]int32, len(vars)) for i, n := range vars { idx[n] = int32(i) } return vars, idx } -func (lv *Liveness) initcache() { +func (lv *liveness) initcache() { if lv.cache.initialized { - Fatalf("liveness cache initialized twice") + base.Fatalf("liveness cache initialized twice") return } lv.cache.initialized = true for i, node := range lv.vars { - switch node.Class() { - case PPARAM: + switch node.Class { + case ir.PPARAM: // A return instruction with a p.to is a tail return, which brings // the stack pointer back up (if it ever went down) and then jumps // to a new function entirely. That form of instruction must read @@ -242,7 +232,7 @@ func (lv *Liveness) initcache() { // function runs. lv.cache.tailuevar = append(lv.cache.tailuevar, int32(i)) - case PPARAMOUT: + case ir.PPARAMOUT: // All results are live at every return point. // Note that this point is after escaping return values // are copied back to the stack using their PAUTOHEAP references. @@ -268,23 +258,28 @@ const ( // valueEffects returns the index of a variable in lv.vars and the // liveness effects v has on that variable. // If v does not affect any tracked variables, it returns -1, 0. -func (lv *Liveness) valueEffects(v *ssa.Value) (int32, liveEffect) { - n, e := affectedNode(v) - if e == 0 || n == nil || n.Op != ONAME { // cheapest checks first +func (lv *liveness) valueEffects(v *ssa.Value) (int32, liveEffect) { + n, e := affectedVar(v) + if e == 0 || n == nil { // cheapest checks first return -1, 0 } - // AllocFrame has dropped unused variables from // lv.fn.Func.Dcl, but they might still be referenced by // OpVarFoo pseudo-ops. Ignore them to prevent "lost track of // variable" ICEs (issue 19632). switch v.Op { case ssa.OpVarDef, ssa.OpVarKill, ssa.OpVarLive, ssa.OpKeepAlive: - if !n.Name.Used() { + if !n.Used() { return -1, 0 } } + if n.Class == ir.PPARAM && !n.Addrtaken() && n.Type().Width > int64(types.PtrSize) { + // Only aggregate-typed arguments that are not address-taken can be + // partially live. + lv.partLiveArgs[n] = true + } + var effect liveEffect // Read is a read, obviously. // @@ -295,7 +290,7 @@ func (lv *Liveness) valueEffects(v *ssa.Value) (int32, liveEffect) { if e&(ssa.SymRead|ssa.SymAddr) != 0 { effect |= uevar } - if e&ssa.SymWrite != 0 && (!isfat(n.Type) || v.Op == ssa.OpVarDef) { + if e&ssa.SymWrite != 0 && (!isfat(n.Type()) || v.Op == ssa.OpVarDef) { effect |= varkill } @@ -309,23 +304,39 @@ func (lv *Liveness) valueEffects(v *ssa.Value) (int32, liveEffect) { return -1, 0 } -// affectedNode returns the *Node affected by v -func affectedNode(v *ssa.Value) (*Node, ssa.SymEffect) { +// affectedVar returns the *ir.Name node affected by v +func affectedVar(v *ssa.Value) (*ir.Name, ssa.SymEffect) { // Special cases. switch v.Op { case ssa.OpLoadReg: - n, _ := AutoVar(v.Args[0]) + n, _ := ssa.AutoVar(v.Args[0]) return n, ssa.SymRead case ssa.OpStoreReg: - n, _ := AutoVar(v) + n, _ := ssa.AutoVar(v) return n, ssa.SymWrite + case ssa.OpArgIntReg: + // This forces the spill slot for the register to be live at function entry. + // one of the following holds for a function F with pointer-valued register arg X: + // 0. No GC (so an uninitialized spill slot is okay) + // 1. GC at entry of F. GC is precise, but the spills around morestack initialize X's spill slot + // 2. Stack growth at entry of F. Same as GC. + // 3. GC occurs within F itself. This has to be from preemption, and thus GC is conservative. + // a. X is in a register -- then X is seen, and the spill slot is also scanned conservatively. + // b. X is spilled -- the spill slot is initialized, and scanned conservatively + // c. X is not live -- the spill slot is scanned conservatively, and it may contain X from an earlier spill. + // 4. GC within G, transitively called from F + // a. X is live at call site, therefore is spilled, to its spill slot (which is live because of subsequent LoadReg). + // b. X is not live at call site -- but neither is its spill slot. + n, _ := ssa.AutoVar(v) + return n, ssa.SymRead + case ssa.OpVarLive: - return v.Aux.(*Node), ssa.SymRead + return v.Aux.(*ir.Name), ssa.SymRead case ssa.OpVarDef, ssa.OpVarKill: - return v.Aux.(*Node), ssa.SymWrite + return v.Aux.(*ir.Name), ssa.SymWrite case ssa.OpKeepAlive: - n, _ := AutoVar(v.Args[0]) + n, _ := ssa.AutoVar(v.Args[0]) return n, ssa.SymRead } @@ -338,24 +349,24 @@ func affectedNode(v *ssa.Value) (*Node, ssa.SymEffect) { case nil, *obj.LSym: // ok, but no node return nil, e - case *Node: + case *ir.Name: return a, e default: - Fatalf("weird aux: %s", v.LongString()) + base.Fatalf("weird aux: %s", v.LongString()) return nil, e } } type livenessFuncCache struct { - be []BlockEffects - livenessMap LivenessMap + be []blockEffects + livenessMap Map } // Constructs a new liveness structure used to hold the global state of the // liveness computation. The cfg argument is a slice of *BasicBlocks and the // vars argument is a slice of *Nodes. -func newliveness(fn *Node, f *ssa.Func, vars []*Node, idx map[*Node]int32, stkptrsize int64) *Liveness { - lv := &Liveness{ +func newliveness(fn *ir.Func, f *ssa.Func, vars []*ir.Name, idx map[*ir.Name]int32, stkptrsize int64) *liveness { + lv := &liveness{ fn: fn, f: f, vars: vars, @@ -373,133 +384,68 @@ func newliveness(fn *Node, f *ssa.Func, vars []*Node, idx map[*Node]int32, stkpt if cap(lc.be) >= f.NumBlocks() { lv.be = lc.be[:f.NumBlocks()] } - lv.livenessMap = LivenessMap{vals: lc.livenessMap.vals, deferreturn: LivenessDontCare} - lc.livenessMap.vals = nil + lv.livenessMap = Map{Vals: lc.livenessMap.Vals, DeferReturn: objw.LivenessDontCare} + lc.livenessMap.Vals = nil } if lv.be == nil { - lv.be = make([]BlockEffects, f.NumBlocks()) + lv.be = make([]blockEffects, f.NumBlocks()) } nblocks := int32(len(f.Blocks)) nvars := int32(len(vars)) - bulk := bvbulkalloc(nvars, nblocks*7) + bulk := bitvec.NewBulk(nvars, nblocks*7) for _, b := range f.Blocks { be := lv.blockEffects(b) - be.uevar = bulk.next() - be.varkill = bulk.next() - be.livein = bulk.next() - be.liveout = bulk.next() + be.uevar = bulk.Next() + be.varkill = bulk.Next() + be.livein = bulk.Next() + be.liveout = bulk.Next() } lv.livenessMap.reset() lv.markUnsafePoints() - return lv -} - -func (lv *Liveness) blockEffects(b *ssa.Block) *BlockEffects { - return &lv.be[b.ID] -} - -// NOTE: The bitmap for a specific type t could be cached in t after -// the first run and then simply copied into bv at the correct offset -// on future calls with the same type t. -func onebitwalktype1(t *types.Type, off int64, bv bvec) { - if t.Align > 0 && off&int64(t.Align-1) != 0 { - Fatalf("onebitwalktype1: invalid initial alignment: type %v has alignment %d, but offset is %v", t, t.Align, off) - } - if !t.HasPointers() { - // Note: this case ensures that pointers to go:notinheap types - // are not considered pointers by garbage collection and stack copying. - return - } - - switch t.Etype { - case TPTR, TUNSAFEPTR, TFUNC, TCHAN, TMAP: - if off&int64(Widthptr-1) != 0 { - Fatalf("onebitwalktype1: invalid alignment, %v", t) - } - bv.Set(int32(off / int64(Widthptr))) // pointer - case TSTRING: - // struct { byte *str; intgo len; } - if off&int64(Widthptr-1) != 0 { - Fatalf("onebitwalktype1: invalid alignment, %v", t) - } - bv.Set(int32(off / int64(Widthptr))) //pointer in first slot + lv.partLiveArgs = make(map[*ir.Name]bool) - case TINTER: - // struct { Itab *tab; void *data; } - // or, when isnilinter(t)==true: - // struct { Type *type; void *data; } - if off&int64(Widthptr-1) != 0 { - Fatalf("onebitwalktype1: invalid alignment, %v", t) - } - // The first word of an interface is a pointer, but we don't - // treat it as such. - // 1. If it is a non-empty interface, the pointer points to an itab - // which is always in persistentalloc space. - // 2. If it is an empty interface, the pointer points to a _type. - // a. If it is a compile-time-allocated type, it points into - // the read-only data section. - // b. If it is a reflect-allocated type, it points into the Go heap. - // Reflect is responsible for keeping a reference to - // the underlying type so it won't be GCd. - // If we ever have a moving GC, we need to change this for 2b (as - // well as scan itabs to update their itab._type fields). - bv.Set(int32(off/int64(Widthptr) + 1)) // pointer in second slot - - case TSLICE: - // struct { byte *array; uintgo len; uintgo cap; } - if off&int64(Widthptr-1) != 0 { - Fatalf("onebitwalktype1: invalid TARRAY alignment, %v", t) - } - bv.Set(int32(off / int64(Widthptr))) // pointer in first slot (BitsPointer) - - case TARRAY: - elt := t.Elem() - if elt.Width == 0 { - // Short-circuit for #20739. - break - } - for i := int64(0); i < t.NumElem(); i++ { - onebitwalktype1(elt, off, bv) - off += elt.Width - } + lv.enableClobber() - case TSTRUCT: - for _, f := range t.Fields().Slice() { - onebitwalktype1(f.Type, off+f.Offset, bv) - } + return lv +} - default: - Fatalf("onebitwalktype1: unexpected type, %v", t) - } +func (lv *liveness) blockEffects(b *ssa.Block) *blockEffects { + return &lv.be[b.ID] } // Generates live pointer value maps for arguments and local variables. The // this argument and the in arguments are always assumed live. The vars // argument is a slice of *Nodes. -func (lv *Liveness) pointerMap(liveout bvec, vars []*Node, args, locals bvec) { +func (lv *liveness) pointerMap(liveout bitvec.BitVec, vars []*ir.Name, args, locals bitvec.BitVec) { for i := int32(0); ; i++ { i = liveout.Next(i) if i < 0 { break } node := vars[i] - switch node.Class() { - case PAUTO: - onebitwalktype1(node.Type, node.Xoffset+lv.stkptrsize, locals) - - case PPARAM, PPARAMOUT: - onebitwalktype1(node.Type, node.Xoffset, args) + switch node.Class { + case ir.PPARAM, ir.PPARAMOUT: + if !node.IsOutputParamInRegisters() { + if node.FrameOffset() < 0 { + lv.f.Fatalf("Node %v has frameoffset %d\n", node.Sym().Name, node.FrameOffset()) + } + typebits.Set(node.Type(), node.FrameOffset(), args) + break + } + fallthrough // PPARAMOUT in registers acts memory-allocates like an AUTO + case ir.PAUTO: + typebits.Set(node.Type(), node.FrameOffset()+lv.stkptrsize, locals) } } } -// allUnsafe indicates that all points in this function are +// IsUnsafe indicates that all points in this function are // unsafe-points. -func allUnsafe(f *ssa.Func) bool { +func IsUnsafe(f *ssa.Func) bool { // The runtime assumes the only safe-points are function // prologues (because that's how it used to be). We could and // should improve that, but for now keep consider all points @@ -509,18 +455,18 @@ func allUnsafe(f *ssa.Func) bool { // go:nosplit functions are similar. Since safe points used to // be coupled with stack checks, go:nosplit often actually // means "no safe points in this function". - return compiling_runtime || f.NoSplit + return base.Flag.CompilingRuntime || f.NoSplit } // markUnsafePoints finds unsafe points and computes lv.unsafePoints. -func (lv *Liveness) markUnsafePoints() { - if allUnsafe(lv.f) { +func (lv *liveness) markUnsafePoints() { + if IsUnsafe(lv.f) { // No complex analysis necessary. lv.allUnsafe = true return } - lv.unsafePoints = bvalloc(int32(lv.f.NumValues())) + lv.unsafePoints = bitvec.New(int32(lv.f.NumValues())) // Mark architecture-specific unsafe points. for _, b := range lv.f.Blocks { @@ -564,7 +510,7 @@ func (lv *Liveness) markUnsafePoints() { var load *ssa.Value v := wbBlock.Controls[0] for { - if sym, ok := v.Aux.(*obj.LSym); ok && sym == writeBarrier { + if sym, ok := v.Aux.(*obj.LSym); ok && sym == ir.Syms.WriteBarrier { load = v break } @@ -631,11 +577,11 @@ func (lv *Liveness) markUnsafePoints() { // nice to only flood as far as the unsafe.Pointer -> uintptr // conversion, but it's hard to know which argument of an Add // or Sub to follow. - var flooded bvec + var flooded bitvec.BitVec var flood func(b *ssa.Block, vi int) flood = func(b *ssa.Block, vi int) { - if flooded.n == 0 { - flooded = bvalloc(int32(lv.f.NumBlocks())) + if flooded.N == 0 { + flooded = bitvec.New(int32(lv.f.NumBlocks())) } if flooded.Get(int32(b.ID)) { return @@ -676,14 +622,14 @@ func (lv *Liveness) markUnsafePoints() { // This does not necessarily mean the instruction is a safe-point. In // particular, call Values can have a stack map in case the callee // grows the stack, but not themselves be a safe-point. -func (lv *Liveness) hasStackMap(v *ssa.Value) bool { +func (lv *liveness) hasStackMap(v *ssa.Value) bool { if !v.Op.IsCall() { return false } // typedmemclr and typedmemmove are write barriers and // deeply non-preemptible. They are unsafe points and // hence should not have liveness maps. - if sym, ok := v.Aux.(*ssa.AuxCall); ok && (sym.Fn == typedmemclr || sym.Fn == typedmemmove) { + if sym, ok := v.Aux.(*ssa.AuxCall); ok && (sym.Fn == ir.Syms.Typedmemclr || sym.Fn == ir.Syms.Typedmemmove) { return false } return true @@ -692,7 +638,7 @@ func (lv *Liveness) hasStackMap(v *ssa.Value) bool { // Initializes the sets for solving the live variables. Visits all the // instructions in each basic block to summarizes the information at each basic // block -func (lv *Liveness) prologue() { +func (lv *liveness) prologue() { lv.initcache() for _, b := range lv.f.Blocks { @@ -714,12 +660,12 @@ func (lv *Liveness) prologue() { } // Solve the liveness dataflow equations. -func (lv *Liveness) solve() { +func (lv *liveness) solve() { // These temporary bitvectors exist to avoid successive allocations and // frees within the loop. nvars := int32(len(lv.vars)) - newlivein := bvalloc(nvars) - newliveout := bvalloc(nvars) + newlivein := bitvec.New(nvars) + newliveout := bitvec.New(nvars) // Walk blocks in postorder ordering. This improves convergence. po := lv.f.Postorder() @@ -774,10 +720,10 @@ func (lv *Liveness) solve() { // Visits all instructions in a basic block and computes a bit vector of live // variables at each safe point locations. -func (lv *Liveness) epilogue() { +func (lv *liveness) epilogue() { nvars := int32(len(lv.vars)) - liveout := bvalloc(nvars) - livedefer := bvalloc(nvars) // always-live variables + liveout := bitvec.New(nvars) + livedefer := bitvec.New(nvars) // always-live variables // If there is a defer (that could recover), then all output // parameters are live all the time. In addition, any locals @@ -786,14 +732,14 @@ func (lv *Liveness) epilogue() { // pointers to copy values back to the stack). // TODO: if the output parameter is heap-allocated, then we // don't need to keep the stack copy live? - if lv.fn.Func.HasDefer() { + if lv.fn.HasDefer() { for i, n := range lv.vars { - if n.Class() == PPARAMOUT { - if n.Name.IsOutputParamHeapAddr() { + if n.Class == ir.PPARAMOUT { + if n.IsOutputParamHeapAddr() { // Just to be paranoid. Heap addresses are PAUTOs. - Fatalf("variable %v both output param and heap output param", n) + base.Fatalf("variable %v both output param and heap output param", n) } - if n.Name.Param.Heapaddr != nil { + if n.Heapaddr != nil { // If this variable moved to the heap, then // its stack copy is not live. continue @@ -801,22 +747,22 @@ func (lv *Liveness) epilogue() { // Note: zeroing is handled by zeroResults in walk.go. livedefer.Set(int32(i)) } - if n.Name.IsOutputParamHeapAddr() { + if n.IsOutputParamHeapAddr() { // This variable will be overwritten early in the function // prologue (from the result of a mallocgc) but we need to // zero it in case that malloc causes a stack scan. - n.Name.SetNeedzero(true) + n.SetNeedzero(true) livedefer.Set(int32(i)) } - if n.Name.OpenDeferSlot() { + if n.OpenDeferSlot() { // Open-coded defer args slots must be live // everywhere in a function, since a panic can // occur (almost) anywhere. Because it is live // everywhere, it must be zeroed on entry. livedefer.Set(int32(i)) // It was already marked as Needzero when created. - if !n.Name.Needzero() { - Fatalf("all pointer-containing defer arg slots should have Needzero set") + if !n.Needzero() { + base.Fatalf("all pointer-containing defer arg slots should have Needzero set") } } } @@ -831,7 +777,7 @@ func (lv *Liveness) epilogue() { { // Reserve an entry for function entry. - live := bvalloc(nvars) + live := bitvec.New(nvars) lv.livevars = append(lv.livevars, live) } @@ -845,7 +791,7 @@ func (lv *Liveness) epilogue() { continue } - live := bvalloc(nvars) + live := bitvec.New(nvars) lv.livevars = append(lv.livevars, live) } @@ -878,7 +824,7 @@ func (lv *Liveness) epilogue() { if b == lv.f.Entry { if index != 0 { - Fatalf("bad index for entry point: %v", index) + base.Fatalf("bad index for entry point: %v", index) } // Check to make sure only input variables are live. @@ -886,10 +832,10 @@ func (lv *Liveness) epilogue() { if !liveout.Get(int32(i)) { continue } - if n.Class() == PPARAM { + if n.Class == ir.PPARAM { continue // ok } - Fatalf("bad live variable at entry of %v: %L", lv.fn.Func.Nname, n) + base.FatalfAt(n.Pos(), "bad live variable at entry of %v: %L", lv.fn.Nname, n) } // Record live variables. @@ -897,30 +843,34 @@ func (lv *Liveness) epilogue() { live.Or(*live, liveout) } + if lv.doClobber { + lv.clobber(b) + } + // The liveness maps for this block are now complete. Compact them. lv.compact(b) } // If we have an open-coded deferreturn call, make a liveness map for it. - if lv.fn.Func.OpenCodedDeferDisallowed() { - lv.livenessMap.deferreturn = LivenessDontCare + if lv.fn.OpenCodedDeferDisallowed() { + lv.livenessMap.DeferReturn = objw.LivenessDontCare } else { - lv.livenessMap.deferreturn = LivenessIndex{ - stackMapIndex: lv.stackMapSet.add(livedefer), - isUnsafePoint: false, + lv.livenessMap.DeferReturn = objw.LivenessIndex{ + StackMapIndex: lv.stackMapSet.add(livedefer), + IsUnsafePoint: false, } } // Done compacting. Throw out the stack map set. - lv.stackMaps = lv.stackMapSet.extractUniqe() + lv.stackMaps = lv.stackMapSet.extractUnique() lv.stackMapSet = bvecSet{} // Useful sanity check: on entry to the function, // the only things that can possibly be live are the // input parameters. for j, n := range lv.vars { - if n.Class() != PPARAM && lv.stackMaps[0].Get(int32(j)) { - lv.f.Fatalf("%v %L recorded as live on entry", lv.fn.Func.Nname, n) + if n.Class != ir.PPARAM && lv.stackMaps[0].Get(int32(j)) { + lv.f.Fatalf("%v %L recorded as live on entry", lv.fn.Nname, n) } } } @@ -941,7 +891,7 @@ func (lv *Liveness) epilogue() { // is actually a net loss: we save about 50k of argument bitmaps but the new // PCDATA tables cost about 100k. So for now we keep using a single index for // both bitmap lists. -func (lv *Liveness) compact(b *ssa.Block) { +func (lv *liveness) compact(b *ssa.Block) { pos := 0 if b == lv.f.Entry { // Handle entry stack map. @@ -950,10 +900,10 @@ func (lv *Liveness) compact(b *ssa.Block) { } for _, v := range b.Values { hasStackMap := lv.hasStackMap(v) - isUnsafePoint := lv.allUnsafe || lv.unsafePoints.Get(int32(v.ID)) - idx := LivenessIndex{StackMapDontCare, isUnsafePoint} + isUnsafePoint := lv.allUnsafe || v.Op != ssa.OpClobber && lv.unsafePoints.Get(int32(v.ID)) + idx := objw.LivenessIndex{StackMapIndex: objw.StackMapDontCare, IsUnsafePoint: isUnsafePoint} if hasStackMap { - idx.stackMapIndex = lv.stackMapSet.add(lv.livevars[pos]) + idx.StackMapIndex = lv.stackMapSet.add(lv.livevars[pos]) pos++ } if hasStackMap || isUnsafePoint { @@ -965,8 +915,171 @@ func (lv *Liveness) compact(b *ssa.Block) { lv.livevars = lv.livevars[:0] } -func (lv *Liveness) showlive(v *ssa.Value, live bvec) { - if debuglive == 0 || lv.fn.funcname() == "init" || strings.HasPrefix(lv.fn.funcname(), ".") { +func (lv *liveness) enableClobber() { + // The clobberdead experiment inserts code to clobber pointer slots in all + // the dead variables (locals and args) at every synchronous safepoint. + if !base.Flag.ClobberDead { + return + } + if lv.fn.Pragma&ir.CgoUnsafeArgs != 0 { + // C or assembly code uses the exact frame layout. Don't clobber. + return + } + if len(lv.vars) > 10000 || len(lv.f.Blocks) > 10000 { + // Be careful to avoid doing too much work. + // Bail if >10000 variables or >10000 blocks. + // Otherwise, giant functions make this experiment generate too much code. + return + } + if lv.f.Name == "forkAndExecInChild" { + // forkAndExecInChild calls vfork on some platforms. + // The code we add here clobbers parts of the stack in the child. + // When the parent resumes, it is using the same stack frame. But the + // child has clobbered stack variables that the parent needs. Boom! + // In particular, the sys argument gets clobbered. + return + } + if lv.f.Name == "wbBufFlush" || + ((lv.f.Name == "callReflect" || lv.f.Name == "callMethod") && lv.fn.ABIWrapper()) { + // runtime.wbBufFlush must not modify its arguments. See the comments + // in runtime/mwbbuf.go:wbBufFlush. + // + // reflect.callReflect and reflect.callMethod are called from special + // functions makeFuncStub and methodValueCall. The runtime expects + // that it can find the first argument (ctxt) at 0(SP) in makeFuncStub + // and methodValueCall's frame (see runtime/traceback.go:getArgInfo). + // Normally callReflect and callMethod already do not modify the + // argument, and keep it alive. But the compiler-generated ABI wrappers + // don't do that. Special case the wrappers to not clobber its arguments. + lv.noClobberArgs = true + } + if h := os.Getenv("GOCLOBBERDEADHASH"); h != "" { + // Clobber only functions where the hash of the function name matches a pattern. + // Useful for binary searching for a miscompiled function. + hstr := "" + for _, b := range sha1.Sum([]byte(lv.f.Name)) { + hstr += fmt.Sprintf("%08b", b) + } + if !strings.HasSuffix(hstr, h) { + return + } + fmt.Printf("\t\t\tCLOBBERDEAD %s\n", lv.f.Name) + } + lv.doClobber = true +} + +// Inserts code to clobber pointer slots in all the dead variables (locals and args) +// at every synchronous safepoint in b. +func (lv *liveness) clobber(b *ssa.Block) { + // Copy block's values to a temporary. + oldSched := append([]*ssa.Value{}, b.Values...) + b.Values = b.Values[:0] + idx := 0 + + // Clobber pointer slots in all dead variables at entry. + if b == lv.f.Entry { + for len(oldSched) > 0 && len(oldSched[0].Args) == 0 { + // Skip argless ops. We need to skip at least + // the lowered ClosurePtr op, because it + // really wants to be first. This will also + // skip ops like InitMem and SP, which are ok. + b.Values = append(b.Values, oldSched[0]) + oldSched = oldSched[1:] + } + clobber(lv, b, lv.livevars[0]) + idx++ + } + + // Copy values into schedule, adding clobbering around safepoints. + for _, v := range oldSched { + if !lv.hasStackMap(v) { + b.Values = append(b.Values, v) + continue + } + clobber(lv, b, lv.livevars[idx]) + b.Values = append(b.Values, v) + idx++ + } +} + +// clobber generates code to clobber pointer slots in all dead variables +// (those not marked in live). Clobbering instructions are added to the end +// of b.Values. +func clobber(lv *liveness, b *ssa.Block, live bitvec.BitVec) { + for i, n := range lv.vars { + if !live.Get(int32(i)) && !n.Addrtaken() && !n.OpenDeferSlot() && !n.IsOutputParamHeapAddr() { + // Don't clobber stack objects (address-taken). They are + // tracked dynamically. + // Also don't clobber slots that are live for defers (see + // the code setting livedefer in epilogue). + if lv.noClobberArgs && n.Class == ir.PPARAM { + continue + } + clobberVar(b, n) + } + } +} + +// clobberVar generates code to trash the pointers in v. +// Clobbering instructions are added to the end of b.Values. +func clobberVar(b *ssa.Block, v *ir.Name) { + clobberWalk(b, v, 0, v.Type()) +} + +// b = block to which we append instructions +// v = variable +// offset = offset of (sub-portion of) variable to clobber (in bytes) +// t = type of sub-portion of v. +func clobberWalk(b *ssa.Block, v *ir.Name, offset int64, t *types.Type) { + if !t.HasPointers() { + return + } + switch t.Kind() { + case types.TPTR, + types.TUNSAFEPTR, + types.TFUNC, + types.TCHAN, + types.TMAP: + clobberPtr(b, v, offset) + + case types.TSTRING: + // struct { byte *str; int len; } + clobberPtr(b, v, offset) + + case types.TINTER: + // struct { Itab *tab; void *data; } + // or, when isnilinter(t)==true: + // struct { Type *type; void *data; } + clobberPtr(b, v, offset) + clobberPtr(b, v, offset+int64(types.PtrSize)) + + case types.TSLICE: + // struct { byte *array; int len; int cap; } + clobberPtr(b, v, offset) + + case types.TARRAY: + for i := int64(0); i < t.NumElem(); i++ { + clobberWalk(b, v, offset+i*t.Elem().Size(), t.Elem()) + } + + case types.TSTRUCT: + for _, t1 := range t.Fields().Slice() { + clobberWalk(b, v, offset+t1.Offset, t1.Type) + } + + default: + base.Fatalf("clobberWalk: unexpected type, %v", t) + } +} + +// clobberPtr generates a clobber of the pointer at offset offset in v. +// The clobber instruction is added at the end of b. +func clobberPtr(b *ssa.Block, v *ir.Name, offset int64) { + b.NewValue0IA(src.NoXPos, ssa.OpClobber, types.TypeVoid, offset, v) +} + +func (lv *liveness) showlive(v *ssa.Value, live bitvec.BitVec) { + if base.Flag.Live == 0 || ir.FuncName(lv.fn) == "init" || strings.HasPrefix(ir.FuncName(lv.fn), ".") { return } if !(v == nil || v.Op.IsCall()) { @@ -978,14 +1091,14 @@ func (lv *Liveness) showlive(v *ssa.Value, live bvec) { return } - pos := lv.fn.Func.Nname.Pos + pos := lv.fn.Nname.Pos() if v != nil { pos = v.Pos } s := "live at " if v == nil { - s += fmt.Sprintf("entry to %s:", lv.fn.funcname()) + s += fmt.Sprintf("entry to %s:", ir.FuncName(lv.fn)) } else if sym, ok := v.Aux.(*ssa.AuxCall); ok && sym.Fn != nil { fn := sym.Fn.Name if pos := strings.Index(fn, "."); pos >= 0 { @@ -1002,10 +1115,10 @@ func (lv *Liveness) showlive(v *ssa.Value, live bvec) { } } - Warnl(pos, s) + base.WarnfAt(pos, s) } -func (lv *Liveness) printbvec(printed bool, name string, live bvec) bool { +func (lv *liveness) printbvec(printed bool, name string, live bitvec.BitVec) bool { if live.IsEmpty() { return printed } @@ -1022,14 +1135,14 @@ func (lv *Liveness) printbvec(printed bool, name string, live bvec) bool { if !live.Get(int32(i)) { continue } - fmt.Printf("%s%s", comma, n.Sym.Name) + fmt.Printf("%s%s", comma, n.Sym().Name) comma = "," } return true } // printeffect is like printbvec, but for valueEffects. -func (lv *Liveness) printeffect(printed bool, name string, pos int32, x bool) bool { +func (lv *liveness) printeffect(printed bool, name string, pos int32, x bool) bool { if !x { return printed } @@ -1040,7 +1153,7 @@ func (lv *Liveness) printeffect(printed bool, name string, pos int32, x bool) bo } fmt.Printf("%s=", name) if x { - fmt.Printf("%s", lv.vars[pos].Sym.Name) + fmt.Printf("%s", lv.vars[pos].Sym().Name) } return true @@ -1049,8 +1162,8 @@ func (lv *Liveness) printeffect(printed bool, name string, pos int32, x bool) bo // Prints the computed liveness information and inputs, for debugging. // This format synthesizes the information used during the multiple passes // into a single presentation. -func (lv *Liveness) printDebug() { - fmt.Printf("liveness: %s\n", lv.fn.funcname()) +func (lv *liveness) printDebug() { + fmt.Printf("liveness: %s\n", ir.FuncName(lv.fn)) for i, b := range lv.f.Blocks { if i > 0 { @@ -1088,7 +1201,7 @@ func (lv *Liveness) printDebug() { if b == lv.f.Entry { live := lv.stackMaps[0] - fmt.Printf("(%s) function entry\n", linestr(lv.fn.Func.Nname.Pos)) + fmt.Printf("(%s) function entry\n", base.FmtPos(lv.fn.Nname.Pos())) fmt.Printf("\tlive=") printed = false for j, n := range lv.vars { @@ -1105,7 +1218,7 @@ func (lv *Liveness) printDebug() { } for _, v := range b.Values { - fmt.Printf("(%s) %v\n", linestr(v.Pos), v.LongString()) + fmt.Printf("(%s) %v\n", base.FmtPos(v.Pos), v.LongString()) pcdata := lv.livenessMap.Get(v) @@ -1121,7 +1234,7 @@ func (lv *Liveness) printDebug() { fmt.Printf("\tlive=") printed = false if pcdata.StackMapValid() { - live := lv.stackMaps[pcdata.stackMapIndex] + live := lv.stackMaps[pcdata.StackMapIndex] for j, n := range lv.vars { if !live.Get(int32(j)) { continue @@ -1136,7 +1249,7 @@ func (lv *Liveness) printDebug() { fmt.Printf("\n") } - if pcdata.isUnsafePoint { + if pcdata.IsUnsafePoint { fmt.Printf("\tunsafe-point\n") } } @@ -1158,23 +1271,25 @@ func (lv *Liveness) printDebug() { // first word dumped is the total number of bitmaps. The second word is the // length of the bitmaps. All bitmaps are assumed to be of equal length. The // remaining bytes are the raw bitmaps. -func (lv *Liveness) emit() (argsSym, liveSym *obj.LSym) { +func (lv *liveness) emit() (argsSym, liveSym *obj.LSym) { // Size args bitmaps to be just large enough to hold the largest pointer. // First, find the largest Xoffset node we care about. - // (Nodes without pointers aren't in lv.vars; see livenessShouldTrack.) - var maxArgNode *Node + // (Nodes without pointers aren't in lv.vars; see ShouldTrack.) + var maxArgNode *ir.Name for _, n := range lv.vars { - switch n.Class() { - case PPARAM, PPARAMOUT: - if maxArgNode == nil || n.Xoffset > maxArgNode.Xoffset { - maxArgNode = n + switch n.Class { + case ir.PPARAM, ir.PPARAMOUT: + if !n.IsOutputParamInRegisters() { + if maxArgNode == nil || n.FrameOffset() > maxArgNode.FrameOffset() { + maxArgNode = n + } } } } // Next, find the offset of the largest pointer in the largest node. var maxArgs int64 if maxArgNode != nil { - maxArgs = maxArgNode.Xoffset + typeptrdata(maxArgNode.Type) + maxArgs = maxArgNode.FrameOffset() + types.PtrDataSize(maxArgNode.Type()) } // Size locals bitmaps to be stkptrsize sized. @@ -1189,13 +1304,13 @@ func (lv *Liveness) emit() (argsSym, liveSym *obj.LSym) { // Temporary symbols for encoding bitmaps. var argsSymTmp, liveSymTmp obj.LSym - args := bvalloc(int32(maxArgs / int64(Widthptr))) - aoff := duint32(&argsSymTmp, 0, uint32(len(lv.stackMaps))) // number of bitmaps - aoff = duint32(&argsSymTmp, aoff, uint32(args.n)) // number of bits in each bitmap + args := bitvec.New(int32(maxArgs / int64(types.PtrSize))) + aoff := objw.Uint32(&argsSymTmp, 0, uint32(len(lv.stackMaps))) // number of bitmaps + aoff = objw.Uint32(&argsSymTmp, aoff, uint32(args.N)) // number of bits in each bitmap - locals := bvalloc(int32(maxLocals / int64(Widthptr))) - loff := duint32(&liveSymTmp, 0, uint32(len(lv.stackMaps))) // number of bitmaps - loff = duint32(&liveSymTmp, loff, uint32(locals.n)) // number of bits in each bitmap + locals := bitvec.New(int32(maxLocals / int64(types.PtrSize))) + loff := objw.Uint32(&liveSymTmp, 0, uint32(len(lv.stackMaps))) // number of bitmaps + loff = objw.Uint32(&liveSymTmp, loff, uint32(locals.N)) // number of bits in each bitmap for _, live := range lv.stackMaps { args.Clear() @@ -1203,8 +1318,8 @@ func (lv *Liveness) emit() (argsSym, liveSym *obj.LSym) { lv.pointerMap(live, lv.vars, args, locals) - aoff = dbvec(&argsSymTmp, aoff, args) - loff = dbvec(&liveSymTmp, loff, locals) + aoff = objw.BitVec(&argsSymTmp, aoff, args) + loff = objw.BitVec(&liveSymTmp, loff, locals) } // Give these LSyms content-addressable names, @@ -1214,7 +1329,7 @@ func (lv *Liveness) emit() (argsSym, liveSym *obj.LSym) { // These symbols will be added to Ctxt.Data by addGCLocals // after parallel compilation is done. makeSym := func(tmpSym *obj.LSym) *obj.LSym { - return Ctxt.LookupInit(fmt.Sprintf("gclocals·%x", md5.Sum(tmpSym.P)), func(lsym *obj.LSym) { + return base.Ctxt.LookupInit(fmt.Sprintf("gclocals·%x", md5.Sum(tmpSym.P)), func(lsym *obj.LSym) { lsym.P = tmpSym.P lsym.Set(obj.AttrContentAddressable, true) }) @@ -1222,30 +1337,31 @@ func (lv *Liveness) emit() (argsSym, liveSym *obj.LSym) { return makeSym(&argsSymTmp), makeSym(&liveSymTmp) } -// Entry pointer for liveness analysis. Solves for the liveness of +// Entry pointer for Compute analysis. Solves for the Compute of // pointer variables in the function and emits a runtime data // structure read by the garbage collector. -// Returns a map from GC safe points to their corresponding stack map index. -func liveness(e *ssafn, f *ssa.Func, pp *Progs) LivenessMap { +// Returns a map from GC safe points to their corresponding stack map index, +// and a map that contains all input parameters that may be partially live. +func Compute(curfn *ir.Func, f *ssa.Func, stkptrsize int64, pp *objw.Progs) (Map, map[*ir.Name]bool) { // Construct the global liveness state. - vars, idx := getvariables(e.curfn) - lv := newliveness(e.curfn, f, vars, idx, e.stkptrsize) + vars, idx := getvariables(curfn) + lv := newliveness(curfn, f, vars, idx, stkptrsize) // Run the dataflow framework. lv.prologue() lv.solve() lv.epilogue() - if debuglive > 0 { + if base.Flag.Live > 0 { lv.showlive(nil, lv.stackMaps[0]) for _, b := range f.Blocks { for _, val := range b.Values { if idx := lv.livenessMap.Get(val); idx.StackMapValid() { - lv.showlive(val, lv.stackMaps[idx.stackMapIndex]) + lv.showlive(val, lv.stackMaps[idx.StackMapIndex]) } } } } - if debuglive >= 2 { + if base.Flag.Live >= 2 { lv.printDebug() } @@ -1254,33 +1370,96 @@ func liveness(e *ssafn, f *ssa.Func, pp *Progs) LivenessMap { cache := f.Cache.Liveness.(*livenessFuncCache) if cap(lv.be) < 2000 { // Threshold from ssa.Cache slices. for i := range lv.be { - lv.be[i] = BlockEffects{} + lv.be[i] = blockEffects{} } cache.be = lv.be } - if len(lv.livenessMap.vals) < 2000 { + if len(lv.livenessMap.Vals) < 2000 { cache.livenessMap = lv.livenessMap } } // Emit the live pointer map data structures - ls := e.curfn.Func.lsym + ls := curfn.LSym fninfo := ls.Func() fninfo.GCArgs, fninfo.GCLocals = lv.emit() p := pp.Prog(obj.AFUNCDATA) - Addrconst(&p.From, objabi.FUNCDATA_ArgsPointerMaps) + p.From.SetConst(objabi.FUNCDATA_ArgsPointerMaps) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN p.To.Sym = fninfo.GCArgs p = pp.Prog(obj.AFUNCDATA) - Addrconst(&p.From, objabi.FUNCDATA_LocalsPointerMaps) + p.From.SetConst(objabi.FUNCDATA_LocalsPointerMaps) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN p.To.Sym = fninfo.GCLocals - return lv.livenessMap + if x := lv.emitStackObjects(); x != nil { + p := pp.Prog(obj.AFUNCDATA) + p.From.SetConst(objabi.FUNCDATA_StackObjects) + p.To.Type = obj.TYPE_MEM + p.To.Name = obj.NAME_EXTERN + p.To.Sym = x + } + + return lv.livenessMap, lv.partLiveArgs +} + +func (lv *liveness) emitStackObjects() *obj.LSym { + var vars []*ir.Name + for _, n := range lv.fn.Dcl { + if shouldTrack(n) && n.Addrtaken() && n.Esc() != ir.EscHeap { + vars = append(vars, n) + } + } + if len(vars) == 0 { + return nil + } + + // Sort variables from lowest to highest address. + sort.Slice(vars, func(i, j int) bool { return vars[i].FrameOffset() < vars[j].FrameOffset() }) + + // Populate the stack object data. + // Format must match runtime/stack.go:stackObjectRecord. + x := base.Ctxt.Lookup(lv.fn.LSym.Name + ".stkobj") + lv.fn.LSym.Func().StackObjects = x + off := 0 + off = objw.Uintptr(x, off, uint64(len(vars))) + for _, v := range vars { + // Note: arguments and return values have non-negative Xoffset, + // in which case the offset is relative to argp. + // Locals have a negative Xoffset, in which case the offset is relative to varp. + // We already limit the frame size, so the offset and the object size + // should not be too big. + frameOffset := v.FrameOffset() + if frameOffset != int64(int32(frameOffset)) { + base.Fatalf("frame offset too big: %v %d", v, frameOffset) + } + off = objw.Uint32(x, off, uint32(frameOffset)) + + t := v.Type() + sz := t.Width + if sz != int64(int32(sz)) { + base.Fatalf("stack object too big: %v of type %v, size %d", v, t, sz) + } + lsym, useGCProg, ptrdata := reflectdata.GCSym(t) + if useGCProg { + ptrdata = -ptrdata + } + off = objw.Uint32(x, off, uint32(sz)) + off = objw.Uint32(x, off, uint32(ptrdata)) + off = objw.SymPtr(x, off, lsym, 0) + } + + if base.Flag.Live != 0 { + for _, v := range vars { + base.WarnfAt(v.Pos(), "stack object %v %v", v, v.Type()) + } + } + + return x } // isfat reports whether a variable of type t needs multiple assignments to initialize. @@ -1298,17 +1477,17 @@ func liveness(e *ssafn, f *ssa.Func, pp *Progs) LivenessMap { // to fully initialize t. func isfat(t *types.Type) bool { if t != nil { - switch t.Etype { - case TSLICE, TSTRING, - TINTER: // maybe remove later + switch t.Kind() { + case types.TSLICE, types.TSTRING, + types.TINTER: // maybe remove later return true - case TARRAY: + case types.TARRAY: // Array of 1 element, check if element is fat if t.NumElem() == 1 { return isfat(t.Elem()) } return true - case TSTRUCT: + case types.TSTRUCT: // Struct with 1 field, check if field is fat if t.NumFields() == 1 { return isfat(t.Field(0).Type) @@ -1319,3 +1498,38 @@ func isfat(t *types.Type) bool { return false } + +// WriteFuncMap writes the pointer bitmaps for bodyless function fn's +// inputs and outputs as the value of symbol .args_stackmap. +// If fn has outputs, two bitmaps are written, otherwise just one. +func WriteFuncMap(fn *ir.Func, abiInfo *abi.ABIParamResultInfo) { + if ir.FuncName(fn) == "_" || fn.Sym().Linkname != "" { + return + } + nptr := int(abiInfo.ArgWidth() / int64(types.PtrSize)) + bv := bitvec.New(int32(nptr) * 2) + + for _, p := range abiInfo.InParams() { + typebits.Set(p.Type, p.FrameOffset(abiInfo), bv) + } + + nbitmap := 1 + if fn.Type().NumResults() > 0 { + nbitmap = 2 + } + lsym := base.Ctxt.Lookup(fn.LSym.Name + ".args_stackmap") + off := objw.Uint32(lsym, 0, uint32(nbitmap)) + off = objw.Uint32(lsym, off, uint32(bv.N)) + off = objw.BitVec(lsym, off, bv) + + if fn.Type().NumResults() > 0 { + for _, p := range abiInfo.OutParams() { + if len(p.Registers) == 0 { + typebits.Set(p.Type, p.FrameOffset(abiInfo), bv) + } + } + off = objw.BitVec(lsym, off, bv) + } + + objw.Global(lsym, int32(off), obj.RODATA|obj.LOCAL) +} diff --git a/src/cmd/compile/internal/logopt/escape.go b/src/cmd/compile/internal/logopt/escape.go index 802f967aa669f2fc37379d849ddd749489ef32aa..9660e938b4ae12ece29e11bd71f3160b741b4c8b 100644 --- a/src/cmd/compile/internal/logopt/escape.go +++ b/src/cmd/compile/internal/logopt/escape.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build go1.8 // +build go1.8 package logopt diff --git a/src/cmd/compile/internal/logopt/escape_bootstrap.go b/src/cmd/compile/internal/logopt/escape_bootstrap.go index 66ff0b8f220f9cc3992811ac5b54a88b7d96897e..cc04eaadfd117256df6ceb3b0ee4820209230db1 100644 --- a/src/cmd/compile/internal/logopt/escape_bootstrap.go +++ b/src/cmd/compile/internal/logopt/escape_bootstrap.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !go1.8 // +build !go1.8 package logopt diff --git a/src/cmd/compile/internal/logopt/log_opts.go b/src/cmd/compile/internal/logopt/log_opts.go index 37a049d6403dcb5216d41912a308b5e43433cb8d..97ebf569448982ddc478cbe5cc2cf6211aa8bc2e 100644 --- a/src/cmd/compile/internal/logopt/log_opts.go +++ b/src/cmd/compile/internal/logopt/log_opts.go @@ -6,10 +6,10 @@ package logopt import ( "cmd/internal/obj" - "cmd/internal/objabi" "cmd/internal/src" "encoding/json" "fmt" + "internal/buildcfg" "io" "log" "net/url" @@ -408,7 +408,7 @@ func uprootedPath(filename string) string { if !strings.HasPrefix(filename, "$GOROOT/") { return filename } - return objabi.GOROOT + filename[len("$GOROOT"):] + return buildcfg.GOROOT + filename[len("$GOROOT"):] } // FlushLoggedOpts flushes all the accumulated optimization log entries. @@ -448,7 +448,7 @@ func FlushLoggedOpts(ctxt *obj.Link, slashPkgPath string) { currentFile = p0f w = writerForLSP(subdirpath, currentFile) encoder = json.NewEncoder(w) - encoder.Encode(VersionHeader{Version: 0, Package: slashPkgPath, Goos: objabi.GOOS, Goarch: objabi.GOARCH, GcVersion: objabi.Version, File: currentFile}) + encoder.Encode(VersionHeader{Version: 0, Package: slashPkgPath, Goos: buildcfg.GOOS, Goarch: buildcfg.GOARCH, GcVersion: buildcfg.Version, File: currentFile}) } // The first "target" is the most important one. diff --git a/src/cmd/compile/internal/logopt/logopt_test.go b/src/cmd/compile/internal/logopt/logopt_test.go index e121c1abd228638c7d7cacd46e06b1110ecadc24..71976174b03517def512290e47d86a4980a02963 100644 --- a/src/cmd/compile/internal/logopt/logopt_test.go +++ b/src/cmd/compile/internal/logopt/logopt_test.go @@ -132,7 +132,7 @@ func TestLogOpt(t *testing.T) { // Check at both 1 and 8-byte alignments. t.Run("Copy", func(t *testing.T) { const copyCode = `package x -func s128a1(x *[128]int8) [128]int8 { +func s128a1(x *[128]int8) [128]int8 { return *x } func s127a1(x *[127]int8) [127]int8 { @@ -219,7 +219,7 @@ func s15a8(x *[15]int64) [15]int64 { `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":11},"end":{"line":4,"character":11}}},"message":"inlineLoc"},`+ `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow: from \u0026y.b (address-of)"},`+ `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":9},"end":{"line":4,"character":9}}},"message":"inlineLoc"},`+ - `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow: from ~R0 = \u003cN\u003e (assign-pair)"},`+ + `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow: from ~R0 = \u0026y.b (assign-pair)"},`+ `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":3},"end":{"line":9,"character":3}}},"message":"escflow: flow: ~r2 = ~R0:"},`+ `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":3},"end":{"line":9,"character":3}}},"message":"escflow: from return (*int)(~R0) (return)"}]}`) }) diff --git a/src/cmd/compile/internal/mips/galign.go b/src/cmd/compile/internal/mips/galign.go index be40c16dde0b7baeca74f251714c6ef9636c9adf..f892923ba038f32a370873af79a9671e55e65a1b 100644 --- a/src/cmd/compile/internal/mips/galign.go +++ b/src/cmd/compile/internal/mips/galign.go @@ -5,24 +5,24 @@ package mips import ( - "cmd/compile/internal/gc" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/internal/obj/mips" - "cmd/internal/objabi" + "internal/buildcfg" ) -func Init(arch *gc.Arch) { +func Init(arch *ssagen.ArchInfo) { arch.LinkArch = &mips.Linkmips - if objabi.GOARCH == "mipsle" { + if buildcfg.GOARCH == "mipsle" { arch.LinkArch = &mips.Linkmipsle } arch.REGSP = mips.REGSP arch.MAXWIDTH = (1 << 31) - 1 - arch.SoftFloat = (objabi.GOMIPS == "softfloat") + arch.SoftFloat = (buildcfg.GOMIPS == "softfloat") arch.ZeroRange = zerorange arch.Ginsnop = ginsnop arch.Ginsnopdefer = ginsnop - arch.SSAMarkMoves = func(s *gc.SSAGenState, b *ssa.Block) {} + arch.SSAMarkMoves = func(s *ssagen.State, b *ssa.Block) {} arch.SSAGenValue = ssaGenValue arch.SSAGenBlock = ssaGenBlock } diff --git a/src/cmd/compile/internal/mips/ggen.go b/src/cmd/compile/internal/mips/ggen.go index 5e867721c3fcf6b08cd5140ceb43eb8013f6f700..1a5125207dd0ea3b2fd10f582f1f81ba5e80f813 100644 --- a/src/cmd/compile/internal/mips/ggen.go +++ b/src/cmd/compile/internal/mips/ggen.go @@ -5,20 +5,22 @@ package mips import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/objw" + "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/mips" ) // TODO(mips): implement DUFFZERO -func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { +func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { if cnt == 0 { return p } - if cnt < int64(4*gc.Widthptr) { - for i := int64(0); i < cnt; i += int64(gc.Widthptr) { - p = pp.Appendpp(p, mips.AMOVW, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGSP, gc.Ctxt.FixedFrameSize()+off+i) + if cnt < int64(4*types.PtrSize) { + for i := int64(0); i < cnt; i += int64(types.PtrSize) { + p = pp.Append(p, mips.AMOVW, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGSP, base.Ctxt.FixedFrameSize()+off+i) } } else { //fmt.Printf("zerorange frame:%v, lo: %v, hi:%v \n", frame ,lo, hi) @@ -28,22 +30,22 @@ func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { // MOVW R0, (Widthptr)r1 // ADD $Widthptr, r1 // BNE r1, r2, loop - p = pp.Appendpp(p, mips.AADD, obj.TYPE_CONST, 0, gc.Ctxt.FixedFrameSize()+off-4, obj.TYPE_REG, mips.REGRT1, 0) + p = pp.Append(p, mips.AADD, obj.TYPE_CONST, 0, base.Ctxt.FixedFrameSize()+off-4, obj.TYPE_REG, mips.REGRT1, 0) p.Reg = mips.REGSP - p = pp.Appendpp(p, mips.AADD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, mips.REGRT2, 0) + p = pp.Append(p, mips.AADD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, mips.REGRT2, 0) p.Reg = mips.REGRT1 - p = pp.Appendpp(p, mips.AMOVW, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGRT1, int64(gc.Widthptr)) + p = pp.Append(p, mips.AMOVW, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGRT1, int64(types.PtrSize)) p1 := p - p = pp.Appendpp(p, mips.AADD, obj.TYPE_CONST, 0, int64(gc.Widthptr), obj.TYPE_REG, mips.REGRT1, 0) - p = pp.Appendpp(p, mips.ABNE, obj.TYPE_REG, mips.REGRT1, 0, obj.TYPE_BRANCH, 0, 0) + p = pp.Append(p, mips.AADD, obj.TYPE_CONST, 0, int64(types.PtrSize), obj.TYPE_REG, mips.REGRT1, 0) + p = pp.Append(p, mips.ABNE, obj.TYPE_REG, mips.REGRT1, 0, obj.TYPE_BRANCH, 0, 0) p.Reg = mips.REGRT2 - gc.Patch(p, p1) + p.To.SetTarget(p1) } return p } -func ginsnop(pp *gc.Progs) *obj.Prog { +func ginsnop(pp *objw.Progs) *obj.Prog { p := pp.Prog(mips.ANOR) p.From.Type = obj.TYPE_REG p.From.Reg = mips.REG_R0 diff --git a/src/cmd/compile/internal/mips/ssa.go b/src/cmd/compile/internal/mips/ssa.go index 9d11c6bf53ae78652fa7fadf2eedff4943dcc385..e0447f38cbf23347c05a831d304581259eb9b092 100644 --- a/src/cmd/compile/internal/mips/ssa.go +++ b/src/cmd/compile/internal/mips/ssa.go @@ -7,9 +7,11 @@ package mips import ( "math" - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/logopt" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/mips" @@ -75,7 +77,7 @@ func storeByType(t *types.Type, r int16) obj.As { panic("bad store type") } -func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { +func ssaGenValue(s *ssagen.State, v *ssa.Value) { switch v.Op { case ssa.OpCopy, ssa.OpMIPSMOVWreg: t := v.Type @@ -110,9 +112,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Reg = y } case ssa.OpMIPSMOVWnop: - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } // nothing to do case ssa.OpLoadReg: if v.Type.IsFlags() { @@ -121,7 +120,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { } r := v.Reg() p := s.Prog(loadByType(v.Type, r)) - gc.AddrAuto(&p.From, v.Args[0]) + ssagen.AddrAuto(&p.From, v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = r if isHILO(r) { @@ -151,7 +150,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(storeByType(v.Type, r)) p.From.Type = obj.TYPE_REG p.From.Reg = r - gc.AddrAuto(&p.To, v) + ssagen.AddrAuto(&p.To, v) case ssa.OpMIPSADD, ssa.OpMIPSSUB, ssa.OpMIPSAND, @@ -242,9 +241,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpMIPSCMOVZ: - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[2].Reg() @@ -252,9 +248,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpMIPSCMOVZzero: - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[1].Reg() @@ -286,10 +279,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { v.Fatalf("aux is of unknown type %T", v.Aux) case *obj.LSym: wantreg = "SB" - gc.AddAux(&p.From, v) - case *gc.Node: + ssagen.AddAux(&p.From, v) + case *ir.Name: wantreg = "SP" - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) case nil: // No sym, just MOVW $off(SP), R wantreg = "SP" @@ -310,7 +303,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpMIPSMOVBstore, @@ -323,7 +316,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpMIPSMOVBstorezero, ssa.OpMIPSMOVHstorezero, ssa.OpMIPSMOVWstorezero: @@ -332,7 +325,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = mips.REGZERO p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpMIPSMOVBreg, ssa.OpMIPSMOVBUreg, ssa.OpMIPSMOVHreg, @@ -370,6 +363,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpMIPSMOVDF, ssa.OpMIPSNEGF, ssa.OpMIPSNEGD, + ssa.OpMIPSSQRTF, ssa.OpMIPSSQRTD, ssa.OpMIPSCLZ: p := s.Prog(v.Op.Asm()) @@ -425,7 +419,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p4.From.Reg = v.Args[1].Reg() p4.Reg = mips.REG_R1 p4.To.Type = obj.TYPE_BRANCH - gc.Patch(p4, p2) + p4.To.SetTarget(p2) case ssa.OpMIPSLoweredMove: // SUBU $4, R1 // MOVW 4(R1), Rtmp @@ -478,7 +472,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p6.From.Reg = v.Args[2].Reg() p6.Reg = mips.REG_R1 p6.To.Type = obj.TYPE_BRANCH - gc.Patch(p6, p2) + p6.To.SetTarget(p2) case ssa.OpMIPSCALLstatic, ssa.OpMIPSCALLclosure, ssa.OpMIPSCALLinter: s.Call(v) case ssa.OpMIPSLoweredWB: @@ -490,13 +484,13 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.BoundsCheckFunc[v.AuxInt] + p.To.Sym = ssagen.BoundsCheckFunc[v.AuxInt] s.UseArgs(8) // space used in callee args area by assembly stubs case ssa.OpMIPSLoweredPanicExtendA, ssa.OpMIPSLoweredPanicExtendB, ssa.OpMIPSLoweredPanicExtendC: p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.ExtendCheckFunc[v.AuxInt] + p.To.Sym = ssagen.ExtendCheckFunc[v.AuxInt] s.UseArgs(12) // space used in callee args area by assembly stubs case ssa.OpMIPSLoweredAtomicLoad8, ssa.OpMIPSLoweredAtomicLoad32: @@ -575,7 +569,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.From.Type = obj.TYPE_REG p3.From.Reg = mips.REGTMP p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) s.Prog(mips.ASYNC) case ssa.OpMIPSLoweredAtomicAdd: @@ -611,7 +605,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.From.Type = obj.TYPE_REG p3.From.Reg = mips.REGTMP p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) s.Prog(mips.ASYNC) @@ -655,7 +649,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.From.Type = obj.TYPE_REG p3.From.Reg = mips.REGTMP p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) s.Prog(mips.ASYNC) @@ -699,7 +693,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.From.Type = obj.TYPE_REG p3.From.Reg = mips.REGTMP p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) s.Prog(mips.ASYNC) @@ -748,26 +742,26 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p5.From.Type = obj.TYPE_REG p5.From.Reg = v.Reg0() p5.To.Type = obj.TYPE_BRANCH - gc.Patch(p5, p1) + p5.To.SetTarget(p1) s.Prog(mips.ASYNC) p6 := s.Prog(obj.ANOP) - gc.Patch(p2, p6) + p2.To.SetTarget(p6) case ssa.OpMIPSLoweredNilCheck: // Issue a load which will fault if arg is nil. p := s.Prog(mips.AMOVB) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = mips.REGTMP if logopt.Enabled() { logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name) } - if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers - gc.Warnl(v.Pos, "generated nil check") + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") } case ssa.OpMIPSFPFlagTrue, ssa.OpMIPSFPFlagFalse: @@ -791,12 +785,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.OpMIPSLoweredGetClosurePtr: // Closure pointer is R22 (mips.REGCTXT). - gc.CheckLoweredGetClosurePtr(v) + ssagen.CheckLoweredGetClosurePtr(v) case ssa.OpMIPSLoweredGetCallerSP: // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(mips.AMOVW) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -gc.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.FixedFrameSize() p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -804,7 +798,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.AGETCALLERPC) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() - case ssa.OpClobber: + case ssa.OpClobber, ssa.OpClobberReg: // TODO: implement for clobberdead experiment. Nop is ok for now. default: v.Fatalf("genValue not implemented: %s", v.LongString()) @@ -824,13 +818,13 @@ var blockJump = map[ssa.BlockKind]struct { ssa.BlockMIPSFPF: {mips.ABFPF, mips.ABFPT}, } -func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { switch b.Kind { case ssa.BlockPlain: if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockDefer: // defer returns in R1: @@ -841,11 +835,11 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.From.Reg = mips.REGZERO p.Reg = mips.REG_R1 p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[1].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()}) if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockExit: case ssa.BlockRet: diff --git a/src/cmd/compile/internal/mips64/galign.go b/src/cmd/compile/internal/mips64/galign.go index 90c381a50b6df3445d7a795fbfab472879b64722..af81366e51bdf51c4cd88b55da03440da29773c6 100644 --- a/src/cmd/compile/internal/mips64/galign.go +++ b/src/cmd/compile/internal/mips64/galign.go @@ -5,25 +5,25 @@ package mips64 import ( - "cmd/compile/internal/gc" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/internal/obj/mips" - "cmd/internal/objabi" + "internal/buildcfg" ) -func Init(arch *gc.Arch) { +func Init(arch *ssagen.ArchInfo) { arch.LinkArch = &mips.Linkmips64 - if objabi.GOARCH == "mips64le" { + if buildcfg.GOARCH == "mips64le" { arch.LinkArch = &mips.Linkmips64le } arch.REGSP = mips.REGSP arch.MAXWIDTH = 1 << 50 - arch.SoftFloat = objabi.GOMIPS64 == "softfloat" + arch.SoftFloat = buildcfg.GOMIPS64 == "softfloat" arch.ZeroRange = zerorange arch.Ginsnop = ginsnop arch.Ginsnopdefer = ginsnop - arch.SSAMarkMoves = func(s *gc.SSAGenState, b *ssa.Block) {} + arch.SSAMarkMoves = func(s *ssagen.State, b *ssa.Block) {} arch.SSAGenValue = ssaGenValue arch.SSAGenBlock = ssaGenBlock } diff --git a/src/cmd/compile/internal/mips64/ggen.go b/src/cmd/compile/internal/mips64/ggen.go index 04e7a66e417b95c8a5bcd957fb11566f71e9ba45..37bb871958bf4e827dc6d829c4f4853951b93351 100644 --- a/src/cmd/compile/internal/mips64/ggen.go +++ b/src/cmd/compile/internal/mips64/ggen.go @@ -5,26 +5,28 @@ package mips64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/mips" ) -func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { +func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { if cnt == 0 { return p } - if cnt < int64(4*gc.Widthptr) { - for i := int64(0); i < cnt; i += int64(gc.Widthptr) { - p = pp.Appendpp(p, mips.AMOVV, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGSP, 8+off+i) + if cnt < int64(4*types.PtrSize) { + for i := int64(0); i < cnt; i += int64(types.PtrSize) { + p = pp.Append(p, mips.AMOVV, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGSP, 8+off+i) } - } else if cnt <= int64(128*gc.Widthptr) { - p = pp.Appendpp(p, mips.AADDV, obj.TYPE_CONST, 0, 8+off-8, obj.TYPE_REG, mips.REGRT1, 0) + } else if cnt <= int64(128*types.PtrSize) { + p = pp.Append(p, mips.AADDV, obj.TYPE_CONST, 0, 8+off-8, obj.TYPE_REG, mips.REGRT1, 0) p.Reg = mips.REGSP - p = pp.Appendpp(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) + p = pp.Append(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffzero - p.To.Offset = 8 * (128 - cnt/int64(gc.Widthptr)) + p.To.Sym = ir.Syms.Duffzero + p.To.Offset = 8 * (128 - cnt/int64(types.PtrSize)) } else { // ADDV $(8+frame+lo-8), SP, r1 // ADDV $cnt, r1, r2 @@ -32,22 +34,22 @@ func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { // MOVV R0, (Widthptr)r1 // ADDV $Widthptr, r1 // BNE r1, r2, loop - p = pp.Appendpp(p, mips.AADDV, obj.TYPE_CONST, 0, 8+off-8, obj.TYPE_REG, mips.REGRT1, 0) + p = pp.Append(p, mips.AADDV, obj.TYPE_CONST, 0, 8+off-8, obj.TYPE_REG, mips.REGRT1, 0) p.Reg = mips.REGSP - p = pp.Appendpp(p, mips.AADDV, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, mips.REGRT2, 0) + p = pp.Append(p, mips.AADDV, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, mips.REGRT2, 0) p.Reg = mips.REGRT1 - p = pp.Appendpp(p, mips.AMOVV, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGRT1, int64(gc.Widthptr)) + p = pp.Append(p, mips.AMOVV, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGRT1, int64(types.PtrSize)) p1 := p - p = pp.Appendpp(p, mips.AADDV, obj.TYPE_CONST, 0, int64(gc.Widthptr), obj.TYPE_REG, mips.REGRT1, 0) - p = pp.Appendpp(p, mips.ABNE, obj.TYPE_REG, mips.REGRT1, 0, obj.TYPE_BRANCH, 0, 0) + p = pp.Append(p, mips.AADDV, obj.TYPE_CONST, 0, int64(types.PtrSize), obj.TYPE_REG, mips.REGRT1, 0) + p = pp.Append(p, mips.ABNE, obj.TYPE_REG, mips.REGRT1, 0, obj.TYPE_BRANCH, 0, 0) p.Reg = mips.REGRT2 - gc.Patch(p, p1) + p.To.SetTarget(p1) } return p } -func ginsnop(pp *gc.Progs) *obj.Prog { +func ginsnop(pp *objw.Progs) *obj.Prog { p := pp.Prog(mips.ANOR) p.From.Type = obj.TYPE_REG p.From.Reg = mips.REG_R0 diff --git a/src/cmd/compile/internal/mips64/ssa.go b/src/cmd/compile/internal/mips64/ssa.go index 2727c4d8a8340b6481f972a277f98844c9067623..e821a00876fa799bc6b0a4413ed48acb445e336e 100644 --- a/src/cmd/compile/internal/mips64/ssa.go +++ b/src/cmd/compile/internal/mips64/ssa.go @@ -7,9 +7,11 @@ package mips64 import ( "math" - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/logopt" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/mips" @@ -83,7 +85,7 @@ func storeByType(t *types.Type, r int16) obj.As { panic("bad store type") } -func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { +func ssaGenValue(s *ssagen.State, v *ssa.Value) { switch v.Op { case ssa.OpCopy, ssa.OpMIPS64MOVVreg: if v.Type.IsMemory() { @@ -113,9 +115,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Reg = y } case ssa.OpMIPS64MOVVnop: - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } // nothing to do case ssa.OpLoadReg: if v.Type.IsFlags() { @@ -124,7 +123,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { } r := v.Reg() p := s.Prog(loadByType(v.Type, r)) - gc.AddrAuto(&p.From, v.Args[0]) + ssagen.AddrAuto(&p.From, v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = r if isHILO(r) { @@ -154,7 +153,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(storeByType(v.Type, r)) p.From.Type = obj.TYPE_REG p.From.Reg = r - gc.AddrAuto(&p.To, v) + ssagen.AddrAuto(&p.To, v) case ssa.OpMIPS64ADDV, ssa.OpMIPS64SUBV, ssa.OpMIPS64AND, @@ -260,10 +259,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { v.Fatalf("aux is of unknown type %T", v.Aux) case *obj.LSym: wantreg = "SB" - gc.AddAux(&p.From, v) - case *gc.Node: + ssagen.AddAux(&p.From, v) + case *ir.Name: wantreg = "SP" - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) case nil: // No sym, just MOVV $off(SP), R wantreg = "SP" @@ -286,7 +285,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpMIPS64MOVBstore, @@ -300,7 +299,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpMIPS64MOVBstorezero, ssa.OpMIPS64MOVHstorezero, ssa.OpMIPS64MOVWstorezero, @@ -310,7 +309,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = mips.REGZERO p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpMIPS64MOVBreg, ssa.OpMIPS64MOVBUreg, ssa.OpMIPS64MOVHreg, @@ -356,6 +355,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpMIPS64MOVDF, ssa.OpMIPS64NEGF, ssa.OpMIPS64NEGD, + ssa.OpMIPS64SQRTF, ssa.OpMIPS64SQRTD: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG @@ -381,7 +381,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p = s.Prog(obj.ADUFFZERO) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffzero + p.To.Sym = ir.Syms.Duffzero p.To.Offset = v.AuxInt case ssa.OpMIPS64LoweredZero: // SUBV $8, R1 @@ -426,12 +426,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p4.From.Reg = v.Args[1].Reg() p4.Reg = mips.REG_R1 p4.To.Type = obj.TYPE_BRANCH - gc.Patch(p4, p2) + p4.To.SetTarget(p2) case ssa.OpMIPS64DUFFCOPY: p := s.Prog(obj.ADUFFCOPY) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffcopy + p.To.Sym = ir.Syms.Duffcopy p.To.Offset = v.AuxInt case ssa.OpMIPS64LoweredMove: // SUBV $8, R1 @@ -488,7 +488,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p6.From.Reg = v.Args[2].Reg() p6.Reg = mips.REG_R1 p6.To.Type = obj.TYPE_BRANCH - gc.Patch(p6, p2) + p6.To.SetTarget(p2) case ssa.OpMIPS64CALLstatic, ssa.OpMIPS64CALLclosure, ssa.OpMIPS64CALLinter: s.Call(v) case ssa.OpMIPS64LoweredWB: @@ -500,7 +500,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.BoundsCheckFunc[v.AuxInt] + p.To.Sym = ssagen.BoundsCheckFunc[v.AuxInt] s.UseArgs(16) // space used in callee args area by assembly stubs case ssa.OpMIPS64LoweredAtomicLoad8, ssa.OpMIPS64LoweredAtomicLoad32, ssa.OpMIPS64LoweredAtomicLoad64: as := mips.AMOVV @@ -577,7 +577,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.From.Type = obj.TYPE_REG p3.From.Reg = mips.REGTMP p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) s.Prog(mips.ASYNC) case ssa.OpMIPS64LoweredAtomicAdd32, ssa.OpMIPS64LoweredAtomicAdd64: // SYNC @@ -614,7 +614,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.From.Type = obj.TYPE_REG p3.From.Reg = mips.REGTMP p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) s.Prog(mips.ASYNC) p4 := s.Prog(mips.AADDVU) p4.From.Type = obj.TYPE_REG @@ -657,7 +657,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.From.Type = obj.TYPE_REG p3.From.Reg = mips.REGTMP p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) s.Prog(mips.ASYNC) p4 := s.Prog(mips.AADDVU) p4.From.Type = obj.TYPE_CONST @@ -710,22 +710,22 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p5.From.Type = obj.TYPE_REG p5.From.Reg = v.Reg0() p5.To.Type = obj.TYPE_BRANCH - gc.Patch(p5, p1) + p5.To.SetTarget(p1) p6 := s.Prog(mips.ASYNC) - gc.Patch(p2, p6) + p2.To.SetTarget(p6) case ssa.OpMIPS64LoweredNilCheck: // Issue a load which will fault if arg is nil. p := s.Prog(mips.AMOVB) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = mips.REGTMP if logopt.Enabled() { logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name) } - if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers - gc.Warnl(v.Pos, "generated nil check") + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") } case ssa.OpMIPS64FPFlagTrue, ssa.OpMIPS64FPFlagFalse: @@ -749,15 +749,15 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.To.Type = obj.TYPE_REG p3.To.Reg = v.Reg() p4 := s.Prog(obj.ANOP) // not a machine instruction, for branch to land - gc.Patch(p2, p4) + p2.To.SetTarget(p4) case ssa.OpMIPS64LoweredGetClosurePtr: // Closure pointer is R22 (mips.REGCTXT). - gc.CheckLoweredGetClosurePtr(v) + ssagen.CheckLoweredGetClosurePtr(v) case ssa.OpMIPS64LoweredGetCallerSP: // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(mips.AMOVV) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -gc.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.FixedFrameSize() p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -765,7 +765,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.AGETCALLERPC) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() - case ssa.OpClobber: + case ssa.OpClobber, ssa.OpClobberReg: // TODO: implement for clobberdead experiment. Nop is ok for now. default: v.Fatalf("genValue not implemented: %s", v.LongString()) @@ -785,13 +785,13 @@ var blockJump = map[ssa.BlockKind]struct { ssa.BlockMIPS64FPF: {mips.ABFPF, mips.ABFPT}, } -func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { switch b.Kind { case ssa.BlockPlain: if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockDefer: // defer returns in R1: @@ -802,11 +802,11 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.From.Reg = mips.REGZERO p.Reg = mips.REG_R1 p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[1].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()}) if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockExit: case ssa.BlockRet: diff --git a/src/cmd/compile/internal/noder/decl.go b/src/cmd/compile/internal/noder/decl.go new file mode 100644 index 0000000000000000000000000000000000000000..4ca2eb4740a4916e171d2cfe278a38c9b472e9f1 --- /dev/null +++ b/src/cmd/compile/internal/noder/decl.go @@ -0,0 +1,260 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/compile/internal/types2" +) + +// TODO(mdempsky): Skip blank declarations? Probably only safe +// for declarations without pragmas. + +func (g *irgen) decls(decls []syntax.Decl) []ir.Node { + var res ir.Nodes + for _, decl := range decls { + switch decl := decl.(type) { + case *syntax.ConstDecl: + g.constDecl(&res, decl) + case *syntax.FuncDecl: + g.funcDecl(&res, decl) + case *syntax.TypeDecl: + if ir.CurFunc == nil { + continue // already handled in irgen.generate + } + g.typeDecl(&res, decl) + case *syntax.VarDecl: + g.varDecl(&res, decl) + default: + g.unhandled("declaration", decl) + } + } + return res +} + +func (g *irgen) importDecl(p *noder, decl *syntax.ImportDecl) { + // TODO(mdempsky): Merge with gcimports so we don't have to import + // packages twice. + + g.pragmaFlags(decl.Pragma, 0) + + ipkg := importfile(decl) + if ipkg == ir.Pkgs.Unsafe { + p.importedUnsafe = true + } + if ipkg.Path == "embed" { + p.importedEmbed = true + } +} + +func (g *irgen) constDecl(out *ir.Nodes, decl *syntax.ConstDecl) { + g.pragmaFlags(decl.Pragma, 0) + + for _, name := range decl.NameList { + name, obj := g.def(name) + + // For untyped numeric constants, make sure the value + // representation matches what the rest of the + // compiler (really just iexport) expects. + // TODO(mdempsky): Revisit after #43891 is resolved. + val := obj.(*types2.Const).Val() + switch name.Type() { + case types.UntypedInt, types.UntypedRune: + val = constant.ToInt(val) + case types.UntypedFloat: + val = constant.ToFloat(val) + case types.UntypedComplex: + val = constant.ToComplex(val) + } + name.SetVal(val) + + out.Append(ir.NewDecl(g.pos(decl), ir.ODCLCONST, name)) + } +} + +func (g *irgen) funcDecl(out *ir.Nodes, decl *syntax.FuncDecl) { + fn := ir.NewFunc(g.pos(decl)) + fn.Nname, _ = g.def(decl.Name) + fn.Nname.Func = fn + fn.Nname.Defn = fn + + fn.Pragma = g.pragmaFlags(decl.Pragma, funcPragmas) + if fn.Pragma&ir.Systemstack != 0 && fn.Pragma&ir.Nosplit != 0 { + base.ErrorfAt(fn.Pos(), "go:nosplit and go:systemstack cannot be combined") + } + + if decl.Name.Value == "init" && decl.Recv == nil { + g.target.Inits = append(g.target.Inits, fn) + } + + g.funcBody(fn, decl.Recv, decl.Type, decl.Body) + + out.Append(fn) +} + +func (g *irgen) typeDecl(out *ir.Nodes, decl *syntax.TypeDecl) { + if decl.Alias { + name, _ := g.def(decl.Name) + g.pragmaFlags(decl.Pragma, 0) + + // TODO(mdempsky): This matches how typecheckdef marks aliases for + // export, but this won't generalize to exporting function-scoped + // type aliases. We should maybe just use n.Alias() instead. + if ir.CurFunc == nil { + name.Sym().Def = ir.TypeNode(name.Type()) + } + + out.Append(ir.NewDecl(g.pos(decl), ir.ODCLTYPE, name)) + return + } + + // Prevent size calculations until we set the underlying type. + types.DeferCheckSize() + + name, obj := g.def(decl.Name) + ntyp, otyp := name.Type(), obj.Type() + if ir.CurFunc != nil { + typecheck.TypeGen++ + ntyp.Vargen = typecheck.TypeGen + } + + pragmas := g.pragmaFlags(decl.Pragma, typePragmas) + name.SetPragma(pragmas) // TODO(mdempsky): Is this still needed? + + if pragmas&ir.NotInHeap != 0 { + ntyp.SetNotInHeap(true) + } + + // We need to use g.typeExpr(decl.Type) here to ensure that for + // chained, defined-type declarations like: + // + // type T U + // + // //go:notinheap + // type U struct { … } + // + // we mark both T and U as NotInHeap. If we instead used just + // g.typ(otyp.Underlying()), then we'd instead set T's underlying + // type directly to the struct type (which is not marked NotInHeap) + // and fail to mark T as NotInHeap. + // + // Also, we rely here on Type.SetUnderlying allowing passing a + // defined type and handling forward references like from T to U + // above. Contrast with go/types's Named.SetUnderlying, which + // disallows this. + // + // [mdempsky: Subtleties like these are why I always vehemently + // object to new type pragmas.] + ntyp.SetUnderlying(g.typeExpr(decl.Type)) + if len(decl.TParamList) > 0 { + // Set HasTParam if there are any tparams, even if no tparams are + // used in the type itself (e.g., if it is an empty struct, or no + // fields in the struct use the tparam). + ntyp.SetHasTParam(true) + } + types.ResumeCheckSize() + + if otyp, ok := otyp.(*types2.Named); ok && otyp.NumMethods() != 0 { + methods := make([]*types.Field, otyp.NumMethods()) + for i := range methods { + m := otyp.Method(i) + meth := g.obj(m) + methods[i] = types.NewField(meth.Pos(), g.selector(m), meth.Type()) + methods[i].Nname = meth + } + ntyp.Methods().Set(methods) + } + + out.Append(ir.NewDecl(g.pos(decl), ir.ODCLTYPE, name)) +} + +func (g *irgen) varDecl(out *ir.Nodes, decl *syntax.VarDecl) { + pos := g.pos(decl) + names := make([]*ir.Name, len(decl.NameList)) + for i, name := range decl.NameList { + names[i], _ = g.def(name) + } + values := g.exprList(decl.Values) + + if decl.Pragma != nil { + pragma := decl.Pragma.(*pragmas) + // TODO(mdempsky): Plumb noder.importedEmbed through to here. + varEmbed(g.makeXPos, names[0], decl, pragma, true) + g.reportUnused(pragma) + } + + var as2 *ir.AssignListStmt + if len(values) != 0 && len(names) != len(values) { + as2 = ir.NewAssignListStmt(pos, ir.OAS2, make([]ir.Node, len(names)), values) + } + + for i, name := range names { + if ir.CurFunc != nil { + out.Append(ir.NewDecl(pos, ir.ODCL, name)) + } + if as2 != nil { + as2.Lhs[i] = name + name.Defn = as2 + } else { + as := ir.NewAssignStmt(pos, name, nil) + if len(values) != 0 { + as.Y = values[i] + name.Defn = as + } else if ir.CurFunc == nil { + name.Defn = as + } + lhs := []ir.Node{as.X} + rhs := []ir.Node{} + if as.Y != nil { + rhs = []ir.Node{as.Y} + } + transformAssign(as, lhs, rhs) + as.X = lhs[0] + if as.Y != nil { + as.Y = rhs[0] + } + as.SetTypecheck(1) + out.Append(as) + } + } + if as2 != nil { + transformAssign(as2, as2.Lhs, as2.Rhs) + as2.SetTypecheck(1) + out.Append(as2) + } +} + +// pragmaFlags returns any specified pragma flags included in allowed, +// and reports errors about any other, unexpected pragmas. +func (g *irgen) pragmaFlags(pragma syntax.Pragma, allowed ir.PragmaFlag) ir.PragmaFlag { + if pragma == nil { + return 0 + } + p := pragma.(*pragmas) + present := p.Flag & allowed + p.Flag &^= allowed + g.reportUnused(p) + return present +} + +// reportUnused reports errors about any unused pragmas. +func (g *irgen) reportUnused(pragma *pragmas) { + for _, pos := range pragma.Pos { + if pos.Flag&pragma.Flag != 0 { + base.ErrorfAt(g.makeXPos(pos.Pos), "misplaced compiler directive") + } + } + if len(pragma.Embeds) > 0 { + for _, e := range pragma.Embeds { + base.ErrorfAt(g.makeXPos(e.Pos), "misplaced go:embed directive") + } + } +} diff --git a/src/cmd/compile/internal/noder/expr.go b/src/cmd/compile/internal/noder/expr.go new file mode 100644 index 0000000000000000000000000000000000000000..c7695ed920435291bab7c64a0cbfdfb08b8b8a7e --- /dev/null +++ b/src/cmd/compile/internal/noder/expr.go @@ -0,0 +1,400 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/compile/internal/types2" + "cmd/internal/src" +) + +func (g *irgen) expr(expr syntax.Expr) ir.Node { + if expr == nil { + return nil + } + + if expr, ok := expr.(*syntax.Name); ok && expr.Value == "_" { + return ir.BlankNode + } + + tv, ok := g.info.Types[expr] + if !ok { + base.FatalfAt(g.pos(expr), "missing type for %v (%T)", expr, expr) + } + switch { + case tv.IsBuiltin(): + // Qualified builtins, such as unsafe.Add and unsafe.Slice. + if expr, ok := expr.(*syntax.SelectorExpr); ok { + if name, ok := expr.X.(*syntax.Name); ok { + if _, ok := g.info.Uses[name].(*types2.PkgName); ok { + return g.use(expr.Sel) + } + } + } + return g.use(expr.(*syntax.Name)) + case tv.IsType(): + return ir.TypeNode(g.typ(tv.Type)) + case tv.IsValue(), tv.IsVoid(): + // ok + default: + base.FatalfAt(g.pos(expr), "unrecognized type-checker result") + } + + // The gc backend expects all expressions to have a concrete type, and + // types2 mostly satisfies this expectation already. But there are a few + // cases where the Go spec doesn't require converting to concrete type, + // and so types2 leaves them untyped. So we need to fix those up here. + typ := tv.Type + if basic, ok := typ.(*types2.Basic); ok && basic.Info()&types2.IsUntyped != 0 { + switch basic.Kind() { + case types2.UntypedNil: + // ok; can appear in type switch case clauses + // TODO(mdempsky): Handle as part of type switches instead? + case types2.UntypedBool: + typ = types2.Typ[types2.Bool] // expression in "if" or "for" condition + case types2.UntypedString: + typ = types2.Typ[types2.String] // argument to "append" or "copy" calls + default: + base.FatalfAt(g.pos(expr), "unexpected untyped type: %v", basic) + } + } + + // Constant expression. + if tv.Value != nil { + return Const(g.pos(expr), g.typ(typ), tv.Value) + } + + n := g.expr0(typ, expr) + if n.Typecheck() != 1 && n.Typecheck() != 3 { + base.FatalfAt(g.pos(expr), "missed typecheck: %+v", n) + } + if !g.match(n.Type(), typ, tv.HasOk()) { + base.FatalfAt(g.pos(expr), "expected %L to have type %v", n, typ) + } + return n +} + +func (g *irgen) expr0(typ types2.Type, expr syntax.Expr) ir.Node { + pos := g.pos(expr) + + switch expr := expr.(type) { + case *syntax.Name: + if _, isNil := g.info.Uses[expr].(*types2.Nil); isNil { + return Nil(pos, g.typ(typ)) + } + return g.use(expr) + + case *syntax.CompositeLit: + return g.compLit(typ, expr) + + case *syntax.FuncLit: + return g.funcLit(typ, expr) + + case *syntax.AssertExpr: + return Assert(pos, g.expr(expr.X), g.typeExpr(expr.Type)) + + case *syntax.CallExpr: + fun := g.expr(expr.Fun) + + // The key for the Inferred map is the CallExpr (if inferring + // types required the function arguments) or the IndexExpr below + // (if types could be inferred without the function arguments). + if inferred, ok := g.info.Inferred[expr]; ok && len(inferred.Targs) > 0 { + // This is the case where inferring types required the + // types of the function arguments. + targs := make([]ir.Node, len(inferred.Targs)) + for i, targ := range inferred.Targs { + targs[i] = ir.TypeNode(g.typ(targ)) + } + if fun.Op() == ir.OFUNCINST { + // Replace explicit type args with the full list that + // includes the additional inferred type args + fun.(*ir.InstExpr).Targs = targs + } else { + // Create a function instantiation here, given + // there are only inferred type args (e.g. + // min(5,6), where min is a generic function) + inst := ir.NewInstExpr(pos, ir.OFUNCINST, fun, targs) + typed(fun.Type(), inst) + fun = inst + } + + } + return Call(pos, g.typ(typ), fun, g.exprs(expr.ArgList), expr.HasDots) + + case *syntax.IndexExpr: + var targs []ir.Node + + if inferred, ok := g.info.Inferred[expr]; ok && len(inferred.Targs) > 0 { + // This is the partial type inference case where the types + // can be inferred from other type arguments without using + // the types of the function arguments. + targs = make([]ir.Node, len(inferred.Targs)) + for i, targ := range inferred.Targs { + targs[i] = ir.TypeNode(g.typ(targ)) + } + } else if _, ok := expr.Index.(*syntax.ListExpr); ok { + targs = g.exprList(expr.Index) + } else { + index := g.expr(expr.Index) + if index.Op() != ir.OTYPE { + // This is just a normal index expression + return Index(pos, g.typ(typ), g.expr(expr.X), index) + } + // This is generic function instantiation with a single type + targs = []ir.Node{index} + } + // This is a generic function instantiation (e.g. min[int]). + // Generic type instantiation is handled in the type + // section of expr() above (using g.typ). + x := g.expr(expr.X) + if x.Op() != ir.ONAME || x.Type().Kind() != types.TFUNC { + panic("Incorrect argument for generic func instantiation") + } + n := ir.NewInstExpr(pos, ir.OFUNCINST, x, targs) + typed(g.typ(typ), n) + return n + + case *syntax.ParenExpr: + return g.expr(expr.X) // skip parens; unneeded after parse+typecheck + + case *syntax.SelectorExpr: + // Qualified identifier. + if name, ok := expr.X.(*syntax.Name); ok { + if _, ok := g.info.Uses[name].(*types2.PkgName); ok { + return g.use(expr.Sel) + } + } + return g.selectorExpr(pos, typ, expr) + + case *syntax.SliceExpr: + return Slice(pos, g.typ(typ), g.expr(expr.X), g.expr(expr.Index[0]), g.expr(expr.Index[1]), g.expr(expr.Index[2])) + + case *syntax.Operation: + if expr.Y == nil { + return Unary(pos, g.typ(typ), g.op(expr.Op, unOps[:]), g.expr(expr.X)) + } + switch op := g.op(expr.Op, binOps[:]); op { + case ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE: + return Compare(pos, g.typ(typ), op, g.expr(expr.X), g.expr(expr.Y)) + default: + return Binary(pos, op, g.typ(typ), g.expr(expr.X), g.expr(expr.Y)) + } + + default: + g.unhandled("expression", expr) + panic("unreachable") + } +} + +// selectorExpr resolves the choice of ODOT, ODOTPTR, OCALLPART (eventually +// ODOTMETH & ODOTINTER), and OMETHEXPR and deals with embedded fields here rather +// than in typecheck.go. +func (g *irgen) selectorExpr(pos src.XPos, typ types2.Type, expr *syntax.SelectorExpr) ir.Node { + x := g.expr(expr.X) + if x.Type().HasTParam() { + // Leave a method call on a type param as an OXDOT, since it can + // only be fully transformed once it has an instantiated type. + n := ir.NewSelectorExpr(pos, ir.OXDOT, x, typecheck.Lookup(expr.Sel.Value)) + typed(g.typ(typ), n) + return n + } + + selinfo := g.info.Selections[expr] + // Everything up to the last selection is an implicit embedded field access, + // and the last selection is determined by selinfo.Kind(). + index := selinfo.Index() + embeds, last := index[:len(index)-1], index[len(index)-1] + + origx := x + for _, ix := range embeds { + x = Implicit(DotField(pos, x, ix)) + } + + kind := selinfo.Kind() + if kind == types2.FieldVal { + return DotField(pos, x, last) + } + + // TODO(danscales,mdempsky): Interface method sets are not sorted the + // same between types and types2. In particular, using "last" here + // without conversion will likely fail if an interface contains + // unexported methods from two different packages (due to cross-package + // interface embedding). + + var n ir.Node + method2 := selinfo.Obj().(*types2.Func) + + if kind == types2.MethodExpr { + // OMETHEXPR is unusual in using directly the node and type of the + // original OTYPE node (origx) before passing through embedded + // fields, even though the method is selected from the type + // (x.Type()) reached after following the embedded fields. We will + // actually drop any ODOT nodes we created due to the embedded + // fields. + n = MethodExpr(pos, origx, x.Type(), last) + } else { + // Add implicit addr/deref for method values, if needed. + if x.Type().IsInterface() { + n = DotMethod(pos, x, last) + } else { + recvType2 := method2.Type().(*types2.Signature).Recv().Type() + _, wantPtr := recvType2.(*types2.Pointer) + havePtr := x.Type().IsPtr() + + if havePtr != wantPtr { + if havePtr { + x = Implicit(Deref(pos, x.Type().Elem(), x)) + } else { + x = Implicit(Addr(pos, x)) + } + } + recvType2Base := recvType2 + if wantPtr { + recvType2Base = types2.AsPointer(recvType2).Elem() + } + if len(types2.AsNamed(recvType2Base).TParams()) > 0 { + // recvType2 is the original generic type that is + // instantiated for this method call. + // selinfo.Recv() is the instantiated type + recvType2 = recvType2Base + // method is the generic method associated with the gen type + method := g.obj(types2.AsNamed(recvType2).Method(last)) + n = ir.NewSelectorExpr(pos, ir.OCALLPART, x, method.Sym()) + n.(*ir.SelectorExpr).Selection = types.NewField(pos, method.Sym(), method.Type()) + n.(*ir.SelectorExpr).Selection.Nname = method + typed(method.Type(), n) + + // selinfo.Targs() are the types used to + // instantiate the type of receiver + targs2 := getTargs(selinfo) + targs := make([]ir.Node, len(targs2)) + for i, targ2 := range targs2 { + targs[i] = ir.TypeNode(g.typ(targ2)) + } + + // Create function instantiation with the type + // args for the receiver type for the method call. + n = ir.NewInstExpr(pos, ir.OFUNCINST, n, targs) + typed(g.typ(typ), n) + return n + } + + if !g.match(x.Type(), recvType2, false) { + base.FatalfAt(pos, "expected %L to have type %v", x, recvType2) + } else { + n = DotMethod(pos, x, last) + } + } + } + if have, want := n.Sym(), g.selector(method2); have != want { + base.FatalfAt(pos, "bad Sym: have %v, want %v", have, want) + } + return n +} + +// getTargs gets the targs associated with the receiver of a selected method +func getTargs(selinfo *types2.Selection) []types2.Type { + r := selinfo.Recv() + if p := types2.AsPointer(r); p != nil { + r = p.Elem() + } + n := types2.AsNamed(r) + if n == nil { + base.Fatalf("Incorrect type for selinfo %v", selinfo) + } + return n.TArgs() +} + +func (g *irgen) exprList(expr syntax.Expr) []ir.Node { + switch expr := expr.(type) { + case nil: + return nil + case *syntax.ListExpr: + return g.exprs(expr.ElemList) + default: + return []ir.Node{g.expr(expr)} + } +} + +func (g *irgen) exprs(exprs []syntax.Expr) []ir.Node { + nodes := make([]ir.Node, len(exprs)) + for i, expr := range exprs { + nodes[i] = g.expr(expr) + } + return nodes +} + +func (g *irgen) compLit(typ types2.Type, lit *syntax.CompositeLit) ir.Node { + if ptr, ok := typ.Underlying().(*types2.Pointer); ok { + n := ir.NewAddrExpr(g.pos(lit), g.compLit(ptr.Elem(), lit)) + n.SetOp(ir.OPTRLIT) + return typed(g.typ(typ), n) + } + + _, isStruct := typ.Underlying().(*types2.Struct) + + exprs := make([]ir.Node, len(lit.ElemList)) + for i, elem := range lit.ElemList { + switch elem := elem.(type) { + case *syntax.KeyValueExpr: + if isStruct { + exprs[i] = ir.NewStructKeyExpr(g.pos(elem), g.name(elem.Key.(*syntax.Name)), g.expr(elem.Value)) + } else { + exprs[i] = ir.NewKeyExpr(g.pos(elem), g.expr(elem.Key), g.expr(elem.Value)) + } + default: + exprs[i] = g.expr(elem) + } + } + + n := ir.NewCompLitExpr(g.pos(lit), ir.OCOMPLIT, nil, exprs) + typed(g.typ(typ), n) + return transformCompLit(n) +} + +func (g *irgen) funcLit(typ2 types2.Type, expr *syntax.FuncLit) ir.Node { + fn := ir.NewFunc(g.pos(expr)) + fn.SetIsHiddenClosure(ir.CurFunc != nil) + + fn.Nname = ir.NewNameAt(g.pos(expr), typecheck.ClosureName(ir.CurFunc)) + ir.MarkFunc(fn.Nname) + typ := g.typ(typ2) + fn.Nname.Func = fn + fn.Nname.Defn = fn + typed(typ, fn.Nname) + fn.SetTypecheck(1) + + fn.OClosure = ir.NewClosureExpr(g.pos(expr), fn) + typed(typ, fn.OClosure) + + g.funcBody(fn, nil, expr.Type, expr.Body) + + ir.FinishCaptureNames(fn.Pos(), ir.CurFunc, fn) + + // TODO(mdempsky): ir.CaptureName should probably handle + // copying these fields from the canonical variable. + for _, cv := range fn.ClosureVars { + cv.SetType(cv.Canonical().Type()) + cv.SetTypecheck(1) + cv.SetWalkdef(1) + } + + g.target.Decls = append(g.target.Decls, fn) + + return fn.OClosure +} + +func (g *irgen) typeExpr(typ syntax.Expr) *types.Type { + n := g.expr(typ) + if n.Op() != ir.OTYPE { + base.FatalfAt(g.pos(typ), "expected type: %L", n) + } + return n.Type() +} diff --git a/src/cmd/compile/internal/noder/func.go b/src/cmd/compile/internal/noder/func.go new file mode 100644 index 0000000000000000000000000000000000000000..702138157c53b8186760e616c9541aa0e90670d6 --- /dev/null +++ b/src/cmd/compile/internal/noder/func.go @@ -0,0 +1,74 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +func (g *irgen) funcBody(fn *ir.Func, recv *syntax.Field, sig *syntax.FuncType, block *syntax.BlockStmt) { + typecheck.Func(fn) + + // TODO(mdempsky): Remove uses of ir.CurFunc and + // typecheck.DeclContext after we stop relying on typecheck + // for desugaring. + outerfn, outerctxt := ir.CurFunc, typecheck.DeclContext + ir.CurFunc = fn + + typ := fn.Type() + if param := typ.Recv(); param != nil { + g.defParam(param, recv, ir.PPARAM) + } + for i, param := range typ.Params().FieldSlice() { + g.defParam(param, sig.ParamList[i], ir.PPARAM) + } + for i, result := range typ.Results().FieldSlice() { + g.defParam(result, sig.ResultList[i], ir.PPARAMOUT) + } + + // We may have type-checked a call to this function already and + // calculated its size, including parameter offsets. Now that we've + // created the parameter Names, force a recalculation to ensure + // their offsets are correct. + typ.Align = 0 + types.CalcSize(typ) + + if block != nil { + typecheck.DeclContext = ir.PAUTO + + fn.Body = g.stmts(block.List) + if fn.Body == nil { + fn.Body = []ir.Node{ir.NewBlockStmt(src.NoXPos, nil)} + } + fn.Endlineno = g.makeXPos(block.Rbrace) + + if base.Flag.Dwarf { + g.recordScopes(fn, sig) + } + } + + ir.CurFunc, typecheck.DeclContext = outerfn, outerctxt +} + +func (g *irgen) defParam(param *types.Field, decl *syntax.Field, class ir.Class) { + typecheck.DeclContext = class + + var name *ir.Name + if decl.Name != nil { + name, _ = g.def(decl.Name) + } else if class == ir.PPARAMOUT { + name = g.obj(g.info.Implicits[decl]) + } + + if name != nil { + param.Nname = name + param.Sym = name.Sym() // in case it was renamed + } +} diff --git a/src/cmd/compile/internal/noder/helpers.go b/src/cmd/compile/internal/noder/helpers.go new file mode 100644 index 0000000000000000000000000000000000000000..9da0e493007a0687899eb59fe64f2eaa03d95e0c --- /dev/null +++ b/src/cmd/compile/internal/noder/helpers.go @@ -0,0 +1,325 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +// Helpers for constructing typed IR nodes. +// +// TODO(mdempsky): Move into their own package so they can be easily +// reused by iimport and frontend optimizations. + +type ImplicitNode interface { + ir.Node + SetImplicit(x bool) +} + +// Implicit returns n after marking it as Implicit. +func Implicit(n ImplicitNode) ImplicitNode { + n.SetImplicit(true) + return n +} + +// typed returns n after setting its type to typ. +func typed(typ *types.Type, n ir.Node) ir.Node { + n.SetType(typ) + n.SetTypecheck(1) + return n +} + +// Values + +func Const(pos src.XPos, typ *types.Type, val constant.Value) ir.Node { + return typed(typ, ir.NewBasicLit(pos, val)) +} + +func Nil(pos src.XPos, typ *types.Type) ir.Node { + return typed(typ, ir.NewNilExpr(pos)) +} + +// Expressions + +func Addr(pos src.XPos, x ir.Node) *ir.AddrExpr { + n := typecheck.NodAddrAt(pos, x) + switch x.Op() { + case ir.OARRAYLIT, ir.OMAPLIT, ir.OSLICELIT, ir.OSTRUCTLIT: + n.SetOp(ir.OPTRLIT) + } + typed(types.NewPtr(x.Type()), n) + return n +} + +func Assert(pos src.XPos, x ir.Node, typ *types.Type) ir.Node { + return typed(typ, ir.NewTypeAssertExpr(pos, x, nil)) +} + +func Binary(pos src.XPos, op ir.Op, typ *types.Type, x, y ir.Node) ir.Node { + switch op { + case ir.OANDAND, ir.OOROR: + return typed(x.Type(), ir.NewLogicalExpr(pos, op, x, y)) + case ir.OADD: + n := ir.NewBinaryExpr(pos, op, x, y) + if x.Type().HasTParam() || y.Type().HasTParam() { + // Delay transformAdd() if either arg has a type param, + // since it needs to know the exact types to decide whether + // to transform OADD to OADDSTR. + n.SetType(typ) + n.SetTypecheck(3) + return n + } + typed(typ, n) + return transformAdd(n) + default: + return typed(x.Type(), ir.NewBinaryExpr(pos, op, x, y)) + } +} + +func Call(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool) ir.Node { + n := ir.NewCallExpr(pos, ir.OCALL, fun, args) + n.IsDDD = dots + // n.Use will be changed to ir.CallUseStmt in g.stmt() if this call is + // just a statement (any return values are ignored). + n.Use = ir.CallUseExpr + + if fun.Op() == ir.OTYPE { + // Actually a type conversion, not a function call. + if fun.Type().HasTParam() || args[0].Type().HasTParam() { + // For type params, don't typecheck until we actually know + // the type. + return typed(typ, n) + } + typed(typ, n) + return transformConvCall(n) + } + + if fun, ok := fun.(*ir.Name); ok && fun.BuiltinOp != 0 { + // For Builtin ops, we currently stay with using the old + // typechecker to transform the call to a more specific expression + // and possibly use more specific ops. However, for a bunch of the + // ops, we delay doing the old typechecker if any of the args have + // type params, for a variety of reasons: + // + // OMAKE: hard to choose specific ops OMAKESLICE, etc. until arg type is known + // OREAL/OIMAG: can't determine type float32/float64 until arg type know + // OLEN/OCAP: old typechecker will complain if arg is not obviously a slice/array. + // OAPPEND: old typechecker will complain if arg is not obviously slice, etc. + // + // We will eventually break out the transforming functionality + // needed for builtin's, and call it here or during stenciling, as + // appropriate. + switch fun.BuiltinOp { + case ir.OMAKE, ir.OREAL, ir.OIMAG, ir.OLEN, ir.OCAP, ir.OAPPEND: + hasTParam := false + for _, arg := range args { + if arg.Type().HasTParam() { + hasTParam = true + break + } + } + if hasTParam { + return typed(typ, n) + } + } + + typed(typ, n) + return transformBuiltin(n) + } + + // Add information, now that we know that fun is actually being called. + switch fun := fun.(type) { + case *ir.ClosureExpr: + fun.Func.SetClosureCalled(true) + case *ir.SelectorExpr: + if fun.Op() == ir.OCALLPART { + op := ir.ODOTMETH + if fun.X.Type().IsInterface() { + op = ir.ODOTINTER + } + fun.SetOp(op) + // Set the type to include the receiver, since that's what + // later parts of the compiler expect + fun.SetType(fun.Selection.Type) + } + } + + if fun.Type().HasTParam() { + // If the fun arg is or has a type param, don't do any extra + // transformations, since we may not have needed properties yet + // (e.g. number of return values, etc). The type param is probably + // described by a structural constraint that requires it to be a + // certain function type, etc., but we don't want to analyze that. + return typed(typ, n) + } + + if fun.Op() == ir.OXDOT { + if !fun.(*ir.SelectorExpr).X.Type().HasTParam() { + base.FatalfAt(pos, "Expecting type param receiver in %v", fun) + } + // For methods called in a generic function, don't do any extra + // transformations. We will do those later when we create the + // instantiated function and have the correct receiver type. + typed(typ, n) + return n + } + if fun.Op() != ir.OFUNCINST { + // If no type params, do the normal call transformations. This + // will convert OCALL to OCALLFUNC. + typed(typ, n) + transformCall(n) + return n + } + + // Leave the op as OCALL, which indicates the call still needs typechecking. + typed(typ, n) + return n +} + +func Compare(pos src.XPos, typ *types.Type, op ir.Op, x, y ir.Node) ir.Node { + n := ir.NewBinaryExpr(pos, op, x, y) + if x.Type().HasTParam() || y.Type().HasTParam() { + // Delay transformCompare() if either arg has a type param, since + // it needs to know the exact types to decide on any needed conversions. + n.SetType(typ) + n.SetTypecheck(3) + return n + } + typed(typ, n) + transformCompare(n) + return n +} + +func Deref(pos src.XPos, typ *types.Type, x ir.Node) *ir.StarExpr { + n := ir.NewStarExpr(pos, x) + typed(typ, n) + return n +} + +func DotField(pos src.XPos, x ir.Node, index int) *ir.SelectorExpr { + op, typ := ir.ODOT, x.Type() + if typ.IsPtr() { + op, typ = ir.ODOTPTR, typ.Elem() + } + if !typ.IsStruct() { + base.FatalfAt(pos, "DotField of non-struct: %L", x) + } + + // TODO(mdempsky): This is the backend's responsibility. + types.CalcSize(typ) + + field := typ.Field(index) + return dot(pos, field.Type, op, x, field) +} + +func DotMethod(pos src.XPos, x ir.Node, index int) *ir.SelectorExpr { + method := method(x.Type(), index) + + // Method value. + typ := typecheck.NewMethodType(method.Type, nil) + return dot(pos, typ, ir.OCALLPART, x, method) +} + +// MethodExpr returns a OMETHEXPR node with the indicated index into the methods +// of typ. The receiver type is set from recv, which is different from typ if the +// method was accessed via embedded fields. Similarly, the X value of the +// ir.SelectorExpr is recv, the original OTYPE node before passing through the +// embedded fields. +func MethodExpr(pos src.XPos, recv ir.Node, embed *types.Type, index int) *ir.SelectorExpr { + method := method(embed, index) + typ := typecheck.NewMethodType(method.Type, recv.Type()) + // The method expression T.m requires a wrapper when T + // is different from m's declared receiver type. We + // normally generate these wrappers while writing out + // runtime type descriptors, which is always done for + // types declared at package scope. However, we need + // to make sure to generate wrappers for anonymous + // receiver types too. + if recv.Sym() == nil { + typecheck.NeedRuntimeType(recv.Type()) + } + return dot(pos, typ, ir.OMETHEXPR, recv, method) +} + +func dot(pos src.XPos, typ *types.Type, op ir.Op, x ir.Node, selection *types.Field) *ir.SelectorExpr { + n := ir.NewSelectorExpr(pos, op, x, selection.Sym) + n.Selection = selection + typed(typ, n) + return n +} + +// TODO(mdempsky): Move to package types. +func method(typ *types.Type, index int) *types.Field { + if typ.IsInterface() { + return typ.AllMethods().Index(index) + } + return types.ReceiverBaseType(typ).Methods().Index(index) +} + +func Index(pos src.XPos, typ *types.Type, x, index ir.Node) ir.Node { + n := ir.NewIndexExpr(pos, x, index) + if x.Type().HasTParam() { + // transformIndex needs to know exact type + n.SetType(typ) + n.SetTypecheck(3) + return n + } + typed(typ, n) + // transformIndex will modify n.Type() for OINDEXMAP. + transformIndex(n) + return n +} + +func Slice(pos src.XPos, typ *types.Type, x, low, high, max ir.Node) ir.Node { + op := ir.OSLICE + if max != nil { + op = ir.OSLICE3 + } + n := ir.NewSliceExpr(pos, op, x, low, high, max) + if x.Type().HasTParam() { + // transformSlice needs to know if x.Type() is a string or an array or a slice. + n.SetType(typ) + n.SetTypecheck(3) + return n + } + typed(typ, n) + transformSlice(n) + return n +} + +func Unary(pos src.XPos, typ *types.Type, op ir.Op, x ir.Node) ir.Node { + switch op { + case ir.OADDR: + return Addr(pos, x) + case ir.ODEREF: + return Deref(pos, typ, x) + } + + if op == ir.ORECV { + if typ.IsFuncArgStruct() && typ.NumFields() == 2 { + // Remove the second boolean type (if provided by type2), + // since that works better with the rest of the compiler + // (which will add it back in later). + assert(typ.Field(1).Type.Kind() == types.TBOOL) + typ = typ.Field(0).Type + } + } + return typed(typ, ir.NewUnaryExpr(pos, op, x)) +} + +// Statements + +var one = constant.MakeInt64(1) + +func IncDec(pos src.XPos, op ir.Op, x ir.Node) *ir.AssignOpStmt { + assert(x.Type() != nil) + return ir.NewAssignOpStmt(pos, op, x, typecheck.DefaultLit(ir.NewBasicLit(pos, one), x.Type())) +} diff --git a/src/cmd/compile/internal/noder/import.go b/src/cmd/compile/internal/noder/import.go new file mode 100644 index 0000000000000000000000000000000000000000..701e9001c859ea8d2819b0e2850a1604e8e0c978 --- /dev/null +++ b/src/cmd/compile/internal/noder/import.go @@ -0,0 +1,507 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "errors" + "fmt" + "internal/buildcfg" + "io" + "os" + pathpkg "path" + "runtime" + "sort" + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "cmd/compile/internal/base" + "cmd/compile/internal/importer" + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/compile/internal/types2" + "cmd/internal/archive" + "cmd/internal/bio" + "cmd/internal/goobj" + "cmd/internal/objabi" + "cmd/internal/src" +) + +// Temporary import helper to get type2-based type-checking going. +type gcimports struct { + packages map[string]*types2.Package +} + +func (m *gcimports) Import(path string) (*types2.Package, error) { + return m.ImportFrom(path, "" /* no vendoring */, 0) +} + +func (m *gcimports) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) { + if mode != 0 { + panic("mode must be 0") + } + + path, err := resolveImportPath(path) + if err != nil { + return nil, err + } + + lookup := func(path string) (io.ReadCloser, error) { return openPackage(path) } + return importer.Import(m.packages, path, srcDir, lookup) +} + +func isDriveLetter(b byte) bool { + return 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' +} + +// is this path a local name? begins with ./ or ../ or / +func islocalname(name string) bool { + return strings.HasPrefix(name, "/") || + runtime.GOOS == "windows" && len(name) >= 3 && isDriveLetter(name[0]) && name[1] == ':' && name[2] == '/' || + strings.HasPrefix(name, "./") || name == "." || + strings.HasPrefix(name, "../") || name == ".." +} + +func openPackage(path string) (*os.File, error) { + if islocalname(path) { + if base.Flag.NoLocalImports { + return nil, errors.New("local imports disallowed") + } + + if base.Flag.Cfg.PackageFile != nil { + return os.Open(base.Flag.Cfg.PackageFile[path]) + } + + // try .a before .o. important for building libraries: + // if there is an array.o in the array.a library, + // want to find all of array.a, not just array.o. + if file, err := os.Open(fmt.Sprintf("%s.a", path)); err == nil { + return file, nil + } + if file, err := os.Open(fmt.Sprintf("%s.o", path)); err == nil { + return file, nil + } + return nil, errors.New("file not found") + } + + // local imports should be canonicalized already. + // don't want to see "encoding/../encoding/base64" + // as different from "encoding/base64". + if q := pathpkg.Clean(path); q != path { + return nil, fmt.Errorf("non-canonical import path %q (should be %q)", path, q) + } + + if base.Flag.Cfg.PackageFile != nil { + return os.Open(base.Flag.Cfg.PackageFile[path]) + } + + for _, dir := range base.Flag.Cfg.ImportDirs { + if file, err := os.Open(fmt.Sprintf("%s/%s.a", dir, path)); err == nil { + return file, nil + } + if file, err := os.Open(fmt.Sprintf("%s/%s.o", dir, path)); err == nil { + return file, nil + } + } + + if buildcfg.GOROOT != "" { + suffix := "" + if base.Flag.InstallSuffix != "" { + suffix = "_" + base.Flag.InstallSuffix + } else if base.Flag.Race { + suffix = "_race" + } else if base.Flag.MSan { + suffix = "_msan" + } + + if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.a", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil { + return file, nil + } + if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.o", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil { + return file, nil + } + } + return nil, errors.New("file not found") +} + +// myheight tracks the local package's height based on packages +// imported so far. +var myheight int + +// resolveImportPath resolves an import path as it appears in a Go +// source file to the package's full path. +func resolveImportPath(path string) (string, error) { + // The package name main is no longer reserved, + // but we reserve the import path "main" to identify + // the main package, just as we reserve the import + // path "math" to identify the standard math package. + if path == "main" { + return "", errors.New("cannot import \"main\"") + } + + if base.Ctxt.Pkgpath != "" && path == base.Ctxt.Pkgpath { + return "", fmt.Errorf("import %q while compiling that package (import cycle)", path) + } + + if mapped, ok := base.Flag.Cfg.ImportMap[path]; ok { + path = mapped + } + + if islocalname(path) { + if path[0] == '/' { + return "", errors.New("import path cannot be absolute path") + } + + prefix := base.Flag.D + if prefix == "" { + // Questionable, but when -D isn't specified, historically we + // resolve local import paths relative to the directory the + // compiler's current directory, not the respective source + // file's directory. + prefix = base.Ctxt.Pathname + } + path = pathpkg.Join(prefix, path) + + if err := checkImportPath(path, true); err != nil { + return "", err + } + } + + return path, nil +} + +// TODO(mdempsky): Return an error instead. +func importfile(decl *syntax.ImportDecl) *types.Pkg { + if decl.Path.Kind != syntax.StringLit { + base.Errorf("import path must be a string") + return nil + } + + path, err := strconv.Unquote(decl.Path.Value) + if err != nil { + base.Errorf("import path must be a string") + return nil + } + + if err := checkImportPath(path, false); err != nil { + base.Errorf("%s", err.Error()) + return nil + } + + path, err = resolveImportPath(path) + if err != nil { + base.Errorf("%s", err) + return nil + } + + importpkg := types.NewPkg(path, "") + if importpkg.Direct { + return importpkg // already fully loaded + } + importpkg.Direct = true + typecheck.Target.Imports = append(typecheck.Target.Imports, importpkg) + + if path == "unsafe" { + return importpkg // initialized with universe + } + + f, err := openPackage(path) + if err != nil { + base.Errorf("could not import %q: %v", path, err) + base.ErrorExit() + } + imp := bio.NewReader(f) + defer imp.Close() + file := f.Name() + + // check object header + p, err := imp.ReadString('\n') + if err != nil { + base.Errorf("import %s: reading input: %v", file, err) + base.ErrorExit() + } + + if p == "!\n" { // package archive + // package export block should be first + sz := archive.ReadHeader(imp.Reader, "__.PKGDEF") + if sz <= 0 { + base.Errorf("import %s: not a package file", file) + base.ErrorExit() + } + p, err = imp.ReadString('\n') + if err != nil { + base.Errorf("import %s: reading input: %v", file, err) + base.ErrorExit() + } + } + + if !strings.HasPrefix(p, "go object ") { + base.Errorf("import %s: not a go object file: %s", file, p) + base.ErrorExit() + } + q := objabi.HeaderString() + if p != q { + base.Errorf("import %s: object is [%s] expected [%s]", file, p, q) + base.ErrorExit() + } + + // process header lines + for { + p, err = imp.ReadString('\n') + if err != nil { + base.Errorf("import %s: reading input: %v", file, err) + base.ErrorExit() + } + if p == "\n" { + break // header ends with blank line + } + } + + // Expect $$B\n to signal binary import format. + + // look for $$ + var c byte + for { + c, err = imp.ReadByte() + if err != nil { + break + } + if c == '$' { + c, err = imp.ReadByte() + if c == '$' || err != nil { + break + } + } + } + + // get character after $$ + if err == nil { + c, _ = imp.ReadByte() + } + + var fingerprint goobj.FingerprintType + switch c { + case '\n': + base.Errorf("cannot import %s: old export format no longer supported (recompile library)", path) + return nil + + case 'B': + if base.Debug.Export != 0 { + fmt.Printf("importing %s (%s)\n", path, file) + } + imp.ReadByte() // skip \n after $$B + + c, err = imp.ReadByte() + if err != nil { + base.Errorf("import %s: reading input: %v", file, err) + base.ErrorExit() + } + + // Indexed format is distinguished by an 'i' byte, + // whereas previous export formats started with 'c', 'd', or 'v'. + if c != 'i' { + base.Errorf("import %s: unexpected package format byte: %v", file, c) + base.ErrorExit() + } + fingerprint = typecheck.ReadImports(importpkg, imp) + + default: + base.Errorf("no import in %q", path) + base.ErrorExit() + } + + // assume files move (get installed) so don't record the full path + if base.Flag.Cfg.PackageFile != nil { + // If using a packageFile map, assume path_ can be recorded directly. + base.Ctxt.AddImport(path, fingerprint) + } else { + // For file "/Users/foo/go/pkg/darwin_amd64/math.a" record "math.a". + base.Ctxt.AddImport(file[len(file)-len(path)-len(".a"):], fingerprint) + } + + if importpkg.Height >= myheight { + myheight = importpkg.Height + 1 + } + + return importpkg +} + +// The linker uses the magic symbol prefixes "go." and "type." +// Avoid potential confusion between import paths and symbols +// by rejecting these reserved imports for now. Also, people +// "can do weird things in GOPATH and we'd prefer they didn't +// do _that_ weird thing" (per rsc). See also #4257. +var reservedimports = []string{ + "go", + "type", +} + +func checkImportPath(path string, allowSpace bool) error { + if path == "" { + return errors.New("import path is empty") + } + + if strings.Contains(path, "\x00") { + return errors.New("import path contains NUL") + } + + for _, ri := range reservedimports { + if path == ri { + return fmt.Errorf("import path %q is reserved and cannot be used", path) + } + } + + for _, r := range path { + switch { + case r == utf8.RuneError: + return fmt.Errorf("import path contains invalid UTF-8 sequence: %q", path) + case r < 0x20 || r == 0x7f: + return fmt.Errorf("import path contains control character: %q", path) + case r == '\\': + return fmt.Errorf("import path contains backslash; use slash: %q", path) + case !allowSpace && unicode.IsSpace(r): + return fmt.Errorf("import path contains space character: %q", path) + case strings.ContainsRune("!\"#$%&'()*,:;<=>?[]^`{|}", r): + return fmt.Errorf("import path contains invalid character '%c': %q", r, path) + } + } + + return nil +} + +func pkgnotused(lineno src.XPos, path string, name string) { + // If the package was imported with a name other than the final + // import path element, show it explicitly in the error message. + // Note that this handles both renamed imports and imports of + // packages containing unconventional package declarations. + // Note that this uses / always, even on Windows, because Go import + // paths always use forward slashes. + elem := path + if i := strings.LastIndex(elem, "/"); i >= 0 { + elem = elem[i+1:] + } + if name == "" || elem == name { + base.ErrorfAt(lineno, "imported and not used: %q", path) + } else { + base.ErrorfAt(lineno, "imported and not used: %q as %s", path, name) + } +} + +func mkpackage(pkgname string) { + if types.LocalPkg.Name == "" { + if pkgname == "_" { + base.Errorf("invalid package name _") + } + types.LocalPkg.Name = pkgname + } else { + if pkgname != types.LocalPkg.Name { + base.Errorf("package %s; expected %s", pkgname, types.LocalPkg.Name) + } + } +} + +func clearImports() { + type importedPkg struct { + pos src.XPos + path string + name string + } + var unused []importedPkg + + for _, s := range types.LocalPkg.Syms { + n := ir.AsNode(s.Def) + if n == nil { + continue + } + if n.Op() == ir.OPACK { + // throw away top-level package name left over + // from previous file. + // leave s->block set to cause redeclaration + // errors if a conflicting top-level name is + // introduced by a different file. + p := n.(*ir.PkgName) + if !p.Used && base.SyntaxErrors() == 0 { + unused = append(unused, importedPkg{p.Pos(), p.Pkg.Path, s.Name}) + } + s.Def = nil + continue + } + if types.IsDotAlias(s) { + // throw away top-level name left over + // from previous import . "x" + // We'll report errors after type checking in CheckDotImports. + s.Def = nil + continue + } + } + + sort.Slice(unused, func(i, j int) bool { return unused[i].pos.Before(unused[j].pos) }) + for _, pkg := range unused { + pkgnotused(pkg.pos, pkg.path, pkg.name) + } +} + +// CheckDotImports reports errors for any unused dot imports. +func CheckDotImports() { + for _, pack := range dotImports { + if !pack.Used { + base.ErrorfAt(pack.Pos(), "imported and not used: %q", pack.Pkg.Path) + } + } + + // No longer needed; release memory. + dotImports = nil + typecheck.DotImportRefs = nil +} + +// dotImports tracks all PkgNames that have been dot-imported. +var dotImports []*ir.PkgName + +// find all the exported symbols in package referenced by PkgName, +// and make them available in the current package +func importDot(pack *ir.PkgName) { + if typecheck.DotImportRefs == nil { + typecheck.DotImportRefs = make(map[*ir.Ident]*ir.PkgName) + } + + opkg := pack.Pkg + for _, s := range opkg.Syms { + if s.Def == nil { + if _, ok := typecheck.DeclImporter[s]; !ok { + continue + } + } + if !types.IsExported(s.Name) || strings.ContainsRune(s.Name, 0xb7) { // 0xb7 = center dot + continue + } + s1 := typecheck.Lookup(s.Name) + if s1.Def != nil { + pkgerror := fmt.Sprintf("during import %q", opkg.Path) + typecheck.Redeclared(base.Pos, s1, pkgerror) + continue + } + + id := ir.NewIdent(src.NoXPos, s) + typecheck.DotImportRefs[id] = pack + s1.Def = id + s1.Block = 1 + } + + dotImports = append(dotImports, pack) +} + +// importName is like oldname, +// but it reports an error if sym is from another package and not exported. +func importName(sym *types.Sym) ir.Node { + n := oldname(sym) + if !types.IsExported(sym.Name) && sym.Pkg != types.LocalPkg { + n.SetDiag(true) + base.Errorf("cannot refer to unexported name %s.%s", sym.Pkg.Name, sym.Name) + } + return n +} diff --git a/src/cmd/compile/internal/noder/irgen.go b/src/cmd/compile/internal/noder/irgen.go new file mode 100644 index 0000000000000000000000000000000000000000..3e0d3285ab916ec0661ba9601149a3209138f02c --- /dev/null +++ b/src/cmd/compile/internal/noder/irgen.go @@ -0,0 +1,203 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "fmt" + "os" + + "cmd/compile/internal/base" + "cmd/compile/internal/dwarfgen" + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/compile/internal/types2" + "cmd/internal/src" +) + +// check2 type checks a Go package using types2, and then generates IR +// using the results. +func check2(noders []*noder) { + if base.SyntaxErrors() != 0 { + base.ErrorExit() + } + + // setup and syntax error reporting + var m posMap + files := make([]*syntax.File, len(noders)) + for i, p := range noders { + m.join(&p.posMap) + files[i] = p.file + } + + // typechecking + conf := types2.Config{ + GoVersion: base.Flag.Lang, + IgnoreLabels: true, // parser already checked via syntax.CheckBranches mode + CompilerErrorMessages: true, // use error strings matching existing compiler errors + Error: func(err error) { + terr := err.(types2.Error) + base.ErrorfAt(m.makeXPos(terr.Pos), "%s", terr.Msg) + }, + Importer: &gcimports{ + packages: make(map[string]*types2.Package), + }, + Sizes: &gcSizes{}, + } + info := types2.Info{ + Types: make(map[syntax.Expr]types2.TypeAndValue), + Defs: make(map[*syntax.Name]types2.Object), + Uses: make(map[*syntax.Name]types2.Object), + Selections: make(map[*syntax.SelectorExpr]*types2.Selection), + Implicits: make(map[syntax.Node]types2.Object), + Scopes: make(map[syntax.Node]*types2.Scope), + Inferred: make(map[syntax.Expr]types2.Inferred), + // expand as needed + } + pkg, err := conf.Check(base.Ctxt.Pkgpath, files, &info) + files = nil + base.ExitIfErrors() + if err != nil { + base.FatalfAt(src.NoXPos, "conf.Check error: %v", err) + } + if base.Flag.G < 2 { + os.Exit(0) + } + + g := irgen{ + target: typecheck.Target, + self: pkg, + info: &info, + posMap: m, + objs: make(map[types2.Object]*ir.Name), + typs: make(map[types2.Type]*types.Type), + } + g.generate(noders) + + if base.Flag.G < 3 { + os.Exit(0) + } +} + +type irgen struct { + target *ir.Package + self *types2.Package + info *types2.Info + + posMap + objs map[types2.Object]*ir.Name + typs map[types2.Type]*types.Type + marker dwarfgen.ScopeMarker + + // Fully-instantiated generic types whose methods should be instantiated + instTypeList []*types.Type +} + +func (g *irgen) generate(noders []*noder) { + types.LocalPkg.Name = g.self.Name() + typecheck.TypecheckAllowed = true + + // Prevent size calculations until we set the underlying type + // for all package-block defined types. + types.DeferCheckSize() + + // At this point, types2 has already handled name resolution and + // type checking. We just need to map from its object and type + // representations to those currently used by the rest of the + // compiler. This happens mostly in 3 passes. + + // 1. Process all import declarations. We use the compiler's own + // importer for this, rather than types2's gcimporter-derived one, + // to handle extensions and inline function bodies correctly. + // + // Also, we need to do this in a separate pass, because mappings are + // instantiated on demand. If we interleaved processing import + // declarations with other declarations, it's likely we'd end up + // wanting to map an object/type from another source file, but not + // yet have the import data it relies on. + declLists := make([][]syntax.Decl, len(noders)) +Outer: + for i, p := range noders { + g.pragmaFlags(p.file.Pragma, ir.GoBuildPragma) + for j, decl := range p.file.DeclList { + switch decl := decl.(type) { + case *syntax.ImportDecl: + g.importDecl(p, decl) + default: + declLists[i] = p.file.DeclList[j:] + continue Outer // no more ImportDecls + } + } + } + types.LocalPkg.Height = myheight + + // 2. Process all package-block type declarations. As with imports, + // we need to make sure all types are properly instantiated before + // trying to map any expressions that utilize them. In particular, + // we need to make sure type pragmas are already known (see comment + // in irgen.typeDecl). + // + // We could perhaps instead defer processing of package-block + // variable initializers and function bodies, like noder does, but + // special-casing just package-block type declarations minimizes the + // differences between processing package-block and function-scoped + // declarations. + for _, declList := range declLists { + for _, decl := range declList { + switch decl := decl.(type) { + case *syntax.TypeDecl: + g.typeDecl((*ir.Nodes)(&g.target.Decls), decl) + } + } + } + types.ResumeCheckSize() + + // 3. Process all remaining declarations. + for _, declList := range declLists { + g.target.Decls = append(g.target.Decls, g.decls(declList)...) + } + + if base.Flag.W > 1 { + for _, n := range g.target.Decls { + s := fmt.Sprintf("\nafter noder2 %v", n) + ir.Dump(s, n) + } + } + + typecheck.DeclareUniverse() + + for _, p := range noders { + // Process linkname and cgo pragmas. + p.processPragmas() + + // Double check for any type-checking inconsistencies. This can be + // removed once we're confident in IR generation results. + syntax.Walk(p.file, func(n syntax.Node) bool { + g.validate(n) + return false + }) + } + + // Create any needed stencils of generic functions + g.stencil() + + // For now, remove all generic functions from g.target.Decl, since they + // have been used for stenciling, but don't compile. TODO: We will + // eventually export any exportable generic functions. + j := 0 + for i, decl := range g.target.Decls { + if decl.Op() != ir.ODCLFUNC || !decl.Type().HasTParam() { + g.target.Decls[j] = g.target.Decls[i] + j++ + } + } + g.target.Decls = g.target.Decls[:j] +} + +func (g *irgen) unhandled(what string, p poser) { + base.FatalfAt(g.pos(p), "unhandled %s: %T", what, p) + panic("unreachable") +} diff --git a/src/cmd/compile/internal/gc/lex.go b/src/cmd/compile/internal/noder/lex.go similarity index 65% rename from src/cmd/compile/internal/gc/lex.go rename to src/cmd/compile/internal/noder/lex.go index 7cce371408ae81f2e476da906e5197a454a0193f..66a56a50ec8ebcf2f1f63ecbfa545b0e64c7b37b 100644 --- a/src/cmd/compile/internal/gc/lex.go +++ b/src/cmd/compile/internal/noder/lex.go @@ -2,23 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package noder import ( - "cmd/compile/internal/syntax" - "cmd/internal/objabi" - "cmd/internal/src" "fmt" + "internal/buildcfg" "strings" -) -// lineno is the source position at the start of the most recently lexed token. -// TODO(gri) rename and eventually remove -var lineno src.XPos - -func makePos(base *src.PosBase, line, col uint) src.XPos { - return Ctxt.PosTable.XPos(src.MakePos(base, line, col)) -} + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" +) func isSpace(c rune) bool { return c == ' ' || c == '\t' || c == '\n' || c == '\r' @@ -28,78 +21,52 @@ func isQuoted(s string) bool { return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' } -type PragmaFlag int16 - const ( - // Func pragmas. - Nointerface PragmaFlag = 1 << iota - Noescape // func parameters don't escape - Norace // func must not have race detector annotations - Nosplit // func should not execute on separate stack - Noinline // func should not be inlined - NoCheckPtr // func should not be instrumented by checkptr - CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all - UintptrEscapes // pointers converted to uintptr escape - - // Runtime-only func pragmas. - // See ../../../../runtime/README.md for detailed descriptions. - Systemstack // func must run on system stack - Nowritebarrier // emit compiler error instead of write barrier - Nowritebarrierrec // error on write barrier in this or recursive callees - Yeswritebarrierrec // cancels Nowritebarrierrec in this function and callees - - // Runtime and cgo type pragmas - NotInHeap // values of this type must not be heap allocated - - // Go command pragmas - GoBuildPragma -) - -const ( - FuncPragmas = Nointerface | - Noescape | - Norace | - Nosplit | - Noinline | - NoCheckPtr | - CgoUnsafeArgs | - UintptrEscapes | - Systemstack | - Nowritebarrier | - Nowritebarrierrec | - Yeswritebarrierrec - - TypePragmas = NotInHeap + funcPragmas = ir.Nointerface | + ir.Noescape | + ir.Norace | + ir.Nosplit | + ir.Noinline | + ir.NoCheckPtr | + ir.RegisterParams | // TODO(register args) remove after register abi is working + ir.CgoUnsafeArgs | + ir.UintptrEscapes | + ir.Systemstack | + ir.Nowritebarrier | + ir.Nowritebarrierrec | + ir.Yeswritebarrierrec + + typePragmas = ir.NotInHeap ) -func pragmaFlag(verb string) PragmaFlag { +func pragmaFlag(verb string) ir.PragmaFlag { switch verb { case "go:build": - return GoBuildPragma + return ir.GoBuildPragma case "go:nointerface": - if objabi.Fieldtrack_enabled != 0 { - return Nointerface + if buildcfg.Experiment.FieldTrack { + return ir.Nointerface } case "go:noescape": - return Noescape + return ir.Noescape case "go:norace": - return Norace + return ir.Norace case "go:nosplit": - return Nosplit | NoCheckPtr // implies NoCheckPtr (see #34972) + return ir.Nosplit | ir.NoCheckPtr // implies NoCheckPtr (see #34972) case "go:noinline": - return Noinline + return ir.Noinline case "go:nocheckptr": - return NoCheckPtr + return ir.NoCheckPtr case "go:systemstack": - return Systemstack + return ir.Systemstack case "go:nowritebarrier": - return Nowritebarrier + return ir.Nowritebarrier case "go:nowritebarrierrec": - return Nowritebarrierrec | Nowritebarrier // implies Nowritebarrier + return ir.Nowritebarrierrec | ir.Nowritebarrier // implies Nowritebarrier case "go:yeswritebarrierrec": - return Yeswritebarrierrec + return ir.Yeswritebarrierrec case "go:cgo_unsafe_args": - return CgoUnsafeArgs | NoCheckPtr // implies NoCheckPtr (see #34968) + return ir.CgoUnsafeArgs | ir.NoCheckPtr // implies NoCheckPtr (see #34968) case "go:uintptrescapes": // For the next function declared in the file // any uintptr arguments may be pointer values @@ -112,9 +79,11 @@ func pragmaFlag(verb string) PragmaFlag { // call. The conversion to uintptr must appear // in the argument list. // Used in syscall/dll_windows.go. - return UintptrEscapes + return ir.UintptrEscapes + case "go:registerparams": // TODO(register args) remove after register abi is working + return ir.RegisterParams case "go:notinheap": - return NotInHeap + return ir.NotInHeap } return 0 } @@ -141,7 +110,7 @@ func (p *noder) pragcgo(pos syntax.Pos, text string) { case len(f) == 3 && !isQuoted(f[1]) && !isQuoted(f[2]): case len(f) == 4 && !isQuoted(f[1]) && !isQuoted(f[2]) && isQuoted(f[3]): f[3] = strings.Trim(f[3], `"`) - if objabi.GOOS == "aix" && f[3] != "" { + if buildcfg.GOOS == "aix" && f[3] != "" { // On Aix, library pattern must be "lib.a/object.o" // or "lib.a/libname.so.X" n := strings.Split(f[3], "/") diff --git a/src/cmd/compile/internal/gc/lex_test.go b/src/cmd/compile/internal/noder/lex_test.go similarity index 99% rename from src/cmd/compile/internal/gc/lex_test.go rename to src/cmd/compile/internal/noder/lex_test.go index b2081a1732bbb49382645b492351e7cdccba9cb0..85a3f06759ad7b477b111db6b0c4ad1ab71b615d 100644 --- a/src/cmd/compile/internal/gc/lex_test.go +++ b/src/cmd/compile/internal/noder/lex_test.go @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package noder import ( - "cmd/compile/internal/syntax" "reflect" "runtime" "testing" + + "cmd/compile/internal/syntax" ) func eq(a, b []string) bool { diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go new file mode 100644 index 0000000000000000000000000000000000000000..5fcad096c28ae5f5aa413bfc1a0ead3f7212e630 --- /dev/null +++ b/src/cmd/compile/internal/noder/noder.go @@ -0,0 +1,1880 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "fmt" + "go/constant" + "go/token" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "cmd/compile/internal/base" + "cmd/compile/internal/dwarfgen" + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/objabi" + "cmd/internal/src" +) + +func LoadPackage(filenames []string) { + base.Timer.Start("fe", "parse") + + mode := syntax.CheckBranches + if base.Flag.G != 0 { + mode |= syntax.AllowGenerics + } + + // Limit the number of simultaneously open files. + sem := make(chan struct{}, runtime.GOMAXPROCS(0)+10) + + noders := make([]*noder, len(filenames)) + for i, filename := range filenames { + p := noder{ + err: make(chan syntax.Error), + trackScopes: base.Flag.Dwarf, + } + noders[i] = &p + + filename := filename + go func() { + sem <- struct{}{} + defer func() { <-sem }() + defer close(p.err) + fbase := syntax.NewFileBase(filename) + + f, err := os.Open(filename) + if err != nil { + p.error(syntax.Error{Msg: err.Error()}) + return + } + defer f.Close() + + p.file, _ = syntax.Parse(fbase, f, p.error, p.pragma, mode) // errors are tracked via p.error + }() + } + + var lines uint + for _, p := range noders { + for e := range p.err { + p.errorAt(e.Pos, "%s", e.Msg) + } + if p.file == nil { + base.ErrorExit() + } + lines += p.file.EOF.Line() + } + base.Timer.AddEvent(int64(lines), "lines") + + if base.Flag.G != 0 { + // Use types2 to type-check and possibly generate IR. + check2(noders) + return + } + + for _, p := range noders { + p.node() + p.file = nil // release memory + } + + if base.SyntaxErrors() != 0 { + base.ErrorExit() + } + types.CheckDclstack() + + for _, p := range noders { + p.processPragmas() + } + + // Typecheck. + types.LocalPkg.Height = myheight + typecheck.DeclareUniverse() + typecheck.TypecheckAllowed = true + + // Process top-level declarations in phases. + + // Phase 1: const, type, and names and types of funcs. + // This will gather all the information about types + // and methods but doesn't depend on any of it. + // + // We also defer type alias declarations until phase 2 + // to avoid cycles like #18640. + // TODO(gri) Remove this again once we have a fix for #25838. + + // Don't use range--typecheck can add closures to Target.Decls. + base.Timer.Start("fe", "typecheck", "top1") + for i := 0; i < len(typecheck.Target.Decls); i++ { + n := typecheck.Target.Decls[i] + if op := n.Op(); op != ir.ODCL && op != ir.OAS && op != ir.OAS2 && (op != ir.ODCLTYPE || !n.(*ir.Decl).X.Alias()) { + typecheck.Target.Decls[i] = typecheck.Stmt(n) + } + } + + // Phase 2: Variable assignments. + // To check interface assignments, depends on phase 1. + + // Don't use range--typecheck can add closures to Target.Decls. + base.Timer.Start("fe", "typecheck", "top2") + for i := 0; i < len(typecheck.Target.Decls); i++ { + n := typecheck.Target.Decls[i] + if op := n.Op(); op == ir.ODCL || op == ir.OAS || op == ir.OAS2 || op == ir.ODCLTYPE && n.(*ir.Decl).X.Alias() { + typecheck.Target.Decls[i] = typecheck.Stmt(n) + } + } + + // Phase 3: Type check function bodies. + // Don't use range--typecheck can add closures to Target.Decls. + base.Timer.Start("fe", "typecheck", "func") + var fcount int64 + for i := 0; i < len(typecheck.Target.Decls); i++ { + n := typecheck.Target.Decls[i] + if n.Op() == ir.ODCLFUNC { + if base.Flag.W > 1 { + s := fmt.Sprintf("\nbefore typecheck %v", n) + ir.Dump(s, n) + } + typecheck.FuncBody(n.(*ir.Func)) + if base.Flag.W > 1 { + s := fmt.Sprintf("\nafter typecheck %v", n) + ir.Dump(s, n) + } + fcount++ + } + } + + // Phase 4: Check external declarations. + // TODO(mdempsky): This should be handled when type checking their + // corresponding ODCL nodes. + base.Timer.Start("fe", "typecheck", "externdcls") + for i, n := range typecheck.Target.Externs { + if n.Op() == ir.ONAME { + typecheck.Target.Externs[i] = typecheck.Expr(typecheck.Target.Externs[i]) + } + } + + // Phase 5: With all user code type-checked, it's now safe to verify map keys. + // With all user code typechecked, it's now safe to verify unused dot imports. + typecheck.CheckMapKeys() + CheckDotImports() + base.ExitIfErrors() +} + +func (p *noder) errorAt(pos syntax.Pos, format string, args ...interface{}) { + base.ErrorfAt(p.makeXPos(pos), format, args...) +} + +// TODO(gri) Can we eliminate fileh in favor of absFilename? +func fileh(name string) string { + return objabi.AbsFile("", name, base.Flag.TrimPath) +} + +func absFilename(name string) string { + return objabi.AbsFile(base.Ctxt.Pathname, name, base.Flag.TrimPath) +} + +// noder transforms package syntax's AST into a Node tree. +type noder struct { + posMap + + file *syntax.File + linknames []linkname + pragcgobuf [][]string + err chan syntax.Error + importedUnsafe bool + importedEmbed bool + trackScopes bool + + funcState *funcState +} + +// funcState tracks all per-function state to make handling nested +// functions easier. +type funcState struct { + // scopeVars is a stack tracking the number of variables declared in + // the current function at the moment each open scope was opened. + scopeVars []int + marker dwarfgen.ScopeMarker + + lastCloseScopePos syntax.Pos +} + +func (p *noder) funcBody(fn *ir.Func, block *syntax.BlockStmt) { + outerFuncState := p.funcState + p.funcState = new(funcState) + typecheck.StartFuncBody(fn) + + if block != nil { + body := p.stmts(block.List) + if body == nil { + body = []ir.Node{ir.NewBlockStmt(base.Pos, nil)} + } + fn.Body = body + + base.Pos = p.makeXPos(block.Rbrace) + fn.Endlineno = base.Pos + } + + typecheck.FinishFuncBody() + p.funcState.marker.WriteTo(fn) + p.funcState = outerFuncState +} + +func (p *noder) openScope(pos syntax.Pos) { + fs := p.funcState + types.Markdcl() + + if p.trackScopes { + fs.scopeVars = append(fs.scopeVars, len(ir.CurFunc.Dcl)) + fs.marker.Push(p.makeXPos(pos)) + } +} + +func (p *noder) closeScope(pos syntax.Pos) { + fs := p.funcState + fs.lastCloseScopePos = pos + types.Popdcl() + + if p.trackScopes { + scopeVars := fs.scopeVars[len(fs.scopeVars)-1] + fs.scopeVars = fs.scopeVars[:len(fs.scopeVars)-1] + if scopeVars == len(ir.CurFunc.Dcl) { + // no variables were declared in this scope, so we can retract it. + fs.marker.Unpush() + } else { + fs.marker.Pop(p.makeXPos(pos)) + } + } +} + +// closeAnotherScope is like closeScope, but it reuses the same mark +// position as the last closeScope call. This is useful for "for" and +// "if" statements, as their implicit blocks always end at the same +// position as an explicit block. +func (p *noder) closeAnotherScope() { + p.closeScope(p.funcState.lastCloseScopePos) +} + +// linkname records a //go:linkname directive. +type linkname struct { + pos syntax.Pos + local string + remote string +} + +func (p *noder) node() { + p.importedUnsafe = false + p.importedEmbed = false + + p.setlineno(p.file.PkgName) + mkpackage(p.file.PkgName.Value) + + if pragma, ok := p.file.Pragma.(*pragmas); ok { + pragma.Flag &^= ir.GoBuildPragma + p.checkUnused(pragma) + } + + typecheck.Target.Decls = append(typecheck.Target.Decls, p.decls(p.file.DeclList)...) + + base.Pos = src.NoXPos + clearImports() +} + +func (p *noder) processPragmas() { + for _, l := range p.linknames { + if !p.importedUnsafe { + p.errorAt(l.pos, "//go:linkname only allowed in Go files that import \"unsafe\"") + continue + } + n := ir.AsNode(typecheck.Lookup(l.local).Def) + if n == nil || n.Op() != ir.ONAME { + // TODO(mdempsky): Change to p.errorAt before Go 1.17 release. + // base.WarnfAt(p.makeXPos(l.pos), "//go:linkname must refer to declared function or variable (will be an error in Go 1.17)") + continue + } + if n.Sym().Linkname != "" { + p.errorAt(l.pos, "duplicate //go:linkname for %s", l.local) + continue + } + n.Sym().Linkname = l.remote + } + typecheck.Target.CgoPragmas = append(typecheck.Target.CgoPragmas, p.pragcgobuf...) +} + +func (p *noder) decls(decls []syntax.Decl) (l []ir.Node) { + var cs constState + + for _, decl := range decls { + p.setlineno(decl) + switch decl := decl.(type) { + case *syntax.ImportDecl: + p.importDecl(decl) + + case *syntax.VarDecl: + l = append(l, p.varDecl(decl)...) + + case *syntax.ConstDecl: + l = append(l, p.constDecl(decl, &cs)...) + + case *syntax.TypeDecl: + l = append(l, p.typeDecl(decl)) + + case *syntax.FuncDecl: + l = append(l, p.funcDecl(decl)) + + default: + panic("unhandled Decl") + } + } + + return +} + +func (p *noder) importDecl(imp *syntax.ImportDecl) { + if imp.Path == nil || imp.Path.Bad { + return // avoid follow-on errors if there was a syntax error + } + + if pragma, ok := imp.Pragma.(*pragmas); ok { + p.checkUnused(pragma) + } + + ipkg := importfile(imp) + if ipkg == nil { + if base.Errors() == 0 { + base.Fatalf("phase error in import") + } + return + } + + if ipkg == ir.Pkgs.Unsafe { + p.importedUnsafe = true + } + if ipkg.Path == "embed" { + p.importedEmbed = true + } + + var my *types.Sym + if imp.LocalPkgName != nil { + my = p.name(imp.LocalPkgName) + } else { + my = typecheck.Lookup(ipkg.Name) + } + + pack := ir.NewPkgName(p.pos(imp), my, ipkg) + + switch my.Name { + case ".": + importDot(pack) + return + case "init": + base.ErrorfAt(pack.Pos(), "cannot import package as init - init must be a func") + return + case "_": + return + } + if my.Def != nil { + typecheck.Redeclared(pack.Pos(), my, "as imported package name") + } + my.Def = pack + my.Lastlineno = pack.Pos() + my.Block = 1 // at top level +} + +func (p *noder) varDecl(decl *syntax.VarDecl) []ir.Node { + names := p.declNames(ir.ONAME, decl.NameList) + typ := p.typeExprOrNil(decl.Type) + exprs := p.exprList(decl.Values) + + if pragma, ok := decl.Pragma.(*pragmas); ok { + varEmbed(p.makeXPos, names[0], decl, pragma, p.importedEmbed) + p.checkUnused(pragma) + } + + var init []ir.Node + p.setlineno(decl) + + if len(names) > 1 && len(exprs) == 1 { + as2 := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, exprs) + for _, v := range names { + as2.Lhs.Append(v) + typecheck.Declare(v, typecheck.DeclContext) + v.Ntype = typ + v.Defn = as2 + if ir.CurFunc != nil { + init = append(init, ir.NewDecl(base.Pos, ir.ODCL, v)) + } + } + + return append(init, as2) + } + + for i, v := range names { + var e ir.Node + if i < len(exprs) { + e = exprs[i] + } + + typecheck.Declare(v, typecheck.DeclContext) + v.Ntype = typ + + if ir.CurFunc != nil { + init = append(init, ir.NewDecl(base.Pos, ir.ODCL, v)) + } + as := ir.NewAssignStmt(base.Pos, v, e) + init = append(init, as) + if e != nil || ir.CurFunc == nil { + v.Defn = as + } + } + + if len(exprs) != 0 && len(names) != len(exprs) { + base.Errorf("assignment mismatch: %d variables but %d values", len(names), len(exprs)) + } + + return init +} + +// constState tracks state between constant specifiers within a +// declaration group. This state is kept separate from noder so nested +// constant declarations are handled correctly (e.g., issue 15550). +type constState struct { + group *syntax.Group + typ ir.Ntype + values []ir.Node + iota int64 +} + +func (p *noder) constDecl(decl *syntax.ConstDecl, cs *constState) []ir.Node { + if decl.Group == nil || decl.Group != cs.group { + *cs = constState{ + group: decl.Group, + } + } + + if pragma, ok := decl.Pragma.(*pragmas); ok { + p.checkUnused(pragma) + } + + names := p.declNames(ir.OLITERAL, decl.NameList) + typ := p.typeExprOrNil(decl.Type) + + var values []ir.Node + if decl.Values != nil { + values = p.exprList(decl.Values) + cs.typ, cs.values = typ, values + } else { + if typ != nil { + base.Errorf("const declaration cannot have type without expression") + } + typ, values = cs.typ, cs.values + } + + nn := make([]ir.Node, 0, len(names)) + for i, n := range names { + if i >= len(values) { + base.Errorf("missing value in const declaration") + break + } + v := values[i] + if decl.Values == nil { + v = ir.DeepCopy(n.Pos(), v) + } + typecheck.Declare(n, typecheck.DeclContext) + + n.Ntype = typ + n.Defn = v + n.SetIota(cs.iota) + + nn = append(nn, ir.NewDecl(p.pos(decl), ir.ODCLCONST, n)) + } + + if len(values) > len(names) { + base.Errorf("extra expression in const declaration") + } + + cs.iota++ + + return nn +} + +func (p *noder) typeDecl(decl *syntax.TypeDecl) ir.Node { + n := p.declName(ir.OTYPE, decl.Name) + typecheck.Declare(n, typecheck.DeclContext) + + // decl.Type may be nil but in that case we got a syntax error during parsing + typ := p.typeExprOrNil(decl.Type) + + n.Ntype = typ + n.SetAlias(decl.Alias) + if pragma, ok := decl.Pragma.(*pragmas); ok { + if !decl.Alias { + n.SetPragma(pragma.Flag & typePragmas) + pragma.Flag &^= typePragmas + } + p.checkUnused(pragma) + } + + nod := ir.NewDecl(p.pos(decl), ir.ODCLTYPE, n) + if n.Alias() && !types.AllowsGoVersion(types.LocalPkg, 1, 9) { + base.ErrorfAt(nod.Pos(), "type aliases only supported as of -lang=go1.9") + } + return nod +} + +func (p *noder) declNames(op ir.Op, names []*syntax.Name) []*ir.Name { + nodes := make([]*ir.Name, 0, len(names)) + for _, name := range names { + nodes = append(nodes, p.declName(op, name)) + } + return nodes +} + +func (p *noder) declName(op ir.Op, name *syntax.Name) *ir.Name { + return ir.NewDeclNameAt(p.pos(name), op, p.name(name)) +} + +func (p *noder) funcDecl(fun *syntax.FuncDecl) ir.Node { + name := p.name(fun.Name) + t := p.signature(fun.Recv, fun.Type) + f := ir.NewFunc(p.pos(fun)) + + if fun.Recv == nil { + if name.Name == "init" { + name = renameinit() + if len(t.Params) > 0 || len(t.Results) > 0 { + base.ErrorfAt(f.Pos(), "func init must have no arguments and no return values") + } + typecheck.Target.Inits = append(typecheck.Target.Inits, f) + } + + if types.LocalPkg.Name == "main" && name.Name == "main" { + if len(t.Params) > 0 || len(t.Results) > 0 { + base.ErrorfAt(f.Pos(), "func main must have no arguments and no return values") + } + } + } else { + f.Shortname = name + name = ir.BlankNode.Sym() // filled in by tcFunc + } + + f.Nname = ir.NewNameAt(p.pos(fun.Name), name) + f.Nname.Func = f + f.Nname.Defn = f + f.Nname.Ntype = t + + if pragma, ok := fun.Pragma.(*pragmas); ok { + f.Pragma = pragma.Flag & funcPragmas + if pragma.Flag&ir.Systemstack != 0 && pragma.Flag&ir.Nosplit != 0 { + base.ErrorfAt(f.Pos(), "go:nosplit and go:systemstack cannot be combined") + } + pragma.Flag &^= funcPragmas + p.checkUnused(pragma) + } + + if fun.Recv == nil { + typecheck.Declare(f.Nname, ir.PFUNC) + } + + p.funcBody(f, fun.Body) + + if fun.Body != nil { + if f.Pragma&ir.Noescape != 0 { + base.ErrorfAt(f.Pos(), "can only use //go:noescape with external func implementations") + } + } else { + if base.Flag.Complete || strings.HasPrefix(ir.FuncName(f), "init.") { + // Linknamed functions are allowed to have no body. Hopefully + // the linkname target has a body. See issue 23311. + isLinknamed := false + for _, n := range p.linknames { + if ir.FuncName(f) == n.local { + isLinknamed = true + break + } + } + if !isLinknamed { + base.ErrorfAt(f.Pos(), "missing function body") + } + } + } + + return f +} + +func (p *noder) signature(recv *syntax.Field, typ *syntax.FuncType) *ir.FuncType { + var rcvr *ir.Field + if recv != nil { + rcvr = p.param(recv, false, false) + } + return ir.NewFuncType(p.pos(typ), rcvr, + p.params(typ.ParamList, true), + p.params(typ.ResultList, false)) +} + +func (p *noder) params(params []*syntax.Field, dddOk bool) []*ir.Field { + nodes := make([]*ir.Field, 0, len(params)) + for i, param := range params { + p.setlineno(param) + nodes = append(nodes, p.param(param, dddOk, i+1 == len(params))) + } + return nodes +} + +func (p *noder) param(param *syntax.Field, dddOk, final bool) *ir.Field { + var name *types.Sym + if param.Name != nil { + name = p.name(param.Name) + } + + typ := p.typeExpr(param.Type) + n := ir.NewField(p.pos(param), name, typ, nil) + + // rewrite ...T parameter + if typ, ok := typ.(*ir.SliceType); ok && typ.DDD { + if !dddOk { + // We mark these as syntax errors to get automatic elimination + // of multiple such errors per line (see ErrorfAt in subr.go). + base.Errorf("syntax error: cannot use ... in receiver or result parameter list") + } else if !final { + if param.Name == nil { + base.Errorf("syntax error: cannot use ... with non-final parameter") + } else { + p.errorAt(param.Name.Pos(), "syntax error: cannot use ... with non-final parameter %s", param.Name.Value) + } + } + typ.DDD = false + n.IsDDD = true + } + + return n +} + +func (p *noder) exprList(expr syntax.Expr) []ir.Node { + switch expr := expr.(type) { + case nil: + return nil + case *syntax.ListExpr: + return p.exprs(expr.ElemList) + default: + return []ir.Node{p.expr(expr)} + } +} + +func (p *noder) exprs(exprs []syntax.Expr) []ir.Node { + nodes := make([]ir.Node, 0, len(exprs)) + for _, expr := range exprs { + nodes = append(nodes, p.expr(expr)) + } + return nodes +} + +func (p *noder) expr(expr syntax.Expr) ir.Node { + p.setlineno(expr) + switch expr := expr.(type) { + case nil, *syntax.BadExpr: + return nil + case *syntax.Name: + return p.mkname(expr) + case *syntax.BasicLit: + n := ir.NewBasicLit(p.pos(expr), p.basicLit(expr)) + if expr.Kind == syntax.RuneLit { + n.SetType(types.UntypedRune) + } + n.SetDiag(expr.Bad || n.Val().Kind() == constant.Unknown) // avoid follow-on errors if there was a syntax error + return n + case *syntax.CompositeLit: + n := ir.NewCompLitExpr(p.pos(expr), ir.OCOMPLIT, p.typeExpr(expr.Type), nil) + l := p.exprs(expr.ElemList) + for i, e := range l { + l[i] = p.wrapname(expr.ElemList[i], e) + } + n.List = l + base.Pos = p.makeXPos(expr.Rbrace) + return n + case *syntax.KeyValueExpr: + // use position of expr.Key rather than of expr (which has position of ':') + return ir.NewKeyExpr(p.pos(expr.Key), p.expr(expr.Key), p.wrapname(expr.Value, p.expr(expr.Value))) + case *syntax.FuncLit: + return p.funcLit(expr) + case *syntax.ParenExpr: + return ir.NewParenExpr(p.pos(expr), p.expr(expr.X)) + case *syntax.SelectorExpr: + // parser.new_dotname + obj := p.expr(expr.X) + if obj.Op() == ir.OPACK { + pack := obj.(*ir.PkgName) + pack.Used = true + return importName(pack.Pkg.Lookup(expr.Sel.Value)) + } + n := ir.NewSelectorExpr(base.Pos, ir.OXDOT, obj, p.name(expr.Sel)) + n.SetPos(p.pos(expr)) // lineno may have been changed by p.expr(expr.X) + return n + case *syntax.IndexExpr: + return ir.NewIndexExpr(p.pos(expr), p.expr(expr.X), p.expr(expr.Index)) + case *syntax.SliceExpr: + op := ir.OSLICE + if expr.Full { + op = ir.OSLICE3 + } + x := p.expr(expr.X) + var index [3]ir.Node + for i, n := range &expr.Index { + if n != nil { + index[i] = p.expr(n) + } + } + return ir.NewSliceExpr(p.pos(expr), op, x, index[0], index[1], index[2]) + case *syntax.AssertExpr: + return ir.NewTypeAssertExpr(p.pos(expr), p.expr(expr.X), p.typeExpr(expr.Type)) + case *syntax.Operation: + if expr.Op == syntax.Add && expr.Y != nil { + return p.sum(expr) + } + x := p.expr(expr.X) + if expr.Y == nil { + pos, op := p.pos(expr), p.unOp(expr.Op) + switch op { + case ir.OADDR: + return typecheck.NodAddrAt(pos, x) + case ir.ODEREF: + return ir.NewStarExpr(pos, x) + } + return ir.NewUnaryExpr(pos, op, x) + } + + pos, op, y := p.pos(expr), p.binOp(expr.Op), p.expr(expr.Y) + switch op { + case ir.OANDAND, ir.OOROR: + return ir.NewLogicalExpr(pos, op, x, y) + } + return ir.NewBinaryExpr(pos, op, x, y) + case *syntax.CallExpr: + n := ir.NewCallExpr(p.pos(expr), ir.OCALL, p.expr(expr.Fun), p.exprs(expr.ArgList)) + n.IsDDD = expr.HasDots + return n + + case *syntax.ArrayType: + var len ir.Node + if expr.Len != nil { + len = p.expr(expr.Len) + } + return ir.NewArrayType(p.pos(expr), len, p.typeExpr(expr.Elem)) + case *syntax.SliceType: + return ir.NewSliceType(p.pos(expr), p.typeExpr(expr.Elem)) + case *syntax.DotsType: + t := ir.NewSliceType(p.pos(expr), p.typeExpr(expr.Elem)) + t.DDD = true + return t + case *syntax.StructType: + return p.structType(expr) + case *syntax.InterfaceType: + return p.interfaceType(expr) + case *syntax.FuncType: + return p.signature(nil, expr) + case *syntax.MapType: + return ir.NewMapType(p.pos(expr), + p.typeExpr(expr.Key), p.typeExpr(expr.Value)) + case *syntax.ChanType: + return ir.NewChanType(p.pos(expr), + p.typeExpr(expr.Elem), p.chanDir(expr.Dir)) + + case *syntax.TypeSwitchGuard: + var tag *ir.Ident + if expr.Lhs != nil { + tag = ir.NewIdent(p.pos(expr.Lhs), p.name(expr.Lhs)) + if ir.IsBlank(tag) { + base.Errorf("invalid variable name %v in type switch", tag) + } + } + return ir.NewTypeSwitchGuard(p.pos(expr), tag, p.expr(expr.X)) + } + panic("unhandled Expr") +} + +// sum efficiently handles very large summation expressions (such as +// in issue #16394). In particular, it avoids left recursion and +// collapses string literals. +func (p *noder) sum(x syntax.Expr) ir.Node { + // While we need to handle long sums with asymptotic + // efficiency, the vast majority of sums are very small: ~95% + // have only 2 or 3 operands, and ~99% of string literals are + // never concatenated. + + adds := make([]*syntax.Operation, 0, 2) + for { + add, ok := x.(*syntax.Operation) + if !ok || add.Op != syntax.Add || add.Y == nil { + break + } + adds = append(adds, add) + x = add.X + } + + // nstr is the current rightmost string literal in the + // summation (if any), and chunks holds its accumulated + // substrings. + // + // Consider the expression x + "a" + "b" + "c" + y. When we + // reach the string literal "a", we assign nstr to point to + // its corresponding Node and initialize chunks to {"a"}. + // Visiting the subsequent string literals "b" and "c", we + // simply append their values to chunks. Finally, when we + // reach the non-constant operand y, we'll join chunks to form + // "abc" and reassign the "a" string literal's value. + // + // N.B., we need to be careful about named string constants + // (indicated by Sym != nil) because 1) we can't modify their + // value, as doing so would affect other uses of the string + // constant, and 2) they may have types, which we need to + // handle correctly. For now, we avoid these problems by + // treating named string constants the same as non-constant + // operands. + var nstr ir.Node + chunks := make([]string, 0, 1) + + n := p.expr(x) + if ir.IsConst(n, constant.String) && n.Sym() == nil { + nstr = n + chunks = append(chunks, ir.StringVal(nstr)) + } + + for i := len(adds) - 1; i >= 0; i-- { + add := adds[i] + + r := p.expr(add.Y) + if ir.IsConst(r, constant.String) && r.Sym() == nil { + if nstr != nil { + // Collapse r into nstr instead of adding to n. + chunks = append(chunks, ir.StringVal(r)) + continue + } + + nstr = r + chunks = append(chunks, ir.StringVal(nstr)) + } else { + if len(chunks) > 1 { + nstr.SetVal(constant.MakeString(strings.Join(chunks, ""))) + } + nstr = nil + chunks = chunks[:0] + } + n = ir.NewBinaryExpr(p.pos(add), ir.OADD, n, r) + } + if len(chunks) > 1 { + nstr.SetVal(constant.MakeString(strings.Join(chunks, ""))) + } + + return n +} + +func (p *noder) typeExpr(typ syntax.Expr) ir.Ntype { + // TODO(mdempsky): Be stricter? typecheck should handle errors anyway. + n := p.expr(typ) + if n == nil { + return nil + } + return n.(ir.Ntype) +} + +func (p *noder) typeExprOrNil(typ syntax.Expr) ir.Ntype { + if typ != nil { + return p.typeExpr(typ) + } + return nil +} + +func (p *noder) chanDir(dir syntax.ChanDir) types.ChanDir { + switch dir { + case 0: + return types.Cboth + case syntax.SendOnly: + return types.Csend + case syntax.RecvOnly: + return types.Crecv + } + panic("unhandled ChanDir") +} + +func (p *noder) structType(expr *syntax.StructType) ir.Node { + l := make([]*ir.Field, 0, len(expr.FieldList)) + for i, field := range expr.FieldList { + p.setlineno(field) + var n *ir.Field + if field.Name == nil { + n = p.embedded(field.Type) + } else { + n = ir.NewField(p.pos(field), p.name(field.Name), p.typeExpr(field.Type), nil) + } + if i < len(expr.TagList) && expr.TagList[i] != nil { + n.Note = constant.StringVal(p.basicLit(expr.TagList[i])) + } + l = append(l, n) + } + + p.setlineno(expr) + return ir.NewStructType(p.pos(expr), l) +} + +func (p *noder) interfaceType(expr *syntax.InterfaceType) ir.Node { + l := make([]*ir.Field, 0, len(expr.MethodList)) + for _, method := range expr.MethodList { + p.setlineno(method) + var n *ir.Field + if method.Name == nil { + n = ir.NewField(p.pos(method), nil, importName(p.packname(method.Type)).(ir.Ntype), nil) + } else { + mname := p.name(method.Name) + if mname.IsBlank() { + base.Errorf("methods must have a unique non-blank name") + continue + } + sig := p.typeExpr(method.Type).(*ir.FuncType) + sig.Recv = fakeRecv() + n = ir.NewField(p.pos(method), mname, sig, nil) + } + l = append(l, n) + } + + return ir.NewInterfaceType(p.pos(expr), l) +} + +func (p *noder) packname(expr syntax.Expr) *types.Sym { + switch expr := expr.(type) { + case *syntax.Name: + name := p.name(expr) + if n := oldname(name); n.Name() != nil && n.Name().PkgName != nil { + n.Name().PkgName.Used = true + } + return name + case *syntax.SelectorExpr: + name := p.name(expr.X.(*syntax.Name)) + def := ir.AsNode(name.Def) + if def == nil { + base.Errorf("undefined: %v", name) + return name + } + var pkg *types.Pkg + if def.Op() != ir.OPACK { + base.Errorf("%v is not a package", name) + pkg = types.LocalPkg + } else { + def := def.(*ir.PkgName) + def.Used = true + pkg = def.Pkg + } + return pkg.Lookup(expr.Sel.Value) + } + panic(fmt.Sprintf("unexpected packname: %#v", expr)) +} + +func (p *noder) embedded(typ syntax.Expr) *ir.Field { + op, isStar := typ.(*syntax.Operation) + if isStar { + if op.Op != syntax.Mul || op.Y != nil { + panic("unexpected Operation") + } + typ = op.X + } + + sym := p.packname(typ) + n := ir.NewField(p.pos(typ), typecheck.Lookup(sym.Name), importName(sym).(ir.Ntype), nil) + n.Embedded = true + + if isStar { + n.Ntype = ir.NewStarExpr(p.pos(op), n.Ntype) + } + return n +} + +func (p *noder) stmts(stmts []syntax.Stmt) []ir.Node { + return p.stmtsFall(stmts, false) +} + +func (p *noder) stmtsFall(stmts []syntax.Stmt, fallOK bool) []ir.Node { + var nodes []ir.Node + for i, stmt := range stmts { + s := p.stmtFall(stmt, fallOK && i+1 == len(stmts)) + if s == nil { + } else if s.Op() == ir.OBLOCK && len(s.(*ir.BlockStmt).List) > 0 { + // Inline non-empty block. + // Empty blocks must be preserved for CheckReturn. + nodes = append(nodes, s.(*ir.BlockStmt).List...) + } else { + nodes = append(nodes, s) + } + } + return nodes +} + +func (p *noder) stmt(stmt syntax.Stmt) ir.Node { + return p.stmtFall(stmt, false) +} + +func (p *noder) stmtFall(stmt syntax.Stmt, fallOK bool) ir.Node { + p.setlineno(stmt) + switch stmt := stmt.(type) { + case nil, *syntax.EmptyStmt: + return nil + case *syntax.LabeledStmt: + return p.labeledStmt(stmt, fallOK) + case *syntax.BlockStmt: + l := p.blockStmt(stmt) + if len(l) == 0 { + // TODO(mdempsky): Line number? + return ir.NewBlockStmt(base.Pos, nil) + } + return ir.NewBlockStmt(src.NoXPos, l) + case *syntax.ExprStmt: + return p.wrapname(stmt, p.expr(stmt.X)) + case *syntax.SendStmt: + return ir.NewSendStmt(p.pos(stmt), p.expr(stmt.Chan), p.expr(stmt.Value)) + case *syntax.DeclStmt: + return ir.NewBlockStmt(src.NoXPos, p.decls(stmt.DeclList)) + case *syntax.AssignStmt: + if stmt.Rhs == nil { + pos := p.pos(stmt) + n := ir.NewAssignOpStmt(pos, p.binOp(stmt.Op), p.expr(stmt.Lhs), ir.NewBasicLit(pos, one)) + n.IncDec = true + return n + } + + if stmt.Op != 0 && stmt.Op != syntax.Def { + n := ir.NewAssignOpStmt(p.pos(stmt), p.binOp(stmt.Op), p.expr(stmt.Lhs), p.expr(stmt.Rhs)) + return n + } + + rhs := p.exprList(stmt.Rhs) + if list, ok := stmt.Lhs.(*syntax.ListExpr); ok && len(list.ElemList) != 1 || len(rhs) != 1 { + n := ir.NewAssignListStmt(p.pos(stmt), ir.OAS2, nil, nil) + n.Def = stmt.Op == syntax.Def + n.Lhs = p.assignList(stmt.Lhs, n, n.Def) + n.Rhs = rhs + return n + } + + n := ir.NewAssignStmt(p.pos(stmt), nil, nil) + n.Def = stmt.Op == syntax.Def + n.X = p.assignList(stmt.Lhs, n, n.Def)[0] + n.Y = rhs[0] + return n + + case *syntax.BranchStmt: + var op ir.Op + switch stmt.Tok { + case syntax.Break: + op = ir.OBREAK + case syntax.Continue: + op = ir.OCONTINUE + case syntax.Fallthrough: + if !fallOK { + base.Errorf("fallthrough statement out of place") + } + op = ir.OFALL + case syntax.Goto: + op = ir.OGOTO + default: + panic("unhandled BranchStmt") + } + var sym *types.Sym + if stmt.Label != nil { + sym = p.name(stmt.Label) + } + return ir.NewBranchStmt(p.pos(stmt), op, sym) + case *syntax.CallStmt: + var op ir.Op + switch stmt.Tok { + case syntax.Defer: + op = ir.ODEFER + case syntax.Go: + op = ir.OGO + default: + panic("unhandled CallStmt") + } + return ir.NewGoDeferStmt(p.pos(stmt), op, p.expr(stmt.Call)) + case *syntax.ReturnStmt: + n := ir.NewReturnStmt(p.pos(stmt), p.exprList(stmt.Results)) + if len(n.Results) == 0 && ir.CurFunc != nil { + for _, ln := range ir.CurFunc.Dcl { + if ln.Class == ir.PPARAM { + continue + } + if ln.Class != ir.PPARAMOUT { + break + } + if ln.Sym().Def != ln { + base.Errorf("%s is shadowed during return", ln.Sym().Name) + } + } + } + return n + case *syntax.IfStmt: + return p.ifStmt(stmt) + case *syntax.ForStmt: + return p.forStmt(stmt) + case *syntax.SwitchStmt: + return p.switchStmt(stmt) + case *syntax.SelectStmt: + return p.selectStmt(stmt) + } + panic("unhandled Stmt") +} + +func (p *noder) assignList(expr syntax.Expr, defn ir.InitNode, colas bool) []ir.Node { + if !colas { + return p.exprList(expr) + } + + var exprs []syntax.Expr + if list, ok := expr.(*syntax.ListExpr); ok { + exprs = list.ElemList + } else { + exprs = []syntax.Expr{expr} + } + + res := make([]ir.Node, len(exprs)) + seen := make(map[*types.Sym]bool, len(exprs)) + + newOrErr := false + for i, expr := range exprs { + p.setlineno(expr) + res[i] = ir.BlankNode + + name, ok := expr.(*syntax.Name) + if !ok { + p.errorAt(expr.Pos(), "non-name %v on left side of :=", p.expr(expr)) + newOrErr = true + continue + } + + sym := p.name(name) + if sym.IsBlank() { + continue + } + + if seen[sym] { + p.errorAt(expr.Pos(), "%v repeated on left side of :=", sym) + newOrErr = true + continue + } + seen[sym] = true + + if sym.Block == types.Block { + res[i] = oldname(sym) + continue + } + + newOrErr = true + n := typecheck.NewName(sym) + typecheck.Declare(n, typecheck.DeclContext) + n.Defn = defn + defn.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, n)) + res[i] = n + } + + if !newOrErr { + base.ErrorfAt(defn.Pos(), "no new variables on left side of :=") + } + return res +} + +func (p *noder) blockStmt(stmt *syntax.BlockStmt) []ir.Node { + p.openScope(stmt.Pos()) + nodes := p.stmts(stmt.List) + p.closeScope(stmt.Rbrace) + return nodes +} + +func (p *noder) ifStmt(stmt *syntax.IfStmt) ir.Node { + p.openScope(stmt.Pos()) + init := p.stmt(stmt.Init) + n := ir.NewIfStmt(p.pos(stmt), p.expr(stmt.Cond), p.blockStmt(stmt.Then), nil) + if init != nil { + *n.PtrInit() = []ir.Node{init} + } + if stmt.Else != nil { + e := p.stmt(stmt.Else) + if e.Op() == ir.OBLOCK { + e := e.(*ir.BlockStmt) + n.Else = e.List + } else { + n.Else = []ir.Node{e} + } + } + p.closeAnotherScope() + return n +} + +func (p *noder) forStmt(stmt *syntax.ForStmt) ir.Node { + p.openScope(stmt.Pos()) + if r, ok := stmt.Init.(*syntax.RangeClause); ok { + if stmt.Cond != nil || stmt.Post != nil { + panic("unexpected RangeClause") + } + + n := ir.NewRangeStmt(p.pos(r), nil, nil, p.expr(r.X), nil) + if r.Lhs != nil { + n.Def = r.Def + lhs := p.assignList(r.Lhs, n, n.Def) + n.Key = lhs[0] + if len(lhs) > 1 { + n.Value = lhs[1] + } + } + n.Body = p.blockStmt(stmt.Body) + p.closeAnotherScope() + return n + } + + n := ir.NewForStmt(p.pos(stmt), p.stmt(stmt.Init), p.expr(stmt.Cond), p.stmt(stmt.Post), p.blockStmt(stmt.Body)) + p.closeAnotherScope() + return n +} + +func (p *noder) switchStmt(stmt *syntax.SwitchStmt) ir.Node { + p.openScope(stmt.Pos()) + + init := p.stmt(stmt.Init) + n := ir.NewSwitchStmt(p.pos(stmt), p.expr(stmt.Tag), nil) + if init != nil { + *n.PtrInit() = []ir.Node{init} + } + + var tswitch *ir.TypeSwitchGuard + if l := n.Tag; l != nil && l.Op() == ir.OTYPESW { + tswitch = l.(*ir.TypeSwitchGuard) + } + n.Cases = p.caseClauses(stmt.Body, tswitch, stmt.Rbrace) + + p.closeScope(stmt.Rbrace) + return n +} + +func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *ir.TypeSwitchGuard, rbrace syntax.Pos) []*ir.CaseClause { + nodes := make([]*ir.CaseClause, 0, len(clauses)) + for i, clause := range clauses { + p.setlineno(clause) + if i > 0 { + p.closeScope(clause.Pos()) + } + p.openScope(clause.Pos()) + + n := ir.NewCaseStmt(p.pos(clause), p.exprList(clause.Cases), nil) + if tswitch != nil && tswitch.Tag != nil { + nn := typecheck.NewName(tswitch.Tag.Sym()) + typecheck.Declare(nn, typecheck.DeclContext) + n.Var = nn + // keep track of the instances for reporting unused + nn.Defn = tswitch + } + + // Trim trailing empty statements. We omit them from + // the Node AST anyway, and it's easier to identify + // out-of-place fallthrough statements without them. + body := clause.Body + for len(body) > 0 { + if _, ok := body[len(body)-1].(*syntax.EmptyStmt); !ok { + break + } + body = body[:len(body)-1] + } + + n.Body = p.stmtsFall(body, true) + if l := len(n.Body); l > 0 && n.Body[l-1].Op() == ir.OFALL { + if tswitch != nil { + base.Errorf("cannot fallthrough in type switch") + } + if i+1 == len(clauses) { + base.Errorf("cannot fallthrough final case in switch") + } + } + + nodes = append(nodes, n) + } + if len(clauses) > 0 { + p.closeScope(rbrace) + } + return nodes +} + +func (p *noder) selectStmt(stmt *syntax.SelectStmt) ir.Node { + return ir.NewSelectStmt(p.pos(stmt), p.commClauses(stmt.Body, stmt.Rbrace)) +} + +func (p *noder) commClauses(clauses []*syntax.CommClause, rbrace syntax.Pos) []*ir.CommClause { + nodes := make([]*ir.CommClause, len(clauses)) + for i, clause := range clauses { + p.setlineno(clause) + if i > 0 { + p.closeScope(clause.Pos()) + } + p.openScope(clause.Pos()) + + nodes[i] = ir.NewCommStmt(p.pos(clause), p.stmt(clause.Comm), p.stmts(clause.Body)) + } + if len(clauses) > 0 { + p.closeScope(rbrace) + } + return nodes +} + +func (p *noder) labeledStmt(label *syntax.LabeledStmt, fallOK bool) ir.Node { + sym := p.name(label.Label) + lhs := ir.NewLabelStmt(p.pos(label), sym) + + var ls ir.Node + if label.Stmt != nil { // TODO(mdempsky): Should always be present. + ls = p.stmtFall(label.Stmt, fallOK) + // Attach label directly to control statement too. + if ls != nil { + switch ls.Op() { + case ir.OFOR: + ls := ls.(*ir.ForStmt) + ls.Label = sym + case ir.ORANGE: + ls := ls.(*ir.RangeStmt) + ls.Label = sym + case ir.OSWITCH: + ls := ls.(*ir.SwitchStmt) + ls.Label = sym + case ir.OSELECT: + ls := ls.(*ir.SelectStmt) + ls.Label = sym + } + } + } + + l := []ir.Node{lhs} + if ls != nil { + if ls.Op() == ir.OBLOCK { + ls := ls.(*ir.BlockStmt) + l = append(l, ls.List...) + } else { + l = append(l, ls) + } + } + return ir.NewBlockStmt(src.NoXPos, l) +} + +var unOps = [...]ir.Op{ + syntax.Recv: ir.ORECV, + syntax.Mul: ir.ODEREF, + syntax.And: ir.OADDR, + + syntax.Not: ir.ONOT, + syntax.Xor: ir.OBITNOT, + syntax.Add: ir.OPLUS, + syntax.Sub: ir.ONEG, +} + +func (p *noder) unOp(op syntax.Operator) ir.Op { + if uint64(op) >= uint64(len(unOps)) || unOps[op] == 0 { + panic("invalid Operator") + } + return unOps[op] +} + +var binOps = [...]ir.Op{ + syntax.OrOr: ir.OOROR, + syntax.AndAnd: ir.OANDAND, + + syntax.Eql: ir.OEQ, + syntax.Neq: ir.ONE, + syntax.Lss: ir.OLT, + syntax.Leq: ir.OLE, + syntax.Gtr: ir.OGT, + syntax.Geq: ir.OGE, + + syntax.Add: ir.OADD, + syntax.Sub: ir.OSUB, + syntax.Or: ir.OOR, + syntax.Xor: ir.OXOR, + + syntax.Mul: ir.OMUL, + syntax.Div: ir.ODIV, + syntax.Rem: ir.OMOD, + syntax.And: ir.OAND, + syntax.AndNot: ir.OANDNOT, + syntax.Shl: ir.OLSH, + syntax.Shr: ir.ORSH, +} + +func (p *noder) binOp(op syntax.Operator) ir.Op { + if uint64(op) >= uint64(len(binOps)) || binOps[op] == 0 { + panic("invalid Operator") + } + return binOps[op] +} + +// checkLangCompat reports an error if the representation of a numeric +// literal is not compatible with the current language version. +func checkLangCompat(lit *syntax.BasicLit) { + s := lit.Value + if len(s) <= 2 || types.AllowsGoVersion(types.LocalPkg, 1, 13) { + return + } + // len(s) > 2 + if strings.Contains(s, "_") { + base.ErrorfVers("go1.13", "underscores in numeric literals") + return + } + if s[0] != '0' { + return + } + radix := s[1] + if radix == 'b' || radix == 'B' { + base.ErrorfVers("go1.13", "binary literals") + return + } + if radix == 'o' || radix == 'O' { + base.ErrorfVers("go1.13", "0o/0O-style octal literals") + return + } + if lit.Kind != syntax.IntLit && (radix == 'x' || radix == 'X') { + base.ErrorfVers("go1.13", "hexadecimal floating-point literals") + } +} + +func (p *noder) basicLit(lit *syntax.BasicLit) constant.Value { + // We don't use the errors of the conversion routines to determine + // if a literal string is valid because the conversion routines may + // accept a wider syntax than the language permits. Rely on lit.Bad + // instead. + if lit.Bad { + return constant.MakeUnknown() + } + + switch lit.Kind { + case syntax.IntLit, syntax.FloatLit, syntax.ImagLit: + checkLangCompat(lit) + // The max. mantissa precision for untyped numeric values + // is 512 bits, or 4048 bits for each of the two integer + // parts of a fraction for floating-point numbers that are + // represented accurately in the go/constant package. + // Constant literals that are longer than this many bits + // are not meaningful; and excessively long constants may + // consume a lot of space and time for a useless conversion. + // Cap constant length with a generous upper limit that also + // allows for separators between all digits. + const limit = 10000 + if len(lit.Value) > limit { + p.errorAt(lit.Pos(), "excessively long constant: %s... (%d chars)", lit.Value[:10], len(lit.Value)) + return constant.MakeUnknown() + } + } + + v := constant.MakeFromLiteral(lit.Value, tokenForLitKind[lit.Kind], 0) + if v.Kind() == constant.Unknown { + // TODO(mdempsky): Better error message? + p.errorAt(lit.Pos(), "malformed constant: %s", lit.Value) + } + + return v +} + +var tokenForLitKind = [...]token.Token{ + syntax.IntLit: token.INT, + syntax.RuneLit: token.CHAR, + syntax.FloatLit: token.FLOAT, + syntax.ImagLit: token.IMAG, + syntax.StringLit: token.STRING, +} + +func (p *noder) name(name *syntax.Name) *types.Sym { + return typecheck.Lookup(name.Value) +} + +func (p *noder) mkname(name *syntax.Name) ir.Node { + // TODO(mdempsky): Set line number? + return mkname(p.name(name)) +} + +func (p *noder) wrapname(n syntax.Node, x ir.Node) ir.Node { + // These nodes do not carry line numbers. + // Introduce a wrapper node to give them the correct line. + switch x.Op() { + case ir.OTYPE, ir.OLITERAL: + if x.Sym() == nil { + break + } + fallthrough + case ir.ONAME, ir.ONONAME, ir.OPACK: + p := ir.NewParenExpr(p.pos(n), x) + p.SetImplicit(true) + return p + } + return x +} + +func (p *noder) setlineno(n syntax.Node) { + if n != nil { + base.Pos = p.pos(n) + } +} + +// error is called concurrently if files are parsed concurrently. +func (p *noder) error(err error) { + p.err <- err.(syntax.Error) +} + +// pragmas that are allowed in the std lib, but don't have +// a syntax.Pragma value (see lex.go) associated with them. +var allowedStdPragmas = map[string]bool{ + "go:cgo_export_static": true, + "go:cgo_export_dynamic": true, + "go:cgo_import_static": true, + "go:cgo_import_dynamic": true, + "go:cgo_ldflag": true, + "go:cgo_dynamic_linker": true, + "go:embed": true, + "go:generate": true, +} + +// *pragmas is the value stored in a syntax.pragmas during parsing. +type pragmas struct { + Flag ir.PragmaFlag // collected bits + Pos []pragmaPos // position of each individual flag + Embeds []pragmaEmbed +} + +type pragmaPos struct { + Flag ir.PragmaFlag + Pos syntax.Pos +} + +type pragmaEmbed struct { + Pos syntax.Pos + Patterns []string +} + +func (p *noder) checkUnused(pragma *pragmas) { + for _, pos := range pragma.Pos { + if pos.Flag&pragma.Flag != 0 { + p.errorAt(pos.Pos, "misplaced compiler directive") + } + } + if len(pragma.Embeds) > 0 { + for _, e := range pragma.Embeds { + p.errorAt(e.Pos, "misplaced go:embed directive") + } + } +} + +func (p *noder) checkUnusedDuringParse(pragma *pragmas) { + for _, pos := range pragma.Pos { + if pos.Flag&pragma.Flag != 0 { + p.error(syntax.Error{Pos: pos.Pos, Msg: "misplaced compiler directive"}) + } + } + if len(pragma.Embeds) > 0 { + for _, e := range pragma.Embeds { + p.error(syntax.Error{Pos: e.Pos, Msg: "misplaced go:embed directive"}) + } + } +} + +// pragma is called concurrently if files are parsed concurrently. +func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.Pragma) syntax.Pragma { + pragma, _ := old.(*pragmas) + if pragma == nil { + pragma = new(pragmas) + } + + if text == "" { + // unused pragma; only called with old != nil. + p.checkUnusedDuringParse(pragma) + return nil + } + + if strings.HasPrefix(text, "line ") { + // line directives are handled by syntax package + panic("unreachable") + } + + if !blankLine { + // directive must be on line by itself + p.error(syntax.Error{Pos: pos, Msg: "misplaced compiler directive"}) + return pragma + } + + switch { + case strings.HasPrefix(text, "go:linkname "): + f := strings.Fields(text) + if !(2 <= len(f) && len(f) <= 3) { + p.error(syntax.Error{Pos: pos, Msg: "usage: //go:linkname localname [linkname]"}) + break + } + // The second argument is optional. If omitted, we use + // the default object symbol name for this and + // linkname only serves to mark this symbol as + // something that may be referenced via the object + // symbol name from another package. + var target string + if len(f) == 3 { + target = f[2] + } else if base.Ctxt.Pkgpath != "" { + // Use the default object symbol name if the + // user didn't provide one. + target = objabi.PathToPrefix(base.Ctxt.Pkgpath) + "." + f[1] + } else { + p.error(syntax.Error{Pos: pos, Msg: "//go:linkname requires linkname argument or -p compiler flag"}) + break + } + p.linknames = append(p.linknames, linkname{pos, f[1], target}) + + case text == "go:embed", strings.HasPrefix(text, "go:embed "): + args, err := parseGoEmbed(text[len("go:embed"):]) + if err != nil { + p.error(syntax.Error{Pos: pos, Msg: err.Error()}) + } + if len(args) == 0 { + p.error(syntax.Error{Pos: pos, Msg: "usage: //go:embed pattern..."}) + break + } + pragma.Embeds = append(pragma.Embeds, pragmaEmbed{pos, args}) + + case strings.HasPrefix(text, "go:cgo_import_dynamic "): + // This is permitted for general use because Solaris + // code relies on it in golang.org/x/sys/unix and others. + fields := pragmaFields(text) + if len(fields) >= 4 { + lib := strings.Trim(fields[3], `"`) + if lib != "" && !safeArg(lib) && !isCgoGeneratedFile(pos) { + p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("invalid library name %q in cgo_import_dynamic directive", lib)}) + } + p.pragcgo(pos, text) + pragma.Flag |= pragmaFlag("go:cgo_import_dynamic") + break + } + fallthrough + case strings.HasPrefix(text, "go:cgo_"): + // For security, we disallow //go:cgo_* directives other + // than cgo_import_dynamic outside cgo-generated files. + // Exception: they are allowed in the standard library, for runtime and syscall. + if !isCgoGeneratedFile(pos) && !base.Flag.Std { + p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in cgo-generated code", text)}) + } + p.pragcgo(pos, text) + fallthrough // because of //go:cgo_unsafe_args + default: + verb := text + if i := strings.Index(text, " "); i >= 0 { + verb = verb[:i] + } + flag := pragmaFlag(verb) + const runtimePragmas = ir.Systemstack | ir.Nowritebarrier | ir.Nowritebarrierrec | ir.Yeswritebarrierrec + if !base.Flag.CompilingRuntime && flag&runtimePragmas != 0 { + p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in runtime", verb)}) + } + if flag == 0 && !allowedStdPragmas[verb] && base.Flag.Std { + p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s is not allowed in the standard library", verb)}) + } + pragma.Flag |= flag + pragma.Pos = append(pragma.Pos, pragmaPos{flag, pos}) + } + + return pragma +} + +// isCgoGeneratedFile reports whether pos is in a file +// generated by cgo, which is to say a file with name +// beginning with "_cgo_". Such files are allowed to +// contain cgo directives, and for security reasons +// (primarily misuse of linker flags), other files are not. +// See golang.org/issue/23672. +func isCgoGeneratedFile(pos syntax.Pos) bool { + return strings.HasPrefix(filepath.Base(filepath.Clean(fileh(pos.Base().Filename()))), "_cgo_") +} + +// safeArg reports whether arg is a "safe" command-line argument, +// meaning that when it appears in a command-line, it probably +// doesn't have some special meaning other than its own name. +// This is copied from SafeArg in cmd/go/internal/load/pkg.go. +func safeArg(name string) bool { + if name == "" { + return false + } + c := name[0] + return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf +} + +func mkname(sym *types.Sym) ir.Node { + n := oldname(sym) + if n.Name() != nil && n.Name().PkgName != nil { + n.Name().PkgName.Used = true + } + return n +} + +// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns. +// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings. +// go/build/read.go also processes these strings and contains similar logic. +func parseGoEmbed(args string) ([]string, error) { + var list []string + for args = strings.TrimSpace(args); args != ""; args = strings.TrimSpace(args) { + var path string + Switch: + switch args[0] { + default: + i := len(args) + for j, c := range args { + if unicode.IsSpace(c) { + i = j + break + } + } + path = args[:i] + args = args[i:] + + case '`': + i := strings.Index(args[1:], "`") + if i < 0 { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) + } + path = args[1 : 1+i] + args = args[1+i+1:] + + case '"': + i := 1 + for ; i < len(args); i++ { + if args[i] == '\\' { + i++ + continue + } + if args[i] == '"' { + q, err := strconv.Unquote(args[:i+1]) + if err != nil { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1]) + } + path = q + args = args[i+1:] + break Switch + } + } + if i >= len(args) { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) + } + } + + if args != "" { + r, _ := utf8.DecodeRuneInString(args) + if !unicode.IsSpace(r) { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) + } + } + list = append(list, path) + } + return list, nil +} + +func fakeRecv() *ir.Field { + return ir.NewField(base.Pos, nil, nil, types.FakeRecvType()) +} + +func (p *noder) funcLit(expr *syntax.FuncLit) ir.Node { + xtype := p.typeExpr(expr.Type) + + fn := ir.NewFunc(p.pos(expr)) + fn.SetIsHiddenClosure(ir.CurFunc != nil) + + fn.Nname = ir.NewNameAt(p.pos(expr), ir.BlankNode.Sym()) // filled in by tcClosure + fn.Nname.Func = fn + fn.Nname.Ntype = xtype + fn.Nname.Defn = fn + + clo := ir.NewClosureExpr(p.pos(expr), fn) + fn.OClosure = clo + + p.funcBody(fn, expr.Body) + + ir.FinishCaptureNames(base.Pos, ir.CurFunc, fn) + + return clo +} + +// A function named init is a special case. +// It is called by the initialization before main is run. +// To make it unique within a package and also uncallable, +// the name, normally "pkg.init", is altered to "pkg.init.0". +var renameinitgen int + +func renameinit() *types.Sym { + s := typecheck.LookupNum("init.", renameinitgen) + renameinitgen++ + return s +} + +// oldname returns the Node that declares symbol s in the current scope. +// If no such Node currently exists, an ONONAME Node is returned instead. +// Automatically creates a new closure variable if the referenced symbol was +// declared in a different (containing) function. +func oldname(s *types.Sym) ir.Node { + if s.Pkg != types.LocalPkg { + return ir.NewIdent(base.Pos, s) + } + + n := ir.AsNode(s.Def) + if n == nil { + // Maybe a top-level declaration will come along later to + // define s. resolve will check s.Def again once all input + // source has been processed. + return ir.NewIdent(base.Pos, s) + } + + if n, ok := n.(*ir.Name); ok { + // TODO(rsc): If there is an outer variable x and we + // are parsing x := 5 inside the closure, until we get to + // the := it looks like a reference to the outer x so we'll + // make x a closure variable unnecessarily. + return ir.CaptureName(base.Pos, ir.CurFunc, n) + } + + return n +} + +func varEmbed(makeXPos func(syntax.Pos) src.XPos, name *ir.Name, decl *syntax.VarDecl, pragma *pragmas, haveEmbed bool) { + if pragma.Embeds == nil { + return + } + + pragmaEmbeds := pragma.Embeds + pragma.Embeds = nil + pos := makeXPos(pragmaEmbeds[0].Pos) + + if !haveEmbed { + base.ErrorfAt(pos, "go:embed only allowed in Go files that import \"embed\"") + return + } + if len(decl.NameList) > 1 { + base.ErrorfAt(pos, "go:embed cannot apply to multiple vars") + return + } + if decl.Values != nil { + base.ErrorfAt(pos, "go:embed cannot apply to var with initializer") + return + } + if decl.Type == nil { + // Should not happen, since Values == nil now. + base.ErrorfAt(pos, "go:embed cannot apply to var without type") + return + } + if typecheck.DeclContext != ir.PEXTERN { + base.ErrorfAt(pos, "go:embed cannot apply to var inside func") + return + } + + var embeds []ir.Embed + for _, e := range pragmaEmbeds { + embeds = append(embeds, ir.Embed{Pos: makeXPos(e.Pos), Patterns: e.Patterns}) + } + typecheck.Target.Embeds = append(typecheck.Target.Embeds, name) + name.Embed = &embeds +} diff --git a/src/cmd/compile/internal/noder/object.go b/src/cmd/compile/internal/noder/object.go new file mode 100644 index 0000000000000000000000000000000000000000..82cce1ace0fcff81856f1751498f009c2d5fa84b --- /dev/null +++ b/src/cmd/compile/internal/noder/object.go @@ -0,0 +1,184 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/compile/internal/types2" + "cmd/internal/src" +) + +func (g *irgen) def(name *syntax.Name) (*ir.Name, types2.Object) { + obj, ok := g.info.Defs[name] + if !ok { + base.FatalfAt(g.pos(name), "unknown name %v", name) + } + return g.obj(obj), obj +} + +// use returns the Name node associated with the use of name. The returned node +// will have the correct type and be marked as typechecked. +func (g *irgen) use(name *syntax.Name) *ir.Name { + obj2, ok := g.info.Uses[name] + if !ok { + base.FatalfAt(g.pos(name), "unknown name %v", name) + } + obj := ir.CaptureName(g.pos(obj2), ir.CurFunc, g.obj(obj2)) + if obj.Defn != nil && obj.Defn.Op() == ir.ONAME { + // If CaptureName created a closure variable, then transfer the + // type of the captured name to the new closure variable. + obj.SetTypecheck(1) + obj.SetType(obj.Defn.Type()) + } + return obj +} + +// obj returns the Name that represents the given object. If no such Name exists +// yet, it will be implicitly created. The returned node will have the correct +// type and be marked as typechecked. +// +// For objects declared at function scope, ir.CurFunc must already be +// set to the respective function when the Name is created. +func (g *irgen) obj(obj types2.Object) *ir.Name { + // For imported objects, we use iimport directly instead of mapping + // the types2 representation. + if obj.Pkg() != g.self { + sym := g.sym(obj) + if sym.Def != nil { + return sym.Def.(*ir.Name) + } + n := typecheck.Resolve(ir.NewIdent(src.NoXPos, sym)) + if n, ok := n.(*ir.Name); ok { + n.SetTypecheck(1) + return n + } + base.FatalfAt(g.pos(obj), "failed to resolve %v", obj) + } + + if name, ok := g.objs[obj]; ok { + return name // previously mapped + } + + var name *ir.Name + pos := g.pos(obj) + + class := typecheck.DeclContext + if obj.Parent() == g.self.Scope() { + class = ir.PEXTERN // forward reference to package-block declaration + } + + // "You are in a maze of twisting little passages, all different." + switch obj := obj.(type) { + case *types2.Const: + name = g.objCommon(pos, ir.OLITERAL, g.sym(obj), class, g.typ(obj.Type())) + + case *types2.Func: + sig := obj.Type().(*types2.Signature) + var sym *types.Sym + var typ *types.Type + if recv := sig.Recv(); recv == nil { + if obj.Name() == "init" { + sym = renameinit() + } else { + sym = g.sym(obj) + } + typ = g.typ(sig) + } else { + sym = g.selector(obj) + if !sym.IsBlank() { + sym = ir.MethodSym(g.typ(recv.Type()), sym) + } + typ = g.signature(g.param(recv), sig) + } + name = g.objCommon(pos, ir.ONAME, sym, ir.PFUNC, typ) + + case *types2.TypeName: + if obj.IsAlias() { + name = g.objCommon(pos, ir.OTYPE, g.sym(obj), class, g.typ(obj.Type())) + } else { + name = ir.NewDeclNameAt(pos, ir.OTYPE, g.sym(obj)) + g.objFinish(name, class, types.NewNamed(name)) + } + + case *types2.Var: + var sym *types.Sym + if class == ir.PPARAMOUT { + // Backend needs names for result parameters, + // even if they're anonymous or blank. + switch obj.Name() { + case "": + sym = typecheck.LookupNum("~r", len(ir.CurFunc.Dcl)) // 'r' for "result" + case "_": + sym = typecheck.LookupNum("~b", len(ir.CurFunc.Dcl)) // 'b' for "blank" + } + } + if sym == nil { + sym = g.sym(obj) + } + name = g.objCommon(pos, ir.ONAME, sym, class, g.typ(obj.Type())) + + default: + g.unhandled("object", obj) + } + + g.objs[obj] = name + name.SetTypecheck(1) + return name +} + +func (g *irgen) objCommon(pos src.XPos, op ir.Op, sym *types.Sym, class ir.Class, typ *types.Type) *ir.Name { + name := ir.NewDeclNameAt(pos, op, sym) + g.objFinish(name, class, typ) + return name +} + +func (g *irgen) objFinish(name *ir.Name, class ir.Class, typ *types.Type) { + sym := name.Sym() + + name.SetType(typ) + name.Class = class + if name.Class == ir.PFUNC { + sym.SetFunc(true) + } + + name.SetTypecheck(1) + name.SetWalkdef(1) + + if ir.IsBlank(name) { + return + } + + switch class { + case ir.PEXTERN: + g.target.Externs = append(g.target.Externs, name) + fallthrough + case ir.PFUNC: + sym.Def = name + if name.Class == ir.PFUNC && name.Type().Recv() != nil { + break // methods are exported with their receiver type + } + if types.IsExported(sym.Name) { + if name.Class == ir.PFUNC && name.Type().NumTParams() > 0 { + base.FatalfAt(name.Pos(), "Cannot export a generic function (yet): %v", name) + } + typecheck.Export(name) + } + if base.Flag.AsmHdr != "" && !name.Sym().Asm() { + name.Sym().SetAsm(true) + g.target.Asms = append(g.target.Asms, name) + } + + default: + // Function-scoped declaration. + name.Curfn = ir.CurFunc + if name.Op() == ir.ONAME { + ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, name) + } + } +} diff --git a/src/cmd/compile/internal/noder/posmap.go b/src/cmd/compile/internal/noder/posmap.go new file mode 100644 index 0000000000000000000000000000000000000000..a6d3e2d7ef4d9ac0aa241eede47c23b2c6595e85 --- /dev/null +++ b/src/cmd/compile/internal/noder/posmap.go @@ -0,0 +1,83 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/syntax" + "cmd/internal/src" +) + +// A posMap handles mapping from syntax.Pos to src.XPos. +type posMap struct { + bases map[*syntax.PosBase]*src.PosBase + cache struct { + last *syntax.PosBase + base *src.PosBase + } +} + +type poser interface{ Pos() syntax.Pos } +type ender interface{ End() syntax.Pos } + +func (m *posMap) pos(p poser) src.XPos { return m.makeXPos(p.Pos()) } +func (m *posMap) end(p ender) src.XPos { return m.makeXPos(p.End()) } + +func (m *posMap) makeXPos(pos syntax.Pos) src.XPos { + if !pos.IsKnown() { + // TODO(mdempsky): Investigate restoring base.Fatalf. + return src.NoXPos + } + + posBase := m.makeSrcPosBase(pos.Base()) + return base.Ctxt.PosTable.XPos(src.MakePos(posBase, pos.Line(), pos.Col())) +} + +// makeSrcPosBase translates from a *syntax.PosBase to a *src.PosBase. +func (m *posMap) makeSrcPosBase(b0 *syntax.PosBase) *src.PosBase { + // fast path: most likely PosBase hasn't changed + if m.cache.last == b0 { + return m.cache.base + } + + b1, ok := m.bases[b0] + if !ok { + fn := b0.Filename() + if b0.IsFileBase() { + b1 = src.NewFileBase(fn, absFilename(fn)) + } else { + // line directive base + p0 := b0.Pos() + p0b := p0.Base() + if p0b == b0 { + panic("infinite recursion in makeSrcPosBase") + } + p1 := src.MakePos(m.makeSrcPosBase(p0b), p0.Line(), p0.Col()) + b1 = src.NewLinePragmaBase(p1, fn, fileh(fn), b0.Line(), b0.Col()) + } + if m.bases == nil { + m.bases = make(map[*syntax.PosBase]*src.PosBase) + } + m.bases[b0] = b1 + } + + // update cache + m.cache.last = b0 + m.cache.base = b1 + + return b1 +} + +func (m *posMap) join(other *posMap) { + if m.bases == nil { + m.bases = make(map[*syntax.PosBase]*src.PosBase) + } + for k, v := range other.bases { + if m.bases[k] != nil { + base.Fatalf("duplicate posmap bases") + } + m.bases[k] = v + } +} diff --git a/src/cmd/compile/internal/noder/scopes.go b/src/cmd/compile/internal/noder/scopes.go new file mode 100644 index 0000000000000000000000000000000000000000..eb518474c637a04a7a0e31921bd65463323742ed --- /dev/null +++ b/src/cmd/compile/internal/noder/scopes.go @@ -0,0 +1,64 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" + "cmd/compile/internal/types2" +) + +// recordScopes populates fn.Parents and fn.Marks based on the scoping +// information provided by types2. +func (g *irgen) recordScopes(fn *ir.Func, sig *syntax.FuncType) { + scope, ok := g.info.Scopes[sig] + if !ok { + base.FatalfAt(fn.Pos(), "missing scope for %v", fn) + } + + for i, n := 0, scope.NumChildren(); i < n; i++ { + g.walkScope(scope.Child(i)) + } + + g.marker.WriteTo(fn) +} + +func (g *irgen) walkScope(scope *types2.Scope) bool { + // types2 doesn't provide a proper API for determining the + // lexical element a scope represents, so we have to resort to + // string matching. Conveniently though, this allows us to + // skip both function types and function literals, neither of + // which are interesting to us here. + if strings.HasPrefix(scope.String(), "function scope ") { + return false + } + + g.marker.Push(g.pos(scope)) + + haveVars := false + for _, name := range scope.Names() { + if obj, ok := scope.Lookup(name).(*types2.Var); ok && obj.Name() != "_" { + haveVars = true + break + } + } + + for i, n := 0, scope.NumChildren(); i < n; i++ { + if g.walkScope(scope.Child(i)) { + haveVars = true + } + } + + if haveVars { + g.marker.Pop(g.end(scope)) + } else { + g.marker.Unpush() + } + + return haveVars +} diff --git a/src/cmd/compile/internal/noder/sizes.go b/src/cmd/compile/internal/noder/sizes.go new file mode 100644 index 0000000000000000000000000000000000000000..23f206267547f4fd467fc91a93f0ab3998c2db5c --- /dev/null +++ b/src/cmd/compile/internal/noder/sizes.go @@ -0,0 +1,150 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "fmt" + + "cmd/compile/internal/types" + "cmd/compile/internal/types2" +) + +// Code below based on go/types.StdSizes. +// Intentional differences are marked with "gc:". + +type gcSizes struct{} + +func (s *gcSizes) Alignof(T types2.Type) int64 { + // For arrays and structs, alignment is defined in terms + // of alignment of the elements and fields, respectively. + switch t := T.Underlying().(type) { + case *types2.Array: + // spec: "For a variable x of array type: unsafe.Alignof(x) + // is the same as unsafe.Alignof(x[0]), but at least 1." + return s.Alignof(t.Elem()) + case *types2.Struct: + // spec: "For a variable x of struct type: unsafe.Alignof(x) + // is the largest of the values unsafe.Alignof(x.f) for each + // field f of x, but at least 1." + max := int64(1) + for i, nf := 0, t.NumFields(); i < nf; i++ { + if a := s.Alignof(t.Field(i).Type()); a > max { + max = a + } + } + return max + case *types2.Slice, *types2.Interface: + // Multiword data structures are effectively structs + // in which each element has size PtrSize. + return int64(types.PtrSize) + case *types2.Basic: + // Strings are like slices and interfaces. + if t.Info()&types2.IsString != 0 { + return int64(types.PtrSize) + } + } + a := s.Sizeof(T) // may be 0 + // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." + if a < 1 { + return 1 + } + // complex{64,128} are aligned like [2]float{32,64}. + if isComplex(T) { + a /= 2 + } + if a > int64(types.RegSize) { + return int64(types.RegSize) + } + return a +} + +func isComplex(T types2.Type) bool { + basic, ok := T.Underlying().(*types2.Basic) + return ok && basic.Info()&types2.IsComplex != 0 +} + +func (s *gcSizes) Offsetsof(fields []*types2.Var) []int64 { + offsets := make([]int64, len(fields)) + var o int64 + for i, f := range fields { + typ := f.Type() + a := s.Alignof(typ) + o = types.Rnd(o, a) + offsets[i] = o + o += s.Sizeof(typ) + } + return offsets +} + +func (s *gcSizes) Sizeof(T types2.Type) int64 { + switch t := T.Underlying().(type) { + case *types2.Basic: + k := t.Kind() + if int(k) < len(basicSizes) { + if s := basicSizes[k]; s > 0 { + return int64(s) + } + } + switch k { + case types2.String: + return int64(types.PtrSize) * 2 + case types2.Int, types2.Uint, types2.Uintptr, types2.UnsafePointer: + return int64(types.PtrSize) + } + panic(fmt.Sprintf("unimplemented basic: %v (kind %v)", T, k)) + case *types2.Array: + n := t.Len() + if n <= 0 { + return 0 + } + // n > 0 + // gc: Size includes alignment padding. + return s.Sizeof(t.Elem()) * n + case *types2.Slice: + return int64(types.PtrSize) * 3 + case *types2.Struct: + n := t.NumFields() + if n == 0 { + return 0 + } + fields := make([]*types2.Var, n) + for i := range fields { + fields[i] = t.Field(i) + } + offsets := s.Offsetsof(fields) + + // gc: The last field of a non-zero-sized struct is not allowed to + // have size 0. + last := s.Sizeof(fields[n-1].Type()) + if last == 0 && offsets[n-1] > 0 { + last = 1 + } + + // gc: Size includes alignment padding. + return types.Rnd(offsets[n-1]+last, s.Alignof(t)) + case *types2.Interface: + return int64(types.PtrSize) * 2 + case *types2.Chan, *types2.Map, *types2.Pointer, *types2.Signature: + return int64(types.PtrSize) + default: + panic(fmt.Sprintf("unimplemented type: %T", t)) + } +} + +var basicSizes = [...]byte{ + types2.Bool: 1, + types2.Int8: 1, + types2.Int16: 2, + types2.Int32: 4, + types2.Int64: 8, + types2.Uint8: 1, + types2.Uint16: 2, + types2.Uint32: 4, + types2.Uint64: 8, + types2.Float32: 4, + types2.Float64: 8, + types2.Complex64: 8, + types2.Complex128: 16, +} diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go new file mode 100644 index 0000000000000000000000000000000000000000..3ebc8dff6d84322d2d5c2379e9497a87ebb1d73b --- /dev/null +++ b/src/cmd/compile/internal/noder/stencil.go @@ -0,0 +1,917 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file will evolve, since we plan to do a mix of stenciling and passing +// around dictionaries. + +package noder + +import ( + "bytes" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" + "fmt" + "strings" +) + +// For catching problems as we add more features +// TODO(danscales): remove assertions or replace with base.FatalfAt() +func assert(p bool) { + if !p { + panic("assertion failed") + } +} + +// stencil scans functions for instantiated generic function calls and creates the +// required instantiations for simple generic functions. It also creates +// instantiated methods for all fully-instantiated generic types that have been +// encountered already or new ones that are encountered during the stenciling +// process. +func (g *irgen) stencil() { + g.target.Stencils = make(map[*types.Sym]*ir.Func) + + // Instantiate the methods of instantiated generic types that we have seen so far. + g.instantiateMethods() + + // Don't use range(g.target.Decls) - we also want to process any new instantiated + // functions that are created during this loop, in order to handle generic + // functions calling other generic functions. + for i := 0; i < len(g.target.Decls); i++ { + decl := g.target.Decls[i] + + // Look for function instantiations in bodies of non-generic + // functions or in global assignments (ignore global type and + // constant declarations). + switch decl.Op() { + case ir.ODCLFUNC: + if decl.Type().HasTParam() { + // Skip any generic functions + continue + } + // transformCall() below depends on CurFunc being set. + ir.CurFunc = decl.(*ir.Func) + + case ir.OAS, ir.OAS2, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV, ir.OASOP: + // These are all the various kinds of global assignments, + // whose right-hand-sides might contain a function + // instantiation. + + default: + // The other possible ops at the top level are ODCLCONST + // and ODCLTYPE, which don't have any function + // instantiations. + continue + } + + // For all non-generic code, search for any function calls using + // generic function instantiations. Then create the needed + // instantiated function if it hasn't been created yet, and change + // to calling that function directly. + modified := false + foundFuncInst := false + ir.Visit(decl, func(n ir.Node) { + if n.Op() == ir.OFUNCINST { + // We found a function instantiation that is not + // immediately called. + foundFuncInst = true + } + if n.Op() != ir.OCALL || n.(*ir.CallExpr).X.Op() != ir.OFUNCINST { + return + } + // We have found a function call using a generic function + // instantiation. + call := n.(*ir.CallExpr) + inst := call.X.(*ir.InstExpr) + st := g.getInstantiationForNode(inst) + // Replace the OFUNCINST with a direct reference to the + // new stenciled function + call.X = st.Nname + if inst.X.Op() == ir.OCALLPART { + // When we create an instantiation of a method + // call, we make it a function. So, move the + // receiver to be the first arg of the function + // call. + withRecv := make([]ir.Node, len(call.Args)+1) + dot := inst.X.(*ir.SelectorExpr) + withRecv[0] = dot.X + copy(withRecv[1:], call.Args) + call.Args = withRecv + } + // Transform the Call now, which changes OCALL + // to OCALLFUNC and does typecheckaste/assignconvfn. + transformCall(call) + modified = true + }) + + // If we found an OFUNCINST without a corresponding call in the + // above decl, then traverse the nodes of decl again (with + // EditChildren rather than Visit), where we actually change the + // OFUNCINST node to an ONAME for the instantiated function. + // EditChildren is more expensive than Visit, so we only do this + // in the infrequent case of an OFUNCINSt without a corresponding + // call. + if foundFuncInst { + var edit func(ir.Node) ir.Node + edit = func(x ir.Node) ir.Node { + if x.Op() == ir.OFUNCINST { + st := g.getInstantiationForNode(x.(*ir.InstExpr)) + return st.Nname + } + ir.EditChildren(x, edit) + return x + } + edit(decl) + } + if base.Flag.W > 1 && modified { + ir.Dump(fmt.Sprintf("\nmodified %v", decl), decl) + } + ir.CurFunc = nil + // We may have seen new fully-instantiated generic types while + // instantiating any needed functions/methods in the above + // function. If so, instantiate all the methods of those types + // (which will then lead to more function/methods to scan in the loop). + g.instantiateMethods() + } + +} + +// instantiateMethods instantiates all the methods of all fully-instantiated +// generic types that have been added to g.instTypeList. +func (g *irgen) instantiateMethods() { + for i := 0; i < len(g.instTypeList); i++ { + typ := g.instTypeList[i] + // Get the base generic type by looking up the symbol of the + // generic (uninstantiated) name. + baseSym := typ.Sym().Pkg.Lookup(genericTypeName(typ.Sym())) + baseType := baseSym.Def.(*ir.Name).Type() + for j, m := range typ.Methods().Slice() { + name := m.Nname.(*ir.Name) + targs := make([]ir.Node, len(typ.RParams())) + for k, targ := range typ.RParams() { + targs[k] = ir.TypeNode(targ) + } + baseNname := baseType.Methods().Slice()[j].Nname.(*ir.Name) + name.Func = g.getInstantiation(baseNname, targs, true) + } + } + g.instTypeList = nil + +} + +// genericSym returns the name of the base generic type for the type named by +// sym. It simply returns the name obtained by removing everything after the +// first bracket ("["). +func genericTypeName(sym *types.Sym) string { + return sym.Name[0:strings.Index(sym.Name, "[")] +} + +// getInstantiationForNode returns the function/method instantiation for a +// InstExpr node inst. +func (g *irgen) getInstantiationForNode(inst *ir.InstExpr) *ir.Func { + if meth, ok := inst.X.(*ir.SelectorExpr); ok { + return g.getInstantiation(meth.Selection.Nname.(*ir.Name), inst.Targs, true) + } else { + return g.getInstantiation(inst.X.(*ir.Name), inst.Targs, false) + } +} + +// getInstantiation gets the instantiantion of the function or method nameNode +// with the type arguments targs. If the instantiated function is not already +// cached, then it calls genericSubst to create the new instantiation. +func (g *irgen) getInstantiation(nameNode *ir.Name, targs []ir.Node, isMeth bool) *ir.Func { + sym := makeInstName(nameNode.Sym(), targs, isMeth) + st := g.target.Stencils[sym] + if st == nil { + // If instantiation doesn't exist yet, create it and add + // to the list of decls. + st = g.genericSubst(sym, nameNode, targs, isMeth) + g.target.Stencils[sym] = st + g.target.Decls = append(g.target.Decls, st) + if base.Flag.W > 1 { + ir.Dump(fmt.Sprintf("\nstenciled %v", st), st) + } + } + return st +} + +// makeInstName makes the unique name for a stenciled generic function or method, +// based on the name of the function fy=nsym and the targs. It replaces any +// existing bracket type list in the name. makeInstName asserts that fnsym has +// brackets in its name if and only if hasBrackets is true. +// TODO(danscales): remove the assertions and the hasBrackets argument later. +// +// Names of declared generic functions have no brackets originally, so hasBrackets +// should be false. Names of generic methods already have brackets, since the new +// type parameter is specified in the generic type of the receiver (e.g. func +// (func (v *value[T]).set(...) { ... } has the original name (*value[T]).set. +// +// The standard naming is something like: 'genFn[int,bool]' for functions and +// '(*genType[int,bool]).methodName' for methods +func makeInstName(fnsym *types.Sym, targs []ir.Node, hasBrackets bool) *types.Sym { + b := bytes.NewBufferString("") + name := fnsym.Name + i := strings.Index(name, "[") + assert(hasBrackets == (i >= 0)) + if i >= 0 { + b.WriteString(name[0:i]) + } else { + b.WriteString(name) + } + b.WriteString("[") + for i, targ := range targs { + if i > 0 { + b.WriteString(",") + } + b.WriteString(targ.Type().String()) + } + b.WriteString("]") + if i >= 0 { + i2 := strings.Index(name[i:], "]") + assert(i2 >= 0) + b.WriteString(name[i+i2+1:]) + } + return typecheck.Lookup(b.String()) +} + +// Struct containing info needed for doing the substitution as we create the +// instantiation of a generic function with specified type arguments. +type subster struct { + g *irgen + isMethod bool // If a method is being instantiated + newf *ir.Func // Func node for the new stenciled function + tparams []*types.Field + targs []ir.Node + // The substitution map from name nodes in the generic function to the + // name nodes in the new stenciled function. + vars map[*ir.Name]*ir.Name +} + +// genericSubst returns a new function with name newsym. The function is an +// instantiation of a generic function or method specified by namedNode with type +// args targs. For a method with a generic receiver, it returns an instantiated +// function type where the receiver becomes the first parameter. Otherwise the +// instantiated method would still need to be transformed by later compiler +// phases. +func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []ir.Node, isMethod bool) *ir.Func { + var tparams []*types.Field + if isMethod { + // Get the type params from the method receiver (after skipping + // over any pointer) + recvType := nameNode.Type().Recv().Type + recvType = deref(recvType) + tparams = make([]*types.Field, len(recvType.RParams())) + for i, rparam := range recvType.RParams() { + tparams[i] = types.NewField(src.NoXPos, nil, rparam) + } + } else { + tparams = nameNode.Type().TParams().Fields().Slice() + } + gf := nameNode.Func + // Pos of the instantiated function is same as the generic function + newf := ir.NewFunc(gf.Pos()) + newf.Pragma = gf.Pragma // copy over pragmas from generic function to stenciled implementation. + newf.Nname = ir.NewNameAt(gf.Pos(), newsym) + newf.Nname.Func = newf + newf.Nname.Defn = newf + newsym.Def = newf.Nname + savef := ir.CurFunc + // transformCall/transformReturn (called during stenciling of the body) + // depend on ir.CurFunc being set. + ir.CurFunc = newf + + assert(len(tparams) == len(targs)) + + subst := &subster{ + g: g, + isMethod: isMethod, + newf: newf, + tparams: tparams, + targs: targs, + vars: make(map[*ir.Name]*ir.Name), + } + + newf.Dcl = make([]*ir.Name, len(gf.Dcl)) + for i, n := range gf.Dcl { + newf.Dcl[i] = subst.node(n).(*ir.Name) + } + + // Ugly: we have to insert the Name nodes of the parameters/results into + // the function type. The current function type has no Nname fields set, + // because it came via conversion from the types2 type. + oldt := nameNode.Type() + // We also transform a generic method type to the corresponding + // instantiated function type where the receiver is the first parameter. + newt := types.NewSignature(oldt.Pkg(), nil, nil, + subst.fields(ir.PPARAM, append(oldt.Recvs().FieldSlice(), oldt.Params().FieldSlice()...), newf.Dcl), + subst.fields(ir.PPARAMOUT, oldt.Results().FieldSlice(), newf.Dcl)) + + newf.Nname.SetType(newt) + ir.MarkFunc(newf.Nname) + newf.SetTypecheck(1) + newf.Nname.SetTypecheck(1) + + // Make sure name/type of newf is set before substituting the body. + newf.Body = subst.list(gf.Body) + ir.CurFunc = savef + + return newf +} + +// node is like DeepCopy(), but creates distinct ONAME nodes, and also descends +// into closures. It substitutes type arguments for type parameters in all the new +// nodes. +func (subst *subster) node(n ir.Node) ir.Node { + // Use closure to capture all state needed by the ir.EditChildren argument. + var edit func(ir.Node) ir.Node + edit = func(x ir.Node) ir.Node { + switch x.Op() { + case ir.OTYPE: + return ir.TypeNode(subst.typ(x.Type())) + + case ir.ONAME: + name := x.(*ir.Name) + if v := subst.vars[name]; v != nil { + return v + } + m := ir.NewNameAt(name.Pos(), name.Sym()) + if name.IsClosureVar() { + m.SetIsClosureVar(true) + } + t := x.Type() + if t == nil { + assert(name.BuiltinOp != 0) + } else { + newt := subst.typ(t) + m.SetType(newt) + } + m.BuiltinOp = name.BuiltinOp + m.Curfn = subst.newf + m.Class = name.Class + m.Func = name.Func + subst.vars[name] = m + m.SetTypecheck(1) + return m + case ir.OLITERAL, ir.ONIL: + if x.Sym() != nil { + return x + } + } + m := ir.Copy(x) + if _, isExpr := m.(ir.Expr); isExpr { + t := x.Type() + if t == nil { + // t can be nil only if this is a call that has no + // return values, so allow that and otherwise give + // an error. + _, isCallExpr := m.(*ir.CallExpr) + _, isStructKeyExpr := m.(*ir.StructKeyExpr) + if !isCallExpr && !isStructKeyExpr && x.Op() != ir.OPANIC && + x.Op() != ir.OCLOSE { + base.Fatalf(fmt.Sprintf("Nil type for %v", x)) + } + } else if x.Op() != ir.OCLOSURE { + m.SetType(subst.typ(x.Type())) + } + } + ir.EditChildren(m, edit) + + if x.Typecheck() == 3 { + // These are nodes whose transforms were delayed until + // their instantiated type was known. + m.SetTypecheck(1) + if typecheck.IsCmp(x.Op()) { + transformCompare(m.(*ir.BinaryExpr)) + } else { + switch x.Op() { + case ir.OSLICE, ir.OSLICE3: + transformSlice(m.(*ir.SliceExpr)) + + case ir.OADD: + m = transformAdd(m.(*ir.BinaryExpr)) + + case ir.OINDEX: + transformIndex(m.(*ir.IndexExpr)) + + case ir.OAS2: + as2 := m.(*ir.AssignListStmt) + transformAssign(as2, as2.Lhs, as2.Rhs) + + case ir.OAS: + as := m.(*ir.AssignStmt) + lhs, rhs := []ir.Node{as.X}, []ir.Node{as.Y} + transformAssign(as, lhs, rhs) + + case ir.OASOP: + as := m.(*ir.AssignOpStmt) + transformCheckAssign(as, as.X) + + case ir.ORETURN: + transformReturn(m.(*ir.ReturnStmt)) + + case ir.OSEND: + transformSend(m.(*ir.SendStmt)) + + default: + base.Fatalf("Unexpected node with Typecheck() == 3") + } + } + } + + switch x.Op() { + case ir.OLITERAL: + t := m.Type() + if t != x.Type() { + // types2 will give us a constant with a type T, + // if an untyped constant is used with another + // operand of type T (in a provably correct way). + // When we substitute in the type args during + // stenciling, we now know the real type of the + // constant. We may then need to change the + // BasicLit.val to be the correct type (e.g. + // convert an int64Val constant to a floatVal + // constant). + m.SetType(types.UntypedInt) // use any untyped type for DefaultLit to work + m = typecheck.DefaultLit(m, t) + } + + case ir.OXDOT: + // A method value/call via a type param will have been + // left as an OXDOT. When we see this during stenciling, + // finish the transformation, now that we have the + // instantiated receiver type. We need to do this now, + // since the access/selection to the method for the real + // type is very different from the selection for the type + // param. m will be transformed to an OCALLPART node. It + // will be transformed to an ODOTMETH or ODOTINTER node if + // we find in the OCALL case below that the method value + // is actually called. + transformDot(m.(*ir.SelectorExpr), false) + m.SetTypecheck(1) + + case ir.OCALL: + call := m.(*ir.CallExpr) + switch call.X.Op() { + case ir.OTYPE: + // Transform the conversion, now that we know the + // type argument. + m = transformConvCall(m.(*ir.CallExpr)) + + case ir.OCALLPART: + // Redo the transformation of OXDOT, now that we + // know the method value is being called. Then + // transform the call. + call.X.(*ir.SelectorExpr).SetOp(ir.OXDOT) + transformDot(call.X.(*ir.SelectorExpr), true) + transformCall(call) + + case ir.ODOT, ir.ODOTPTR: + // An OXDOT for a generic receiver was resolved to + // an access to a field which has a function + // value. Transform the call to that function, now + // that the OXDOT was resolved. + transformCall(call) + + case ir.ONAME: + name := call.X.Name() + if name.BuiltinOp != ir.OXXX { + switch name.BuiltinOp { + case ir.OMAKE, ir.OREAL, ir.OIMAG, ir.OLEN, ir.OCAP, ir.OAPPEND: + // Transform these builtins now that we + // know the type of the args. + m = transformBuiltin(call) + default: + base.FatalfAt(call.Pos(), "Unexpected builtin op") + } + } else { + // This is the case of a function value that was a + // type parameter (implied to be a function via a + // structural constraint) which is now resolved. + transformCall(call) + } + + case ir.OCLOSURE: + transformCall(call) + + case ir.OFUNCINST: + // A call with an OFUNCINST will get transformed + // in stencil() once we have created & attached the + // instantiation to be called. + + default: + base.FatalfAt(call.Pos(), fmt.Sprintf("Unexpected op with CALL during stenciling: %v", call.X.Op())) + } + + case ir.OCLOSURE: + x := x.(*ir.ClosureExpr) + // Need to duplicate x.Func.Nname, x.Func.Dcl, x.Func.ClosureVars, and + // x.Func.Body. + oldfn := x.Func + newfn := ir.NewFunc(oldfn.Pos()) + if oldfn.ClosureCalled() { + newfn.SetClosureCalled(true) + } + newfn.SetIsHiddenClosure(true) + m.(*ir.ClosureExpr).Func = newfn + // Closure name can already have brackets, if it derives + // from a generic method + newsym := makeInstName(oldfn.Nname.Sym(), subst.targs, subst.isMethod) + newfn.Nname = ir.NewNameAt(oldfn.Nname.Pos(), newsym) + newfn.Nname.Func = newfn + newfn.Nname.Defn = newfn + ir.MarkFunc(newfn.Nname) + newfn.OClosure = m.(*ir.ClosureExpr) + + saveNewf := subst.newf + ir.CurFunc = newfn + subst.newf = newfn + newfn.Dcl = subst.namelist(oldfn.Dcl) + newfn.ClosureVars = subst.namelist(oldfn.ClosureVars) + + typed(subst.typ(oldfn.Nname.Type()), newfn.Nname) + typed(newfn.Nname.Type(), m) + newfn.SetTypecheck(1) + + // Make sure type of closure function is set before doing body. + newfn.Body = subst.list(oldfn.Body) + subst.newf = saveNewf + ir.CurFunc = saveNewf + + subst.g.target.Decls = append(subst.g.target.Decls, newfn) + } + return m + } + + return edit(n) +} + +func (subst *subster) namelist(l []*ir.Name) []*ir.Name { + s := make([]*ir.Name, len(l)) + for i, n := range l { + s[i] = subst.node(n).(*ir.Name) + if n.Defn != nil { + s[i].Defn = subst.node(n.Defn) + } + if n.Outer != nil { + s[i].Outer = subst.node(n.Outer).(*ir.Name) + } + } + return s +} + +func (subst *subster) list(l []ir.Node) []ir.Node { + s := make([]ir.Node, len(l)) + for i, n := range l { + s[i] = subst.node(n) + } + return s +} + +// tstruct substitutes type params in types of the fields of a structure type. For +// each field, if Nname is set, tstruct also translates the Nname using +// subst.vars, if Nname is in subst.vars. To always force the creation of a new +// (top-level) struct, regardless of whether anything changed with the types or +// names of the struct's fields, set force to true. +func (subst *subster) tstruct(t *types.Type, force bool) *types.Type { + if t.NumFields() == 0 { + if t.HasTParam() { + // For an empty struct, we need to return a new type, + // since it may now be fully instantiated (HasTParam + // becomes false). + return types.NewStruct(t.Pkg(), nil) + } + return t + } + var newfields []*types.Field + if force { + newfields = make([]*types.Field, t.NumFields()) + } + for i, f := range t.Fields().Slice() { + t2 := subst.typ(f.Type) + if (t2 != f.Type || f.Nname != nil) && newfields == nil { + newfields = make([]*types.Field, t.NumFields()) + for j := 0; j < i; j++ { + newfields[j] = t.Field(j) + } + } + if newfields != nil { + // TODO(danscales): make sure this works for the field + // names of embedded types (which should keep the name of + // the type param, not the instantiated type). + newfields[i] = types.NewField(f.Pos, f.Sym, t2) + if f.Nname != nil { + // f.Nname may not be in subst.vars[] if this is + // a function name or a function instantiation type + // that we are translating + v := subst.vars[f.Nname.(*ir.Name)] + // Be careful not to put a nil var into Nname, + // since Nname is an interface, so it would be a + // non-nil interface. + if v != nil { + newfields[i].Nname = v + } + } + } + } + if newfields != nil { + return types.NewStruct(t.Pkg(), newfields) + } + return t + +} + +// tinter substitutes type params in types of the methods of an interface type. +func (subst *subster) tinter(t *types.Type) *types.Type { + if t.Methods().Len() == 0 { + return t + } + var newfields []*types.Field + for i, f := range t.Methods().Slice() { + t2 := subst.typ(f.Type) + if (t2 != f.Type || f.Nname != nil) && newfields == nil { + newfields = make([]*types.Field, t.Methods().Len()) + for j := 0; j < i; j++ { + newfields[j] = t.Methods().Index(j) + } + } + if newfields != nil { + newfields[i] = types.NewField(f.Pos, f.Sym, t2) + } + } + if newfields != nil { + return types.NewInterface(t.Pkg(), newfields) + } + return t +} + +// instTypeName creates a name for an instantiated type, based on the name of the +// generic type and the type args +func instTypeName(name string, targs []*types.Type) string { + b := bytes.NewBufferString(name) + b.WriteByte('[') + for i, targ := range targs { + if i > 0 { + b.WriteByte(',') + } + b.WriteString(targ.String()) + } + b.WriteByte(']') + return b.String() +} + +// typ computes the type obtained by substituting any type parameter in t with the +// corresponding type argument in subst. If t contains no type parameters, the +// result is t; otherwise the result is a new type. It deals with recursive types +// by using TFORW types and finding partially or fully created types via sym.Def. +func (subst *subster) typ(t *types.Type) *types.Type { + if !t.HasTParam() && t.Kind() != types.TFUNC { + // Note: function types need to be copied regardless, as the + // types of closures may contain declarations that need + // to be copied. See #45738. + return t + } + + if t.Kind() == types.TTYPEPARAM { + for i, tp := range subst.tparams { + if tp.Type == t { + return subst.targs[i].Type() + } + } + // If t is a simple typeparam T, then t has the name/symbol 'T' + // and t.Underlying() == t. + // + // However, consider the type definition: 'type P[T any] T'. We + // might use this definition so we can have a variant of type T + // that we can add new methods to. Suppose t is a reference to + // P[T]. t has the name 'P[T]', but its kind is TTYPEPARAM, + // because P[T] is defined as T. If we look at t.Underlying(), it + // is different, because the name of t.Underlying() is 'T' rather + // than 'P[T]'. But the kind of t.Underlying() is also TTYPEPARAM. + // In this case, we do the needed recursive substitution in the + // case statement below. + if t.Underlying() == t { + // t is a simple typeparam that didn't match anything in tparam + return t + } + // t is a more complex typeparam (e.g. P[T], as above, whose + // definition is just T). + assert(t.Sym() != nil) + } + + var newsym *types.Sym + var neededTargs []*types.Type + var forw *types.Type + + if t.Sym() != nil { + // Translate the type params for this type according to + // the tparam/targs mapping from subst. + neededTargs = make([]*types.Type, len(t.RParams())) + for i, rparam := range t.RParams() { + neededTargs[i] = subst.typ(rparam) + } + // For a named (defined) type, we have to change the name of the + // type as well. We do this first, so we can look up if we've + // already seen this type during this substitution or other + // definitions/substitutions. + genName := genericTypeName(t.Sym()) + newsym = t.Sym().Pkg.Lookup(instTypeName(genName, neededTargs)) + if newsym.Def != nil { + // We've already created this instantiated defined type. + return newsym.Def.Type() + } + + // In order to deal with recursive generic types, create a TFORW + // type initially and set the Def field of its sym, so it can be + // found if this type appears recursively within the type. + forw = newIncompleteNamedType(t.Pos(), newsym) + //println("Creating new type by sub", newsym.Name, forw.HasTParam()) + forw.SetRParams(neededTargs) + } + + var newt *types.Type + + switch t.Kind() { + case types.TTYPEPARAM: + if t.Sym() == newsym { + // The substitution did not change the type. + return t + } + // Substitute the underlying typeparam (e.g. T in P[T], see + // the example describing type P[T] above). + newt = subst.typ(t.Underlying()) + assert(newt != t) + + case types.TARRAY: + elem := t.Elem() + newelem := subst.typ(elem) + if newelem != elem { + newt = types.NewArray(newelem, t.NumElem()) + } + + case types.TPTR: + elem := t.Elem() + newelem := subst.typ(elem) + if newelem != elem { + newt = types.NewPtr(newelem) + } + + case types.TSLICE: + elem := t.Elem() + newelem := subst.typ(elem) + if newelem != elem { + newt = types.NewSlice(newelem) + } + + case types.TSTRUCT: + newt = subst.tstruct(t, false) + if newt == t { + newt = nil + } + + case types.TFUNC: + newrecvs := subst.tstruct(t.Recvs(), false) + newparams := subst.tstruct(t.Params(), false) + newresults := subst.tstruct(t.Results(), false) + if newrecvs != t.Recvs() || newparams != t.Params() || newresults != t.Results() { + // If any types have changed, then the all the fields of + // of recv, params, and results must be copied, because they have + // offset fields that are dependent, and so must have an + // independent copy for each new signature. + var newrecv *types.Field + if newrecvs.NumFields() > 0 { + if newrecvs == t.Recvs() { + newrecvs = subst.tstruct(t.Recvs(), true) + } + newrecv = newrecvs.Field(0) + } + if newparams == t.Params() { + newparams = subst.tstruct(t.Params(), true) + } + if newresults == t.Results() { + newresults = subst.tstruct(t.Results(), true) + } + newt = types.NewSignature(t.Pkg(), newrecv, t.TParams().FieldSlice(), newparams.FieldSlice(), newresults.FieldSlice()) + } + + case types.TINTER: + newt = subst.tinter(t) + if newt == t { + newt = nil + } + + case types.TMAP: + newkey := subst.typ(t.Key()) + newval := subst.typ(t.Elem()) + if newkey != t.Key() || newval != t.Elem() { + newt = types.NewMap(newkey, newval) + } + + case types.TCHAN: + elem := t.Elem() + newelem := subst.typ(elem) + if newelem != elem { + newt = types.NewChan(newelem, t.ChanDir()) + if !newt.HasTParam() { + // TODO(danscales): not sure why I have to do this + // only for channels..... + types.CheckSize(newt) + } + } + } + if newt == nil { + // Even though there were typeparams in the type, there may be no + // change if this is a function type for a function call (which will + // have its own tparams/targs in the function instantiation). + return t + } + + if t.Sym() == nil { + // Not a named type, so there was no forwarding type and there are + // no methods to substitute. + assert(t.Methods().Len() == 0) + return newt + } + + forw.SetUnderlying(newt) + newt = forw + + if t.Kind() != types.TINTER && t.Methods().Len() > 0 { + // Fill in the method info for the new type. + var newfields []*types.Field + newfields = make([]*types.Field, t.Methods().Len()) + for i, f := range t.Methods().Slice() { + t2 := subst.typ(f.Type) + oldsym := f.Nname.Sym() + newsym := makeInstName(oldsym, subst.targs, true) + var nname *ir.Name + if newsym.Def != nil { + nname = newsym.Def.(*ir.Name) + } else { + nname = ir.NewNameAt(f.Pos, newsym) + nname.SetType(t2) + newsym.Def = nname + } + newfields[i] = types.NewField(f.Pos, f.Sym, t2) + newfields[i].Nname = nname + } + newt.Methods().Set(newfields) + if !newt.HasTParam() { + // Generate all the methods for a new fully-instantiated type. + subst.g.instTypeList = append(subst.g.instTypeList, newt) + } + } + return newt +} + +// fields sets the Nname field for the Field nodes inside a type signature, based +// on the corresponding in/out parameters in dcl. It depends on the in and out +// parameters being in order in dcl. +func (subst *subster) fields(class ir.Class, oldfields []*types.Field, dcl []*ir.Name) []*types.Field { + // Find the starting index in dcl of declarations of the class (either + // PPARAM or PPARAMOUT). + var i int + for i = range dcl { + if dcl[i].Class == class { + break + } + } + + // Create newfields nodes that are copies of the oldfields nodes, but + // with substitution for any type params, and with Nname set to be the node in + // Dcl for the corresponding PPARAM or PPARAMOUT. + newfields := make([]*types.Field, len(oldfields)) + for j := range oldfields { + newfields[j] = oldfields[j].Copy() + newfields[j].Type = subst.typ(oldfields[j].Type) + // A param field will be missing from dcl if its name is + // unspecified or specified as "_". So, we compare the dcl sym + // with the field sym. If they don't match, this dcl (if there is + // one left) must apply to a later field. + if i < len(dcl) && dcl[i].Sym() == oldfields[j].Sym { + newfields[j].Nname = dcl[i] + i++ + } + } + return newfields +} + +// defer does a single defer of type t, if it is a pointer type. +func deref(t *types.Type) *types.Type { + if t.IsPtr() { + return t.Elem() + } + return t +} + +// newIncompleteNamedType returns a TFORW type t with name specified by sym, such +// that t.nod and sym.Def are set correctly. +func newIncompleteNamedType(pos src.XPos, sym *types.Sym) *types.Type { + name := ir.NewDeclNameAt(pos, ir.OTYPE, sym) + forw := types.NewNamed(name) + name.SetType(forw) + sym.Def = name + return forw +} diff --git a/src/cmd/compile/internal/noder/stmt.go b/src/cmd/compile/internal/noder/stmt.go new file mode 100644 index 0000000000000000000000000000000000000000..32a1483b4aabeb2fddeb646fc897ccf216cc52cb --- /dev/null +++ b/src/cmd/compile/internal/noder/stmt.go @@ -0,0 +1,363 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +func (g *irgen) stmts(stmts []syntax.Stmt) []ir.Node { + var nodes []ir.Node + for _, stmt := range stmts { + switch s := g.stmt(stmt).(type) { + case nil: // EmptyStmt + case *ir.BlockStmt: + nodes = append(nodes, s.List...) + default: + nodes = append(nodes, s) + } + } + return nodes +} + +func (g *irgen) stmt(stmt syntax.Stmt) ir.Node { + switch stmt := stmt.(type) { + case nil, *syntax.EmptyStmt: + return nil + case *syntax.LabeledStmt: + return g.labeledStmt(stmt) + case *syntax.BlockStmt: + return ir.NewBlockStmt(g.pos(stmt), g.blockStmt(stmt)) + case *syntax.ExprStmt: + x := g.expr(stmt.X) + if call, ok := x.(*ir.CallExpr); ok { + call.Use = ir.CallUseStmt + } + return x + case *syntax.SendStmt: + n := ir.NewSendStmt(g.pos(stmt), g.expr(stmt.Chan), g.expr(stmt.Value)) + if n.Chan.Type().HasTParam() || n.Value.Type().HasTParam() { + // Delay transforming the send if the channel or value + // have a type param. + n.SetTypecheck(3) + return n + } + transformSend(n) + n.SetTypecheck(1) + return n + case *syntax.DeclStmt: + return ir.NewBlockStmt(g.pos(stmt), g.decls(stmt.DeclList)) + + case *syntax.AssignStmt: + if stmt.Op != 0 && stmt.Op != syntax.Def { + op := g.op(stmt.Op, binOps[:]) + var n *ir.AssignOpStmt + if stmt.Rhs == nil { + n = IncDec(g.pos(stmt), op, g.expr(stmt.Lhs)) + } else { + n = ir.NewAssignOpStmt(g.pos(stmt), op, g.expr(stmt.Lhs), g.expr(stmt.Rhs)) + } + if n.X.Typecheck() == 3 { + n.SetTypecheck(3) + return n + } + transformAsOp(n) + n.SetTypecheck(1) + return n + } + + names, lhs := g.assignList(stmt.Lhs, stmt.Op == syntax.Def) + rhs := g.exprList(stmt.Rhs) + + // We must delay transforming the assign statement if any of the + // lhs or rhs nodes are also delayed, since transformAssign needs + // to know the types of the left and right sides in various cases. + delay := false + for _, e := range lhs { + if e.Typecheck() == 3 { + delay = true + break + } + } + for _, e := range rhs { + if e.Typecheck() == 3 { + delay = true + break + } + } + + if len(lhs) == 1 && len(rhs) == 1 { + n := ir.NewAssignStmt(g.pos(stmt), lhs[0], rhs[0]) + n.Def = initDefn(n, names) + + if delay { + n.SetTypecheck(3) + return n + } + + lhs, rhs := []ir.Node{n.X}, []ir.Node{n.Y} + transformAssign(n, lhs, rhs) + n.X, n.Y = lhs[0], rhs[0] + n.SetTypecheck(1) + return n + } + + n := ir.NewAssignListStmt(g.pos(stmt), ir.OAS2, lhs, rhs) + n.Def = initDefn(n, names) + if delay { + n.SetTypecheck(3) + return n + } + transformAssign(n, n.Lhs, n.Rhs) + n.SetTypecheck(1) + return n + + case *syntax.BranchStmt: + return ir.NewBranchStmt(g.pos(stmt), g.tokOp(int(stmt.Tok), branchOps[:]), g.name(stmt.Label)) + case *syntax.CallStmt: + return ir.NewGoDeferStmt(g.pos(stmt), g.tokOp(int(stmt.Tok), callOps[:]), g.expr(stmt.Call)) + case *syntax.ReturnStmt: + n := ir.NewReturnStmt(g.pos(stmt), g.exprList(stmt.Results)) + for _, e := range n.Results { + if e.Type().HasTParam() { + // Delay transforming the return statement if any of the + // return values have a type param. + n.SetTypecheck(3) + return n + } + } + transformReturn(n) + n.SetTypecheck(1) + return n + case *syntax.IfStmt: + return g.ifStmt(stmt) + case *syntax.ForStmt: + return g.forStmt(stmt) + case *syntax.SelectStmt: + n := g.selectStmt(stmt) + transformSelect(n.(*ir.SelectStmt)) + n.SetTypecheck(1) + return n + case *syntax.SwitchStmt: + return g.switchStmt(stmt) + + default: + g.unhandled("statement", stmt) + panic("unreachable") + } +} + +// TODO(mdempsky): Investigate replacing with switch statements or dense arrays. + +var branchOps = [...]ir.Op{ + syntax.Break: ir.OBREAK, + syntax.Continue: ir.OCONTINUE, + syntax.Fallthrough: ir.OFALL, + syntax.Goto: ir.OGOTO, +} + +var callOps = [...]ir.Op{ + syntax.Defer: ir.ODEFER, + syntax.Go: ir.OGO, +} + +func (g *irgen) tokOp(tok int, ops []ir.Op) ir.Op { + // TODO(mdempsky): Validate. + return ops[tok] +} + +func (g *irgen) op(op syntax.Operator, ops []ir.Op) ir.Op { + // TODO(mdempsky): Validate. + return ops[op] +} + +func (g *irgen) assignList(expr syntax.Expr, def bool) ([]*ir.Name, []ir.Node) { + if !def { + return nil, g.exprList(expr) + } + + var exprs []syntax.Expr + if list, ok := expr.(*syntax.ListExpr); ok { + exprs = list.ElemList + } else { + exprs = []syntax.Expr{expr} + } + + var names []*ir.Name + res := make([]ir.Node, len(exprs)) + for i, expr := range exprs { + expr := expr.(*syntax.Name) + if expr.Value == "_" { + res[i] = ir.BlankNode + continue + } + + if obj, ok := g.info.Uses[expr]; ok { + res[i] = g.obj(obj) + continue + } + + name, _ := g.def(expr) + names = append(names, name) + res[i] = name + } + + return names, res +} + +// initDefn marks the given names as declared by defn and populates +// its Init field with ODCL nodes. It then reports whether any names +// were so declared, which can be used to initialize defn.Def. +func initDefn(defn ir.InitNode, names []*ir.Name) bool { + if len(names) == 0 { + return false + } + + init := make([]ir.Node, len(names)) + for i, name := range names { + name.Defn = defn + init[i] = ir.NewDecl(name.Pos(), ir.ODCL, name) + } + defn.SetInit(init) + return true +} + +func (g *irgen) blockStmt(stmt *syntax.BlockStmt) []ir.Node { + return g.stmts(stmt.List) +} + +func (g *irgen) ifStmt(stmt *syntax.IfStmt) ir.Node { + init := g.stmt(stmt.Init) + n := ir.NewIfStmt(g.pos(stmt), g.expr(stmt.Cond), g.blockStmt(stmt.Then), nil) + if stmt.Else != nil { + e := g.stmt(stmt.Else) + if e.Op() == ir.OBLOCK { + e := e.(*ir.BlockStmt) + n.Else = e.List + } else { + n.Else = []ir.Node{e} + } + } + return g.init(init, n) +} + +// unpackTwo returns the first two nodes in list. If list has fewer +// than 2 nodes, then the missing nodes are replaced with nils. +func unpackTwo(list []ir.Node) (fst, snd ir.Node) { + switch len(list) { + case 0: + return nil, nil + case 1: + return list[0], nil + default: + return list[0], list[1] + } +} + +func (g *irgen) forStmt(stmt *syntax.ForStmt) ir.Node { + if r, ok := stmt.Init.(*syntax.RangeClause); ok { + names, lhs := g.assignList(r.Lhs, r.Def) + key, value := unpackTwo(lhs) + n := ir.NewRangeStmt(g.pos(r), key, value, g.expr(r.X), g.blockStmt(stmt.Body)) + n.Def = initDefn(n, names) + return n + } + + return ir.NewForStmt(g.pos(stmt), g.stmt(stmt.Init), g.expr(stmt.Cond), g.stmt(stmt.Post), g.blockStmt(stmt.Body)) +} + +func (g *irgen) selectStmt(stmt *syntax.SelectStmt) ir.Node { + body := make([]*ir.CommClause, len(stmt.Body)) + for i, clause := range stmt.Body { + body[i] = ir.NewCommStmt(g.pos(clause), g.stmt(clause.Comm), g.stmts(clause.Body)) + } + return ir.NewSelectStmt(g.pos(stmt), body) +} + +func (g *irgen) switchStmt(stmt *syntax.SwitchStmt) ir.Node { + pos := g.pos(stmt) + init := g.stmt(stmt.Init) + + var expr ir.Node + switch tag := stmt.Tag.(type) { + case *syntax.TypeSwitchGuard: + var ident *ir.Ident + if tag.Lhs != nil { + ident = ir.NewIdent(g.pos(tag.Lhs), g.name(tag.Lhs)) + } + expr = ir.NewTypeSwitchGuard(pos, ident, g.expr(tag.X)) + default: + expr = g.expr(tag) + } + + body := make([]*ir.CaseClause, len(stmt.Body)) + for i, clause := range stmt.Body { + // Check for an implicit clause variable before + // visiting body, because it may contain function + // literals that reference it, and then it'll be + // associated to the wrong function. + // + // Also, override its position to the clause's colon, so that + // dwarfgen can find the right scope for it later. + // TODO(mdempsky): We should probably just store the scope + // directly in the ir.Name. + var cv *ir.Name + if obj, ok := g.info.Implicits[clause]; ok { + cv = g.obj(obj) + cv.SetPos(g.makeXPos(clause.Colon)) + } + body[i] = ir.NewCaseStmt(g.pos(clause), g.exprList(clause.Cases), g.stmts(clause.Body)) + body[i].Var = cv + } + + return g.init(init, ir.NewSwitchStmt(pos, expr, body)) +} + +func (g *irgen) labeledStmt(label *syntax.LabeledStmt) ir.Node { + sym := g.name(label.Label) + lhs := ir.NewLabelStmt(g.pos(label), sym) + ls := g.stmt(label.Stmt) + + // Attach label directly to control statement too. + switch ls := ls.(type) { + case *ir.ForStmt: + ls.Label = sym + case *ir.RangeStmt: + ls.Label = sym + case *ir.SelectStmt: + ls.Label = sym + case *ir.SwitchStmt: + ls.Label = sym + } + + l := []ir.Node{lhs} + if ls != nil { + if ls.Op() == ir.OBLOCK { + ls := ls.(*ir.BlockStmt) + l = append(l, ls.List...) + } else { + l = append(l, ls) + } + } + return ir.NewBlockStmt(src.NoXPos, l) +} + +func (g *irgen) init(init ir.Node, stmt ir.InitNode) ir.InitNode { + if init != nil { + stmt.SetInit([]ir.Node{init}) + } + return stmt +} + +func (g *irgen) name(name *syntax.Name) *types.Sym { + if name == nil { + return nil + } + return typecheck.Lookup(name.Value) +} diff --git a/src/cmd/compile/internal/noder/transform.go b/src/cmd/compile/internal/noder/transform.go new file mode 100644 index 0000000000000000000000000000000000000000..2859089e69b91386010db02268764a66aa2de7aa --- /dev/null +++ b/src/cmd/compile/internal/noder/transform.go @@ -0,0 +1,961 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains transformation functions on nodes, which are the +// transformations that the typecheck package does that are distinct from the +// typechecking functionality. These transform functions are pared-down copies of +// the original typechecking functions, with all code removed that is related to: +// +// - Detecting compile-time errors (already done by types2) +// - Setting the actual type of existing nodes (already done based on +// type info from types2) +// - Dealing with untyped constants (which types2 has already resolved) +// +// Each of the transformation functions requires that node passed in has its type +// and typecheck flag set. If the transformation function replaces or adds new +// nodes, it will set the type and typecheck flag for those new nodes. + +package noder + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "fmt" + "go/constant" +) + +// Transformation functions for expressions + +// transformAdd transforms an addition operation (currently just addition of +// strings). Corresponds to the "binary operators" case in typecheck.typecheck1. +func transformAdd(n *ir.BinaryExpr) ir.Node { + assert(n.Type() != nil && n.Typecheck() == 1) + l := n.X + if l.Type().IsString() { + var add *ir.AddStringExpr + if l.Op() == ir.OADDSTR { + add = l.(*ir.AddStringExpr) + add.SetPos(n.Pos()) + } else { + add = ir.NewAddStringExpr(n.Pos(), []ir.Node{l}) + } + r := n.Y + if r.Op() == ir.OADDSTR { + r := r.(*ir.AddStringExpr) + add.List.Append(r.List.Take()...) + } else { + add.List.Append(r) + } + typed(l.Type(), add) + return add + } + return n +} + +// Corresponds to typecheck.stringtoruneslit. +func stringtoruneslit(n *ir.ConvExpr) ir.Node { + if n.X.Op() != ir.OLITERAL || n.X.Val().Kind() != constant.String { + base.Fatalf("stringtoarraylit %v", n) + } + + var list []ir.Node + i := 0 + eltType := n.Type().Elem() + for _, r := range ir.StringVal(n.X) { + elt := ir.NewKeyExpr(base.Pos, ir.NewInt(int64(i)), ir.NewInt(int64(r))) + // Change from untyped int to the actual element type determined + // by types2. No need to change elt.Key, since the array indexes + // are just used for setting up the element ordering. + elt.Value.SetType(eltType) + list = append(list, elt) + i++ + } + + nn := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(n.Type()), nil) + nn.List = list + typed(n.Type(), nn) + // Need to transform the OCOMPLIT. + return transformCompLit(nn) +} + +// transformConv transforms an OCONV node as needed, based on the types involved, +// etc. Corresponds to typecheck.tcConv. +func transformConv(n *ir.ConvExpr) ir.Node { + t := n.X.Type() + op, _ := typecheck.Convertop(n.X.Op() == ir.OLITERAL, t, n.Type()) + n.SetOp(op) + switch n.Op() { + case ir.OCONVNOP: + if t.Kind() == n.Type().Kind() { + switch t.Kind() { + case types.TFLOAT32, types.TFLOAT64, types.TCOMPLEX64, types.TCOMPLEX128: + // Floating point casts imply rounding and + // so the conversion must be kept. + n.SetOp(ir.OCONV) + } + } + + // Do not convert to []byte literal. See CL 125796. + // Generated code and compiler memory footprint is better without it. + case ir.OSTR2BYTES: + // ok + + case ir.OSTR2RUNES: + if n.X.Op() == ir.OLITERAL { + return stringtoruneslit(n) + } + } + return n +} + +// transformConvCall transforms a conversion call. Corresponds to the OTYPE part of +// typecheck.tcCall. +func transformConvCall(n *ir.CallExpr) ir.Node { + assert(n.Type() != nil && n.Typecheck() == 1) + arg := n.Args[0] + n1 := ir.NewConvExpr(n.Pos(), ir.OCONV, nil, arg) + typed(n.X.Type(), n1) + return transformConv(n1) +} + +// transformCall transforms a normal function/method call. Corresponds to last half +// (non-conversion, non-builtin part) of typecheck.tcCall. +func transformCall(n *ir.CallExpr) { + // n.Type() can be nil for calls with no return value + assert(n.Typecheck() == 1) + transformArgs(n) + l := n.X + t := l.Type() + + switch l.Op() { + case ir.ODOTINTER: + n.SetOp(ir.OCALLINTER) + + case ir.ODOTMETH: + l := l.(*ir.SelectorExpr) + n.SetOp(ir.OCALLMETH) + + tp := t.Recv().Type + + if l.X == nil || !types.Identical(l.X.Type(), tp) { + base.Fatalf("method receiver") + } + + default: + n.SetOp(ir.OCALLFUNC) + } + + typecheckaste(ir.OCALL, n.X, n.IsDDD, t.Params(), n.Args) + if t.NumResults() == 1 { + n.SetType(l.Type().Results().Field(0).Type) + + if n.Op() == ir.OCALLFUNC && n.X.Op() == ir.ONAME { + if sym := n.X.(*ir.Name).Sym(); types.IsRuntimePkg(sym.Pkg) && sym.Name == "getg" { + // Emit code for runtime.getg() directly instead of calling function. + // Most such rewrites (for example the similar one for math.Sqrt) should be done in walk, + // so that the ordering pass can make sure to preserve the semantics of the original code + // (in particular, the exact time of the function call) by introducing temporaries. + // In this case, we know getg() always returns the same result within a given function + // and we want to avoid the temporaries, so we do the rewrite earlier than is typical. + n.SetOp(ir.OGETG) + } + } + return + } +} + +// transformCompare transforms a compare operation (currently just equals/not +// equals). Corresponds to the "comparison operators" case in +// typecheck.typecheck1, including tcArith. +func transformCompare(n *ir.BinaryExpr) { + assert(n.Type() != nil && n.Typecheck() == 1) + if (n.Op() == ir.OEQ || n.Op() == ir.ONE) && !types.Identical(n.X.Type(), n.Y.Type()) { + // Comparison is okay as long as one side is assignable to the + // other. The only allowed case where the conversion is not CONVNOP is + // "concrete == interface". In that case, check comparability of + // the concrete type. The conversion allocates, so only do it if + // the concrete type is huge. + l, r := n.X, n.Y + lt, rt := l.Type(), r.Type() + converted := false + if rt.Kind() != types.TBLANK { + aop, _ := typecheck.Assignop(lt, rt) + if aop != ir.OXXX { + types.CalcSize(lt) + if rt.IsInterface() == lt.IsInterface() || lt.Width >= 1<<16 { + l = ir.NewConvExpr(base.Pos, aop, rt, l) + l.SetTypecheck(1) + } + + converted = true + } + } + + if !converted && lt.Kind() != types.TBLANK { + aop, _ := typecheck.Assignop(rt, lt) + if aop != ir.OXXX { + types.CalcSize(rt) + if rt.IsInterface() == lt.IsInterface() || rt.Width >= 1<<16 { + r = ir.NewConvExpr(base.Pos, aop, lt, r) + r.SetTypecheck(1) + } + } + } + n.X, n.Y = l, r + } +} + +// Corresponds to typecheck.implicitstar. +func implicitstar(n ir.Node) ir.Node { + // insert implicit * if needed for fixed array + t := n.Type() + if !t.IsPtr() { + return n + } + t = t.Elem() + if !t.IsArray() { + return n + } + star := ir.NewStarExpr(base.Pos, n) + star.SetImplicit(true) + return typed(t, star) +} + +// transformIndex transforms an index operation. Corresponds to typecheck.tcIndex. +func transformIndex(n *ir.IndexExpr) { + assert(n.Type() != nil && n.Typecheck() == 1) + n.X = implicitstar(n.X) + l := n.X + t := l.Type() + if t.Kind() == types.TMAP { + n.Index = assignconvfn(n.Index, t.Key()) + n.SetOp(ir.OINDEXMAP) + // Set type to just the map value, not (value, bool). This is + // different from types2, but fits the later stages of the + // compiler better. + n.SetType(t.Elem()) + n.Assigned = false + } +} + +// transformSlice transforms a slice operation. Corresponds to typecheck.tcSlice. +func transformSlice(n *ir.SliceExpr) { + assert(n.Type() != nil && n.Typecheck() == 1) + l := n.X + if l.Type().IsArray() { + addr := typecheck.NodAddr(n.X) + addr.SetImplicit(true) + typed(types.NewPtr(n.X.Type()), addr) + n.X = addr + l = addr + } + t := l.Type() + if t.IsString() { + n.SetOp(ir.OSLICESTR) + } else if t.IsPtr() && t.Elem().IsArray() { + if n.Op().IsSlice3() { + n.SetOp(ir.OSLICE3ARR) + } else { + n.SetOp(ir.OSLICEARR) + } + } +} + +// Transformation functions for statements + +// Corresponds to typecheck.checkassign. +func transformCheckAssign(stmt ir.Node, n ir.Node) { + if n.Op() == ir.OINDEXMAP { + n := n.(*ir.IndexExpr) + n.Assigned = true + return + } +} + +// Corresponds to typecheck.assign. +func transformAssign(stmt ir.Node, lhs, rhs []ir.Node) { + checkLHS := func(i int, typ *types.Type) { + transformCheckAssign(stmt, lhs[i]) + } + + cr := len(rhs) + if len(rhs) == 1 { + if rtyp := rhs[0].Type(); rtyp != nil && rtyp.IsFuncArgStruct() { + cr = rtyp.NumFields() + } + } + + // x, ok = y +assignOK: + for len(lhs) == 2 && cr == 1 { + stmt := stmt.(*ir.AssignListStmt) + r := rhs[0] + + switch r.Op() { + case ir.OINDEXMAP: + stmt.SetOp(ir.OAS2MAPR) + case ir.ORECV: + stmt.SetOp(ir.OAS2RECV) + case ir.ODOTTYPE: + r := r.(*ir.TypeAssertExpr) + stmt.SetOp(ir.OAS2DOTTYPE) + r.SetOp(ir.ODOTTYPE2) + default: + break assignOK + } + checkLHS(0, r.Type()) + checkLHS(1, types.UntypedBool) + return + } + + if len(lhs) != cr { + for i := range lhs { + checkLHS(i, nil) + } + return + } + + // x,y,z = f() + if cr > len(rhs) { + stmt := stmt.(*ir.AssignListStmt) + stmt.SetOp(ir.OAS2FUNC) + r := rhs[0].(*ir.CallExpr) + r.Use = ir.CallUseList + rtyp := r.Type() + + for i := range lhs { + checkLHS(i, rtyp.Field(i).Type) + } + return + } + + for i, r := range rhs { + checkLHS(i, r.Type()) + if lhs[i].Type() != nil { + rhs[i] = assignconvfn(r, lhs[i].Type()) + } + } +} + +// Corresponds to typecheck.typecheckargs. +func transformArgs(n ir.InitNode) { + var list []ir.Node + switch n := n.(type) { + default: + base.Fatalf("typecheckargs %+v", n.Op()) + case *ir.CallExpr: + list = n.Args + if n.IsDDD { + return + } + case *ir.ReturnStmt: + list = n.Results + } + if len(list) != 1 { + return + } + + t := list[0].Type() + if t == nil || !t.IsFuncArgStruct() { + return + } + + // Rewrite f(g()) into t1, t2, ... = g(); f(t1, t2, ...). + + // Save n as n.Orig for fmt.go. + if ir.Orig(n) == n { + n.(ir.OrigNode).SetOrig(ir.SepCopy(n)) + } + + as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil) + as.Rhs.Append(list...) + + // If we're outside of function context, then this call will + // be executed during the generated init function. However, + // init.go hasn't yet created it. Instead, associate the + // temporary variables with InitTodoFunc for now, and init.go + // will reassociate them later when it's appropriate. + static := ir.CurFunc == nil + if static { + ir.CurFunc = typecheck.InitTodoFunc + } + list = nil + for _, f := range t.FieldSlice() { + t := typecheck.Temp(f.Type) + as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, t)) + as.Lhs.Append(t) + list = append(list, t) + } + if static { + ir.CurFunc = nil + } + + switch n := n.(type) { + case *ir.CallExpr: + n.Args = list + case *ir.ReturnStmt: + n.Results = list + } + + transformAssign(as, as.Lhs, as.Rhs) + as.SetTypecheck(1) + n.PtrInit().Append(as) +} + +// assignconvfn converts node n for assignment to type t. Corresponds to +// typecheck.assignconvfn. +func assignconvfn(n ir.Node, t *types.Type) ir.Node { + if t.Kind() == types.TBLANK { + return n + } + + if types.Identical(n.Type(), t) { + return n + } + + op, _ := typecheck.Assignop(n.Type(), t) + + r := ir.NewConvExpr(base.Pos, op, t, n) + r.SetTypecheck(1) + r.SetImplicit(true) + return r +} + +// Corresponds to typecheck.typecheckaste. +func typecheckaste(op ir.Op, call ir.Node, isddd bool, tstruct *types.Type, nl ir.Nodes) { + var t *types.Type + var i int + + lno := base.Pos + defer func() { base.Pos = lno }() + + var n ir.Node + if len(nl) == 1 { + n = nl[0] + } + + i = 0 + for _, tl := range tstruct.Fields().Slice() { + t = tl.Type + if tl.IsDDD() { + if isddd { + n = nl[i] + ir.SetPos(n) + if n.Type() != nil { + nl[i] = assignconvfn(n, t) + } + return + } + + // TODO(mdempsky): Make into ... call with implicit slice. + for ; i < len(nl); i++ { + n = nl[i] + ir.SetPos(n) + if n.Type() != nil { + nl[i] = assignconvfn(n, t.Elem()) + } + } + return + } + + n = nl[i] + ir.SetPos(n) + if n.Type() != nil { + nl[i] = assignconvfn(n, t) + } + i++ + } +} + +// transformSend transforms a send statement, converting the value to appropriate +// type for the channel, as needed. Corresponds of typecheck.tcSend. +func transformSend(n *ir.SendStmt) { + n.Value = assignconvfn(n.Value, n.Chan.Type().Elem()) +} + +// transformReturn transforms a return node, by doing the needed assignments and +// any necessary conversions. Corresponds to typecheck.tcReturn() +func transformReturn(rs *ir.ReturnStmt) { + transformArgs(rs) + nl := rs.Results + if ir.HasNamedResults(ir.CurFunc) && len(nl) == 0 { + return + } + + typecheckaste(ir.ORETURN, nil, false, ir.CurFunc.Type().Results(), nl) +} + +// transformSelect transforms a select node, creating an assignment list as needed +// for each case. Corresponds to typecheck.tcSelect(). +func transformSelect(sel *ir.SelectStmt) { + for _, ncase := range sel.Cases { + if ncase.Comm != nil { + n := ncase.Comm + oselrecv2 := func(dst, recv ir.Node, def bool) { + n := ir.NewAssignListStmt(n.Pos(), ir.OSELRECV2, []ir.Node{dst, ir.BlankNode}, []ir.Node{recv}) + n.Def = def + n.SetTypecheck(1) + ncase.Comm = n + } + switch n.Op() { + case ir.OAS: + // convert x = <-c into x, _ = <-c + // remove implicit conversions; the eventual assignment + // will reintroduce them. + n := n.(*ir.AssignStmt) + if r := n.Y; r.Op() == ir.OCONVNOP || r.Op() == ir.OCONVIFACE { + r := r.(*ir.ConvExpr) + if r.Implicit() { + n.Y = r.X + } + } + oselrecv2(n.X, n.Y, n.Def) + + case ir.OAS2RECV: + n := n.(*ir.AssignListStmt) + n.SetOp(ir.OSELRECV2) + + case ir.ORECV: + // convert <-c into _, _ = <-c + n := n.(*ir.UnaryExpr) + oselrecv2(ir.BlankNode, n, false) + + case ir.OSEND: + break + } + } + } +} + +// transformAsOp transforms an AssignOp statement. Corresponds to OASOP case in +// typecheck1. +func transformAsOp(n *ir.AssignOpStmt) { + transformCheckAssign(n, n.X) +} + +// transformDot transforms an OXDOT (or ODOT) or ODOT, ODOTPTR, ODOTMETH, +// ODOTINTER, or OCALLPART, as appropriate. It adds in extra nodes as needed to +// access embedded fields. Corresponds to typecheck.tcDot. +func transformDot(n *ir.SelectorExpr, isCall bool) ir.Node { + assert(n.Type() != nil && n.Typecheck() == 1) + if n.Op() == ir.OXDOT { + n = typecheck.AddImplicitDots(n) + n.SetOp(ir.ODOT) + } + + t := n.X.Type() + + if n.X.Op() == ir.OTYPE { + return transformMethodExpr(n) + } + + if t.IsPtr() && !t.Elem().IsInterface() { + t = t.Elem() + n.SetOp(ir.ODOTPTR) + } + + f := typecheck.Lookdot(n, t, 0) + assert(f != nil) + + if (n.Op() == ir.ODOTINTER || n.Op() == ir.ODOTMETH) && !isCall { + n.SetOp(ir.OCALLPART) + n.SetType(typecheck.MethodValueWrapper(n).Type()) + } + return n +} + +// Corresponds to typecheck.typecheckMethodExpr. +func transformMethodExpr(n *ir.SelectorExpr) (res ir.Node) { + t := n.X.Type() + + // Compute the method set for t. + var ms *types.Fields + if t.IsInterface() { + ms = t.AllMethods() + } else { + mt := types.ReceiverBaseType(t) + typecheck.CalcMethods(mt) + ms = mt.AllMethods() + + // The method expression T.m requires a wrapper when T + // is different from m's declared receiver type. We + // normally generate these wrappers while writing out + // runtime type descriptors, which is always done for + // types declared at package scope. However, we need + // to make sure to generate wrappers for anonymous + // receiver types too. + if mt.Sym() == nil { + typecheck.NeedRuntimeType(t) + } + } + + s := n.Sel + m := typecheck.Lookdot1(n, s, t, ms, 0) + assert(m != nil) + + n.SetOp(ir.OMETHEXPR) + n.Selection = m + n.SetType(typecheck.NewMethodType(m.Type, n.X.Type())) + return n +} + +// Corresponds to typecheck.tcAppend. +func transformAppend(n *ir.CallExpr) ir.Node { + transformArgs(n) + args := n.Args + t := args[0].Type() + assert(t.IsSlice()) + + if n.IsDDD { + if t.Elem().IsKind(types.TUINT8) && args[1].Type().IsString() { + return n + } + + args[1] = assignconvfn(args[1], t.Underlying()) + return n + } + + as := args[1:] + for i, n := range as { + assert(n.Type() != nil) + as[i] = assignconvfn(n, t.Elem()) + } + return n +} + +// Corresponds to typecheck.tcComplex. +func transformComplex(n *ir.BinaryExpr) ir.Node { + l := n.X + r := n.Y + + assert(types.Identical(l.Type(), r.Type())) + + var t *types.Type + switch l.Type().Kind() { + case types.TFLOAT32: + t = types.Types[types.TCOMPLEX64] + case types.TFLOAT64: + t = types.Types[types.TCOMPLEX128] + default: + panic(fmt.Sprintf("transformComplex: unexpected type %v", l.Type())) + } + + // Must set the type here for generics, because this can't be determined + // by substitution of the generic types. + typed(t, n) + return n +} + +// Corresponds to typecheck.tcDelete. +func transformDelete(n *ir.CallExpr) ir.Node { + transformArgs(n) + args := n.Args + assert(len(args) == 2) + + l := args[0] + r := args[1] + + args[1] = assignconvfn(r, l.Type().Key()) + return n +} + +// Corresponds to typecheck.tcMake. +func transformMake(n *ir.CallExpr) ir.Node { + args := n.Args + + n.Args = nil + l := args[0] + t := l.Type() + assert(t != nil) + + i := 1 + var nn ir.Node + switch t.Kind() { + case types.TSLICE: + l = args[i] + i++ + var r ir.Node + if i < len(args) { + r = args[i] + i++ + } + nn = ir.NewMakeExpr(n.Pos(), ir.OMAKESLICE, l, r) + + case types.TMAP: + if i < len(args) { + l = args[i] + i++ + } else { + l = ir.NewInt(0) + } + nn = ir.NewMakeExpr(n.Pos(), ir.OMAKEMAP, l, nil) + nn.SetEsc(n.Esc()) + + case types.TCHAN: + l = nil + if i < len(args) { + l = args[i] + i++ + } else { + l = ir.NewInt(0) + } + nn = ir.NewMakeExpr(n.Pos(), ir.OMAKECHAN, l, nil) + default: + panic(fmt.Sprintf("transformMake: unexpected type %v", t)) + } + + assert(i == len(args)) + typed(n.Type(), nn) + return nn +} + +// Corresponds to typecheck.tcPanic. +func transformPanic(n *ir.UnaryExpr) ir.Node { + n.X = assignconvfn(n.X, types.Types[types.TINTER]) + return n +} + +// Corresponds to typecheck.tcPrint. +func transformPrint(n *ir.CallExpr) ir.Node { + transformArgs(n) + return n +} + +// Corresponds to typecheck.tcRealImag. +func transformRealImag(n *ir.UnaryExpr) ir.Node { + l := n.X + var t *types.Type + + // Determine result type. + switch l.Type().Kind() { + case types.TCOMPLEX64: + t = types.Types[types.TFLOAT32] + case types.TCOMPLEX128: + t = types.Types[types.TFLOAT64] + default: + panic(fmt.Sprintf("transformRealImag: unexpected type %v", l.Type())) + } + + // Must set the type here for generics, because this can't be determined + // by substitution of the generic types. + typed(t, n) + return n +} + +// Corresponds to typecheck.tcLenCap. +func transformLenCap(n *ir.UnaryExpr) ir.Node { + n.X = implicitstar(n.X) + return n +} + +// Corresponds to Builtin part of tcCall. +func transformBuiltin(n *ir.CallExpr) ir.Node { + // n.Type() can be nil for builtins with no return value + assert(n.Typecheck() == 1) + fun := n.X.(*ir.Name) + op := fun.BuiltinOp + + switch op { + case ir.OAPPEND, ir.ODELETE, ir.OMAKE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER: + n.SetOp(op) + n.X = nil + switch op { + case ir.OAPPEND: + return transformAppend(n) + case ir.ODELETE: + return transformDelete(n) + case ir.OMAKE: + return transformMake(n) + case ir.OPRINT, ir.OPRINTN: + return transformPrint(n) + case ir.ORECOVER: + // nothing more to do + return n + } + + case ir.OCAP, ir.OCLOSE, ir.OIMAG, ir.OLEN, ir.OPANIC, ir.OREAL: + transformArgs(n) + fallthrough + + case ir.ONEW, ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF: + u := ir.NewUnaryExpr(n.Pos(), op, n.Args[0]) + u1 := typed(n.Type(), ir.InitExpr(n.Init(), u)) // typecheckargs can add to old.Init + switch op { + case ir.OCAP, ir.OLEN: + return transformLenCap(u1.(*ir.UnaryExpr)) + case ir.OREAL, ir.OIMAG: + return transformRealImag(u1.(*ir.UnaryExpr)) + case ir.OPANIC: + return transformPanic(u1.(*ir.UnaryExpr)) + case ir.OCLOSE, ir.ONEW, ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF: + // nothing more to do + return u1 + } + + case ir.OCOMPLEX, ir.OCOPY, ir.OUNSAFEADD, ir.OUNSAFESLICE: + transformArgs(n) + b := ir.NewBinaryExpr(n.Pos(), op, n.Args[0], n.Args[1]) + n1 := typed(n.Type(), ir.InitExpr(n.Init(), b)) + if op != ir.OCOMPLEX { + // nothing more to do + return n1 + } + return transformComplex(n1.(*ir.BinaryExpr)) + + default: + panic(fmt.Sprintf("transformBuiltin: unexpected op %v", op)) + } + + return n +} + +func hasKeys(l ir.Nodes) bool { + for _, n := range l { + if n.Op() == ir.OKEY || n.Op() == ir.OSTRUCTKEY { + return true + } + } + return false +} + +// transformArrayLit runs assignconvfn on each array element and returns the +// length of the slice/array that is needed to hold all the array keys/indexes +// (one more than the highest index). Corresponds to typecheck.typecheckarraylit. +func transformArrayLit(elemType *types.Type, bound int64, elts []ir.Node) int64 { + var key, length int64 + for i, elt := range elts { + ir.SetPos(elt) + r := elts[i] + var kv *ir.KeyExpr + if elt.Op() == ir.OKEY { + elt := elt.(*ir.KeyExpr) + key = typecheck.IndexConst(elt.Key) + assert(key >= 0) + kv = elt + r = elt.Value + } + + r = assignconvfn(r, elemType) + if kv != nil { + kv.Value = r + } else { + elts[i] = r + } + + key++ + if key > length { + length = key + } + } + + return length +} + +// transformCompLit transforms n to an OARRAYLIT, OSLICELIT, OMAPLIT, or +// OSTRUCTLIT node, with any needed conversions. Corresponds to +// typecheck.tcCompLit. +func transformCompLit(n *ir.CompLitExpr) (res ir.Node) { + assert(n.Type() != nil && n.Typecheck() == 1) + lno := base.Pos + defer func() { + base.Pos = lno + }() + + // Save original node (including n.Right) + n.SetOrig(ir.Copy(n)) + + ir.SetPos(n) + + t := n.Type() + + switch t.Kind() { + default: + base.Fatalf("transformCompLit %v", t.Kind()) + + case types.TARRAY: + transformArrayLit(t.Elem(), t.NumElem(), n.List) + n.SetOp(ir.OARRAYLIT) + + case types.TSLICE: + length := transformArrayLit(t.Elem(), -1, n.List) + n.SetOp(ir.OSLICELIT) + n.Len = length + + case types.TMAP: + for _, l := range n.List { + ir.SetPos(l) + assert(l.Op() == ir.OKEY) + l := l.(*ir.KeyExpr) + + r := l.Key + l.Key = assignconvfn(r, t.Key()) + + r = l.Value + l.Value = assignconvfn(r, t.Elem()) + } + + n.SetOp(ir.OMAPLIT) + + case types.TSTRUCT: + // Need valid field offsets for Xoffset below. + types.CalcSize(t) + + if len(n.List) != 0 && !hasKeys(n.List) { + // simple list of values + ls := n.List + for i, n1 := range ls { + ir.SetPos(n1) + + f := t.Field(i) + n1 = assignconvfn(n1, f.Type) + sk := ir.NewStructKeyExpr(base.Pos, f.Sym, n1) + sk.Offset = f.Offset + ls[i] = sk + } + assert(len(ls) >= t.NumFields()) + } else { + // keyed list + ls := n.List + for i, l := range ls { + ir.SetPos(l) + + if l.Op() == ir.OKEY { + kv := l.(*ir.KeyExpr) + key := kv.Key + + // Sym might have resolved to name in other top-level + // package, because of import dot. Redirect to correct sym + // before we do the lookup. + s := key.Sym() + if id, ok := key.(*ir.Ident); ok && typecheck.DotImportRefs[id] != nil { + s = typecheck.Lookup(s.Name) + } + + // An OXDOT uses the Sym field to hold + // the field to the right of the dot, + // so s will be non-nil, but an OXDOT + // is never a valid struct literal key. + assert(!(s == nil || s.Pkg != types.LocalPkg || key.Op() == ir.OXDOT || s.IsBlank())) + + l = ir.NewStructKeyExpr(l.Pos(), s, kv.Value) + ls[i] = l + } + + assert(l.Op() == ir.OSTRUCTKEY) + l := l.(*ir.StructKeyExpr) + + f := typecheck.Lookdot1(nil, l.Field, t, t.Fields(), 0) + l.Offset = f.Offset + + l.Value = assignconvfn(l.Value, f.Type) + } + } + + n.SetOp(ir.OSTRUCTLIT) + } + + return n +} diff --git a/src/cmd/compile/internal/noder/types.go b/src/cmd/compile/internal/noder/types.go new file mode 100644 index 0000000000000000000000000000000000000000..8680559a412970277e2513fe6d1620a55d4393cc --- /dev/null +++ b/src/cmd/compile/internal/noder/types.go @@ -0,0 +1,432 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "bytes" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/compile/internal/types2" + "cmd/internal/src" + "strings" +) + +func (g *irgen) pkg(pkg *types2.Package) *types.Pkg { + switch pkg { + case nil: + return types.BuiltinPkg + case g.self: + return types.LocalPkg + case types2.Unsafe: + return ir.Pkgs.Unsafe + } + return types.NewPkg(pkg.Path(), pkg.Name()) +} + +// typ converts a types2.Type to a types.Type, including caching of previously +// translated types. +func (g *irgen) typ(typ types2.Type) *types.Type { + res := g.typ1(typ) + + // Calculate the size for all concrete types seen by the frontend. The old + // typechecker calls CheckSize() a lot, and we want to eliminate calling + // it eventually, so we should do it here instead. We only call it for + // top-level types (i.e. we do it here rather in typ1), to make sure that + // recursive types have been fully constructed before we call CheckSize. + if res != nil && !res.IsUntyped() && !res.IsFuncArgStruct() && !res.HasTParam() { + types.CheckSize(res) + } + return res +} + +// typ1 is like typ, but doesn't call CheckSize, since it may have only +// constructed part of a recursive type. Should not be called from outside this +// file (g.typ is the "external" entry point). +func (g *irgen) typ1(typ types2.Type) *types.Type { + // Cache type2-to-type mappings. Important so that each defined generic + // type (instantiated or not) has a single types.Type representation. + // Also saves a lot of computation and memory by avoiding re-translating + // types2 types repeatedly. + res, ok := g.typs[typ] + if !ok { + res = g.typ0(typ) + g.typs[typ] = res + } + return res +} + +// instTypeName2 creates a name for an instantiated type, base on the type args +// (given as types2 types). +func instTypeName2(name string, targs []types2.Type) string { + b := bytes.NewBufferString(name) + b.WriteByte('[') + for i, targ := range targs { + if i > 0 { + b.WriteByte(',') + } + tname := types2.TypeString(targ, + func(*types2.Package) string { return "" }) + if strings.Index(tname, ", ") >= 0 { + // types2.TypeString puts spaces after a comma in a type + // list, but we don't want spaces in our actual type names + // and method/function names derived from them. + tname = strings.Replace(tname, ", ", ",", -1) + } + b.WriteString(tname) + } + b.WriteByte(']') + return b.String() +} + +// typ0 converts a types2.Type to a types.Type, but doesn't do the caching check +// at the top level. +func (g *irgen) typ0(typ types2.Type) *types.Type { + switch typ := typ.(type) { + case *types2.Basic: + return g.basic(typ) + case *types2.Named: + if typ.TParams() != nil { + // typ is an instantiation of a defined (named) generic type. + // This instantiation should also be a defined (named) type. + // types2 gives us the substituted type in t.Underlying() + // The substituted type may or may not still have type + // params. We might, for example, be substituting one type + // param for another type param. + + if typ.TArgs() == nil { + base.Fatalf("In typ0, Targs should be set if TParams is set") + } + + // When converted to types.Type, typ must have a name, + // based on the names of the type arguments. We need a + // name to deal with recursive generic types (and it also + // looks better when printing types). + instName := instTypeName2(typ.Obj().Name(), typ.TArgs()) + s := g.pkg(typ.Obj().Pkg()).Lookup(instName) + if s.Def != nil { + // We have already encountered this instantiation, + // so use the type we previously created, since there + // must be exactly one instance of a defined type. + return s.Def.Type() + } + + // Create a forwarding type first and put it in the g.typs + // map, in order to deal with recursive generic types. + // Fully set up the extra ntyp information (Def, RParams, + // which may set HasTParam) before translating the + // underlying type itself, so we handle recursion + // correctly, including via method signatures. + ntyp := newIncompleteNamedType(g.pos(typ.Obj().Pos()), s) + g.typs[typ] = ntyp + + // If ntyp still has type params, then we must be + // referencing something like 'value[T2]', as when + // specifying the generic receiver of a method, + // where value was defined as "type value[T any] + // ...". Save the type args, which will now be the + // new type of the current type. + // + // If ntyp does not have type params, we are saving the + // concrete types used to instantiate this type. We'll use + // these when instantiating the methods of the + // instantiated type. + rparams := make([]*types.Type, len(typ.TArgs())) + for i, targ := range typ.TArgs() { + rparams[i] = g.typ1(targ) + } + ntyp.SetRParams(rparams) + //fmt.Printf("Saw new type %v %v\n", instName, ntyp.HasTParam()) + + ntyp.SetUnderlying(g.typ1(typ.Underlying())) + g.fillinMethods(typ, ntyp) + return ntyp + } + obj := g.obj(typ.Obj()) + if obj.Op() != ir.OTYPE { + base.FatalfAt(obj.Pos(), "expected type: %L", obj) + } + return obj.Type() + + case *types2.Array: + return types.NewArray(g.typ1(typ.Elem()), typ.Len()) + case *types2.Chan: + return types.NewChan(g.typ1(typ.Elem()), dirs[typ.Dir()]) + case *types2.Map: + return types.NewMap(g.typ1(typ.Key()), g.typ1(typ.Elem())) + case *types2.Pointer: + return types.NewPtr(g.typ1(typ.Elem())) + case *types2.Signature: + return g.signature(nil, typ) + case *types2.Slice: + return types.NewSlice(g.typ1(typ.Elem())) + + case *types2.Struct: + fields := make([]*types.Field, typ.NumFields()) + for i := range fields { + v := typ.Field(i) + f := types.NewField(g.pos(v), g.selector(v), g.typ1(v.Type())) + f.Note = typ.Tag(i) + if v.Embedded() { + f.Embedded = 1 + } + fields[i] = f + } + return types.NewStruct(g.tpkg(typ), fields) + + case *types2.Interface: + embeddeds := make([]*types.Field, typ.NumEmbeddeds()) + j := 0 + for i := range embeddeds { + // TODO(mdempsky): Get embedding position. + e := typ.EmbeddedType(i) + if t := types2.AsInterface(e); t != nil && t.IsComparable() { + // Ignore predefined type 'comparable', since it + // doesn't resolve and it doesn't have any + // relevant methods. + continue + } + embeddeds[j] = types.NewField(src.NoXPos, nil, g.typ1(e)) + j++ + } + embeddeds = embeddeds[:j] + + methods := make([]*types.Field, typ.NumExplicitMethods()) + for i := range methods { + m := typ.ExplicitMethod(i) + mtyp := g.signature(typecheck.FakeRecv(), m.Type().(*types2.Signature)) + methods[i] = types.NewField(g.pos(m), g.selector(m), mtyp) + } + + return types.NewInterface(g.tpkg(typ), append(embeddeds, methods...)) + + case *types2.TypeParam: + tp := types.NewTypeParam(g.tpkg(typ)) + // Save the name of the type parameter in the sym of the type. + // Include the types2 subscript in the sym name + sym := g.pkg(typ.Obj().Pkg()).Lookup(types2.TypeString(typ, func(*types2.Package) string { return "" })) + tp.SetSym(sym) + // Set g.typs[typ] in case the bound methods reference typ. + g.typs[typ] = tp + + // TODO(danscales): we don't currently need to use the bounds + // anywhere, so eventually we can probably remove. + bound := g.typ1(typ.Bound()) + *tp.Methods() = *bound.Methods() + return tp + + case *types2.Tuple: + // Tuples are used for the type of a function call (i.e. the + // return value of the function). + if typ == nil { + return (*types.Type)(nil) + } + fields := make([]*types.Field, typ.Len()) + for i := range fields { + fields[i] = g.param(typ.At(i)) + } + t := types.NewStruct(types.LocalPkg, fields) + t.StructType().Funarg = types.FunargResults + return t + + default: + base.FatalfAt(src.NoXPos, "unhandled type: %v (%T)", typ, typ) + panic("unreachable") + } +} + +// fillinMethods fills in the method name nodes and types for a defined type. This +// is needed for later typechecking when looking up methods of instantiated types, +// and for actually generating the methods for instantiated types. +func (g *irgen) fillinMethods(typ *types2.Named, ntyp *types.Type) { + if typ.NumMethods() != 0 { + targs := make([]ir.Node, len(typ.TArgs())) + for i, targ := range typ.TArgs() { + targs[i] = ir.TypeNode(g.typ1(targ)) + } + + methods := make([]*types.Field, typ.NumMethods()) + for i := range methods { + m := typ.Method(i) + meth := g.obj(m) + recvType := types2.AsSignature(m.Type()).Recv().Type() + ptr := types2.AsPointer(recvType) + if ptr != nil { + recvType = ptr.Elem() + } + if recvType != types2.Type(typ) { + // Unfortunately, meth is the type of the method of the + // generic type, so we have to do a substitution to get + // the name/type of the method of the instantiated type, + // using m.Type().RParams() and typ.TArgs() + inst2 := instTypeName2("", typ.TArgs()) + name := meth.Sym().Name + i1 := strings.Index(name, "[") + i2 := strings.Index(name[i1:], "]") + assert(i1 >= 0 && i2 >= 0) + // Generate the name of the instantiated method. + name = name[0:i1] + inst2 + name[i1+i2+1:] + newsym := meth.Sym().Pkg.Lookup(name) + var meth2 *ir.Name + if newsym.Def != nil { + meth2 = newsym.Def.(*ir.Name) + } else { + meth2 = ir.NewNameAt(meth.Pos(), newsym) + rparams := types2.AsSignature(m.Type()).RParams() + tparams := make([]*types.Field, len(rparams)) + for i, rparam := range rparams { + tparams[i] = types.NewField(src.NoXPos, nil, g.typ1(rparam.Type())) + } + assert(len(tparams) == len(targs)) + subst := &subster{ + g: g, + tparams: tparams, + targs: targs, + } + // Do the substitution of the type + meth2.SetType(subst.typ(meth.Type())) + newsym.Def = meth2 + } + meth = meth2 + } + methods[i] = types.NewField(meth.Pos(), g.selector(m), meth.Type()) + methods[i].Nname = meth + } + ntyp.Methods().Set(methods) + if !ntyp.HasTParam() { + // Generate all the methods for a new fully-instantiated type. + g.instTypeList = append(g.instTypeList, ntyp) + } + } +} + +func (g *irgen) signature(recv *types.Field, sig *types2.Signature) *types.Type { + tparams2 := sig.TParams() + tparams := make([]*types.Field, len(tparams2)) + for i := range tparams { + tp := tparams2[i] + tparams[i] = types.NewField(g.pos(tp), g.sym(tp), g.typ1(tp.Type())) + } + + do := func(typ *types2.Tuple) []*types.Field { + fields := make([]*types.Field, typ.Len()) + for i := range fields { + fields[i] = g.param(typ.At(i)) + } + return fields + } + params := do(sig.Params()) + results := do(sig.Results()) + if sig.Variadic() { + params[len(params)-1].SetIsDDD(true) + } + + return types.NewSignature(g.tpkg(sig), recv, tparams, params, results) +} + +func (g *irgen) param(v *types2.Var) *types.Field { + return types.NewField(g.pos(v), g.sym(v), g.typ1(v.Type())) +} + +func (g *irgen) sym(obj types2.Object) *types.Sym { + if name := obj.Name(); name != "" { + return g.pkg(obj.Pkg()).Lookup(obj.Name()) + } + return nil +} + +func (g *irgen) selector(obj types2.Object) *types.Sym { + pkg, name := g.pkg(obj.Pkg()), obj.Name() + if types.IsExported(name) { + pkg = types.LocalPkg + } + return pkg.Lookup(name) +} + +// tpkg returns the package that a function, interface, or struct type +// expression appeared in. +// +// Caveat: For the degenerate types "func()", "interface{}", and +// "struct{}", tpkg always returns LocalPkg. However, we only need the +// package information so that go/types can report it via its API, and +// the reason we fail to return the original package for these +// particular types is because go/types does *not* report it for +// them. So in practice this limitation is probably moot. +func (g *irgen) tpkg(typ types2.Type) *types.Pkg { + anyObj := func() types2.Object { + switch typ := typ.(type) { + case *types2.Signature: + if recv := typ.Recv(); recv != nil { + return recv + } + if params := typ.Params(); params.Len() > 0 { + return params.At(0) + } + if results := typ.Results(); results.Len() > 0 { + return results.At(0) + } + case *types2.Struct: + if typ.NumFields() > 0 { + return typ.Field(0) + } + case *types2.Interface: + if typ.NumExplicitMethods() > 0 { + return typ.ExplicitMethod(0) + } + } + return nil + } + + if obj := anyObj(); obj != nil { + return g.pkg(obj.Pkg()) + } + return types.LocalPkg +} + +func (g *irgen) basic(typ *types2.Basic) *types.Type { + switch typ.Name() { + case "byte": + return types.ByteType + case "rune": + return types.RuneType + } + return *basics[typ.Kind()] +} + +var basics = [...]**types.Type{ + types2.Invalid: new(*types.Type), + types2.Bool: &types.Types[types.TBOOL], + types2.Int: &types.Types[types.TINT], + types2.Int8: &types.Types[types.TINT8], + types2.Int16: &types.Types[types.TINT16], + types2.Int32: &types.Types[types.TINT32], + types2.Int64: &types.Types[types.TINT64], + types2.Uint: &types.Types[types.TUINT], + types2.Uint8: &types.Types[types.TUINT8], + types2.Uint16: &types.Types[types.TUINT16], + types2.Uint32: &types.Types[types.TUINT32], + types2.Uint64: &types.Types[types.TUINT64], + types2.Uintptr: &types.Types[types.TUINTPTR], + types2.Float32: &types.Types[types.TFLOAT32], + types2.Float64: &types.Types[types.TFLOAT64], + types2.Complex64: &types.Types[types.TCOMPLEX64], + types2.Complex128: &types.Types[types.TCOMPLEX128], + types2.String: &types.Types[types.TSTRING], + types2.UnsafePointer: &types.Types[types.TUNSAFEPTR], + types2.UntypedBool: &types.UntypedBool, + types2.UntypedInt: &types.UntypedInt, + types2.UntypedRune: &types.UntypedRune, + types2.UntypedFloat: &types.UntypedFloat, + types2.UntypedComplex: &types.UntypedComplex, + types2.UntypedString: &types.UntypedString, + types2.UntypedNil: &types.Types[types.TNIL], +} + +var dirs = [...]types.ChanDir{ + types2.SendRecv: types.Cboth, + types2.SendOnly: types.Csend, + types2.RecvOnly: types.Crecv, +} diff --git a/src/cmd/compile/internal/noder/validate.go b/src/cmd/compile/internal/noder/validate.go new file mode 100644 index 0000000000000000000000000000000000000000..b926222c89c417ccf7a691f3fc9fe60a0c68e1ae --- /dev/null +++ b/src/cmd/compile/internal/noder/validate.go @@ -0,0 +1,115 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/syntax" + "cmd/compile/internal/types" + "cmd/compile/internal/types2" +) + +// match reports whether types t1 and t2 are consistent +// representations for a given expression's type. +func (g *irgen) match(t1 *types.Type, t2 types2.Type, hasOK bool) bool { + tuple, ok := t2.(*types2.Tuple) + if !ok { + // Not a tuple; can use simple type identity comparison. + return types.Identical(t1, g.typ(t2)) + } + + if hasOK { + // For has-ok values, types2 represents the expression's type as a + // 2-element tuple, whereas ir just uses the first type and infers + // that the second type is boolean. Must match either, since we + // sometimes delay the transformation to the ir form. + if tuple.Len() == 2 && types.Identical(t1, g.typ(tuple.At(0).Type())) { + return true + } + return types.Identical(t1, g.typ(t2)) + } + + if t1 == nil || tuple == nil { + return t1 == nil && tuple == nil + } + if !t1.IsFuncArgStruct() { + return false + } + if t1.NumFields() != tuple.Len() { + return false + } + for i, result := range t1.FieldSlice() { + if !types.Identical(result.Type, g.typ(tuple.At(i).Type())) { + return false + } + } + return true +} + +func (g *irgen) validate(n syntax.Node) { + switch n := n.(type) { + case *syntax.CallExpr: + tv := g.info.Types[n.Fun] + if tv.IsBuiltin() { + switch builtin := n.Fun.(type) { + case *syntax.Name: + g.validateBuiltin(builtin.Value, n) + case *syntax.SelectorExpr: + g.validateBuiltin(builtin.Sel.Value, n) + default: + g.unhandled("builtin", n) + } + } + } +} + +func (g *irgen) validateBuiltin(name string, call *syntax.CallExpr) { + switch name { + case "Alignof", "Offsetof", "Sizeof": + // Check that types2+gcSizes calculates sizes the same + // as cmd/compile does. + + got, ok := constant.Int64Val(g.info.Types[call].Value) + if !ok { + base.FatalfAt(g.pos(call), "expected int64 constant value") + } + + want := g.unsafeExpr(name, call.ArgList[0]) + if got != want { + base.FatalfAt(g.pos(call), "got %v from types2, but want %v", got, want) + } + } +} + +// unsafeExpr evaluates the given unsafe builtin function on arg. +func (g *irgen) unsafeExpr(name string, arg syntax.Expr) int64 { + switch name { + case "Alignof": + return g.typ(g.info.Types[arg].Type).Alignment() + case "Sizeof": + return g.typ(g.info.Types[arg].Type).Size() + } + + // Offsetof + + sel := arg.(*syntax.SelectorExpr) + selection := g.info.Selections[sel] + + typ := g.typ(g.info.Types[sel.X].Type) + typ = deref(typ) + + var offset int64 + for _, i := range selection.Index() { + // Ensure field offsets have been calculated. + types.CalcSize(typ) + + f := typ.Field(i) + offset += f.Offset + typ = f.Type + } + return offset +} diff --git a/src/cmd/compile/internal/objw/objw.go b/src/cmd/compile/internal/objw/objw.go new file mode 100644 index 0000000000000000000000000000000000000000..ed5ad754d9b29f2e2e6c8cdd56655080c777e8f7 --- /dev/null +++ b/src/cmd/compile/internal/objw/objw.go @@ -0,0 +1,85 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package objw + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/bitvec" + "cmd/compile/internal/types" + "cmd/internal/obj" +) + +// Uint8 writes an unsigned byte v into s at offset off, +// and returns the next unused offset (i.e., off+1). +func Uint8(s *obj.LSym, off int, v uint8) int { + return UintN(s, off, uint64(v), 1) +} + +func Uint16(s *obj.LSym, off int, v uint16) int { + return UintN(s, off, uint64(v), 2) +} + +func Uint32(s *obj.LSym, off int, v uint32) int { + return UintN(s, off, uint64(v), 4) +} + +func Uintptr(s *obj.LSym, off int, v uint64) int { + return UintN(s, off, v, types.PtrSize) +} + +// UintN writes an unsigned integer v of size wid bytes into s at offset off, +// and returns the next unused offset. +func UintN(s *obj.LSym, off int, v uint64, wid int) int { + if off&(wid-1) != 0 { + base.Fatalf("duintxxLSym: misaligned: v=%d wid=%d off=%d", v, wid, off) + } + s.WriteInt(base.Ctxt, int64(off), wid, int64(v)) + return off + wid +} + +func SymPtr(s *obj.LSym, off int, x *obj.LSym, xoff int) int { + off = int(types.Rnd(int64(off), int64(types.PtrSize))) + s.WriteAddr(base.Ctxt, int64(off), types.PtrSize, x, int64(xoff)) + off += types.PtrSize + return off +} + +func SymPtrWeak(s *obj.LSym, off int, x *obj.LSym, xoff int) int { + off = int(types.Rnd(int64(off), int64(types.PtrSize))) + s.WriteWeakAddr(base.Ctxt, int64(off), types.PtrSize, x, int64(xoff)) + off += types.PtrSize + return off +} + +func SymPtrOff(s *obj.LSym, off int, x *obj.LSym) int { + s.WriteOff(base.Ctxt, int64(off), x, 0) + off += 4 + return off +} + +func SymPtrWeakOff(s *obj.LSym, off int, x *obj.LSym) int { + s.WriteWeakOff(base.Ctxt, int64(off), x, 0) + off += 4 + return off +} + +func Global(s *obj.LSym, width int32, flags int16) { + if flags&obj.LOCAL != 0 { + s.Set(obj.AttrLocal, true) + flags &^= obj.LOCAL + } + base.Ctxt.Globl(s, int64(width), int(flags)) +} + +// Bitvec writes the contents of bv into s as sequence of bytes +// in little-endian order, and returns the next unused offset. +func BitVec(s *obj.LSym, off int, bv bitvec.BitVec) int { + // Runtime reads the bitmaps as byte arrays. Oblige. + for j := 0; int32(j) < bv.N; j += 8 { + word := bv.B[j/32] + off = Uint8(s, off, uint8(word>>(uint(j)%32))) + } + return off +} diff --git a/src/cmd/compile/internal/objw/prog.go b/src/cmd/compile/internal/objw/prog.go new file mode 100644 index 0000000000000000000000000000000000000000..b5ac4dda1eb97361c7f53e76e8238fc4244fcbb8 --- /dev/null +++ b/src/cmd/compile/internal/objw/prog.go @@ -0,0 +1,226 @@ +// Derived from Inferno utils/6c/txt.c +// https://bitbucket.org/inferno-os/inferno-os/src/master/utils/6c/txt.c +// +// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved. +// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net) +// Portions Copyright © 1997-1999 Vita Nuova Limited +// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com) +// Portions Copyright © 2004,2006 Bruce Ellis +// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net) +// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others +// Portions Copyright © 2009 The Go Authors. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package objw + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/src" +) + +var sharedProgArray = new([10000]obj.Prog) // *T instead of T to work around issue 19839 + +// NewProgs returns a new Progs for fn. +// worker indicates which of the backend workers will use the Progs. +func NewProgs(fn *ir.Func, worker int) *Progs { + pp := new(Progs) + if base.Ctxt.CanReuseProgs() { + sz := len(sharedProgArray) / base.Flag.LowerC + pp.Cache = sharedProgArray[sz*worker : sz*(worker+1)] + } + pp.CurFunc = fn + + // prime the pump + pp.Next = pp.NewProg() + pp.Clear(pp.Next) + + pp.Pos = fn.Pos() + pp.SetText(fn) + // PCDATA tables implicitly start with index -1. + pp.PrevLive = LivenessIndex{-1, false} + pp.NextLive = pp.PrevLive + return pp +} + +// Progs accumulates Progs for a function and converts them into machine code. +type Progs struct { + Text *obj.Prog // ATEXT Prog for this function + Next *obj.Prog // next Prog + PC int64 // virtual PC; count of Progs + Pos src.XPos // position to use for new Progs + CurFunc *ir.Func // fn these Progs are for + Cache []obj.Prog // local progcache + CacheIndex int // first free element of progcache + + NextLive LivenessIndex // liveness index for the next Prog + PrevLive LivenessIndex // last emitted liveness index +} + +// LivenessIndex stores the liveness map information for a Value. +type LivenessIndex struct { + StackMapIndex int + + // IsUnsafePoint indicates that this is an unsafe-point. + // + // Note that it's possible for a call Value to have a stack + // map while also being an unsafe-point. This means it cannot + // be preempted at this instruction, but that a preemption or + // stack growth may happen in the called function. + IsUnsafePoint bool +} + +// StackMapDontCare indicates that the stack map index at a Value +// doesn't matter. +// +// This is a sentinel value that should never be emitted to the PCDATA +// stream. We use -1000 because that's obviously never a valid stack +// index (but -1 is). +const StackMapDontCare = -1000 + +// LivenessDontCare indicates that the liveness information doesn't +// matter. Currently it is used in deferreturn liveness when we don't +// actually need it. It should never be emitted to the PCDATA stream. +var LivenessDontCare = LivenessIndex{StackMapDontCare, true} + +func (idx LivenessIndex) StackMapValid() bool { + return idx.StackMapIndex != StackMapDontCare +} + +func (pp *Progs) NewProg() *obj.Prog { + var p *obj.Prog + if pp.CacheIndex < len(pp.Cache) { + p = &pp.Cache[pp.CacheIndex] + pp.CacheIndex++ + } else { + p = new(obj.Prog) + } + p.Ctxt = base.Ctxt + return p +} + +// Flush converts from pp to machine code. +func (pp *Progs) Flush() { + plist := &obj.Plist{Firstpc: pp.Text, Curfn: pp.CurFunc} + obj.Flushplist(base.Ctxt, plist, pp.NewProg, base.Ctxt.Pkgpath) +} + +// Free clears pp and any associated resources. +func (pp *Progs) Free() { + if base.Ctxt.CanReuseProgs() { + // Clear progs to enable GC and avoid abuse. + s := pp.Cache[:pp.CacheIndex] + for i := range s { + s[i] = obj.Prog{} + } + } + // Clear pp to avoid abuse. + *pp = Progs{} +} + +// Prog adds a Prog with instruction As to pp. +func (pp *Progs) Prog(as obj.As) *obj.Prog { + if pp.NextLive.StackMapValid() && pp.NextLive.StackMapIndex != pp.PrevLive.StackMapIndex { + // Emit stack map index change. + idx := pp.NextLive.StackMapIndex + pp.PrevLive.StackMapIndex = idx + p := pp.Prog(obj.APCDATA) + p.From.SetConst(objabi.PCDATA_StackMapIndex) + p.To.SetConst(int64(idx)) + } + if pp.NextLive.IsUnsafePoint != pp.PrevLive.IsUnsafePoint { + // Emit unsafe-point marker. + pp.PrevLive.IsUnsafePoint = pp.NextLive.IsUnsafePoint + p := pp.Prog(obj.APCDATA) + p.From.SetConst(objabi.PCDATA_UnsafePoint) + if pp.NextLive.IsUnsafePoint { + p.To.SetConst(objabi.PCDATA_UnsafePointUnsafe) + } else { + p.To.SetConst(objabi.PCDATA_UnsafePointSafe) + } + } + + p := pp.Next + pp.Next = pp.NewProg() + pp.Clear(pp.Next) + p.Link = pp.Next + + if !pp.Pos.IsKnown() && base.Flag.K != 0 { + base.Warn("prog: unknown position (line 0)") + } + + p.As = as + p.Pos = pp.Pos + if pp.Pos.IsStmt() == src.PosIsStmt { + // Clear IsStmt for later Progs at this pos provided that as can be marked as a stmt + if LosesStmtMark(as) { + return p + } + pp.Pos = pp.Pos.WithNotStmt() + } + return p +} + +func (pp *Progs) Clear(p *obj.Prog) { + obj.Nopout(p) + p.As = obj.AEND + p.Pc = pp.PC + pp.PC++ +} + +func (pp *Progs) Append(p *obj.Prog, as obj.As, ftype obj.AddrType, freg int16, foffset int64, ttype obj.AddrType, treg int16, toffset int64) *obj.Prog { + q := pp.NewProg() + pp.Clear(q) + q.As = as + q.Pos = p.Pos + q.From.Type = ftype + q.From.Reg = freg + q.From.Offset = foffset + q.To.Type = ttype + q.To.Reg = treg + q.To.Offset = toffset + q.Link = p.Link + p.Link = q + return q +} + +func (pp *Progs) SetText(fn *ir.Func) { + if pp.Text != nil { + base.Fatalf("Progs.SetText called twice") + } + ptxt := pp.Prog(obj.ATEXT) + pp.Text = ptxt + + fn.LSym.Func().Text = ptxt + ptxt.From.Type = obj.TYPE_MEM + ptxt.From.Name = obj.NAME_EXTERN + ptxt.From.Sym = fn.LSym +} + +// LosesStmtMark reports whether a prog with op as loses its statement mark on the way to DWARF. +// The attributes from some opcodes are lost in translation. +// TODO: this is an artifact of how funcpctab combines information for instructions at a single PC. +// Should try to fix it there. +func LosesStmtMark(as obj.As) bool { + // is_stmt does not work for these; it DOES for ANOP even though that generates no code. + return as == obj.APCDATA || as == obj.AFUNCDATA +} diff --git a/src/cmd/compile/internal/pkginit/init.go b/src/cmd/compile/internal/pkginit/init.go new file mode 100644 index 0000000000000000000000000000000000000000..7cad2622146d0e580cb257348ec234ecb05ba713 --- /dev/null +++ b/src/cmd/compile/internal/pkginit/init.go @@ -0,0 +1,109 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkginit + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/deadcode" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" +) + +// Task makes and returns an initialization record for the package. +// See runtime/proc.go:initTask for its layout. +// The 3 tasks for initialization are: +// 1) Initialize all of the packages the current package depends on. +// 2) Initialize all the variables that have initializers. +// 3) Run any init functions. +func Task() *ir.Name { + nf := initOrder(typecheck.Target.Decls) + + var deps []*obj.LSym // initTask records for packages the current package depends on + var fns []*obj.LSym // functions to call for package initialization + + // Find imported packages with init tasks. + for _, pkg := range typecheck.Target.Imports { + n := typecheck.Resolve(ir.NewIdent(base.Pos, pkg.Lookup(".inittask"))) + if n.Op() == ir.ONONAME { + continue + } + if n.Op() != ir.ONAME || n.(*ir.Name).Class != ir.PEXTERN { + base.Fatalf("bad inittask: %v", n) + } + deps = append(deps, n.(*ir.Name).Linksym()) + } + + // Make a function that contains all the initialization statements. + if len(nf) > 0 { + base.Pos = nf[0].Pos() // prolog/epilog gets line number of first init stmt + initializers := typecheck.Lookup("init") + fn := typecheck.DeclFunc(initializers, ir.NewFuncType(base.Pos, nil, nil, nil)) + for _, dcl := range typecheck.InitTodoFunc.Dcl { + dcl.Curfn = fn + } + fn.Dcl = append(fn.Dcl, typecheck.InitTodoFunc.Dcl...) + typecheck.InitTodoFunc.Dcl = nil + + fn.Body = nf + typecheck.FinishFuncBody() + + typecheck.Func(fn) + ir.CurFunc = fn + typecheck.Stmts(nf) + ir.CurFunc = nil + typecheck.Target.Decls = append(typecheck.Target.Decls, fn) + fns = append(fns, fn.Linksym()) + } + if typecheck.InitTodoFunc.Dcl != nil { + // We only generate temps using InitTodoFunc if there + // are package-scope initialization statements, so + // something's weird if we get here. + base.Fatalf("InitTodoFunc still has declarations") + } + typecheck.InitTodoFunc = nil + + // Record user init functions. + for _, fn := range typecheck.Target.Inits { + // Must happen after initOrder; see #43444. + deadcode.Func(fn) + + // Skip init functions with empty bodies. + if len(fn.Body) == 1 { + if stmt := fn.Body[0]; stmt.Op() == ir.OBLOCK && len(stmt.(*ir.BlockStmt).List) == 0 { + continue + } + } + fns = append(fns, fn.Nname.Linksym()) + } + + if len(deps) == 0 && len(fns) == 0 && types.LocalPkg.Name != "main" && types.LocalPkg.Name != "runtime" { + return nil // nothing to initialize + } + + // Make an .inittask structure. + sym := typecheck.Lookup(".inittask") + task := typecheck.NewName(sym) + task.SetType(types.Types[types.TUINT8]) // fake type + task.Class = ir.PEXTERN + sym.Def = task + lsym := task.Linksym() + ot := 0 + ot = objw.Uintptr(lsym, ot, 0) // state: not initialized yet + ot = objw.Uintptr(lsym, ot, uint64(len(deps))) + ot = objw.Uintptr(lsym, ot, uint64(len(fns))) + for _, d := range deps { + ot = objw.SymPtr(lsym, ot, d, 0) + } + for _, f := range fns { + ot = objw.SymPtr(lsym, ot, f, 0) + } + // An initTask has pointers, but none into the Go heap. + // It's not quite read only, the state field must be modifiable. + objw.Global(lsym, int32(ot), obj.NOPTR) + return task +} diff --git a/src/cmd/compile/internal/gc/initorder.go b/src/cmd/compile/internal/pkginit/initorder.go similarity index 64% rename from src/cmd/compile/internal/gc/initorder.go rename to src/cmd/compile/internal/pkginit/initorder.go index e2084fd03862ca8ea9f7e79557f2df1038ba9056..97d69629fbae9e5167ec68abe39c9264a8c979cc 100644 --- a/src/cmd/compile/internal/gc/initorder.go +++ b/src/cmd/compile/internal/pkginit/initorder.go @@ -2,12 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package pkginit import ( "bytes" "container/heap" "fmt" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/staticinit" ) // Package initialization @@ -60,56 +64,57 @@ const ( type InitOrder struct { // blocking maps initialization assignments to the assignments // that depend on it. - blocking map[*Node][]*Node + blocking map[ir.Node][]ir.Node // ready is the queue of Pending initialization assignments // that are ready for initialization. ready declOrder + + order map[ir.Node]int } // initOrder computes initialization order for a list l of // package-level declarations (in declaration order) and outputs the // corresponding list of statements to include in the init() function // body. -func initOrder(l []*Node) []*Node { - s := InitSchedule{ - initplans: make(map[*Node]*InitPlan), - inittemps: make(map[*Node]*Node), +func initOrder(l []ir.Node) []ir.Node { + s := staticinit.Schedule{ + Plans: make(map[ir.Node]*staticinit.Plan), + Temps: make(map[ir.Node]*ir.Name), } o := InitOrder{ - blocking: make(map[*Node][]*Node), + blocking: make(map[ir.Node][]ir.Node), + order: make(map[ir.Node]int), } // Process all package-level assignment in declaration order. for _, n := range l { - switch n.Op { - case OAS, OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: + switch n.Op() { + case ir.OAS, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV: o.processAssign(n) - o.flushReady(s.staticInit) - case ODCLCONST, ODCLFUNC, ODCLTYPE: + o.flushReady(s.StaticInit) + case ir.ODCLCONST, ir.ODCLFUNC, ir.ODCLTYPE: // nop default: - Fatalf("unexpected package-level statement: %v", n) + base.Fatalf("unexpected package-level statement: %v", n) } } // Check that all assignments are now Done; if not, there must // have been a dependency cycle. for _, n := range l { - switch n.Op { - case OAS, OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: - if n.Initorder() != InitDone { + switch n.Op() { + case ir.OAS, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV: + if o.order[n] != orderDone { // If there have already been errors // printed, those errors may have // confused us and there might not be // a loop. Let the user fix those // first. - if nerrors > 0 { - errorexit() - } + base.ExitIfErrors() - findInitLoopAndExit(firstLHS(n), new([]*Node), make(map[*Node]bool)) - Fatalf("initialization unfinished, but failed to identify loop") + o.findInitLoopAndExit(firstLHS(n), new([]*ir.Name), new(ir.NameSet)) + base.Fatalf("initialization unfinished, but failed to identify loop") } } } @@ -117,58 +122,56 @@ func initOrder(l []*Node) []*Node { // Invariant consistency check. If this is non-zero, then we // should have found a cycle above. if len(o.blocking) != 0 { - Fatalf("expected empty map: %v", o.blocking) + base.Fatalf("expected empty map: %v", o.blocking) } - return s.out + return s.Out } -func (o *InitOrder) processAssign(n *Node) { - if n.Initorder() != InitNotStarted || n.Xoffset != BADWIDTH { - Fatalf("unexpected state: %v, %v, %v", n, n.Initorder(), n.Xoffset) +func (o *InitOrder) processAssign(n ir.Node) { + if _, ok := o.order[n]; ok { + base.Fatalf("unexpected state: %v, %v", n, o.order[n]) } - - n.SetInitorder(InitPending) - n.Xoffset = 0 + o.order[n] = 0 // Compute number of variable dependencies and build the // inverse dependency ("blocking") graph. for dep := range collectDeps(n, true) { - defn := dep.Name.Defn + defn := dep.Defn // Skip dependencies on functions (PFUNC) and // variables already initialized (InitDone). - if dep.Class() != PEXTERN || defn.Initorder() == InitDone { + if dep.Class != ir.PEXTERN || o.order[defn] == orderDone { continue } - n.Xoffset++ + o.order[n]++ o.blocking[defn] = append(o.blocking[defn], n) } - if n.Xoffset == 0 { + if o.order[n] == 0 { heap.Push(&o.ready, n) } } +const orderDone = -1000 + // flushReady repeatedly applies initialize to the earliest (in // declaration order) assignment ready for initialization and updates // the inverse dependency ("blocking") graph. -func (o *InitOrder) flushReady(initialize func(*Node)) { +func (o *InitOrder) flushReady(initialize func(ir.Node)) { for o.ready.Len() != 0 { - n := heap.Pop(&o.ready).(*Node) - if n.Initorder() != InitPending || n.Xoffset != 0 { - Fatalf("unexpected state: %v, %v, %v", n, n.Initorder(), n.Xoffset) + n := heap.Pop(&o.ready).(ir.Node) + if order, ok := o.order[n]; !ok || order != 0 { + base.Fatalf("unexpected state: %v, %v, %v", n, ok, order) } initialize(n) - n.SetInitorder(InitDone) - n.Xoffset = BADWIDTH + o.order[n] = orderDone blocked := o.blocking[n] delete(o.blocking, n) for _, m := range blocked { - m.Xoffset-- - if m.Xoffset == 0 { + if o.order[m]--; o.order[m] == 0 { heap.Push(&o.ready, m) } } @@ -181,7 +184,7 @@ func (o *InitOrder) flushReady(initialize func(*Node)) { // path points to a slice used for tracking the sequence of // variables/functions visited. Using a pointer to a slice allows the // slice capacity to grow and limit reallocations. -func findInitLoopAndExit(n *Node, path *[]*Node, ok map[*Node]bool) { +func (o *InitOrder) findInitLoopAndExit(n *ir.Name, path *[]*ir.Name, ok *ir.NameSet) { for i, x := range *path { if x == n { reportInitLoopAndExit((*path)[i:]) @@ -191,24 +194,25 @@ func findInitLoopAndExit(n *Node, path *[]*Node, ok map[*Node]bool) { // There might be multiple loops involving n; by sorting // references, we deterministically pick the one reported. - refers := collectDeps(n.Name.Defn, false).Sorted(func(ni, nj *Node) bool { - return ni.Pos.Before(nj.Pos) + refers := collectDeps(n.Defn, false).Sorted(func(ni, nj *ir.Name) bool { + return ni.Pos().Before(nj.Pos()) }) *path = append(*path, n) for _, ref := range refers { // Short-circuit variables that were initialized. - if ref.Class() == PEXTERN && ref.Name.Defn.Initorder() == InitDone || ok[ref] { + if ref.Class == ir.PEXTERN && o.order[ref.Defn] == orderDone || ok.Has(ref) { continue } - findInitLoopAndExit(ref, path, ok) + + o.findInitLoopAndExit(ref, path, ok) } // n is not involved in a cycle. // Record that fact to avoid checking it again when reached another way, // or else this traversal will take exponential time traversing all paths // through the part of the package's call graph implicated in the cycle. - ok[n] = true + ok.Add(n) *path = (*path)[:len(*path)-1] } @@ -216,12 +220,12 @@ func findInitLoopAndExit(n *Node, path *[]*Node, ok map[*Node]bool) { // reportInitLoopAndExit reports and initialization loop as an error // and exits. However, if l is not actually an initialization loop, it // simply returns instead. -func reportInitLoopAndExit(l []*Node) { +func reportInitLoopAndExit(l []*ir.Name) { // Rotate loop so that the earliest variable declaration is at // the start. i := -1 for j, n := range l { - if n.Class() == PEXTERN && (i == -1 || n.Pos.Before(l[i].Pos)) { + if n.Class == ir.PEXTERN && (i == -1 || n.Pos().Before(l[i].Pos())) { i = j } } @@ -239,69 +243,75 @@ func reportInitLoopAndExit(l []*Node) { var msg bytes.Buffer fmt.Fprintf(&msg, "initialization loop:\n") for _, n := range l { - fmt.Fprintf(&msg, "\t%v: %v refers to\n", n.Line(), n) + fmt.Fprintf(&msg, "\t%v: %v refers to\n", ir.Line(n), n) } - fmt.Fprintf(&msg, "\t%v: %v", l[0].Line(), l[0]) + fmt.Fprintf(&msg, "\t%v: %v", ir.Line(l[0]), l[0]) - yyerrorl(l[0].Pos, msg.String()) - errorexit() + base.ErrorfAt(l[0].Pos(), msg.String()) + base.ErrorExit() } // collectDeps returns all of the package-level functions and // variables that declaration n depends on. If transitive is true, // then it also includes the transitive dependencies of any depended // upon functions (but not variables). -func collectDeps(n *Node, transitive bool) NodeSet { +func collectDeps(n ir.Node, transitive bool) ir.NameSet { d := initDeps{transitive: transitive} - switch n.Op { - case OAS: - d.inspect(n.Right) - case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: - d.inspect(n.Right) - case ODCLFUNC: - d.inspectList(n.Nbody) + switch n.Op() { + case ir.OAS: + n := n.(*ir.AssignStmt) + d.inspect(n.Y) + case ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV: + n := n.(*ir.AssignListStmt) + d.inspect(n.Rhs[0]) + case ir.ODCLFUNC: + n := n.(*ir.Func) + d.inspectList(n.Body) default: - Fatalf("unexpected Op: %v", n.Op) + base.Fatalf("unexpected Op: %v", n.Op()) } return d.seen } type initDeps struct { transitive bool - seen NodeSet + seen ir.NameSet + cvisit func(ir.Node) +} + +func (d *initDeps) cachedVisit() func(ir.Node) { + if d.cvisit == nil { + d.cvisit = d.visit // cache closure + } + return d.cvisit } -func (d *initDeps) inspect(n *Node) { inspect(n, d.visit) } -func (d *initDeps) inspectList(l Nodes) { inspectList(l, d.visit) } +func (d *initDeps) inspect(n ir.Node) { ir.Visit(n, d.cachedVisit()) } +func (d *initDeps) inspectList(l ir.Nodes) { ir.VisitList(l, d.cachedVisit()) } // visit calls foundDep on any package-level functions or variables // referenced by n, if any. -func (d *initDeps) visit(n *Node) bool { - switch n.Op { - case ONAME: - if n.isMethodExpression() { - d.foundDep(asNode(n.Type.FuncType().Nname)) - return false - } - - switch n.Class() { - case PEXTERN, PFUNC: +func (d *initDeps) visit(n ir.Node) { + switch n.Op() { + case ir.ONAME: + n := n.(*ir.Name) + switch n.Class { + case ir.PEXTERN, ir.PFUNC: d.foundDep(n) } - case OCLOSURE: - d.inspectList(n.Func.Closure.Nbody) + case ir.OCLOSURE: + n := n.(*ir.ClosureExpr) + d.inspectList(n.Func.Body) - case ODOTMETH, OCALLPART: - d.foundDep(asNode(n.Type.FuncType().Nname)) + case ir.ODOTMETH, ir.OCALLPART, ir.OMETHEXPR: + d.foundDep(ir.MethodExprName(n)) } - - return true } // foundDep records that we've found a dependency on n by adding it to // seen. -func (d *initDeps) foundDep(n *Node) { +func (d *initDeps) foundDep(n *ir.Name) { // Can happen with method expressions involving interface // types; e.g., fixedbugs/issue4495.go. if n == nil { @@ -310,7 +320,7 @@ func (d *initDeps) foundDep(n *Node) { // Names without definitions aren't interesting as far as // initialization ordering goes. - if n.Name.Defn == nil { + if n.Defn == nil { return } @@ -318,8 +328,8 @@ func (d *initDeps) foundDep(n *Node) { return } d.seen.Add(n) - if d.transitive && n.Class() == PFUNC { - d.inspectList(n.Name.Defn.Nbody) + if d.transitive && n.Class == ir.PFUNC { + d.inspectList(n.Defn.(*ir.Func).Body) } } @@ -330,13 +340,15 @@ func (d *initDeps) foundDep(n *Node) { // an OAS node's Pos may not be unique. For example, given the // declaration "var a, b = f(), g()", "a" must be ordered before "b", // but both OAS nodes use the "=" token's position as their Pos. -type declOrder []*Node +type declOrder []ir.Node -func (s declOrder) Len() int { return len(s) } -func (s declOrder) Less(i, j int) bool { return firstLHS(s[i]).Pos.Before(firstLHS(s[j]).Pos) } -func (s declOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s declOrder) Len() int { return len(s) } +func (s declOrder) Less(i, j int) bool { + return firstLHS(s[i]).Pos().Before(firstLHS(s[j]).Pos()) +} +func (s declOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s *declOrder) Push(x interface{}) { *s = append(*s, x.(*Node)) } +func (s *declOrder) Push(x interface{}) { *s = append(*s, x.(ir.Node)) } func (s *declOrder) Pop() interface{} { n := (*s)[len(*s)-1] *s = (*s)[:len(*s)-1] @@ -345,14 +357,16 @@ func (s *declOrder) Pop() interface{} { // firstLHS returns the first expression on the left-hand side of // assignment n. -func firstLHS(n *Node) *Node { - switch n.Op { - case OAS: - return n.Left - case OAS2DOTTYPE, OAS2FUNC, OAS2RECV, OAS2MAPR: - return n.List.First() +func firstLHS(n ir.Node) *ir.Name { + switch n.Op() { + case ir.OAS: + n := n.(*ir.AssignStmt) + return n.X.Name() + case ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2RECV, ir.OAS2MAPR: + n := n.(*ir.AssignListStmt) + return n.Lhs[0].Name() } - Fatalf("unexpected Op: %v", n.Op) + base.Fatalf("unexpected Op: %v", n.Op()) return nil } diff --git a/src/cmd/compile/internal/ppc64/galign.go b/src/cmd/compile/internal/ppc64/galign.go index c8ef567dc3fd0ca94e8c70442913273fba34b2ac..590290fa371008db507ade8df55c54b4bc160f69 100644 --- a/src/cmd/compile/internal/ppc64/galign.go +++ b/src/cmd/compile/internal/ppc64/galign.go @@ -5,14 +5,14 @@ package ppc64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/ssagen" "cmd/internal/obj/ppc64" - "cmd/internal/objabi" + "internal/buildcfg" ) -func Init(arch *gc.Arch) { +func Init(arch *ssagen.ArchInfo) { arch.LinkArch = &ppc64.Linkppc64 - if objabi.GOARCH == "ppc64le" { + if buildcfg.GOARCH == "ppc64le" { arch.LinkArch = &ppc64.Linkppc64le } arch.REGSP = ppc64.REGSP diff --git a/src/cmd/compile/internal/ppc64/ggen.go b/src/cmd/compile/internal/ppc64/ggen.go index a5a772b49179e4b5b934a4842cb88d2d1243af6e..c76962cfb811334d577ffc49b427533f0a8517b8 100644 --- a/src/cmd/compile/internal/ppc64/ggen.go +++ b/src/cmd/compile/internal/ppc64/ggen.go @@ -5,44 +5,47 @@ package ppc64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/ppc64" ) -func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { +func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { if cnt == 0 { return p } - if cnt < int64(4*gc.Widthptr) { - for i := int64(0); i < cnt; i += int64(gc.Widthptr) { - p = pp.Appendpp(p, ppc64.AMOVD, obj.TYPE_REG, ppc64.REGZERO, 0, obj.TYPE_MEM, ppc64.REGSP, gc.Ctxt.FixedFrameSize()+off+i) + if cnt < int64(4*types.PtrSize) { + for i := int64(0); i < cnt; i += int64(types.PtrSize) { + p = pp.Append(p, ppc64.AMOVD, obj.TYPE_REG, ppc64.REGZERO, 0, obj.TYPE_MEM, ppc64.REGSP, base.Ctxt.FixedFrameSize()+off+i) } - } else if cnt <= int64(128*gc.Widthptr) { - p = pp.Appendpp(p, ppc64.AADD, obj.TYPE_CONST, 0, gc.Ctxt.FixedFrameSize()+off-8, obj.TYPE_REG, ppc64.REGRT1, 0) + } else if cnt <= int64(128*types.PtrSize) { + p = pp.Append(p, ppc64.AADD, obj.TYPE_CONST, 0, base.Ctxt.FixedFrameSize()+off-8, obj.TYPE_REG, ppc64.REGRT1, 0) p.Reg = ppc64.REGSP - p = pp.Appendpp(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) + p = pp.Append(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffzero - p.To.Offset = 4 * (128 - cnt/int64(gc.Widthptr)) + p.To.Sym = ir.Syms.Duffzero + p.To.Offset = 4 * (128 - cnt/int64(types.PtrSize)) } else { - p = pp.Appendpp(p, ppc64.AMOVD, obj.TYPE_CONST, 0, gc.Ctxt.FixedFrameSize()+off-8, obj.TYPE_REG, ppc64.REGTMP, 0) - p = pp.Appendpp(p, ppc64.AADD, obj.TYPE_REG, ppc64.REGTMP, 0, obj.TYPE_REG, ppc64.REGRT1, 0) + p = pp.Append(p, ppc64.AMOVD, obj.TYPE_CONST, 0, base.Ctxt.FixedFrameSize()+off-8, obj.TYPE_REG, ppc64.REGTMP, 0) + p = pp.Append(p, ppc64.AADD, obj.TYPE_REG, ppc64.REGTMP, 0, obj.TYPE_REG, ppc64.REGRT1, 0) p.Reg = ppc64.REGSP - p = pp.Appendpp(p, ppc64.AMOVD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, ppc64.REGTMP, 0) - p = pp.Appendpp(p, ppc64.AADD, obj.TYPE_REG, ppc64.REGTMP, 0, obj.TYPE_REG, ppc64.REGRT2, 0) + p = pp.Append(p, ppc64.AMOVD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, ppc64.REGTMP, 0) + p = pp.Append(p, ppc64.AADD, obj.TYPE_REG, ppc64.REGTMP, 0, obj.TYPE_REG, ppc64.REGRT2, 0) p.Reg = ppc64.REGRT1 - p = pp.Appendpp(p, ppc64.AMOVDU, obj.TYPE_REG, ppc64.REGZERO, 0, obj.TYPE_MEM, ppc64.REGRT1, int64(gc.Widthptr)) + p = pp.Append(p, ppc64.AMOVDU, obj.TYPE_REG, ppc64.REGZERO, 0, obj.TYPE_MEM, ppc64.REGRT1, int64(types.PtrSize)) p1 := p - p = pp.Appendpp(p, ppc64.ACMP, obj.TYPE_REG, ppc64.REGRT1, 0, obj.TYPE_REG, ppc64.REGRT2, 0) - p = pp.Appendpp(p, ppc64.ABNE, obj.TYPE_NONE, 0, 0, obj.TYPE_BRANCH, 0, 0) - gc.Patch(p, p1) + p = pp.Append(p, ppc64.ACMP, obj.TYPE_REG, ppc64.REGRT1, 0, obj.TYPE_REG, ppc64.REGRT2, 0) + p = pp.Append(p, ppc64.ABNE, obj.TYPE_NONE, 0, 0, obj.TYPE_BRANCH, 0, 0) + p.To.SetTarget(p1) } return p } -func ginsnop(pp *gc.Progs) *obj.Prog { +func ginsnop(pp *objw.Progs) *obj.Prog { p := pp.Prog(ppc64.AOR) p.From.Type = obj.TYPE_REG p.From.Reg = ppc64.REG_R0 @@ -51,7 +54,7 @@ func ginsnop(pp *gc.Progs) *obj.Prog { return p } -func ginsnopdefer(pp *gc.Progs) *obj.Prog { +func ginsnopdefer(pp *objw.Progs) *obj.Prog { // On PPC64 two nops are required in the defer case. // // (see gc/cgen.go, gc/plive.go -- copy of comment below) @@ -66,7 +69,7 @@ func ginsnopdefer(pp *gc.Progs) *obj.Prog { // on ppc64 in both shared and non-shared modes. ginsnop(pp) - if gc.Ctxt.Flag_shared { + if base.Ctxt.Flag_shared { p := pp.Prog(ppc64.AMOVD) p.From.Type = obj.TYPE_MEM p.From.Offset = 24 diff --git a/src/cmd/compile/internal/ppc64/ssa.go b/src/cmd/compile/internal/ppc64/ssa.go index 3e20c44a4c72b61d9514ec4b977c43d7c6784544..11226f65a0f01639359fe2992e2f266b2301be20 100644 --- a/src/cmd/compile/internal/ppc64/ssa.go +++ b/src/cmd/compile/internal/ppc64/ssa.go @@ -5,19 +5,21 @@ package ppc64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/logopt" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/ppc64" - "cmd/internal/objabi" + "internal/buildcfg" "math" "strings" ) // markMoves marks any MOVXconst ops that need to avoid clobbering flags. -func ssaMarkMoves(s *gc.SSAGenState, b *ssa.Block) { +func ssaMarkMoves(s *ssagen.State, b *ssa.Block) { // flive := b.FlagsLiveAtEnd // if b.Control != nil && b.Control.Type.IsFlags() { // flive = true @@ -99,7 +101,7 @@ func storeByType(t *types.Type) obj.As { panic("bad store type") } -func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { +func ssaGenValue(s *ssagen.State, v *ssa.Value) { switch v.Op { case ssa.OpCopy: t := v.Type @@ -208,7 +210,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // BNE retry p3 := s.Prog(ppc64.ABNE) p3.To.Type = obj.TYPE_BRANCH - gc.Patch(p3, p) + p3.To.SetTarget(p) case ssa.OpPPC64LoweredAtomicAdd32, ssa.OpPPC64LoweredAtomicAdd64: @@ -252,7 +254,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // BNE retry p4 := s.Prog(ppc64.ABNE) p4.To.Type = obj.TYPE_BRANCH - gc.Patch(p4, p) + p4.To.SetTarget(p) // Ensure a 32 bit result if v.Op == ssa.OpPPC64LoweredAtomicAdd32 { @@ -298,7 +300,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // BNE retry p2 := s.Prog(ppc64.ABNE) p2.To.Type = obj.TYPE_BRANCH - gc.Patch(p2, p) + p2.To.SetTarget(p) // ISYNC pisync := s.Prog(ppc64.AISYNC) pisync.To.Type = obj.TYPE_NONE @@ -346,7 +348,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // ISYNC pisync := s.Prog(ppc64.AISYNC) pisync.To.Type = obj.TYPE_NONE - gc.Patch(p2, pisync) + p2.To.SetTarget(pisync) case ssa.OpPPC64LoweredAtomicStore8, ssa.OpPPC64LoweredAtomicStore32, @@ -417,7 +419,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // If it is a Compare-and-Swap-Release operation, set the EH field with // the release hint. if v.AuxInt == 0 { - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: 0}) + p.SetFrom3Const(0) } // CMP reg1,reg2 p1 := s.Prog(cmp) @@ -437,7 +439,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // BNE retry p4 := s.Prog(ppc64.ABNE) p4.To.Type = obj.TYPE_BRANCH - gc.Patch(p4, p) + p4.To.SetTarget(p) // LWSYNC - Assuming shared data not write-through-required nor // caching-inhibited. See Appendix B.2.1.1 in the ISA 2.07b. // If the operation is a CAS-Release, then synchronization is not necessary. @@ -460,20 +462,20 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p7.From.Offset = 0 p7.To.Type = obj.TYPE_REG p7.To.Reg = out - gc.Patch(p2, p7) + p2.To.SetTarget(p7) // done (label) p8 := s.Prog(obj.ANOP) - gc.Patch(p6, p8) + p6.To.SetTarget(p8) case ssa.OpPPC64LoweredGetClosurePtr: // Closure pointer is R11 (already) - gc.CheckLoweredGetClosurePtr(v) + ssagen.CheckLoweredGetClosurePtr(v) case ssa.OpPPC64LoweredGetCallerSP: // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(ppc64.AMOVD) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -gc.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.FixedFrameSize() p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -489,7 +491,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.OpLoadReg: loadOp := loadByType(v.Type) p := s.Prog(loadOp) - gc.AddrAuto(&p.From, v.Args[0]) + ssagen.AddrAuto(&p.From, v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -498,7 +500,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(storeOp) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() - gc.AddrAuto(&p.To, v) + ssagen.AddrAuto(&p.To, v) case ssa.OpPPC64DIVD: // For now, @@ -537,10 +539,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Reg = r p.From.Type = obj.TYPE_REG p.From.Reg = r0 - gc.Patch(pbahead, p) + pbahead.To.SetTarget(p) p = s.Prog(obj.ANOP) - gc.Patch(pbover, p) + pbover.To.SetTarget(p) case ssa.OpPPC64DIVW: // word-width version of above @@ -572,10 +574,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Reg = r p.From.Type = obj.TYPE_REG p.From.Reg = r0 - gc.Patch(pbahead, p) + pbahead.To.SetTarget(p) p = s.Prog(obj.ANOP) - gc.Patch(pbover, p) + pbover.To.SetTarget(p) case ssa.OpPPC64CLRLSLWI: r := v.Reg() @@ -584,7 +586,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) // clrlslwi ra,rs,mb,sh will become rlwinm ra,rs,sh,mb-sh,31-sh as described in ISA p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: ssa.GetPPC64Shiftmb(shifts)} - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: ssa.GetPPC64Shiftsh(shifts)}) + p.SetFrom3Const(ssa.GetPPC64Shiftsh(shifts)) p.Reg = r1 p.To.Type = obj.TYPE_REG p.To.Reg = r @@ -596,7 +598,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) // clrlsldi ra,rs,mb,sh will become rldic ra,rs,sh,mb-sh p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: ssa.GetPPC64Shiftmb(shifts)} - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: ssa.GetPPC64Shiftsh(shifts)}) + p.SetFrom3Const(ssa.GetPPC64Shiftsh(shifts)) p.Reg = r1 p.To.Type = obj.TYPE_REG p.To.Reg = r @@ -608,7 +610,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { shifts := v.AuxInt p := s.Prog(v.Op.Asm()) p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: ssa.GetPPC64Shiftsh(shifts)} - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: ssa.GetPPC64Shiftmb(shifts)}) + p.SetFrom3Const(ssa.GetPPC64Shiftmb(shifts)) p.Reg = r1 p.To.Type = obj.TYPE_REG p.To.Reg = r @@ -651,21 +653,21 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // Auxint holds encoded rotate + mask case ssa.OpPPC64RLWINM, ssa.OpPPC64RLWMI: - rot, _, _, mask := ssa.DecodePPC64RotateMask(v.AuxInt) + rot, mb, me, _ := ssa.DecodePPC64RotateMask(v.AuxInt) p := s.Prog(v.Op.Asm()) p.To = obj.Addr{Type: obj.TYPE_REG, Reg: v.Reg()} p.Reg = v.Args[0].Reg() p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: int64(rot)} - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: int64(mask)}) + p.SetRestArgs([]obj.Addr{{Type: obj.TYPE_CONST, Offset: mb}, {Type: obj.TYPE_CONST, Offset: me}}) // Auxint holds mask case ssa.OpPPC64RLWNM: - _, _, _, mask := ssa.DecodePPC64RotateMask(v.AuxInt) + _, mb, me, _ := ssa.DecodePPC64RotateMask(v.AuxInt) p := s.Prog(v.Op.Asm()) p.To = obj.Addr{Type: obj.TYPE_REG, Reg: v.Reg()} p.Reg = v.Args[0].Reg() p.From = obj.Addr{Type: obj.TYPE_REG, Reg: v.Args[1].Reg()} - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: int64(mask)}) + p.SetRestArgs([]obj.Addr{{Type: obj.TYPE_CONST, Offset: mb}, {Type: obj.TYPE_CONST, Offset: me}}) case ssa.OpPPC64MADDLD: r := v.Reg() @@ -677,7 +679,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Type = obj.TYPE_REG p.From.Reg = r1 p.Reg = r2 - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: r3}) + p.SetFrom3Reg(r3) p.To.Type = obj.TYPE_REG p.To.Reg = r @@ -691,7 +693,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Type = obj.TYPE_REG p.From.Reg = r1 p.Reg = r3 - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: r2}) + p.SetFrom3Reg(r2) p.To.Type = obj.TYPE_REG p.To.Reg = r @@ -718,7 +720,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.OpPPC64SUBFCconst: p := s.Prog(v.Op.Asm()) - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}) + p.SetFrom3Const(v.AuxInt) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() p.To.Type = obj.TYPE_REG @@ -750,13 +752,13 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Reg = v.Reg() } - case *obj.LSym, *gc.Node: + case *obj.LSym, ir.Node: p := s.Prog(ppc64.AMOVD) p.From.Type = obj.TYPE_ADDR p.From.Reg = v.Args[0].Reg() p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) } @@ -796,46 +798,67 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Reg = v.Reg() p.To.Type = obj.TYPE_REG - case ssa.OpPPC64MOVDload: + case ssa.OpPPC64MOVDload, ssa.OpPPC64MOVWload: - // MOVDload uses a DS instruction which requires the offset value of the data to be a multiple of 4. - // For offsets known at compile time, a MOVDload won't be selected, but in the case of a go.string, - // the offset is not known until link time. If the load of a go.string uses relocation for the - // offset field of the instruction, and if the offset is not aligned to 4, then a link error will occur. - // To avoid this problem, the full address of the go.string is computed and loaded into the base register, - // and that base register is used for the MOVDload using a 0 offset. This problem can only occur with - // go.string types because other types will have proper alignment. + // MOVDload and MOVWload are DS form instructions that are restricted to + // offsets that are a multiple of 4. If the offset is not a multple of 4, + // then the address of the symbol to be loaded is computed (base + offset) + // and used as the new base register and the offset field in the instruction + // can be set to zero. - gostring := false - switch n := v.Aux.(type) { - case *obj.LSym: - gostring = strings.HasPrefix(n.Name, "go.string.") + // This same problem can happen with gostrings since the final offset is not + // known yet, but could be unaligned after the relocation is resolved. + // So gostrings are handled the same way. + + // This allows the MOVDload and MOVWload to be generated in more cases and + // eliminates some offset and alignment checking in the rules file. + + fromAddr := obj.Addr{Type: obj.TYPE_MEM, Reg: v.Args[0].Reg()} + ssagen.AddAux(&fromAddr, v) + + genAddr := false + + switch fromAddr.Name { + case obj.NAME_EXTERN, obj.NAME_STATIC: + // Special case for a rule combines the bytes of gostring. + // The v alignment might seem OK, but we don't want to load it + // using an offset because relocation comes later. + genAddr = strings.HasPrefix(fromAddr.Sym.Name, "go.string") || v.Type.Alignment()%4 != 0 || fromAddr.Offset%4 != 0 + default: + genAddr = fromAddr.Offset%4 != 0 } - if gostring { - // Generate full addr of the go.string const - // including AuxInt + if genAddr { + // Load full address into the temp register. p := s.Prog(ppc64.AMOVD) p.From.Type = obj.TYPE_ADDR p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) - p.To.Type = obj.TYPE_REG - p.To.Reg = v.Reg() - // Load go.string using 0 offset - p = s.Prog(v.Op.Asm()) - p.From.Type = obj.TYPE_MEM - p.From.Reg = v.Reg() + ssagen.AddAux(&p.From, v) + // Load target using temp as base register + // and offset zero. Setting NAME_NONE + // prevents any extra offsets from being + // added. p.To.Type = obj.TYPE_REG - p.To.Reg = v.Reg() - break + p.To.Reg = ppc64.REGTMP + fromAddr.Reg = ppc64.REGTMP + // Clear the offset field and other + // information that might be used + // by the assembler to add to the + // final offset value. + fromAddr.Offset = 0 + fromAddr.Name = obj.NAME_NONE + fromAddr.Sym = nil } - // Not a go.string, generate a normal load - fallthrough + p := s.Prog(v.Op.Asm()) + p.From = fromAddr + p.To.Type = obj.TYPE_REG + p.To.Reg = v.Reg() + break - case ssa.OpPPC64MOVWload, ssa.OpPPC64MOVHload, ssa.OpPPC64MOVWZload, ssa.OpPPC64MOVBZload, ssa.OpPPC64MOVHZload, ssa.OpPPC64FMOVDload, ssa.OpPPC64FMOVSload: + case ssa.OpPPC64MOVHload, ssa.OpPPC64MOVWZload, ssa.OpPPC64MOVBZload, ssa.OpPPC64MOVHZload, ssa.OpPPC64FMOVDload, ssa.OpPPC64FMOVSload: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -863,21 +886,60 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() - case ssa.OpPPC64MOVDstorezero, ssa.OpPPC64MOVWstorezero, ssa.OpPPC64MOVHstorezero, ssa.OpPPC64MOVBstorezero: + case ssa.OpPPC64MOVWstorezero, ssa.OpPPC64MOVHstorezero, ssa.OpPPC64MOVBstorezero: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG p.From.Reg = ppc64.REGZERO p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) + + case ssa.OpPPC64MOVDstore, ssa.OpPPC64MOVDstorezero: + + // MOVDstore and MOVDstorezero become DS form instructions that are restricted + // to offset values that are a multple of 4. If the offset field is not a + // multiple of 4, then the full address of the store target is computed (base + + // offset) and used as the new base register and the offset in the instruction + // is set to 0. - case ssa.OpPPC64MOVDstore, ssa.OpPPC64MOVWstore, ssa.OpPPC64MOVHstore, ssa.OpPPC64MOVBstore, ssa.OpPPC64FMOVDstore, ssa.OpPPC64FMOVSstore: + // This allows the MOVDstore and MOVDstorezero to be generated in more cases, + // and prevents checking of the offset value and alignment in the rules. + + toAddr := obj.Addr{Type: obj.TYPE_MEM, Reg: v.Args[0].Reg()} + ssagen.AddAux(&toAddr, v) + + if toAddr.Offset%4 != 0 { + p := s.Prog(ppc64.AMOVD) + p.From.Type = obj.TYPE_ADDR + p.From.Reg = v.Args[0].Reg() + ssagen.AddAux(&p.From, v) + p.To.Type = obj.TYPE_REG + p.To.Reg = ppc64.REGTMP + toAddr.Reg = ppc64.REGTMP + // Clear the offset field and other + // information that might be used + // by the assembler to add to the + // final offset value. + toAddr.Offset = 0 + toAddr.Name = obj.NAME_NONE + toAddr.Sym = nil + } + p := s.Prog(v.Op.Asm()) + p.To = toAddr + p.From.Type = obj.TYPE_REG + if v.Op == ssa.OpPPC64MOVDstorezero { + p.From.Reg = ppc64.REGZERO + } else { + p.From.Reg = v.Args[1].Reg() + } + + case ssa.OpPPC64MOVWstore, ssa.OpPPC64MOVHstore, ssa.OpPPC64MOVBstore, ssa.OpPPC64FMOVDstore, ssa.OpPPC64FMOVSstore: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpPPC64MOVDstoreidx, ssa.OpPPC64MOVWstoreidx, ssa.OpPPC64MOVHstoreidx, ssa.OpPPC64MOVBstoreidx, ssa.OpPPC64FMOVDstoreidx, ssa.OpPPC64FMOVSstoreidx, ssa.OpPPC64MOVDBRstoreidx, ssa.OpPPC64MOVWBRstoreidx, @@ -908,7 +970,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // AuxInt values 4,5,6 implemented with reverse operand order from 0,1,2 if v.AuxInt > 3 { p.Reg = r.Reg - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: v.Args[0].Reg()}) + p.SetFrom3Reg(v.Args[0].Reg()) } else { p.Reg = v.Args[0].Reg() p.SetFrom3(r) @@ -1026,7 +1088,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Offset = ppc64.BO_BCTR p.Reg = ppc64.REG_R0 p.To.Type = obj.TYPE_BRANCH - gc.Patch(p, top) + p.To.SetTarget(top) } // When ctr == 1 the loop was not generated but // there are at least 64 bytes to clear, so add @@ -1226,7 +1288,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Offset = ppc64.BO_BCTR p.Reg = ppc64.REG_R0 p.To.Type = obj.TYPE_BRANCH - gc.Patch(p, top) + p.To.SetTarget(top) } // when ctr == 1 the loop was not generated but @@ -1405,7 +1467,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Offset = ppc64.BO_BCTR p.Reg = ppc64.REG_R0 p.To.Type = obj.TYPE_BRANCH - gc.Patch(p, top) + p.To.SetTarget(top) // srcReg and dstReg were incremented in the loop, so // later instructions start with offset 0. @@ -1474,7 +1536,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case rem >= 8: op, size = ppc64.AMOVD, 8 case rem >= 4: - op, size = ppc64.AMOVW, 4 + op, size = ppc64.AMOVWZ, 4 case rem >= 2: op, size = ppc64.AMOVH, 2 } @@ -1652,7 +1714,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Offset = ppc64.BO_BCTR p.Reg = ppc64.REG_R0 p.To.Type = obj.TYPE_BRANCH - gc.Patch(p, top) + p.To.SetTarget(top) // srcReg and dstReg were incremented in the loop, so // later instructions start with offset 0. @@ -1741,7 +1803,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case rem >= 8: op, size = ppc64.AMOVD, 8 case rem >= 4: - op, size = ppc64.AMOVW, 4 + op, size = ppc64.AMOVWZ, 4 case rem >= 2: op, size = ppc64.AMOVH, 2 } @@ -1782,9 +1844,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { pp.To.Reg = ppc64.REG_LR // Insert a hint this is not a subroutine return. - pp.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: 1}) + pp.SetFrom3Const(1) - if gc.Ctxt.Flag_shared { + if base.Ctxt.Flag_shared { // When compiling Go into PIC, the function we just // called via pointer might have been implemented in // a separate module and so overwritten the TOC @@ -1807,11 +1869,11 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.BoundsCheckFunc[v.AuxInt] + p.To.Sym = ssagen.BoundsCheckFunc[v.AuxInt] s.UseArgs(16) // space used in callee args area by assembly stubs case ssa.OpPPC64LoweredNilCheck: - if objabi.GOOS == "aix" { + if buildcfg.GOOS == "aix" { // CMP Rarg0, R0 // BNE 2(PC) // STW R0, 0(R0) @@ -1838,22 +1900,22 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // NOP (so the BNE has somewhere to land) nop := s.Prog(obj.ANOP) - gc.Patch(p2, nop) + p2.To.SetTarget(nop) } else { // Issue a load which will fault if arg is nil. p := s.Prog(ppc64.AMOVBZ) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = ppc64.REGTMP } if logopt.Enabled() { logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name) } - if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers - gc.Warnl(v.Pos, "generated nil check") + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") } // These should be resolved by rules and not make it here. @@ -1865,7 +1927,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { v.Fatalf("InvertFlags should never make it to codegen %v", v.LongString()) case ssa.OpPPC64FlagEQ, ssa.OpPPC64FlagLT, ssa.OpPPC64FlagGT: v.Fatalf("Flag* ops should never make it to codegen %v", v.LongString()) - case ssa.OpClobber: + case ssa.OpClobber, ssa.OpClobberReg: // TODO: implement for clobberdead experiment. Nop is ok for now. default: v.Fatalf("genValue not implemented: %s", v.LongString()) @@ -1891,7 +1953,7 @@ var blockJump = [...]struct { ssa.BlockPPC64FGT: {ppc64.ABGT, ppc64.ABLE, false, false}, } -func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { switch b.Kind { case ssa.BlockDefer: // defer returns in R3: @@ -1905,18 +1967,18 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p = s.Prog(ppc64.ABNE) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[1].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()}) if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockPlain: if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockExit: case ssa.BlockRet: diff --git a/src/cmd/compile/internal/reflectdata/alg.go b/src/cmd/compile/internal/reflectdata/alg.go new file mode 100644 index 0000000000000000000000000000000000000000..0707e0b61caf4861033d424acc525d028a2bf484 --- /dev/null +++ b/src/cmd/compile/internal/reflectdata/alg.go @@ -0,0 +1,810 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflectdata + +import ( + "fmt" + "math/bits" + "sort" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" +) + +// isRegularMemory reports whether t can be compared/hashed as regular memory. +func isRegularMemory(t *types.Type) bool { + a, _ := types.AlgType(t) + return a == types.AMEM +} + +// eqCanPanic reports whether == on type t could panic (has an interface somewhere). +// t must be comparable. +func eqCanPanic(t *types.Type) bool { + switch t.Kind() { + default: + return false + case types.TINTER: + return true + case types.TARRAY: + return eqCanPanic(t.Elem()) + case types.TSTRUCT: + for _, f := range t.FieldSlice() { + if !f.Sym.IsBlank() && eqCanPanic(f.Type) { + return true + } + } + return false + } +} + +// AlgType returns the fixed-width AMEMxx variants instead of the general +// AMEM kind when possible. +func AlgType(t *types.Type) types.AlgKind { + a, _ := types.AlgType(t) + if a == types.AMEM { + if t.Alignment() < int64(base.Ctxt.Arch.Alignment) && t.Alignment() < t.Width { + // For example, we can't treat [2]int16 as an int32 if int32s require + // 4-byte alignment. See issue 46283. + return a + } + switch t.Width { + case 0: + return types.AMEM0 + case 1: + return types.AMEM8 + case 2: + return types.AMEM16 + case 4: + return types.AMEM32 + case 8: + return types.AMEM64 + case 16: + return types.AMEM128 + } + } + + return a +} + +// genhash returns a symbol which is the closure used to compute +// the hash of a value of type t. +// Note: the generated function must match runtime.typehash exactly. +func genhash(t *types.Type) *obj.LSym { + switch AlgType(t) { + default: + // genhash is only called for types that have equality + base.Fatalf("genhash %v", t) + case types.AMEM0: + return sysClosure("memhash0") + case types.AMEM8: + return sysClosure("memhash8") + case types.AMEM16: + return sysClosure("memhash16") + case types.AMEM32: + return sysClosure("memhash32") + case types.AMEM64: + return sysClosure("memhash64") + case types.AMEM128: + return sysClosure("memhash128") + case types.ASTRING: + return sysClosure("strhash") + case types.AINTER: + return sysClosure("interhash") + case types.ANILINTER: + return sysClosure("nilinterhash") + case types.AFLOAT32: + return sysClosure("f32hash") + case types.AFLOAT64: + return sysClosure("f64hash") + case types.ACPLX64: + return sysClosure("c64hash") + case types.ACPLX128: + return sysClosure("c128hash") + case types.AMEM: + // For other sizes of plain memory, we build a closure + // that calls memhash_varlen. The size of the memory is + // encoded in the first slot of the closure. + closure := TypeLinksymLookup(fmt.Sprintf(".hashfunc%d", t.Width)) + if len(closure.P) > 0 { // already generated + return closure + } + if memhashvarlen == nil { + memhashvarlen = typecheck.LookupRuntimeFunc("memhash_varlen") + } + ot := 0 + ot = objw.SymPtr(closure, ot, memhashvarlen, 0) + ot = objw.Uintptr(closure, ot, uint64(t.Width)) // size encoded in closure + objw.Global(closure, int32(ot), obj.DUPOK|obj.RODATA) + return closure + case types.ASPECIAL: + break + } + + closure := TypeLinksymPrefix(".hashfunc", t) + if len(closure.P) > 0 { // already generated + return closure + } + + // Generate hash functions for subtypes. + // There are cases where we might not use these hashes, + // but in that case they will get dead-code eliminated. + // (And the closure generated by genhash will also get + // dead-code eliminated, as we call the subtype hashers + // directly.) + switch t.Kind() { + case types.TARRAY: + genhash(t.Elem()) + case types.TSTRUCT: + for _, f := range t.FieldSlice() { + genhash(f.Type) + } + } + + sym := TypeSymPrefix(".hash", t) + if base.Flag.LowerR != 0 { + fmt.Printf("genhash %v %v %v\n", closure, sym, t) + } + + base.Pos = base.AutogeneratedPos // less confusing than end of input + typecheck.DeclContext = ir.PEXTERN + + // func sym(p *T, h uintptr) uintptr + args := []*ir.Field{ + ir.NewField(base.Pos, typecheck.Lookup("p"), nil, types.NewPtr(t)), + ir.NewField(base.Pos, typecheck.Lookup("h"), nil, types.Types[types.TUINTPTR]), + } + results := []*ir.Field{ir.NewField(base.Pos, nil, nil, types.Types[types.TUINTPTR])} + tfn := ir.NewFuncType(base.Pos, nil, args, results) + + fn := typecheck.DeclFunc(sym, tfn) + np := ir.AsNode(tfn.Type().Params().Field(0).Nname) + nh := ir.AsNode(tfn.Type().Params().Field(1).Nname) + + switch t.Kind() { + case types.TARRAY: + // An array of pure memory would be handled by the + // standard algorithm, so the element type must not be + // pure memory. + hashel := hashfor(t.Elem()) + + // for i := 0; i < nelem; i++ + ni := typecheck.Temp(types.Types[types.TINT]) + init := ir.NewAssignStmt(base.Pos, ni, ir.NewInt(0)) + cond := ir.NewBinaryExpr(base.Pos, ir.OLT, ni, ir.NewInt(t.NumElem())) + post := ir.NewAssignStmt(base.Pos, ni, ir.NewBinaryExpr(base.Pos, ir.OADD, ni, ir.NewInt(1))) + loop := ir.NewForStmt(base.Pos, nil, cond, post, nil) + loop.PtrInit().Append(init) + + // h = hashel(&p[i], h) + call := ir.NewCallExpr(base.Pos, ir.OCALL, hashel, nil) + + nx := ir.NewIndexExpr(base.Pos, np, ni) + nx.SetBounded(true) + na := typecheck.NodAddr(nx) + call.Args.Append(na) + call.Args.Append(nh) + loop.Body.Append(ir.NewAssignStmt(base.Pos, nh, call)) + + fn.Body.Append(loop) + + case types.TSTRUCT: + // Walk the struct using memhash for runs of AMEM + // and calling specific hash functions for the others. + for i, fields := 0, t.FieldSlice(); i < len(fields); { + f := fields[i] + + // Skip blank fields. + if f.Sym.IsBlank() { + i++ + continue + } + + // Hash non-memory fields with appropriate hash function. + if !isRegularMemory(f.Type) { + hashel := hashfor(f.Type) + call := ir.NewCallExpr(base.Pos, ir.OCALL, hashel, nil) + nx := ir.NewSelectorExpr(base.Pos, ir.OXDOT, np, f.Sym) // TODO: fields from other packages? + na := typecheck.NodAddr(nx) + call.Args.Append(na) + call.Args.Append(nh) + fn.Body.Append(ir.NewAssignStmt(base.Pos, nh, call)) + i++ + continue + } + + // Otherwise, hash a maximal length run of raw memory. + size, next := memrun(t, i) + + // h = hashel(&p.first, size, h) + hashel := hashmem(f.Type) + call := ir.NewCallExpr(base.Pos, ir.OCALL, hashel, nil) + nx := ir.NewSelectorExpr(base.Pos, ir.OXDOT, np, f.Sym) // TODO: fields from other packages? + na := typecheck.NodAddr(nx) + call.Args.Append(na) + call.Args.Append(nh) + call.Args.Append(ir.NewInt(size)) + fn.Body.Append(ir.NewAssignStmt(base.Pos, nh, call)) + + i = next + } + } + + r := ir.NewReturnStmt(base.Pos, nil) + r.Results.Append(nh) + fn.Body.Append(r) + + if base.Flag.LowerR != 0 { + ir.DumpList("genhash body", fn.Body) + } + + typecheck.FinishFuncBody() + + fn.SetDupok(true) + typecheck.Func(fn) + + ir.CurFunc = fn + typecheck.Stmts(fn.Body) + ir.CurFunc = nil + + if base.Debug.DclStack != 0 { + types.CheckDclstack() + } + + fn.SetNilCheckDisabled(true) + typecheck.Target.Decls = append(typecheck.Target.Decls, fn) + + // Build closure. It doesn't close over any variables, so + // it contains just the function pointer. + objw.SymPtr(closure, 0, fn.Linksym(), 0) + objw.Global(closure, int32(types.PtrSize), obj.DUPOK|obj.RODATA) + + return closure +} + +func hashfor(t *types.Type) ir.Node { + var sym *types.Sym + + switch a, _ := types.AlgType(t); a { + case types.AMEM: + base.Fatalf("hashfor with AMEM type") + case types.AINTER: + sym = ir.Pkgs.Runtime.Lookup("interhash") + case types.ANILINTER: + sym = ir.Pkgs.Runtime.Lookup("nilinterhash") + case types.ASTRING: + sym = ir.Pkgs.Runtime.Lookup("strhash") + case types.AFLOAT32: + sym = ir.Pkgs.Runtime.Lookup("f32hash") + case types.AFLOAT64: + sym = ir.Pkgs.Runtime.Lookup("f64hash") + case types.ACPLX64: + sym = ir.Pkgs.Runtime.Lookup("c64hash") + case types.ACPLX128: + sym = ir.Pkgs.Runtime.Lookup("c128hash") + default: + // Note: the caller of hashfor ensured that this symbol + // exists and has a body by calling genhash for t. + sym = TypeSymPrefix(".hash", t) + } + + // TODO(austin): This creates an ir.Name with a nil Func. + n := typecheck.NewName(sym) + ir.MarkFunc(n) + n.SetType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{ + types.NewField(base.Pos, nil, types.NewPtr(t)), + types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]), + }, []*types.Field{ + types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]), + })) + return n +} + +// sysClosure returns a closure which will call the +// given runtime function (with no closed-over variables). +func sysClosure(name string) *obj.LSym { + s := typecheck.LookupRuntimeVar(name + "·f") + if len(s.P) == 0 { + f := typecheck.LookupRuntimeFunc(name) + objw.SymPtr(s, 0, f, 0) + objw.Global(s, int32(types.PtrSize), obj.DUPOK|obj.RODATA) + } + return s +} + +// geneq returns a symbol which is the closure used to compute +// equality for two objects of type t. +func geneq(t *types.Type) *obj.LSym { + switch AlgType(t) { + case types.ANOEQ: + // The runtime will panic if it tries to compare + // a type with a nil equality function. + return nil + case types.AMEM0: + return sysClosure("memequal0") + case types.AMEM8: + return sysClosure("memequal8") + case types.AMEM16: + return sysClosure("memequal16") + case types.AMEM32: + return sysClosure("memequal32") + case types.AMEM64: + return sysClosure("memequal64") + case types.AMEM128: + return sysClosure("memequal128") + case types.ASTRING: + return sysClosure("strequal") + case types.AINTER: + return sysClosure("interequal") + case types.ANILINTER: + return sysClosure("nilinterequal") + case types.AFLOAT32: + return sysClosure("f32equal") + case types.AFLOAT64: + return sysClosure("f64equal") + case types.ACPLX64: + return sysClosure("c64equal") + case types.ACPLX128: + return sysClosure("c128equal") + case types.AMEM: + // make equality closure. The size of the type + // is encoded in the closure. + closure := TypeLinksymLookup(fmt.Sprintf(".eqfunc%d", t.Width)) + if len(closure.P) != 0 { + return closure + } + if memequalvarlen == nil { + memequalvarlen = typecheck.LookupRuntimeFunc("memequal_varlen") + } + ot := 0 + ot = objw.SymPtr(closure, ot, memequalvarlen, 0) + ot = objw.Uintptr(closure, ot, uint64(t.Width)) + objw.Global(closure, int32(ot), obj.DUPOK|obj.RODATA) + return closure + case types.ASPECIAL: + break + } + + closure := TypeLinksymPrefix(".eqfunc", t) + if len(closure.P) > 0 { // already generated + return closure + } + sym := TypeSymPrefix(".eq", t) + if base.Flag.LowerR != 0 { + fmt.Printf("geneq %v\n", t) + } + + // Autogenerate code for equality of structs and arrays. + + base.Pos = base.AutogeneratedPos // less confusing than end of input + typecheck.DeclContext = ir.PEXTERN + + // func sym(p, q *T) bool + tfn := ir.NewFuncType(base.Pos, nil, + []*ir.Field{ir.NewField(base.Pos, typecheck.Lookup("p"), nil, types.NewPtr(t)), ir.NewField(base.Pos, typecheck.Lookup("q"), nil, types.NewPtr(t))}, + []*ir.Field{ir.NewField(base.Pos, typecheck.Lookup("r"), nil, types.Types[types.TBOOL])}) + + fn := typecheck.DeclFunc(sym, tfn) + np := ir.AsNode(tfn.Type().Params().Field(0).Nname) + nq := ir.AsNode(tfn.Type().Params().Field(1).Nname) + nr := ir.AsNode(tfn.Type().Results().Field(0).Nname) + + // Label to jump to if an equality test fails. + neq := typecheck.AutoLabel(".neq") + + // We reach here only for types that have equality but + // cannot be handled by the standard algorithms, + // so t must be either an array or a struct. + switch t.Kind() { + default: + base.Fatalf("geneq %v", t) + + case types.TARRAY: + nelem := t.NumElem() + + // checkAll generates code to check the equality of all array elements. + // If unroll is greater than nelem, checkAll generates: + // + // if eq(p[0], q[0]) && eq(p[1], q[1]) && ... { + // } else { + // return + // } + // + // And so on. + // + // Otherwise it generates: + // + // for i := 0; i < nelem; i++ { + // if eq(p[i], q[i]) { + // } else { + // goto neq + // } + // } + // + // TODO(josharian): consider doing some loop unrolling + // for larger nelem as well, processing a few elements at a time in a loop. + checkAll := func(unroll int64, last bool, eq func(pi, qi ir.Node) ir.Node) { + // checkIdx generates a node to check for equality at index i. + checkIdx := func(i ir.Node) ir.Node { + // pi := p[i] + pi := ir.NewIndexExpr(base.Pos, np, i) + pi.SetBounded(true) + pi.SetType(t.Elem()) + // qi := q[i] + qi := ir.NewIndexExpr(base.Pos, nq, i) + qi.SetBounded(true) + qi.SetType(t.Elem()) + return eq(pi, qi) + } + + if nelem <= unroll { + if last { + // Do last comparison in a different manner. + nelem-- + } + // Generate a series of checks. + for i := int64(0); i < nelem; i++ { + // if check {} else { goto neq } + nif := ir.NewIfStmt(base.Pos, checkIdx(ir.NewInt(i)), nil, nil) + nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq)) + fn.Body.Append(nif) + } + if last { + fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, checkIdx(ir.NewInt(nelem)))) + } + } else { + // Generate a for loop. + // for i := 0; i < nelem; i++ + i := typecheck.Temp(types.Types[types.TINT]) + init := ir.NewAssignStmt(base.Pos, i, ir.NewInt(0)) + cond := ir.NewBinaryExpr(base.Pos, ir.OLT, i, ir.NewInt(nelem)) + post := ir.NewAssignStmt(base.Pos, i, ir.NewBinaryExpr(base.Pos, ir.OADD, i, ir.NewInt(1))) + loop := ir.NewForStmt(base.Pos, nil, cond, post, nil) + loop.PtrInit().Append(init) + // if eq(pi, qi) {} else { goto neq } + nif := ir.NewIfStmt(base.Pos, checkIdx(i), nil, nil) + nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq)) + loop.Body.Append(nif) + fn.Body.Append(loop) + if last { + fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(true))) + } + } + } + + switch t.Elem().Kind() { + case types.TSTRING: + // Do two loops. First, check that all the lengths match (cheap). + // Second, check that all the contents match (expensive). + // TODO: when the array size is small, unroll the length match checks. + checkAll(3, false, func(pi, qi ir.Node) ir.Node { + // Compare lengths. + eqlen, _ := EqString(pi, qi) + return eqlen + }) + checkAll(1, true, func(pi, qi ir.Node) ir.Node { + // Compare contents. + _, eqmem := EqString(pi, qi) + return eqmem + }) + case types.TFLOAT32, types.TFLOAT64: + checkAll(2, true, func(pi, qi ir.Node) ir.Node { + // p[i] == q[i] + return ir.NewBinaryExpr(base.Pos, ir.OEQ, pi, qi) + }) + // TODO: pick apart structs, do them piecemeal too + default: + checkAll(1, true, func(pi, qi ir.Node) ir.Node { + // p[i] == q[i] + return ir.NewBinaryExpr(base.Pos, ir.OEQ, pi, qi) + }) + } + + case types.TSTRUCT: + // Build a list of conditions to satisfy. + // The conditions are a list-of-lists. Conditions are reorderable + // within each inner list. The outer lists must be evaluated in order. + var conds [][]ir.Node + conds = append(conds, []ir.Node{}) + and := func(n ir.Node) { + i := len(conds) - 1 + conds[i] = append(conds[i], n) + } + + // Walk the struct using memequal for runs of AMEM + // and calling specific equality tests for the others. + for i, fields := 0, t.FieldSlice(); i < len(fields); { + f := fields[i] + + // Skip blank-named fields. + if f.Sym.IsBlank() { + i++ + continue + } + + // Compare non-memory fields with field equality. + if !isRegularMemory(f.Type) { + if eqCanPanic(f.Type) { + // Enforce ordering by starting a new set of reorderable conditions. + conds = append(conds, []ir.Node{}) + } + p := ir.NewSelectorExpr(base.Pos, ir.OXDOT, np, f.Sym) + q := ir.NewSelectorExpr(base.Pos, ir.OXDOT, nq, f.Sym) + switch { + case f.Type.IsString(): + eqlen, eqmem := EqString(p, q) + and(eqlen) + and(eqmem) + default: + and(ir.NewBinaryExpr(base.Pos, ir.OEQ, p, q)) + } + if eqCanPanic(f.Type) { + // Also enforce ordering after something that can panic. + conds = append(conds, []ir.Node{}) + } + i++ + continue + } + + // Find maximal length run of memory-only fields. + size, next := memrun(t, i) + + // TODO(rsc): All the calls to newname are wrong for + // cross-package unexported fields. + if s := fields[i:next]; len(s) <= 2 { + // Two or fewer fields: use plain field equality. + for _, f := range s { + and(eqfield(np, nq, f.Sym)) + } + } else { + // More than two fields: use memequal. + and(eqmem(np, nq, f.Sym, size)) + } + i = next + } + + // Sort conditions to put runtime calls last. + // Preserve the rest of the ordering. + var flatConds []ir.Node + for _, c := range conds { + isCall := func(n ir.Node) bool { + return n.Op() == ir.OCALL || n.Op() == ir.OCALLFUNC + } + sort.SliceStable(c, func(i, j int) bool { + return !isCall(c[i]) && isCall(c[j]) + }) + flatConds = append(flatConds, c...) + } + + if len(flatConds) == 0 { + fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(true))) + } else { + for _, c := range flatConds[:len(flatConds)-1] { + // if cond {} else { goto neq } + n := ir.NewIfStmt(base.Pos, c, nil, nil) + n.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq)) + fn.Body.Append(n) + } + fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, flatConds[len(flatConds)-1])) + } + } + + // ret: + // return + ret := typecheck.AutoLabel(".ret") + fn.Body.Append(ir.NewLabelStmt(base.Pos, ret)) + fn.Body.Append(ir.NewReturnStmt(base.Pos, nil)) + + // neq: + // r = false + // return (or goto ret) + fn.Body.Append(ir.NewLabelStmt(base.Pos, neq)) + fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(false))) + if eqCanPanic(t) || anyCall(fn) { + // Epilogue is large, so share it with the equal case. + fn.Body.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, ret)) + } else { + // Epilogue is small, so don't bother sharing. + fn.Body.Append(ir.NewReturnStmt(base.Pos, nil)) + } + // TODO(khr): the epilogue size detection condition above isn't perfect. + // We should really do a generic CL that shares epilogues across + // the board. See #24936. + + if base.Flag.LowerR != 0 { + ir.DumpList("geneq body", fn.Body) + } + + typecheck.FinishFuncBody() + + fn.SetDupok(true) + typecheck.Func(fn) + + ir.CurFunc = fn + typecheck.Stmts(fn.Body) + ir.CurFunc = nil + + if base.Debug.DclStack != 0 { + types.CheckDclstack() + } + + // Disable checknils while compiling this code. + // We are comparing a struct or an array, + // neither of which can be nil, and our comparisons + // are shallow. + fn.SetNilCheckDisabled(true) + typecheck.Target.Decls = append(typecheck.Target.Decls, fn) + + // Generate a closure which points at the function we just generated. + objw.SymPtr(closure, 0, fn.Linksym(), 0) + objw.Global(closure, int32(types.PtrSize), obj.DUPOK|obj.RODATA) + return closure +} + +func anyCall(fn *ir.Func) bool { + return ir.Any(fn, func(n ir.Node) bool { + // TODO(rsc): No methods? + op := n.Op() + return op == ir.OCALL || op == ir.OCALLFUNC + }) +} + +// eqfield returns the node +// p.field == q.field +func eqfield(p ir.Node, q ir.Node, field *types.Sym) ir.Node { + nx := ir.NewSelectorExpr(base.Pos, ir.OXDOT, p, field) + ny := ir.NewSelectorExpr(base.Pos, ir.OXDOT, q, field) + ne := ir.NewBinaryExpr(base.Pos, ir.OEQ, nx, ny) + return ne +} + +// EqString returns the nodes +// len(s) == len(t) +// and +// memequal(s.ptr, t.ptr, len(s)) +// which can be used to construct string equality comparison. +// eqlen must be evaluated before eqmem, and shortcircuiting is required. +func EqString(s, t ir.Node) (eqlen *ir.BinaryExpr, eqmem *ir.CallExpr) { + s = typecheck.Conv(s, types.Types[types.TSTRING]) + t = typecheck.Conv(t, types.Types[types.TSTRING]) + sptr := ir.NewUnaryExpr(base.Pos, ir.OSPTR, s) + tptr := ir.NewUnaryExpr(base.Pos, ir.OSPTR, t) + slen := typecheck.Conv(ir.NewUnaryExpr(base.Pos, ir.OLEN, s), types.Types[types.TUINTPTR]) + tlen := typecheck.Conv(ir.NewUnaryExpr(base.Pos, ir.OLEN, t), types.Types[types.TUINTPTR]) + + fn := typecheck.LookupRuntime("memequal") + fn = typecheck.SubstArgTypes(fn, types.Types[types.TUINT8], types.Types[types.TUINT8]) + call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, []ir.Node{sptr, tptr, ir.Copy(slen)}) + typecheck.Call(call) + + cmp := ir.NewBinaryExpr(base.Pos, ir.OEQ, slen, tlen) + cmp = typecheck.Expr(cmp).(*ir.BinaryExpr) + cmp.SetType(types.Types[types.TBOOL]) + return cmp, call +} + +// EqInterface returns the nodes +// s.tab == t.tab (or s.typ == t.typ, as appropriate) +// and +// ifaceeq(s.tab, s.data, t.data) (or efaceeq(s.typ, s.data, t.data), as appropriate) +// which can be used to construct interface equality comparison. +// eqtab must be evaluated before eqdata, and shortcircuiting is required. +func EqInterface(s, t ir.Node) (eqtab *ir.BinaryExpr, eqdata *ir.CallExpr) { + if !types.Identical(s.Type(), t.Type()) { + base.Fatalf("EqInterface %v %v", s.Type(), t.Type()) + } + // func ifaceeq(tab *uintptr, x, y unsafe.Pointer) (ret bool) + // func efaceeq(typ *uintptr, x, y unsafe.Pointer) (ret bool) + var fn ir.Node + if s.Type().IsEmptyInterface() { + fn = typecheck.LookupRuntime("efaceeq") + } else { + fn = typecheck.LookupRuntime("ifaceeq") + } + + stab := ir.NewUnaryExpr(base.Pos, ir.OITAB, s) + ttab := ir.NewUnaryExpr(base.Pos, ir.OITAB, t) + sdata := ir.NewUnaryExpr(base.Pos, ir.OIDATA, s) + tdata := ir.NewUnaryExpr(base.Pos, ir.OIDATA, t) + sdata.SetType(types.Types[types.TUNSAFEPTR]) + tdata.SetType(types.Types[types.TUNSAFEPTR]) + sdata.SetTypecheck(1) + tdata.SetTypecheck(1) + + call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, []ir.Node{stab, sdata, tdata}) + typecheck.Call(call) + + cmp := ir.NewBinaryExpr(base.Pos, ir.OEQ, stab, ttab) + cmp = typecheck.Expr(cmp).(*ir.BinaryExpr) + cmp.SetType(types.Types[types.TBOOL]) + return cmp, call +} + +// eqmem returns the node +// memequal(&p.field, &q.field [, size]) +func eqmem(p ir.Node, q ir.Node, field *types.Sym, size int64) ir.Node { + nx := typecheck.Expr(typecheck.NodAddr(ir.NewSelectorExpr(base.Pos, ir.OXDOT, p, field))) + ny := typecheck.Expr(typecheck.NodAddr(ir.NewSelectorExpr(base.Pos, ir.OXDOT, q, field))) + + fn, needsize := eqmemfunc(size, nx.Type().Elem()) + call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil) + call.Args.Append(nx) + call.Args.Append(ny) + if needsize { + call.Args.Append(ir.NewInt(size)) + } + + return call +} + +func eqmemfunc(size int64, t *types.Type) (fn *ir.Name, needsize bool) { + switch size { + default: + fn = typecheck.LookupRuntime("memequal") + needsize = true + case 1, 2, 4, 8, 16: + buf := fmt.Sprintf("memequal%d", int(size)*8) + fn = typecheck.LookupRuntime(buf) + } + + fn = typecheck.SubstArgTypes(fn, t, t) + return fn, needsize +} + +// memrun finds runs of struct fields for which memory-only algs are appropriate. +// t is the parent struct type, and start is the field index at which to start the run. +// size is the length in bytes of the memory included in the run. +// next is the index just after the end of the memory run. +func memrun(t *types.Type, start int) (size int64, next int) { + next = start + for { + next++ + if next == t.NumFields() { + break + } + // Stop run after a padded field. + if types.IsPaddedField(t, next-1) { + break + } + // Also, stop before a blank or non-memory field. + if f := t.Field(next); f.Sym.IsBlank() || !isRegularMemory(f.Type) { + break + } + // For issue 46283, don't combine fields if the resulting load would + // require a larger alignment than the component fields. + if base.Ctxt.Arch.Alignment > 1 { + align := t.Alignment() + if off := t.Field(start).Offset; off&(align-1) != 0 { + // Offset is less aligned than the containing type. + // Use offset to determine alignment. + align = 1 << uint(bits.TrailingZeros64(uint64(off))) + } + size := t.Field(next).End() - t.Field(start).Offset + if size > align { + break + } + } + } + return t.Field(next-1).End() - t.Field(start).Offset, next +} + +func hashmem(t *types.Type) ir.Node { + sym := ir.Pkgs.Runtime.Lookup("memhash") + + // TODO(austin): This creates an ir.Name with a nil Func. + n := typecheck.NewName(sym) + ir.MarkFunc(n) + n.SetType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{ + types.NewField(base.Pos, nil, types.NewPtr(t)), + types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]), + types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]), + }, []*types.Field{ + types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]), + })) + return n +} diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go new file mode 100644 index 0000000000000000000000000000000000000000..eb9a8a6c9bc73ade1974d19e63cdbe225224935f --- /dev/null +++ b/src/cmd/compile/internal/reflectdata/reflect.go @@ -0,0 +1,1905 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflectdata + +import ( + "encoding/binary" + "fmt" + "internal/buildcfg" + "os" + "sort" + "strings" + "sync" + + "cmd/compile/internal/base" + "cmd/compile/internal/bitvec" + "cmd/compile/internal/escape" + "cmd/compile/internal/inline" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/typebits" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/gcprog" + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/src" +) + +type itabEntry struct { + t, itype *types.Type + lsym *obj.LSym // symbol of the itab itself + + // symbols of each method in + // the itab, sorted by byte offset; + // filled in by CompileITabs + entries []*obj.LSym +} + +type ptabEntry struct { + s *types.Sym + t *types.Type +} + +func CountTabs() (numPTabs, numITabs int) { + return len(ptabs), len(itabs) +} + +// runtime interface and reflection data structures +var ( + signatmu sync.Mutex // protects signatset and signatslice + signatset = make(map[*types.Type]struct{}) + signatslice []*types.Type + + gcsymmu sync.Mutex // protects gcsymset and gcsymslice + gcsymset = make(map[*types.Type]struct{}) + + itabs []itabEntry + ptabs []*ir.Name +) + +type typeSig struct { + name *types.Sym + isym *obj.LSym + tsym *obj.LSym + type_ *types.Type + mtype *types.Type +} + +// Builds a type representing a Bucket structure for +// the given map type. This type is not visible to users - +// we include only enough information to generate a correct GC +// program for it. +// Make sure this stays in sync with runtime/map.go. +const ( + BUCKETSIZE = 8 + MAXKEYSIZE = 128 + MAXELEMSIZE = 128 +) + +func structfieldSize() int { return 3 * types.PtrSize } // Sizeof(runtime.structfield{}) +func imethodSize() int { return 4 + 4 } // Sizeof(runtime.imethod{}) +func commonSize() int { return 4*types.PtrSize + 8 + 8 } // Sizeof(runtime._type{}) + +func uncommonSize(t *types.Type) int { // Sizeof(runtime.uncommontype{}) + if t.Sym() == nil && len(methods(t)) == 0 { + return 0 + } + return 4 + 2 + 2 + 4 + 4 +} + +func makefield(name string, t *types.Type) *types.Field { + sym := (*types.Pkg)(nil).Lookup(name) + return types.NewField(src.NoXPos, sym, t) +} + +// MapBucketType makes the map bucket type given the type of the map. +func MapBucketType(t *types.Type) *types.Type { + if t.MapType().Bucket != nil { + return t.MapType().Bucket + } + + keytype := t.Key() + elemtype := t.Elem() + types.CalcSize(keytype) + types.CalcSize(elemtype) + if keytype.Width > MAXKEYSIZE { + keytype = types.NewPtr(keytype) + } + if elemtype.Width > MAXELEMSIZE { + elemtype = types.NewPtr(elemtype) + } + + field := make([]*types.Field, 0, 5) + + // The first field is: uint8 topbits[BUCKETSIZE]. + arr := types.NewArray(types.Types[types.TUINT8], BUCKETSIZE) + field = append(field, makefield("topbits", arr)) + + arr = types.NewArray(keytype, BUCKETSIZE) + arr.SetNoalg(true) + keys := makefield("keys", arr) + field = append(field, keys) + + arr = types.NewArray(elemtype, BUCKETSIZE) + arr.SetNoalg(true) + elems := makefield("elems", arr) + field = append(field, elems) + + // If keys and elems have no pointers, the map implementation + // can keep a list of overflow pointers on the side so that + // buckets can be marked as having no pointers. + // Arrange for the bucket to have no pointers by changing + // the type of the overflow field to uintptr in this case. + // See comment on hmap.overflow in runtime/map.go. + otyp := types.Types[types.TUNSAFEPTR] + if !elemtype.HasPointers() && !keytype.HasPointers() { + otyp = types.Types[types.TUINTPTR] + } + overflow := makefield("overflow", otyp) + field = append(field, overflow) + + // link up fields + bucket := types.NewStruct(types.NoPkg, field[:]) + bucket.SetNoalg(true) + types.CalcSize(bucket) + + // Check invariants that map code depends on. + if !types.IsComparable(t.Key()) { + base.Fatalf("unsupported map key type for %v", t) + } + if BUCKETSIZE < 8 { + base.Fatalf("bucket size too small for proper alignment") + } + if keytype.Align > BUCKETSIZE { + base.Fatalf("key align too big for %v", t) + } + if elemtype.Align > BUCKETSIZE { + base.Fatalf("elem align too big for %v", t) + } + if keytype.Width > MAXKEYSIZE { + base.Fatalf("key size to large for %v", t) + } + if elemtype.Width > MAXELEMSIZE { + base.Fatalf("elem size to large for %v", t) + } + if t.Key().Width > MAXKEYSIZE && !keytype.IsPtr() { + base.Fatalf("key indirect incorrect for %v", t) + } + if t.Elem().Width > MAXELEMSIZE && !elemtype.IsPtr() { + base.Fatalf("elem indirect incorrect for %v", t) + } + if keytype.Width%int64(keytype.Align) != 0 { + base.Fatalf("key size not a multiple of key align for %v", t) + } + if elemtype.Width%int64(elemtype.Align) != 0 { + base.Fatalf("elem size not a multiple of elem align for %v", t) + } + if bucket.Align%keytype.Align != 0 { + base.Fatalf("bucket align not multiple of key align %v", t) + } + if bucket.Align%elemtype.Align != 0 { + base.Fatalf("bucket align not multiple of elem align %v", t) + } + if keys.Offset%int64(keytype.Align) != 0 { + base.Fatalf("bad alignment of keys in bmap for %v", t) + } + if elems.Offset%int64(elemtype.Align) != 0 { + base.Fatalf("bad alignment of elems in bmap for %v", t) + } + + // Double-check that overflow field is final memory in struct, + // with no padding at end. + if overflow.Offset != bucket.Width-int64(types.PtrSize) { + base.Fatalf("bad offset of overflow in bmap for %v", t) + } + + t.MapType().Bucket = bucket + + bucket.StructType().Map = t + return bucket +} + +// MapType builds a type representing a Hmap structure for the given map type. +// Make sure this stays in sync with runtime/map.go. +func MapType(t *types.Type) *types.Type { + if t.MapType().Hmap != nil { + return t.MapType().Hmap + } + + bmap := MapBucketType(t) + + // build a struct: + // type hmap struct { + // count int + // flags uint8 + // B uint8 + // noverflow uint16 + // hash0 uint32 + // buckets *bmap + // oldbuckets *bmap + // nevacuate uintptr + // extra unsafe.Pointer // *mapextra + // } + // must match runtime/map.go:hmap. + fields := []*types.Field{ + makefield("count", types.Types[types.TINT]), + makefield("flags", types.Types[types.TUINT8]), + makefield("B", types.Types[types.TUINT8]), + makefield("noverflow", types.Types[types.TUINT16]), + makefield("hash0", types.Types[types.TUINT32]), // Used in walk.go for OMAKEMAP. + makefield("buckets", types.NewPtr(bmap)), // Used in walk.go for OMAKEMAP. + makefield("oldbuckets", types.NewPtr(bmap)), + makefield("nevacuate", types.Types[types.TUINTPTR]), + makefield("extra", types.Types[types.TUNSAFEPTR]), + } + + hmap := types.NewStruct(types.NoPkg, fields) + hmap.SetNoalg(true) + types.CalcSize(hmap) + + // The size of hmap should be 48 bytes on 64 bit + // and 28 bytes on 32 bit platforms. + if size := int64(8 + 5*types.PtrSize); hmap.Width != size { + base.Fatalf("hmap size not correct: got %d, want %d", hmap.Width, size) + } + + t.MapType().Hmap = hmap + hmap.StructType().Map = t + return hmap +} + +// MapIterType builds a type representing an Hiter structure for the given map type. +// Make sure this stays in sync with runtime/map.go. +func MapIterType(t *types.Type) *types.Type { + if t.MapType().Hiter != nil { + return t.MapType().Hiter + } + + hmap := MapType(t) + bmap := MapBucketType(t) + + // build a struct: + // type hiter struct { + // key *Key + // elem *Elem + // t unsafe.Pointer // *MapType + // h *hmap + // buckets *bmap + // bptr *bmap + // overflow unsafe.Pointer // *[]*bmap + // oldoverflow unsafe.Pointer // *[]*bmap + // startBucket uintptr + // offset uint8 + // wrapped bool + // B uint8 + // i uint8 + // bucket uintptr + // checkBucket uintptr + // } + // must match runtime/map.go:hiter. + fields := []*types.Field{ + makefield("key", types.NewPtr(t.Key())), // Used in range.go for TMAP. + makefield("elem", types.NewPtr(t.Elem())), // Used in range.go for TMAP. + makefield("t", types.Types[types.TUNSAFEPTR]), + makefield("h", types.NewPtr(hmap)), + makefield("buckets", types.NewPtr(bmap)), + makefield("bptr", types.NewPtr(bmap)), + makefield("overflow", types.Types[types.TUNSAFEPTR]), + makefield("oldoverflow", types.Types[types.TUNSAFEPTR]), + makefield("startBucket", types.Types[types.TUINTPTR]), + makefield("offset", types.Types[types.TUINT8]), + makefield("wrapped", types.Types[types.TBOOL]), + makefield("B", types.Types[types.TUINT8]), + makefield("i", types.Types[types.TUINT8]), + makefield("bucket", types.Types[types.TUINTPTR]), + makefield("checkBucket", types.Types[types.TUINTPTR]), + } + + // build iterator struct holding the above fields + hiter := types.NewStruct(types.NoPkg, fields) + hiter.SetNoalg(true) + types.CalcSize(hiter) + if hiter.Width != int64(12*types.PtrSize) { + base.Fatalf("hash_iter size not correct %d %d", hiter.Width, 12*types.PtrSize) + } + t.MapType().Hiter = hiter + hiter.StructType().Map = t + return hiter +} + +// methods returns the methods of the non-interface type t, sorted by name. +// Generates stub functions as needed. +func methods(t *types.Type) []*typeSig { + // method type + mt := types.ReceiverBaseType(t) + + if mt == nil { + return nil + } + typecheck.CalcMethods(mt) + + // type stored in interface word + it := t + + if !types.IsDirectIface(it) { + it = types.NewPtr(t) + } + + // make list of methods for t, + // generating code if necessary. + var ms []*typeSig + for _, f := range mt.AllMethods().Slice() { + if f.Sym == nil { + base.Fatalf("method with no sym on %v", mt) + } + if !f.IsMethod() { + base.Fatalf("non-method on %v method %v %v", mt, f.Sym, f) + } + if f.Type.Recv() == nil { + base.Fatalf("receiver with no type on %v method %v %v", mt, f.Sym, f) + } + if f.Nointerface() { + continue + } + + // get receiver type for this particular method. + // if pointer receiver but non-pointer t and + // this is not an embedded pointer inside a struct, + // method does not apply. + if !types.IsMethodApplicable(t, f) { + continue + } + + sig := &typeSig{ + name: f.Sym, + isym: methodWrapper(it, f), + tsym: methodWrapper(t, f), + type_: typecheck.NewMethodType(f.Type, t), + mtype: typecheck.NewMethodType(f.Type, nil), + } + ms = append(ms, sig) + } + + return ms +} + +// imethods returns the methods of the interface type t, sorted by name. +func imethods(t *types.Type) []*typeSig { + var methods []*typeSig + for _, f := range t.AllMethods().Slice() { + if f.Type.Kind() != types.TFUNC || f.Sym == nil { + continue + } + if f.Sym.IsBlank() { + base.Fatalf("unexpected blank symbol in interface method set") + } + if n := len(methods); n > 0 { + last := methods[n-1] + if !last.name.Less(f.Sym) { + base.Fatalf("sigcmp vs sortinter %v %v", last.name, f.Sym) + } + } + + sig := &typeSig{ + name: f.Sym, + mtype: f.Type, + type_: typecheck.NewMethodType(f.Type, nil), + } + methods = append(methods, sig) + + // NOTE(rsc): Perhaps an oversight that + // IfaceType.Method is not in the reflect data. + // Generate the method body, so that compiled + // code can refer to it. + methodWrapper(t, f) + } + + return methods +} + +func dimportpath(p *types.Pkg) { + if p.Pathsym != nil { + return + } + + // If we are compiling the runtime package, there are two runtime packages around + // -- localpkg and Pkgs.Runtime. We don't want to produce import path symbols for + // both of them, so just produce one for localpkg. + if base.Ctxt.Pkgpath == "runtime" && p == ir.Pkgs.Runtime { + return + } + + str := p.Path + if p == types.LocalPkg { + // Note: myimportpath != "", or else dgopkgpath won't call dimportpath. + str = base.Ctxt.Pkgpath + } + + s := base.Ctxt.Lookup("type..importpath." + p.Prefix + ".") + ot := dnameData(s, 0, str, "", nil, false) + objw.Global(s, int32(ot), obj.DUPOK|obj.RODATA) + s.Set(obj.AttrContentAddressable, true) + p.Pathsym = s +} + +func dgopkgpath(s *obj.LSym, ot int, pkg *types.Pkg) int { + if pkg == nil { + return objw.Uintptr(s, ot, 0) + } + + if pkg == types.LocalPkg && base.Ctxt.Pkgpath == "" { + // If we don't know the full import path of the package being compiled + // (i.e. -p was not passed on the compiler command line), emit a reference to + // type..importpath.""., which the linker will rewrite using the correct import path. + // Every package that imports this one directly defines the symbol. + // See also https://groups.google.com/forum/#!topic/golang-dev/myb9s53HxGQ. + ns := base.Ctxt.Lookup(`type..importpath."".`) + return objw.SymPtr(s, ot, ns, 0) + } + + dimportpath(pkg) + return objw.SymPtr(s, ot, pkg.Pathsym, 0) +} + +// dgopkgpathOff writes an offset relocation in s at offset ot to the pkg path symbol. +func dgopkgpathOff(s *obj.LSym, ot int, pkg *types.Pkg) int { + if pkg == nil { + return objw.Uint32(s, ot, 0) + } + if pkg == types.LocalPkg && base.Ctxt.Pkgpath == "" { + // If we don't know the full import path of the package being compiled + // (i.e. -p was not passed on the compiler command line), emit a reference to + // type..importpath.""., which the linker will rewrite using the correct import path. + // Every package that imports this one directly defines the symbol. + // See also https://groups.google.com/forum/#!topic/golang-dev/myb9s53HxGQ. + ns := base.Ctxt.Lookup(`type..importpath."".`) + return objw.SymPtrOff(s, ot, ns) + } + + dimportpath(pkg) + return objw.SymPtrOff(s, ot, pkg.Pathsym) +} + +// dnameField dumps a reflect.name for a struct field. +func dnameField(lsym *obj.LSym, ot int, spkg *types.Pkg, ft *types.Field) int { + if !types.IsExported(ft.Sym.Name) && ft.Sym.Pkg != spkg { + base.Fatalf("package mismatch for %v", ft.Sym) + } + nsym := dname(ft.Sym.Name, ft.Note, nil, types.IsExported(ft.Sym.Name)) + return objw.SymPtr(lsym, ot, nsym, 0) +} + +// dnameData writes the contents of a reflect.name into s at offset ot. +func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported bool) int { + if len(name) >= 1<<29 { + base.Fatalf("name too long: %d %s...", len(name), name[:1024]) + } + if len(tag) >= 1<<29 { + base.Fatalf("tag too long: %d %s...", len(tag), tag[:1024]) + } + var nameLen [binary.MaxVarintLen64]byte + nameLenLen := binary.PutUvarint(nameLen[:], uint64(len(name))) + var tagLen [binary.MaxVarintLen64]byte + tagLenLen := binary.PutUvarint(tagLen[:], uint64(len(tag))) + + // Encode name and tag. See reflect/type.go for details. + var bits byte + l := 1 + nameLenLen + len(name) + if exported { + bits |= 1 << 0 + } + if len(tag) > 0 { + l += tagLenLen + len(tag) + bits |= 1 << 1 + } + if pkg != nil { + bits |= 1 << 2 + } + b := make([]byte, l) + b[0] = bits + copy(b[1:], nameLen[:nameLenLen]) + copy(b[1+nameLenLen:], name) + if len(tag) > 0 { + tb := b[1+nameLenLen+len(name):] + copy(tb, tagLen[:tagLenLen]) + copy(tb[tagLenLen:], tag) + } + + ot = int(s.WriteBytes(base.Ctxt, int64(ot), b)) + + if pkg != nil { + ot = dgopkgpathOff(s, ot, pkg) + } + + return ot +} + +var dnameCount int + +// dname creates a reflect.name for a struct field or method. +func dname(name, tag string, pkg *types.Pkg, exported bool) *obj.LSym { + // Write out data as "type.." to signal two things to the + // linker, first that when dynamically linking, the symbol + // should be moved to a relro section, and second that the + // contents should not be decoded as a type. + sname := "type..namedata." + if pkg == nil { + // In the common case, share data with other packages. + if name == "" { + if exported { + sname += "-noname-exported." + tag + } else { + sname += "-noname-unexported." + tag + } + } else { + if exported { + sname += name + "." + tag + } else { + sname += name + "-" + tag + } + } + } else { + sname = fmt.Sprintf(`%s"".%d`, sname, dnameCount) + dnameCount++ + } + s := base.Ctxt.Lookup(sname) + if len(s.P) > 0 { + return s + } + ot := dnameData(s, 0, name, tag, pkg, exported) + objw.Global(s, int32(ot), obj.DUPOK|obj.RODATA) + s.Set(obj.AttrContentAddressable, true) + return s +} + +// dextratype dumps the fields of a runtime.uncommontype. +// dataAdd is the offset in bytes after the header where the +// backing array of the []method field is written (by dextratypeData). +func dextratype(lsym *obj.LSym, ot int, t *types.Type, dataAdd int) int { + m := methods(t) + if t.Sym() == nil && len(m) == 0 { + return ot + } + noff := int(types.Rnd(int64(ot), int64(types.PtrSize))) + if noff != ot { + base.Fatalf("unexpected alignment in dextratype for %v", t) + } + + for _, a := range m { + writeType(a.type_) + } + + ot = dgopkgpathOff(lsym, ot, typePkg(t)) + + dataAdd += uncommonSize(t) + mcount := len(m) + if mcount != int(uint16(mcount)) { + base.Fatalf("too many methods on %v: %d", t, mcount) + } + xcount := sort.Search(mcount, func(i int) bool { return !types.IsExported(m[i].name.Name) }) + if dataAdd != int(uint32(dataAdd)) { + base.Fatalf("methods are too far away on %v: %d", t, dataAdd) + } + + ot = objw.Uint16(lsym, ot, uint16(mcount)) + ot = objw.Uint16(lsym, ot, uint16(xcount)) + ot = objw.Uint32(lsym, ot, uint32(dataAdd)) + ot = objw.Uint32(lsym, ot, 0) + return ot +} + +func typePkg(t *types.Type) *types.Pkg { + tsym := t.Sym() + if tsym == nil { + switch t.Kind() { + case types.TARRAY, types.TSLICE, types.TPTR, types.TCHAN: + if t.Elem() != nil { + tsym = t.Elem().Sym() + } + } + } + if tsym != nil && tsym.Pkg != types.BuiltinPkg { + return tsym.Pkg + } + return nil +} + +// dextratypeData dumps the backing array for the []method field of +// runtime.uncommontype. +func dextratypeData(lsym *obj.LSym, ot int, t *types.Type) int { + for _, a := range methods(t) { + // ../../../../runtime/type.go:/method + exported := types.IsExported(a.name.Name) + var pkg *types.Pkg + if !exported && a.name.Pkg != typePkg(t) { + pkg = a.name.Pkg + } + nsym := dname(a.name.Name, "", pkg, exported) + + ot = objw.SymPtrOff(lsym, ot, nsym) + ot = dmethodptrOff(lsym, ot, writeType(a.mtype)) + ot = dmethodptrOff(lsym, ot, a.isym) + ot = dmethodptrOff(lsym, ot, a.tsym) + } + return ot +} + +func dmethodptrOff(s *obj.LSym, ot int, x *obj.LSym) int { + objw.Uint32(s, ot, 0) + r := obj.Addrel(s) + r.Off = int32(ot) + r.Siz = 4 + r.Sym = x + r.Type = objabi.R_METHODOFF + return ot + 4 +} + +var kinds = []int{ + types.TINT: objabi.KindInt, + types.TUINT: objabi.KindUint, + types.TINT8: objabi.KindInt8, + types.TUINT8: objabi.KindUint8, + types.TINT16: objabi.KindInt16, + types.TUINT16: objabi.KindUint16, + types.TINT32: objabi.KindInt32, + types.TUINT32: objabi.KindUint32, + types.TINT64: objabi.KindInt64, + types.TUINT64: objabi.KindUint64, + types.TUINTPTR: objabi.KindUintptr, + types.TFLOAT32: objabi.KindFloat32, + types.TFLOAT64: objabi.KindFloat64, + types.TBOOL: objabi.KindBool, + types.TSTRING: objabi.KindString, + types.TPTR: objabi.KindPtr, + types.TSTRUCT: objabi.KindStruct, + types.TINTER: objabi.KindInterface, + types.TCHAN: objabi.KindChan, + types.TMAP: objabi.KindMap, + types.TARRAY: objabi.KindArray, + types.TSLICE: objabi.KindSlice, + types.TFUNC: objabi.KindFunc, + types.TCOMPLEX64: objabi.KindComplex64, + types.TCOMPLEX128: objabi.KindComplex128, + types.TUNSAFEPTR: objabi.KindUnsafePointer, +} + +// tflag is documented in reflect/type.go. +// +// tflag values must be kept in sync with copies in: +// cmd/compile/internal/reflectdata/reflect.go +// cmd/link/internal/ld/decodesym.go +// reflect/type.go +// runtime/type.go +const ( + tflagUncommon = 1 << 0 + tflagExtraStar = 1 << 1 + tflagNamed = 1 << 2 + tflagRegularMemory = 1 << 3 +) + +var ( + memhashvarlen *obj.LSym + memequalvarlen *obj.LSym +) + +// dcommontype dumps the contents of a reflect.rtype (runtime._type). +func dcommontype(lsym *obj.LSym, t *types.Type) int { + types.CalcSize(t) + eqfunc := geneq(t) + + sptrWeak := true + var sptr *obj.LSym + if !t.IsPtr() || t.IsPtrElem() { + tptr := types.NewPtr(t) + if t.Sym() != nil || methods(tptr) != nil { + sptrWeak = false + } + sptr = writeType(tptr) + } + + gcsym, useGCProg, ptrdata := dgcsym(t, true) + delete(gcsymset, t) + + // ../../../../reflect/type.go:/^type.rtype + // actual type structure + // type rtype struct { + // size uintptr + // ptrdata uintptr + // hash uint32 + // tflag tflag + // align uint8 + // fieldAlign uint8 + // kind uint8 + // equal func(unsafe.Pointer, unsafe.Pointer) bool + // gcdata *byte + // str nameOff + // ptrToThis typeOff + // } + ot := 0 + ot = objw.Uintptr(lsym, ot, uint64(t.Width)) + ot = objw.Uintptr(lsym, ot, uint64(ptrdata)) + ot = objw.Uint32(lsym, ot, types.TypeHash(t)) + + var tflag uint8 + if uncommonSize(t) != 0 { + tflag |= tflagUncommon + } + if t.Sym() != nil && t.Sym().Name != "" { + tflag |= tflagNamed + } + if isRegularMemory(t) { + tflag |= tflagRegularMemory + } + + exported := false + p := t.LongString() + // If we're writing out type T, + // we are very likely to write out type *T as well. + // Use the string "*T"[1:] for "T", so that the two + // share storage. This is a cheap way to reduce the + // amount of space taken up by reflect strings. + if !strings.HasPrefix(p, "*") { + p = "*" + p + tflag |= tflagExtraStar + if t.Sym() != nil { + exported = types.IsExported(t.Sym().Name) + } + } else { + if t.Elem() != nil && t.Elem().Sym() != nil { + exported = types.IsExported(t.Elem().Sym().Name) + } + } + + ot = objw.Uint8(lsym, ot, tflag) + + // runtime (and common sense) expects alignment to be a power of two. + i := int(t.Align) + + if i == 0 { + i = 1 + } + if i&(i-1) != 0 { + base.Fatalf("invalid alignment %d for %v", t.Align, t) + } + ot = objw.Uint8(lsym, ot, t.Align) // align + ot = objw.Uint8(lsym, ot, t.Align) // fieldAlign + + i = kinds[t.Kind()] + if types.IsDirectIface(t) { + i |= objabi.KindDirectIface + } + if useGCProg { + i |= objabi.KindGCProg + } + ot = objw.Uint8(lsym, ot, uint8(i)) // kind + if eqfunc != nil { + ot = objw.SymPtr(lsym, ot, eqfunc, 0) // equality function + } else { + ot = objw.Uintptr(lsym, ot, 0) // type we can't do == with + } + ot = objw.SymPtr(lsym, ot, gcsym, 0) // gcdata + + nsym := dname(p, "", nil, exported) + ot = objw.SymPtrOff(lsym, ot, nsym) // str + // ptrToThis + if sptr == nil { + ot = objw.Uint32(lsym, ot, 0) + } else if sptrWeak { + ot = objw.SymPtrWeakOff(lsym, ot, sptr) + } else { + ot = objw.SymPtrOff(lsym, ot, sptr) + } + + return ot +} + +// TrackSym returns the symbol for tracking use of field/method f, assumed +// to be a member of struct/interface type t. +func TrackSym(t *types.Type, f *types.Field) *obj.LSym { + return base.PkgLinksym("go.track", t.ShortString()+"."+f.Sym.Name, obj.ABI0) +} + +func TypeSymPrefix(prefix string, t *types.Type) *types.Sym { + p := prefix + "." + t.ShortString() + s := types.TypeSymLookup(p) + + // This function is for looking up type-related generated functions + // (e.g. eq and hash). Make sure they are indeed generated. + signatmu.Lock() + NeedRuntimeType(t) + signatmu.Unlock() + + //print("algsym: %s -> %+S\n", p, s); + + return s +} + +func TypeSym(t *types.Type) *types.Sym { + if t == nil || (t.IsPtr() && t.Elem() == nil) || t.IsUntyped() { + base.Fatalf("TypeSym %v", t) + } + if t.Kind() == types.TFUNC && t.Recv() != nil { + base.Fatalf("misuse of method type: %v", t) + } + s := types.TypeSym(t) + signatmu.Lock() + NeedRuntimeType(t) + signatmu.Unlock() + return s +} + +func TypeLinksymPrefix(prefix string, t *types.Type) *obj.LSym { + return TypeSymPrefix(prefix, t).Linksym() +} + +func TypeLinksymLookup(name string) *obj.LSym { + return types.TypeSymLookup(name).Linksym() +} + +func TypeLinksym(t *types.Type) *obj.LSym { + return TypeSym(t).Linksym() +} + +func TypePtr(t *types.Type) *ir.AddrExpr { + n := ir.NewLinksymExpr(base.Pos, TypeLinksym(t), types.Types[types.TUINT8]) + return typecheck.Expr(typecheck.NodAddr(n)).(*ir.AddrExpr) +} + +func ITabAddr(t, itype *types.Type) *ir.AddrExpr { + if t == nil || (t.IsPtr() && t.Elem() == nil) || t.IsUntyped() || !itype.IsInterface() || itype.IsEmptyInterface() { + base.Fatalf("ITabAddr(%v, %v)", t, itype) + } + s, existed := ir.Pkgs.Itab.LookupOK(t.ShortString() + "," + itype.ShortString()) + if !existed { + itabs = append(itabs, itabEntry{t: t, itype: itype, lsym: s.Linksym()}) + } + + lsym := s.Linksym() + n := ir.NewLinksymExpr(base.Pos, lsym, types.Types[types.TUINT8]) + return typecheck.Expr(typecheck.NodAddr(n)).(*ir.AddrExpr) +} + +// needkeyupdate reports whether map updates with t as a key +// need the key to be updated. +func needkeyupdate(t *types.Type) bool { + switch t.Kind() { + case types.TBOOL, types.TINT, types.TUINT, types.TINT8, types.TUINT8, types.TINT16, types.TUINT16, types.TINT32, types.TUINT32, + types.TINT64, types.TUINT64, types.TUINTPTR, types.TPTR, types.TUNSAFEPTR, types.TCHAN: + return false + + case types.TFLOAT32, types.TFLOAT64, types.TCOMPLEX64, types.TCOMPLEX128, // floats and complex can be +0/-0 + types.TINTER, + types.TSTRING: // strings might have smaller backing stores + return true + + case types.TARRAY: + return needkeyupdate(t.Elem()) + + case types.TSTRUCT: + for _, t1 := range t.Fields().Slice() { + if needkeyupdate(t1.Type) { + return true + } + } + return false + + default: + base.Fatalf("bad type for map key: %v", t) + return true + } +} + +// hashMightPanic reports whether the hash of a map key of type t might panic. +func hashMightPanic(t *types.Type) bool { + switch t.Kind() { + case types.TINTER: + return true + + case types.TARRAY: + return hashMightPanic(t.Elem()) + + case types.TSTRUCT: + for _, t1 := range t.Fields().Slice() { + if hashMightPanic(t1.Type) { + return true + } + } + return false + + default: + return false + } +} + +// formalType replaces byte and rune aliases with real types. +// They've been separate internally to make error messages +// better, but we have to merge them in the reflect tables. +func formalType(t *types.Type) *types.Type { + if t == types.ByteType || t == types.RuneType { + return types.Types[t.Kind()] + } + return t +} + +func writeType(t *types.Type) *obj.LSym { + t = formalType(t) + if t.IsUntyped() { + base.Fatalf("writeType %v", t) + } + + s := types.TypeSym(t) + lsym := s.Linksym() + if s.Siggen() { + return lsym + } + s.SetSiggen(true) + + // special case (look for runtime below): + // when compiling package runtime, + // emit the type structures for int, float, etc. + tbase := t + + if t.IsPtr() && t.Sym() == nil && t.Elem().Sym() != nil { + tbase = t.Elem() + } + dupok := 0 + if tbase.Sym() == nil { + dupok = obj.DUPOK + } + + if base.Ctxt.Pkgpath != "runtime" || (tbase != types.Types[tbase.Kind()] && tbase != types.ByteType && tbase != types.RuneType && tbase != types.ErrorType) { // int, float, etc + // named types from other files are defined only by those files + if tbase.Sym() != nil && tbase.Sym().Pkg != types.LocalPkg { + if i := typecheck.BaseTypeIndex(t); i >= 0 { + lsym.Pkg = tbase.Sym().Pkg.Prefix + lsym.SymIdx = int32(i) + lsym.Set(obj.AttrIndexed, true) + } + return lsym + } + // TODO(mdempsky): Investigate whether this can happen. + if tbase.Kind() == types.TFORW { + return lsym + } + } + + ot := 0 + switch t.Kind() { + default: + ot = dcommontype(lsym, t) + ot = dextratype(lsym, ot, t, 0) + + case types.TARRAY: + // ../../../../runtime/type.go:/arrayType + s1 := writeType(t.Elem()) + t2 := types.NewSlice(t.Elem()) + s2 := writeType(t2) + ot = dcommontype(lsym, t) + ot = objw.SymPtr(lsym, ot, s1, 0) + ot = objw.SymPtr(lsym, ot, s2, 0) + ot = objw.Uintptr(lsym, ot, uint64(t.NumElem())) + ot = dextratype(lsym, ot, t, 0) + + case types.TSLICE: + // ../../../../runtime/type.go:/sliceType + s1 := writeType(t.Elem()) + ot = dcommontype(lsym, t) + ot = objw.SymPtr(lsym, ot, s1, 0) + ot = dextratype(lsym, ot, t, 0) + + case types.TCHAN: + // ../../../../runtime/type.go:/chanType + s1 := writeType(t.Elem()) + ot = dcommontype(lsym, t) + ot = objw.SymPtr(lsym, ot, s1, 0) + ot = objw.Uintptr(lsym, ot, uint64(t.ChanDir())) + ot = dextratype(lsym, ot, t, 0) + + case types.TFUNC: + for _, t1 := range t.Recvs().Fields().Slice() { + writeType(t1.Type) + } + isddd := false + for _, t1 := range t.Params().Fields().Slice() { + isddd = t1.IsDDD() + writeType(t1.Type) + } + for _, t1 := range t.Results().Fields().Slice() { + writeType(t1.Type) + } + + ot = dcommontype(lsym, t) + inCount := t.NumRecvs() + t.NumParams() + outCount := t.NumResults() + if isddd { + outCount |= 1 << 15 + } + ot = objw.Uint16(lsym, ot, uint16(inCount)) + ot = objw.Uint16(lsym, ot, uint16(outCount)) + if types.PtrSize == 8 { + ot += 4 // align for *rtype + } + + dataAdd := (inCount + t.NumResults()) * types.PtrSize + ot = dextratype(lsym, ot, t, dataAdd) + + // Array of rtype pointers follows funcType. + for _, t1 := range t.Recvs().Fields().Slice() { + ot = objw.SymPtr(lsym, ot, writeType(t1.Type), 0) + } + for _, t1 := range t.Params().Fields().Slice() { + ot = objw.SymPtr(lsym, ot, writeType(t1.Type), 0) + } + for _, t1 := range t.Results().Fields().Slice() { + ot = objw.SymPtr(lsym, ot, writeType(t1.Type), 0) + } + + case types.TINTER: + m := imethods(t) + n := len(m) + for _, a := range m { + writeType(a.type_) + } + + // ../../../../runtime/type.go:/interfaceType + ot = dcommontype(lsym, t) + + var tpkg *types.Pkg + if t.Sym() != nil && t != types.Types[t.Kind()] && t != types.ErrorType { + tpkg = t.Sym().Pkg + } + ot = dgopkgpath(lsym, ot, tpkg) + + ot = objw.SymPtr(lsym, ot, lsym, ot+3*types.PtrSize+uncommonSize(t)) + ot = objw.Uintptr(lsym, ot, uint64(n)) + ot = objw.Uintptr(lsym, ot, uint64(n)) + dataAdd := imethodSize() * n + ot = dextratype(lsym, ot, t, dataAdd) + + for _, a := range m { + // ../../../../runtime/type.go:/imethod + exported := types.IsExported(a.name.Name) + var pkg *types.Pkg + if !exported && a.name.Pkg != tpkg { + pkg = a.name.Pkg + } + nsym := dname(a.name.Name, "", pkg, exported) + + ot = objw.SymPtrOff(lsym, ot, nsym) + ot = objw.SymPtrOff(lsym, ot, writeType(a.type_)) + } + + // ../../../../runtime/type.go:/mapType + case types.TMAP: + s1 := writeType(t.Key()) + s2 := writeType(t.Elem()) + s3 := writeType(MapBucketType(t)) + hasher := genhash(t.Key()) + + ot = dcommontype(lsym, t) + ot = objw.SymPtr(lsym, ot, s1, 0) + ot = objw.SymPtr(lsym, ot, s2, 0) + ot = objw.SymPtr(lsym, ot, s3, 0) + ot = objw.SymPtr(lsym, ot, hasher, 0) + var flags uint32 + // Note: flags must match maptype accessors in ../../../../runtime/type.go + // and maptype builder in ../../../../reflect/type.go:MapOf. + if t.Key().Width > MAXKEYSIZE { + ot = objw.Uint8(lsym, ot, uint8(types.PtrSize)) + flags |= 1 // indirect key + } else { + ot = objw.Uint8(lsym, ot, uint8(t.Key().Width)) + } + + if t.Elem().Width > MAXELEMSIZE { + ot = objw.Uint8(lsym, ot, uint8(types.PtrSize)) + flags |= 2 // indirect value + } else { + ot = objw.Uint8(lsym, ot, uint8(t.Elem().Width)) + } + ot = objw.Uint16(lsym, ot, uint16(MapBucketType(t).Width)) + if types.IsReflexive(t.Key()) { + flags |= 4 // reflexive key + } + if needkeyupdate(t.Key()) { + flags |= 8 // need key update + } + if hashMightPanic(t.Key()) { + flags |= 16 // hash might panic + } + ot = objw.Uint32(lsym, ot, flags) + ot = dextratype(lsym, ot, t, 0) + if u := t.Underlying(); u != t { + // If t is a named map type, also keep the underlying map + // type live in the binary. This is important to make sure that + // a named map and that same map cast to its underlying type via + // reflection, use the same hash function. See issue 37716. + r := obj.Addrel(lsym) + r.Sym = writeType(u) + r.Type = objabi.R_KEEP + } + + case types.TPTR: + if t.Elem().Kind() == types.TANY { + // ../../../../runtime/type.go:/UnsafePointerType + ot = dcommontype(lsym, t) + ot = dextratype(lsym, ot, t, 0) + + break + } + + // ../../../../runtime/type.go:/ptrType + s1 := writeType(t.Elem()) + + ot = dcommontype(lsym, t) + ot = objw.SymPtr(lsym, ot, s1, 0) + ot = dextratype(lsym, ot, t, 0) + + // ../../../../runtime/type.go:/structType + // for security, only the exported fields. + case types.TSTRUCT: + fields := t.Fields().Slice() + for _, t1 := range fields { + writeType(t1.Type) + } + + // All non-exported struct field names within a struct + // type must originate from a single package. By + // identifying and recording that package within the + // struct type descriptor, we can omit that + // information from the field descriptors. + var spkg *types.Pkg + for _, f := range fields { + if !types.IsExported(f.Sym.Name) { + spkg = f.Sym.Pkg + break + } + } + + ot = dcommontype(lsym, t) + ot = dgopkgpath(lsym, ot, spkg) + ot = objw.SymPtr(lsym, ot, lsym, ot+3*types.PtrSize+uncommonSize(t)) + ot = objw.Uintptr(lsym, ot, uint64(len(fields))) + ot = objw.Uintptr(lsym, ot, uint64(len(fields))) + + dataAdd := len(fields) * structfieldSize() + ot = dextratype(lsym, ot, t, dataAdd) + + for _, f := range fields { + // ../../../../runtime/type.go:/structField + ot = dnameField(lsym, ot, spkg, f) + ot = objw.SymPtr(lsym, ot, writeType(f.Type), 0) + offsetAnon := uint64(f.Offset) << 1 + if offsetAnon>>1 != uint64(f.Offset) { + base.Fatalf("%v: bad field offset for %s", t, f.Sym.Name) + } + if f.Embedded != 0 { + offsetAnon |= 1 + } + ot = objw.Uintptr(lsym, ot, offsetAnon) + } + } + + ot = dextratypeData(lsym, ot, t) + objw.Global(lsym, int32(ot), int16(dupok|obj.RODATA)) + + // The linker will leave a table of all the typelinks for + // types in the binary, so the runtime can find them. + // + // When buildmode=shared, all types are in typelinks so the + // runtime can deduplicate type pointers. + keep := base.Ctxt.Flag_dynlink + if !keep && t.Sym() == nil { + // For an unnamed type, we only need the link if the type can + // be created at run time by reflect.PtrTo and similar + // functions. If the type exists in the program, those + // functions must return the existing type structure rather + // than creating a new one. + switch t.Kind() { + case types.TPTR, types.TARRAY, types.TCHAN, types.TFUNC, types.TMAP, types.TSLICE, types.TSTRUCT: + keep = true + } + } + // Do not put Noalg types in typelinks. See issue #22605. + if types.TypeHasNoAlg(t) { + keep = false + } + lsym.Set(obj.AttrMakeTypelink, keep) + + return lsym +} + +// InterfaceMethodOffset returns the offset of the i-th method in the interface +// type descriptor, ityp. +func InterfaceMethodOffset(ityp *types.Type, i int64) int64 { + // interface type descriptor layout is struct { + // _type // commonSize + // pkgpath // 1 word + // []imethod // 3 words (pointing to [...]imethod below) + // uncommontype // uncommonSize + // [...]imethod + // } + // The size of imethod is 8. + return int64(commonSize()+4*types.PtrSize+uncommonSize(ityp)) + i*8 +} + +// for each itabEntry, gather the methods on +// the concrete type that implement the interface +func CompileITabs() { + for i := range itabs { + tab := &itabs[i] + methods := genfun(tab.t, tab.itype) + if len(methods) == 0 { + continue + } + tab.entries = methods + } +} + +// for the given concrete type and interface +// type, return the (sorted) set of methods +// on the concrete type that implement the interface +func genfun(t, it *types.Type) []*obj.LSym { + if t == nil || it == nil { + return nil + } + sigs := imethods(it) + methods := methods(t) + out := make([]*obj.LSym, 0, len(sigs)) + // TODO(mdempsky): Short circuit before calling methods(t)? + // See discussion on CL 105039. + if len(sigs) == 0 { + return nil + } + + // both sigs and methods are sorted by name, + // so we can find the intersect in a single pass + for _, m := range methods { + if m.name == sigs[0].name { + out = append(out, m.isym) + sigs = sigs[1:] + if len(sigs) == 0 { + break + } + } + } + + if len(sigs) != 0 { + base.Fatalf("incomplete itab") + } + + return out +} + +// ITabSym uses the information gathered in +// CompileITabs to de-virtualize interface methods. +// Since this is called by the SSA backend, it shouldn't +// generate additional Nodes, Syms, etc. +func ITabSym(it *obj.LSym, offset int64) *obj.LSym { + var syms []*obj.LSym + if it == nil { + return nil + } + + for i := range itabs { + e := &itabs[i] + if e.lsym == it { + syms = e.entries + break + } + } + if syms == nil { + return nil + } + + // keep this arithmetic in sync with *itab layout + methodnum := int((offset - 2*int64(types.PtrSize) - 8) / int64(types.PtrSize)) + if methodnum >= len(syms) { + return nil + } + return syms[methodnum] +} + +// NeedRuntimeType ensures that a runtime type descriptor is emitted for t. +func NeedRuntimeType(t *types.Type) { + if t.HasTParam() { + // Generic types don't have a runtime type descriptor (but will + // have a dictionary) + return + } + if _, ok := signatset[t]; !ok { + signatset[t] = struct{}{} + signatslice = append(signatslice, t) + } +} + +func WriteRuntimeTypes() { + // Process signatset. Use a loop, as writeType adds + // entries to signatset while it is being processed. + signats := make([]typeAndStr, len(signatslice)) + for len(signatslice) > 0 { + signats = signats[:0] + // Transfer entries to a slice and sort, for reproducible builds. + for _, t := range signatslice { + signats = append(signats, typeAndStr{t: t, short: types.TypeSymName(t), regular: t.String()}) + delete(signatset, t) + } + signatslice = signatslice[:0] + sort.Sort(typesByString(signats)) + for _, ts := range signats { + t := ts.t + writeType(t) + if t.Sym() != nil { + writeType(types.NewPtr(t)) + } + } + } + + // Emit GC data symbols. + gcsyms := make([]typeAndStr, 0, len(gcsymset)) + for t := range gcsymset { + gcsyms = append(gcsyms, typeAndStr{t: t, short: types.TypeSymName(t), regular: t.String()}) + } + sort.Sort(typesByString(gcsyms)) + for _, ts := range gcsyms { + dgcsym(ts.t, true) + } +} + +func WriteTabs() { + // process itabs + for _, i := range itabs { + // dump empty itab symbol into i.sym + // type itab struct { + // inter *interfacetype + // _type *_type + // hash uint32 + // _ [4]byte + // fun [1]uintptr // variable sized + // } + o := objw.SymPtr(i.lsym, 0, writeType(i.itype), 0) + o = objw.SymPtr(i.lsym, o, writeType(i.t), 0) + o = objw.Uint32(i.lsym, o, types.TypeHash(i.t)) // copy of type hash + o += 4 // skip unused field + for _, fn := range genfun(i.t, i.itype) { + o = objw.SymPtrWeak(i.lsym, o, fn, 0) // method pointer for each method + } + // Nothing writes static itabs, so they are read only. + objw.Global(i.lsym, int32(o), int16(obj.DUPOK|obj.RODATA)) + i.lsym.Set(obj.AttrContentAddressable, true) + } + + // process ptabs + if types.LocalPkg.Name == "main" && len(ptabs) > 0 { + ot := 0 + s := base.Ctxt.Lookup("go.plugin.tabs") + for _, p := range ptabs { + // Dump ptab symbol into go.pluginsym package. + // + // type ptab struct { + // name nameOff + // typ typeOff // pointer to symbol + // } + nsym := dname(p.Sym().Name, "", nil, true) + t := p.Type() + if p.Class != ir.PFUNC { + t = types.NewPtr(t) + } + tsym := writeType(t) + ot = objw.SymPtrOff(s, ot, nsym) + ot = objw.SymPtrOff(s, ot, tsym) + // Plugin exports symbols as interfaces. Mark their types + // as UsedInIface. + tsym.Set(obj.AttrUsedInIface, true) + } + objw.Global(s, int32(ot), int16(obj.RODATA)) + + ot = 0 + s = base.Ctxt.Lookup("go.plugin.exports") + for _, p := range ptabs { + ot = objw.SymPtr(s, ot, p.Linksym(), 0) + } + objw.Global(s, int32(ot), int16(obj.RODATA)) + } +} + +func WriteImportStrings() { + // generate import strings for imported packages + for _, p := range types.ImportedPkgList() { + dimportpath(p) + } +} + +func WriteBasicTypes() { + // do basic types if compiling package runtime. + // they have to be in at least one package, + // and runtime is always loaded implicitly, + // so this is as good as any. + // another possible choice would be package main, + // but using runtime means fewer copies in object files. + if base.Ctxt.Pkgpath == "runtime" { + for i := types.Kind(1); i <= types.TBOOL; i++ { + writeType(types.NewPtr(types.Types[i])) + } + writeType(types.NewPtr(types.Types[types.TSTRING])) + writeType(types.NewPtr(types.Types[types.TUNSAFEPTR])) + + // emit type structs for error and func(error) string. + // The latter is the type of an auto-generated wrapper. + writeType(types.NewPtr(types.ErrorType)) + + writeType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{ + types.NewField(base.Pos, nil, types.ErrorType), + }, []*types.Field{ + types.NewField(base.Pos, nil, types.Types[types.TSTRING]), + })) + + // add paths for runtime and main, which 6l imports implicitly. + dimportpath(ir.Pkgs.Runtime) + + if base.Flag.Race { + dimportpath(types.NewPkg("runtime/race", "")) + } + if base.Flag.MSan { + dimportpath(types.NewPkg("runtime/msan", "")) + } + + dimportpath(types.NewPkg("main", "")) + } +} + +type typeAndStr struct { + t *types.Type + short string + regular string +} + +type typesByString []typeAndStr + +func (a typesByString) Len() int { return len(a) } +func (a typesByString) Less(i, j int) bool { + if a[i].short != a[j].short { + return a[i].short < a[j].short + } + // When the only difference between the types is whether + // they refer to byte or uint8, such as **byte vs **uint8, + // the types' ShortStrings can be identical. + // To preserve deterministic sort ordering, sort these by String(). + if a[i].regular != a[j].regular { + return a[i].regular < a[j].regular + } + // Identical anonymous interfaces defined in different locations + // will be equal for the above checks, but different in DWARF output. + // Sort by source position to ensure deterministic order. + // See issues 27013 and 30202. + if a[i].t.Kind() == types.TINTER && a[i].t.AllMethods().Len() > 0 { + return a[i].t.AllMethods().Index(0).Pos.Before(a[j].t.AllMethods().Index(0).Pos) + } + return false +} +func (a typesByString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// maxPtrmaskBytes is the maximum length of a GC ptrmask bitmap, +// which holds 1-bit entries describing where pointers are in a given type. +// Above this length, the GC information is recorded as a GC program, +// which can express repetition compactly. In either form, the +// information is used by the runtime to initialize the heap bitmap, +// and for large types (like 128 or more words), they are roughly the +// same speed. GC programs are never much larger and often more +// compact. (If large arrays are involved, they can be arbitrarily +// more compact.) +// +// The cutoff must be large enough that any allocation large enough to +// use a GC program is large enough that it does not share heap bitmap +// bytes with any other objects, allowing the GC program execution to +// assume an aligned start and not use atomic operations. In the current +// runtime, this means all malloc size classes larger than the cutoff must +// be multiples of four words. On 32-bit systems that's 16 bytes, and +// all size classes >= 16 bytes are 16-byte aligned, so no real constraint. +// On 64-bit systems, that's 32 bytes, and 32-byte alignment is guaranteed +// for size classes >= 256 bytes. On a 64-bit system, 256 bytes allocated +// is 32 pointers, the bits for which fit in 4 bytes. So maxPtrmaskBytes +// must be >= 4. +// +// We used to use 16 because the GC programs do have some constant overhead +// to get started, and processing 128 pointers seems to be enough to +// amortize that overhead well. +// +// To make sure that the runtime's chansend can call typeBitsBulkBarrier, +// we raised the limit to 2048, so that even 32-bit systems are guaranteed to +// use bitmaps for objects up to 64 kB in size. +// +// Also known to reflect/type.go. +// +const maxPtrmaskBytes = 2048 + +// GCSym returns a data symbol containing GC information for type t, along +// with a boolean reporting whether the UseGCProg bit should be set in the +// type kind, and the ptrdata field to record in the reflect type information. +// GCSym may be called in concurrent backend, so it does not emit the symbol +// content. +func GCSym(t *types.Type) (lsym *obj.LSym, useGCProg bool, ptrdata int64) { + // Record that we need to emit the GC symbol. + gcsymmu.Lock() + if _, ok := gcsymset[t]; !ok { + gcsymset[t] = struct{}{} + } + gcsymmu.Unlock() + + return dgcsym(t, false) +} + +// dgcsym returns a data symbol containing GC information for type t, along +// with a boolean reporting whether the UseGCProg bit should be set in the +// type kind, and the ptrdata field to record in the reflect type information. +// When write is true, it writes the symbol data. +func dgcsym(t *types.Type, write bool) (lsym *obj.LSym, useGCProg bool, ptrdata int64) { + ptrdata = types.PtrDataSize(t) + if ptrdata/int64(types.PtrSize) <= maxPtrmaskBytes*8 { + lsym = dgcptrmask(t, write) + return + } + + useGCProg = true + lsym, ptrdata = dgcprog(t, write) + return +} + +// dgcptrmask emits and returns the symbol containing a pointer mask for type t. +func dgcptrmask(t *types.Type, write bool) *obj.LSym { + ptrmask := make([]byte, (types.PtrDataSize(t)/int64(types.PtrSize)+7)/8) + fillptrmask(t, ptrmask) + p := fmt.Sprintf("runtime.gcbits.%x", ptrmask) + + lsym := base.Ctxt.Lookup(p) + if write && !lsym.OnList() { + for i, x := range ptrmask { + objw.Uint8(lsym, i, x) + } + objw.Global(lsym, int32(len(ptrmask)), obj.DUPOK|obj.RODATA|obj.LOCAL) + lsym.Set(obj.AttrContentAddressable, true) + } + return lsym +} + +// fillptrmask fills in ptrmask with 1s corresponding to the +// word offsets in t that hold pointers. +// ptrmask is assumed to fit at least types.PtrDataSize(t)/PtrSize bits. +func fillptrmask(t *types.Type, ptrmask []byte) { + for i := range ptrmask { + ptrmask[i] = 0 + } + if !t.HasPointers() { + return + } + + vec := bitvec.New(8 * int32(len(ptrmask))) + typebits.Set(t, 0, vec) + + nptr := types.PtrDataSize(t) / int64(types.PtrSize) + for i := int64(0); i < nptr; i++ { + if vec.Get(int32(i)) { + ptrmask[i/8] |= 1 << (uint(i) % 8) + } + } +} + +// dgcprog emits and returns the symbol containing a GC program for type t +// along with the size of the data described by the program (in the range +// [types.PtrDataSize(t), t.Width]). +// In practice, the size is types.PtrDataSize(t) except for non-trivial arrays. +// For non-trivial arrays, the program describes the full t.Width size. +func dgcprog(t *types.Type, write bool) (*obj.LSym, int64) { + types.CalcSize(t) + if t.Width == types.BADWIDTH { + base.Fatalf("dgcprog: %v badwidth", t) + } + lsym := TypeLinksymPrefix(".gcprog", t) + var p gcProg + p.init(lsym, write) + p.emit(t, 0) + offset := p.w.BitIndex() * int64(types.PtrSize) + p.end() + if ptrdata := types.PtrDataSize(t); offset < ptrdata || offset > t.Width { + base.Fatalf("dgcprog: %v: offset=%d but ptrdata=%d size=%d", t, offset, ptrdata, t.Width) + } + return lsym, offset +} + +type gcProg struct { + lsym *obj.LSym + symoff int + w gcprog.Writer + write bool +} + +func (p *gcProg) init(lsym *obj.LSym, write bool) { + p.lsym = lsym + p.write = write && !lsym.OnList() + p.symoff = 4 // first 4 bytes hold program length + if !write { + p.w.Init(func(byte) {}) + return + } + p.w.Init(p.writeByte) + if base.Debug.GCProg > 0 { + fmt.Fprintf(os.Stderr, "compile: start GCProg for %v\n", lsym) + p.w.Debug(os.Stderr) + } +} + +func (p *gcProg) writeByte(x byte) { + p.symoff = objw.Uint8(p.lsym, p.symoff, x) +} + +func (p *gcProg) end() { + p.w.End() + if !p.write { + return + } + objw.Uint32(p.lsym, 0, uint32(p.symoff-4)) + objw.Global(p.lsym, int32(p.symoff), obj.DUPOK|obj.RODATA|obj.LOCAL) + p.lsym.Set(obj.AttrContentAddressable, true) + if base.Debug.GCProg > 0 { + fmt.Fprintf(os.Stderr, "compile: end GCProg for %v\n", p.lsym) + } +} + +func (p *gcProg) emit(t *types.Type, offset int64) { + types.CalcSize(t) + if !t.HasPointers() { + return + } + if t.Width == int64(types.PtrSize) { + p.w.Ptr(offset / int64(types.PtrSize)) + return + } + switch t.Kind() { + default: + base.Fatalf("gcProg.emit: unexpected type %v", t) + + case types.TSTRING: + p.w.Ptr(offset / int64(types.PtrSize)) + + case types.TINTER: + // Note: the first word isn't a pointer. See comment in typebits.Set + p.w.Ptr(offset/int64(types.PtrSize) + 1) + + case types.TSLICE: + p.w.Ptr(offset / int64(types.PtrSize)) + + case types.TARRAY: + if t.NumElem() == 0 { + // should have been handled by haspointers check above + base.Fatalf("gcProg.emit: empty array") + } + + // Flatten array-of-array-of-array to just a big array by multiplying counts. + count := t.NumElem() + elem := t.Elem() + for elem.IsArray() { + count *= elem.NumElem() + elem = elem.Elem() + } + + if !p.w.ShouldRepeat(elem.Width/int64(types.PtrSize), count) { + // Cheaper to just emit the bits. + for i := int64(0); i < count; i++ { + p.emit(elem, offset+i*elem.Width) + } + return + } + p.emit(elem, offset) + p.w.ZeroUntil((offset + elem.Width) / int64(types.PtrSize)) + p.w.Repeat(elem.Width/int64(types.PtrSize), count-1) + + case types.TSTRUCT: + for _, t1 := range t.Fields().Slice() { + p.emit(t1.Type, offset+t1.Offset) + } + } +} + +// ZeroAddr returns the address of a symbol with at least +// size bytes of zeros. +func ZeroAddr(size int64) ir.Node { + if size >= 1<<31 { + base.Fatalf("map elem too big %d", size) + } + if ZeroSize < size { + ZeroSize = size + } + lsym := base.PkgLinksym("go.map", "zero", obj.ABI0) + x := ir.NewLinksymExpr(base.Pos, lsym, types.Types[types.TUINT8]) + return typecheck.Expr(typecheck.NodAddr(x)) +} + +func CollectPTabs() { + if !base.Ctxt.Flag_dynlink || types.LocalPkg.Name != "main" { + return + } + for _, exportn := range typecheck.Target.Exports { + s := exportn.Sym() + nn := ir.AsNode(s.Def) + if nn == nil { + continue + } + if nn.Op() != ir.ONAME { + continue + } + n := nn.(*ir.Name) + if !types.IsExported(s.Name) { + continue + } + if s.Pkg.Name != "main" { + continue + } + ptabs = append(ptabs, n) + } +} + +// Generate a wrapper function to convert from +// a receiver of type T to a receiver of type U. +// That is, +// +// func (t T) M() { +// ... +// } +// +// already exists; this function generates +// +// func (u U) M() { +// u.M() +// } +// +// where the types T and U are such that u.M() is valid +// and calls the T.M method. +// The resulting function is for use in method tables. +// +// rcvr - U +// method - M func (t T)(), a TFIELD type struct +func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym { + newnam := ir.MethodSym(rcvr, method.Sym) + lsym := newnam.Linksym() + if newnam.Siggen() { + return lsym + } + newnam.SetSiggen(true) + + if types.Identical(rcvr, method.Type.Recv().Type) { + return lsym + } + + // Only generate (*T).M wrappers for T.M in T's own package. + if rcvr.IsPtr() && rcvr.Elem() == method.Type.Recv().Type && + rcvr.Elem().Sym() != nil && rcvr.Elem().Sym().Pkg != types.LocalPkg { + return lsym + } + + // Only generate I.M wrappers for I in I's own package + // but keep doing it for error.Error (was issue #29304). + if rcvr.IsInterface() && rcvr.Sym() != nil && rcvr.Sym().Pkg != types.LocalPkg && rcvr != types.ErrorType { + return lsym + } + + base.Pos = base.AutogeneratedPos + typecheck.DeclContext = ir.PEXTERN + + tfn := ir.NewFuncType(base.Pos, + ir.NewField(base.Pos, typecheck.Lookup(".this"), nil, rcvr), + typecheck.NewFuncParams(method.Type.Params(), true), + typecheck.NewFuncParams(method.Type.Results(), false)) + + // TODO(austin): SelectorExpr may have created one or more + // ir.Names for these already with a nil Func field. We should + // consolidate these and always attach a Func to the Name. + fn := typecheck.DeclFunc(newnam, tfn) + fn.SetDupok(true) + + nthis := ir.AsNode(tfn.Type().Recv().Nname) + + methodrcvr := method.Type.Recv().Type + + // generate nil pointer check for better error + if rcvr.IsPtr() && rcvr.Elem() == methodrcvr { + // generating wrapper from *T to T. + n := ir.NewIfStmt(base.Pos, nil, nil, nil) + n.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, nthis, typecheck.NodNil()) + call := ir.NewCallExpr(base.Pos, ir.OCALL, typecheck.LookupRuntime("panicwrap"), nil) + n.Body = []ir.Node{call} + fn.Body.Append(n) + } + + dot := typecheck.AddImplicitDots(ir.NewSelectorExpr(base.Pos, ir.OXDOT, nthis, method.Sym)) + + // generate call + // It's not possible to use a tail call when dynamic linking on ppc64le. The + // bad scenario is when a local call is made to the wrapper: the wrapper will + // call the implementation, which might be in a different module and so set + // the TOC to the appropriate value for that module. But if it returns + // directly to the wrapper's caller, nothing will reset it to the correct + // value for that function. + // + // Disable tailcall for RegabiArgs for now. The IR does not connect the + // arguments with the OTAILCALL node, and the arguments are not marshaled + // correctly. + if !base.Flag.Cfg.Instrumenting && rcvr.IsPtr() && methodrcvr.IsPtr() && method.Embedded != 0 && !types.IsInterfaceMethod(method.Type) && !(base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink) && !buildcfg.Experiment.RegabiArgs { + // generate tail call: adjust pointer receiver and jump to embedded method. + left := dot.X // skip final .M + if !left.Type().IsPtr() { + left = typecheck.NodAddr(left) + } + as := ir.NewAssignStmt(base.Pos, nthis, typecheck.ConvNop(left, rcvr)) + fn.Body.Append(as) + fn.Body.Append(ir.NewTailCallStmt(base.Pos, method.Nname.(*ir.Name))) + } else { + fn.SetWrapper(true) // ignore frame for panic+recover matching + call := ir.NewCallExpr(base.Pos, ir.OCALL, dot, nil) + call.Args = ir.ParamNames(tfn.Type()) + call.IsDDD = tfn.Type().IsVariadic() + if method.Type.NumResults() > 0 { + ret := ir.NewReturnStmt(base.Pos, nil) + ret.Results = []ir.Node{call} + fn.Body.Append(ret) + } else { + fn.Body.Append(call) + } + } + + typecheck.FinishFuncBody() + if base.Debug.DclStack != 0 { + types.CheckDclstack() + } + + typecheck.Func(fn) + ir.CurFunc = fn + typecheck.Stmts(fn.Body) + + // Inline calls within (*T).M wrappers. This is safe because we only + // generate those wrappers within the same compilation unit as (T).M. + // TODO(mdempsky): Investigate why we can't enable this more generally. + if rcvr.IsPtr() && rcvr.Elem() == method.Type.Recv().Type && rcvr.Elem().Sym() != nil { + inline.InlineCalls(fn) + } + escape.Batch([]*ir.Func{fn}, false) + + ir.CurFunc = nil + typecheck.Target.Decls = append(typecheck.Target.Decls, fn) + + return lsym +} + +var ZeroSize int64 + +// MarkTypeUsedInInterface marks that type t is converted to an interface. +// This information is used in the linker in dead method elimination. +func MarkTypeUsedInInterface(t *types.Type, from *obj.LSym) { + tsym := TypeLinksym(t) + // Emit a marker relocation. The linker will know the type is converted + // to an interface if "from" is reachable. + r := obj.Addrel(from) + r.Sym = tsym + r.Type = objabi.R_USEIFACE +} + +// MarkUsedIfaceMethod marks that an interface method is used in the current +// function. n is OCALLINTER node. +func MarkUsedIfaceMethod(n *ir.CallExpr) { + // skip unnamed functions (func _()) + if ir.CurFunc.LSym == nil { + return + } + dot := n.X.(*ir.SelectorExpr) + ityp := dot.X.Type() + tsym := TypeLinksym(ityp) + r := obj.Addrel(ir.CurFunc.LSym) + r.Sym = tsym + // dot.Xoffset is the method index * PtrSize (the offset of code pointer + // in itab). + midx := dot.Offset() / int64(types.PtrSize) + r.Add = InterfaceMethodOffset(ityp, midx) + r.Type = objabi.R_USEIFACEMETHOD +} diff --git a/src/cmd/compile/internal/riscv64/galign.go b/src/cmd/compile/internal/riscv64/galign.go index 4db0fac52e42b3e8e4069153c25a7df292b17574..338248a7cf27ab13b71ec4ed25f90a015fdc6511 100644 --- a/src/cmd/compile/internal/riscv64/galign.go +++ b/src/cmd/compile/internal/riscv64/galign.go @@ -5,11 +5,11 @@ package riscv64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/ssagen" "cmd/internal/obj/riscv" ) -func Init(arch *gc.Arch) { +func Init(arch *ssagen.ArchInfo) { arch.LinkArch = &riscv.LinkRISCV64 arch.REGSP = riscv.REG_SP diff --git a/src/cmd/compile/internal/riscv64/ggen.go b/src/cmd/compile/internal/riscv64/ggen.go index f7c03fe7c2173560c569c610490a8c8fe6ecfbc6..9df739456b9e5a50ec503fd3ccdc39d8ccec93e6 100644 --- a/src/cmd/compile/internal/riscv64/ggen.go +++ b/src/cmd/compile/internal/riscv64/ggen.go @@ -5,33 +5,36 @@ package riscv64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/riscv" ) -func zeroRange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { +func zeroRange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { if cnt == 0 { return p } // Adjust the frame to account for LR. - off += gc.Ctxt.FixedFrameSize() + off += base.Ctxt.FixedFrameSize() - if cnt < int64(4*gc.Widthptr) { - for i := int64(0); i < cnt; i += int64(gc.Widthptr) { - p = pp.Appendpp(p, riscv.AMOV, obj.TYPE_REG, riscv.REG_ZERO, 0, obj.TYPE_MEM, riscv.REG_SP, off+i) + if cnt < int64(4*types.PtrSize) { + for i := int64(0); i < cnt; i += int64(types.PtrSize) { + p = pp.Append(p, riscv.AMOV, obj.TYPE_REG, riscv.REG_ZERO, 0, obj.TYPE_MEM, riscv.REG_SP, off+i) } return p } - if cnt <= int64(128*gc.Widthptr) { - p = pp.Appendpp(p, riscv.AADDI, obj.TYPE_CONST, 0, off, obj.TYPE_REG, riscv.REG_A0, 0) + if cnt <= int64(128*types.PtrSize) { + p = pp.Append(p, riscv.AADDI, obj.TYPE_CONST, 0, off, obj.TYPE_REG, riscv.REG_A0, 0) p.Reg = riscv.REG_SP - p = pp.Appendpp(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) + p = pp.Append(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffzero - p.To.Offset = 8 * (128 - cnt/int64(gc.Widthptr)) + p.To.Sym = ir.Syms.Duffzero + p.To.Offset = 8 * (128 - cnt/int64(types.PtrSize)) return p } @@ -42,15 +45,15 @@ func zeroRange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { // MOV ZERO, (T0) // ADD $Widthptr, T0 // BNE T0, T1, loop - p = pp.Appendpp(p, riscv.AADD, obj.TYPE_CONST, 0, off, obj.TYPE_REG, riscv.REG_T0, 0) + p = pp.Append(p, riscv.AADD, obj.TYPE_CONST, 0, off, obj.TYPE_REG, riscv.REG_T0, 0) p.Reg = riscv.REG_SP - p = pp.Appendpp(p, riscv.AADD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, riscv.REG_T1, 0) + p = pp.Append(p, riscv.AADD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, riscv.REG_T1, 0) p.Reg = riscv.REG_T0 - p = pp.Appendpp(p, riscv.AMOV, obj.TYPE_REG, riscv.REG_ZERO, 0, obj.TYPE_MEM, riscv.REG_T0, 0) + p = pp.Append(p, riscv.AMOV, obj.TYPE_REG, riscv.REG_ZERO, 0, obj.TYPE_MEM, riscv.REG_T0, 0) loop := p - p = pp.Appendpp(p, riscv.AADD, obj.TYPE_CONST, 0, int64(gc.Widthptr), obj.TYPE_REG, riscv.REG_T0, 0) - p = pp.Appendpp(p, riscv.ABNE, obj.TYPE_REG, riscv.REG_T0, 0, obj.TYPE_BRANCH, 0, 0) + p = pp.Append(p, riscv.AADD, obj.TYPE_CONST, 0, int64(types.PtrSize), obj.TYPE_REG, riscv.REG_T0, 0) + p = pp.Append(p, riscv.ABNE, obj.TYPE_REG, riscv.REG_T0, 0, obj.TYPE_BRANCH, 0, 0) p.Reg = riscv.REG_T1 - gc.Patch(p, loop) + p.To.SetTarget(loop) return p } diff --git a/src/cmd/compile/internal/riscv64/gsubr.go b/src/cmd/compile/internal/riscv64/gsubr.go index d40bdf7a1d3627487e6a30e9d98b57a8a51989e4..74bccf8d42ab1b00cad26b7c06e79b8824bf294e 100644 --- a/src/cmd/compile/internal/riscv64/gsubr.go +++ b/src/cmd/compile/internal/riscv64/gsubr.go @@ -5,12 +5,12 @@ package riscv64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/objw" "cmd/internal/obj" "cmd/internal/obj/riscv" ) -func ginsnop(pp *gc.Progs) *obj.Prog { +func ginsnop(pp *objw.Progs) *obj.Prog { // Hardware nop is ADD $0, ZERO p := pp.Prog(riscv.AADD) p.From.Type = obj.TYPE_CONST diff --git a/src/cmd/compile/internal/riscv64/ssa.go b/src/cmd/compile/internal/riscv64/ssa.go index 0beb5b4bd180a2460fb69e57311416a9b391202f..64a9b3b33b9aca34cfd15284ff14eb765a67d52a 100644 --- a/src/cmd/compile/internal/riscv64/ssa.go +++ b/src/cmd/compile/internal/riscv64/ssa.go @@ -5,8 +5,10 @@ package riscv64 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/riscv" @@ -91,7 +93,7 @@ func loadByType(t *types.Type) obj.As { case 8: return riscv.AMOVD default: - gc.Fatalf("unknown float width for load %d in type %v", width, t) + base.Fatalf("unknown float width for load %d in type %v", width, t) return 0 } } @@ -118,7 +120,7 @@ func loadByType(t *types.Type) obj.As { case 8: return riscv.AMOV default: - gc.Fatalf("unknown width for load %d in type %v", width, t) + base.Fatalf("unknown width for load %d in type %v", width, t) return 0 } } @@ -134,7 +136,7 @@ func storeByType(t *types.Type) obj.As { case 8: return riscv.AMOVD default: - gc.Fatalf("unknown float width for store %d in type %v", width, t) + base.Fatalf("unknown float width for store %d in type %v", width, t) return 0 } } @@ -149,7 +151,7 @@ func storeByType(t *types.Type) obj.As { case 8: return riscv.AMOV default: - gc.Fatalf("unknown width for store %d in type %v", width, t) + base.Fatalf("unknown width for store %d in type %v", width, t) return 0 } } @@ -178,9 +180,9 @@ func largestMove(alignment int64) (obj.As, int64) { // markMoves marks any MOVXconst ops that need to avoid clobbering flags. // RISC-V has no flags, so this is a no-op. -func ssaMarkMoves(s *gc.SSAGenState, b *ssa.Block) {} +func ssaMarkMoves(s *ssagen.State, b *ssa.Block) {} -func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { +func ssaGenValue(s *ssagen.State, v *ssa.Value) { s.SetPos(v.Pos) switch v.Op { @@ -189,7 +191,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.OpArg: // input args need no code case ssa.OpPhi: - gc.CheckLoweredPhi(v) + ssagen.CheckLoweredPhi(v) case ssa.OpCopy, ssa.OpRISCV64MOVconvert, ssa.OpRISCV64MOVDreg: if v.Type.IsMemory() { return @@ -209,9 +211,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = rd case ssa.OpRISCV64MOVDnop: - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } // nothing to do case ssa.OpLoadReg: if v.Type.IsFlags() { @@ -219,7 +218,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { return } p := s.Prog(loadByType(v.Type)) - gc.AddrAuto(&p.From, v.Args[0]) + ssagen.AddrAuto(&p.From, v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpStoreReg: @@ -230,7 +229,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(storeByType(v.Type)) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() - gc.AddrAuto(&p.To, v) + ssagen.AddrAuto(&p.To, v) case ssa.OpSP, ssa.OpSB, ssa.OpGetG: // nothing to do case ssa.OpRISCV64MOVBreg, ssa.OpRISCV64MOVHreg, ssa.OpRISCV64MOVWreg, @@ -302,7 +301,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.Reg = v.Args[0].Reg() p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() - case ssa.OpRISCV64MOVBconst, ssa.OpRISCV64MOVHconst, ssa.OpRISCV64MOVWconst, ssa.OpRISCV64MOVDconst: + case ssa.OpRISCV64MOVDconst: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = v.AuxInt @@ -321,10 +320,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { v.Fatalf("aux is of unknown type %T", v.Aux) case *obj.LSym: wantreg = "SB" - gc.AddAux(&p.From, v) - case *gc.Node: + ssagen.AddAux(&p.From, v) + case *ir.Name: wantreg = "SP" - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) case nil: // No sym, just MOVW $off(SP), R wantreg = "SP" @@ -340,7 +339,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpRISCV64MOVBstore, ssa.OpRISCV64MOVHstore, ssa.OpRISCV64MOVWstore, ssa.OpRISCV64MOVDstore, @@ -350,14 +349,14 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpRISCV64MOVBstorezero, ssa.OpRISCV64MOVHstorezero, ssa.OpRISCV64MOVWstorezero, ssa.OpRISCV64MOVDstorezero: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG p.From.Reg = riscv.REG_ZERO p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpRISCV64SEQZ, ssa.OpRISCV64SNEZ: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG @@ -375,7 +374,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.BoundsCheckFunc[v.AuxInt] + p.To.Sym = ssagen.BoundsCheckFunc[v.AuxInt] s.UseArgs(16) // space used in callee args area by assembly stubs case ssa.OpRISCV64LoweredAtomicLoad8: @@ -500,7 +499,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p4.From.Reg = riscv.REG_TMP p4.Reg = riscv.REG_ZERO p4.To.Type = obj.TYPE_BRANCH - gc.Patch(p4, p1) + p4.To.SetTarget(p1) p5 := s.Prog(riscv.AMOV) p5.From.Type = obj.TYPE_CONST @@ -509,7 +508,15 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p5.To.Reg = out p6 := s.Prog(obj.ANOP) - gc.Patch(p2, p6) + p2.To.SetTarget(p6) + + case ssa.OpRISCV64LoweredAtomicAnd32, ssa.OpRISCV64LoweredAtomicOr32: + p := s.Prog(v.Op.Asm()) + p.From.Type = obj.TYPE_REG + p.From.Reg = v.Args[1].Reg() + p.To.Type = obj.TYPE_MEM + p.To.Reg = v.Args[0].Reg() + p.RegTo2 = riscv.REG_ZERO case ssa.OpRISCV64LoweredZero: mov, sz := largestMove(v.AuxInt) @@ -535,7 +542,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p3.Reg = v.Args[0].Reg() p3.From.Type = obj.TYPE_REG p3.From.Reg = v.Args[1].Reg() - gc.Patch(p3, p) + p3.To.SetTarget(p) case ssa.OpRISCV64LoweredMove: mov, sz := largestMove(v.AuxInt) @@ -575,7 +582,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p5.Reg = v.Args[1].Reg() p5.From.Type = obj.TYPE_REG p5.From.Reg = v.Args[2].Reg() - gc.Patch(p5, p) + p5.To.SetTarget(p) case ssa.OpRISCV64LoweredNilCheck: // Issue a load which will fault if arg is nil. @@ -583,22 +590,22 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(riscv.AMOVB) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = riscv.REG_ZERO - if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos == 1 in generated wrappers - gc.Warnl(v.Pos, "generated nil check") + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Pos == 1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") } case ssa.OpRISCV64LoweredGetClosurePtr: // Closure pointer is S4 (riscv.REG_CTXT). - gc.CheckLoweredGetClosurePtr(v) + ssagen.CheckLoweredGetClosurePtr(v) case ssa.OpRISCV64LoweredGetCallerSP: // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(riscv.AMOV) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -gc.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.FixedFrameSize() p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -612,16 +619,19 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ADUFFZERO) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffzero + p.To.Sym = ir.Syms.Duffzero p.To.Offset = v.AuxInt case ssa.OpRISCV64DUFFCOPY: p := s.Prog(obj.ADUFFCOPY) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.Duffcopy + p.To.Sym = ir.Syms.Duffcopy p.To.Offset = v.AuxInt + case ssa.OpClobber, ssa.OpClobberReg: + // TODO: implement for clobberdead experiment. Nop is ok for now. + default: v.Fatalf("Unhandled op %v", v.Op) } @@ -642,7 +652,7 @@ var blockBranch = [...]obj.As{ ssa.BlockRISCV64BNEZ: riscv.ABNEZ, } -func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { s.SetPos(b.Pos) switch b.Kind { @@ -655,17 +665,17 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.From.Type = obj.TYPE_REG p.From.Reg = riscv.REG_ZERO p.Reg = riscv.REG_A0 - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[1].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()}) if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockPlain: if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockExit: case ssa.BlockRet: diff --git a/src/cmd/compile/internal/s390x/galign.go b/src/cmd/compile/internal/s390x/galign.go index cb68fd36c14bf682d4861f080f03d10c15440fe1..b004a2db0a39b3f61c9b8e1d93d648d3d44bcd0a 100644 --- a/src/cmd/compile/internal/s390x/galign.go +++ b/src/cmd/compile/internal/s390x/galign.go @@ -5,11 +5,11 @@ package s390x import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/ssagen" "cmd/internal/obj/s390x" ) -func Init(arch *gc.Arch) { +func Init(arch *ssagen.ArchInfo) { arch.LinkArch = &s390x.Links390x arch.REGSP = s390x.REGSP arch.MAXWIDTH = 1 << 50 diff --git a/src/cmd/compile/internal/s390x/ggen.go b/src/cmd/compile/internal/s390x/ggen.go index 5a837d85742367acba04fe01381b3e9671632b71..488a080c468886db6d769269ff5387c93dc33447 100644 --- a/src/cmd/compile/internal/s390x/ggen.go +++ b/src/cmd/compile/internal/s390x/ggen.go @@ -5,7 +5,8 @@ package s390x import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/objw" "cmd/internal/obj" "cmd/internal/obj/s390x" ) @@ -17,20 +18,20 @@ import ( const clearLoopCutoff = 1024 // zerorange clears the stack in the given range. -func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { +func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { if cnt == 0 { return p } // Adjust the frame to account for LR. - off += gc.Ctxt.FixedFrameSize() + off += base.Ctxt.FixedFrameSize() reg := int16(s390x.REGSP) // If the off cannot fit in a 12-bit unsigned displacement then we // need to create a copy of the stack pointer that we can adjust. // We also need to do this if we are going to loop. if off < 0 || off > 4096-clearLoopCutoff || cnt > clearLoopCutoff { - p = pp.Appendpp(p, s390x.AADD, obj.TYPE_CONST, 0, off, obj.TYPE_REG, s390x.REGRT1, 0) + p = pp.Append(p, s390x.AADD, obj.TYPE_CONST, 0, off, obj.TYPE_REG, s390x.REGRT1, 0) p.Reg = int16(s390x.REGSP) reg = s390x.REGRT1 off = 0 @@ -39,12 +40,12 @@ func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { // Generate a loop of large clears. if cnt > clearLoopCutoff { ireg := int16(s390x.REGRT2) // register holds number of remaining loop iterations - p = pp.Appendpp(p, s390x.AMOVD, obj.TYPE_CONST, 0, cnt/256, obj.TYPE_REG, ireg, 0) - p = pp.Appendpp(p, s390x.ACLEAR, obj.TYPE_CONST, 0, 256, obj.TYPE_MEM, reg, off) + p = pp.Append(p, s390x.AMOVD, obj.TYPE_CONST, 0, cnt/256, obj.TYPE_REG, ireg, 0) + p = pp.Append(p, s390x.ACLEAR, obj.TYPE_CONST, 0, 256, obj.TYPE_MEM, reg, off) pl := p - p = pp.Appendpp(p, s390x.AADD, obj.TYPE_CONST, 0, 256, obj.TYPE_REG, reg, 0) - p = pp.Appendpp(p, s390x.ABRCTG, obj.TYPE_REG, ireg, 0, obj.TYPE_BRANCH, 0, 0) - gc.Patch(p, pl) + p = pp.Append(p, s390x.AADD, obj.TYPE_CONST, 0, 256, obj.TYPE_REG, reg, 0) + p = pp.Append(p, s390x.ABRCTG, obj.TYPE_REG, ireg, 0, obj.TYPE_BRANCH, 0, 0) + p.To.SetTarget(pl) cnt = cnt % 256 } @@ -69,11 +70,11 @@ func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { case 2: ins = s390x.AMOVH } - p = pp.Appendpp(p, ins, obj.TYPE_CONST, 0, 0, obj.TYPE_MEM, reg, off) + p = pp.Append(p, ins, obj.TYPE_CONST, 0, 0, obj.TYPE_MEM, reg, off) // Handle clears that would require multiple move instructions with CLEAR (assembled as XC). default: - p = pp.Appendpp(p, s390x.ACLEAR, obj.TYPE_CONST, 0, n, obj.TYPE_MEM, reg, off) + p = pp.Append(p, s390x.ACLEAR, obj.TYPE_CONST, 0, n, obj.TYPE_MEM, reg, off) } cnt -= n @@ -83,6 +84,6 @@ func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { return p } -func ginsnop(pp *gc.Progs) *obj.Prog { +func ginsnop(pp *objw.Progs) *obj.Prog { return pp.Prog(s390x.ANOPH) } diff --git a/src/cmd/compile/internal/s390x/ssa.go b/src/cmd/compile/internal/s390x/ssa.go index 8037357131c79fac0f6ee350a398d994fa5fcaed..ddc05b36add0fa1b9fd22c3697c12be3b124e6eb 100644 --- a/src/cmd/compile/internal/s390x/ssa.go +++ b/src/cmd/compile/internal/s390x/ssa.go @@ -7,16 +7,17 @@ package s390x import ( "math" - "cmd/compile/internal/gc" + "cmd/compile/internal/base" "cmd/compile/internal/logopt" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/s390x" ) // markMoves marks any MOVXconst ops that need to avoid clobbering flags. -func ssaMarkMoves(s *gc.SSAGenState, b *ssa.Block) { +func ssaMarkMoves(s *ssagen.State, b *ssa.Block) { flive := b.FlagsLiveAtEnd for _, c := range b.ControlValues() { flive = c.Type.IsFlags() || flive @@ -134,7 +135,7 @@ func moveByType(t *types.Type) obj.As { // dest := dest(To) op src(From) // and also returns the created obj.Prog so it // may be further adjusted (offset, scale, etc). -func opregreg(s *gc.SSAGenState, op obj.As, dest, src int16) *obj.Prog { +func opregreg(s *ssagen.State, op obj.As, dest, src int16) *obj.Prog { p := s.Prog(op) p.From.Type = obj.TYPE_REG p.To.Type = obj.TYPE_REG @@ -147,7 +148,7 @@ func opregreg(s *gc.SSAGenState, op obj.As, dest, src int16) *obj.Prog { // dest := src(From) op off // and also returns the created obj.Prog so it // may be further adjusted (offset, scale, etc). -func opregregimm(s *gc.SSAGenState, op obj.As, dest, src int16, off int64) *obj.Prog { +func opregregimm(s *ssagen.State, op obj.As, dest, src int16, off int64) *obj.Prog { p := s.Prog(op) p.From.Type = obj.TYPE_CONST p.From.Offset = off @@ -157,7 +158,7 @@ func opregregimm(s *gc.SSAGenState, op obj.As, dest, src int16, off int64) *obj. return p } -func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { +func ssaGenValue(s *ssagen.State, v *ssa.Value) { switch v.Op { case ssa.OpS390XSLD, ssa.OpS390XSLW, ssa.OpS390XSRD, ssa.OpS390XSRW, @@ -174,10 +175,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.Reg = r1 } case ssa.OpS390XRXSBG: - r1 := v.Reg() - if r1 != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } r2 := v.Args[1].Reg() i := v.Aux.(s390x.RotateParams) p := s.Prog(v.Op.Asm()) @@ -187,7 +184,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { {Type: obj.TYPE_CONST, Offset: int64(i.Amount)}, {Type: obj.TYPE_REG, Reg: r2}, }) - p.To = obj.Addr{Type: obj.TYPE_REG, Reg: r1} + p.To = obj.Addr{Type: obj.TYPE_REG, Reg: v.Reg()} case ssa.OpS390XRISBGZ: r1 := v.Reg() r2 := v.Args[0].Reg() @@ -232,12 +229,8 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.Reg = r2 } case ssa.OpS390XADDE, ssa.OpS390XSUBE: - r1 := v.Reg0() - if r1 != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } r2 := v.Args[1].Reg() - opregreg(s, v.Op.Asm(), r1, r2) + opregreg(s, v.Op.Asm(), v.Reg0(), r2) case ssa.OpS390XADDCconst: r1 := v.Reg0() r3 := v.Args[0].Reg() @@ -247,18 +240,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.OpS390XMULLD, ssa.OpS390XMULLW, ssa.OpS390XMULHD, ssa.OpS390XMULHDU, ssa.OpS390XFMULS, ssa.OpS390XFMUL, ssa.OpS390XFDIVS, ssa.OpS390XFDIV: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } - opregreg(s, v.Op.Asm(), r, v.Args[1].Reg()) + opregreg(s, v.Op.Asm(), v.Reg(), v.Args[1].Reg()) case ssa.OpS390XFSUBS, ssa.OpS390XFSUB, ssa.OpS390XFADDS, ssa.OpS390XFADD: - r := v.Reg0() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } - opregreg(s, v.Op.Asm(), r, v.Args[1].Reg()) + opregreg(s, v.Op.Asm(), v.Reg0(), v.Args[1].Reg()) case ssa.OpS390XMLGR: // MLGR Rx R3 -> R2:R3 r0 := v.Args[0].Reg() @@ -273,10 +258,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG case ssa.OpS390XFMADD, ssa.OpS390XFMADDS, ssa.OpS390XFMSUB, ssa.OpS390XFMSUBS: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } r1 := v.Args[1].Reg() r2 := v.Args[2].Reg() p := s.Prog(v.Op.Asm()) @@ -284,7 +265,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = r1 p.Reg = r2 p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.OpS390XFIDBR: switch v.AuxInt { case 0, 1, 3, 4, 5, 6, 7: @@ -360,15 +341,11 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpS390XANDconst, ssa.OpS390XANDWconst, ssa.OpS390XORconst, ssa.OpS390XORWconst, ssa.OpS390XXORconst, ssa.OpS390XXORWconst: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = v.AuxInt p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.OpS390XSLDconst, ssa.OpS390XSLWconst, ssa.OpS390XSRDconst, ssa.OpS390XSRWconst, ssa.OpS390XSRADconst, ssa.OpS390XSRAWconst, @@ -394,14 +371,14 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Type = obj.TYPE_ADDR p.From.Reg = r p.From.Index = i - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpS390XMOVDaddr: p := s.Prog(s390x.AMOVD) p.From.Type = obj.TYPE_ADDR p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpS390XCMP, ssa.OpS390XCMPW, ssa.OpS390XCMPU, ssa.OpS390XCMPWU: @@ -440,16 +417,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.OpS390XANDWload, ssa.OpS390XANDload, ssa.OpS390XORWload, ssa.OpS390XORload, ssa.OpS390XXORWload, ssa.OpS390XXORload: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[1].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.OpS390XMOVDload, ssa.OpS390XMOVWZload, ssa.OpS390XMOVHZload, ssa.OpS390XMOVBZload, ssa.OpS390XMOVDBRload, ssa.OpS390XMOVWBRload, ssa.OpS390XMOVHBRload, @@ -458,7 +431,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpS390XMOVBZloadidx, ssa.OpS390XMOVHZloadidx, ssa.OpS390XMOVWZloadidx, @@ -475,7 +448,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = r p.From.Scale = 1 p.From.Index = i - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpS390XMOVBstore, ssa.OpS390XMOVHstore, ssa.OpS390XMOVWstore, ssa.OpS390XMOVDstore, @@ -486,7 +459,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpS390XMOVBstoreidx, ssa.OpS390XMOVHstoreidx, ssa.OpS390XMOVWstoreidx, ssa.OpS390XMOVDstoreidx, ssa.OpS390XMOVHBRstoreidx, ssa.OpS390XMOVWBRstoreidx, ssa.OpS390XMOVDBRstoreidx, ssa.OpS390XFMOVSstoreidx, ssa.OpS390XFMOVDstoreidx: @@ -502,15 +475,15 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Reg = r p.To.Scale = 1 p.To.Index = i - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpS390XMOVDstoreconst, ssa.OpS390XMOVWstoreconst, ssa.OpS390XMOVHstoreconst, ssa.OpS390XMOVBstoreconst: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST sc := v.AuxValAndOff() - p.From.Offset = sc.Val() + p.From.Offset = sc.Val64() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux2(&p.To, v, sc.Off()) + ssagen.AddAux2(&p.To, v, sc.Off64()) case ssa.OpS390XMOVBreg, ssa.OpS390XMOVHreg, ssa.OpS390XMOVWreg, ssa.OpS390XMOVBZreg, ssa.OpS390XMOVHZreg, ssa.OpS390XMOVWZreg, ssa.OpS390XLDGR, ssa.OpS390XLGDR, @@ -526,10 +499,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST sc := v.AuxValAndOff() - p.From.Offset = sc.Val() + p.From.Offset = sc.Val64() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux2(&p.To, v, sc.Off()) + ssagen.AddAux2(&p.To, v, sc.Off64()) case ssa.OpCopy: if v.Type.IsMemory() { return @@ -545,7 +518,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { return } p := s.Prog(loadByType(v.Type)) - gc.AddrAuto(&p.From, v.Args[0]) + ssagen.AddrAuto(&p.From, v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpStoreReg: @@ -556,10 +529,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(storeByType(v.Type)) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() - gc.AddrAuto(&p.To, v) + ssagen.AddrAuto(&p.To, v) case ssa.OpS390XLoweredGetClosurePtr: // Closure pointer is R12 (already) - gc.CheckLoweredGetClosurePtr(v) + ssagen.CheckLoweredGetClosurePtr(v) case ssa.OpS390XLoweredRound32F, ssa.OpS390XLoweredRound64F: // input is already rounded case ssa.OpS390XLoweredGetG: @@ -573,7 +546,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(s390x.AMOVD) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -gc.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.FixedFrameSize() p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -592,7 +565,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.BoundsCheckFunc[v.AuxInt] + p.To.Sym = ssagen.BoundsCheckFunc[v.AuxInt] s.UseArgs(16) // space used in callee args area by assembly stubs case ssa.OpS390XFLOGR, ssa.OpS390XPOPCNT, ssa.OpS390XNEG, ssa.OpS390XNEGW, @@ -607,17 +580,13 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.OpS390XSumBytes2, ssa.OpS390XSumBytes4, ssa.OpS390XSumBytes8: v.Fatalf("SumBytes generated %s", v.LongString()) case ssa.OpS390XLOCGR: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = int64(v.Aux.(s390x.CCMask)) p.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_REG - p.To.Reg = r - case ssa.OpS390XFSQRT: + p.To.Reg = v.Reg() + case ssa.OpS390XFSQRTS, ssa.OpS390XFSQRT: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() @@ -636,28 +605,28 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(s390x.AMOVBZ) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = s390x.REGTMP if logopt.Enabled() { logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name) } - if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers - gc.Warnl(v.Pos, "generated nil check") + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") } case ssa.OpS390XMVC: vo := v.AuxValAndOff() p := s.Prog(s390x.AMVC) p.From.Type = obj.TYPE_CONST - p.From.Offset = vo.Val() + p.From.Offset = vo.Val64() p.SetFrom3(obj.Addr{ Type: obj.TYPE_MEM, Reg: v.Args[1].Reg(), - Offset: vo.Off(), + Offset: vo.Off64(), }) p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - p.To.Offset = vo.Off() + p.To.Offset = vo.Off64() case ssa.OpS390XSTMG2, ssa.OpS390XSTMG3, ssa.OpS390XSTMG4, ssa.OpS390XSTM2, ssa.OpS390XSTM3, ssa.OpS390XSTM4: for i := 2; i < len(v.Args)-1; i++ { @@ -671,7 +640,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.Reg = v.Args[len(v.Args)-2].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpS390XLoweredMove: // Inputs must be valid pointers to memory, // so adjust arg0 and arg1 as part of the expansion. @@ -708,7 +677,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { bne := s.Prog(s390x.ABLT) bne.To.Type = obj.TYPE_BRANCH - gc.Patch(bne, mvc) + bne.To.SetTarget(mvc) if v.AuxInt > 0 { mvc := s.Prog(s390x.AMVC) @@ -750,7 +719,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { bne := s.Prog(s390x.ABLT) bne.To.Type = obj.TYPE_BRANCH - gc.Patch(bne, clear) + bne.To.SetTarget(clear) if v.AuxInt > 0 { clear := s.Prog(s390x.ACLEAR) @@ -763,7 +732,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg0() case ssa.OpS390XMOVBatomicstore, ssa.OpS390XMOVWatomicstore, ssa.OpS390XMOVDatomicstore: @@ -772,7 +741,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpS390XLAN, ssa.OpS390XLAO: // LA(N|O) Ry, TMP, 0(Rx) op := s.Prog(v.Op.Asm()) @@ -807,7 +776,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.OpS390XLoweredAtomicCas32, ssa.OpS390XLoweredAtomicCas64: // Convert the flags output of CS{,G} into a bool. // CS{,G} arg1, arg2, arg0 @@ -823,7 +792,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { cs.Reg = v.Args[2].Reg() // new cs.To.Type = obj.TYPE_MEM cs.To.Reg = v.Args[0].Reg() - gc.AddAux(&cs.To, v) + ssagen.AddAux(&cs.To, v) // MOVD $0, ret movd := s.Prog(s390x.AMOVD) @@ -845,7 +814,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // NOP (so the BNE has somewhere to land) nop := s.Prog(obj.ANOP) - gc.Patch(bne, nop) + bne.To.SetTarget(nop) case ssa.OpS390XLoweredAtomicExchange32, ssa.OpS390XLoweredAtomicExchange64: // Loop until the CS{,G} succeeds. // MOV{WZ,D} arg0, ret @@ -858,7 +827,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { load.From.Reg = v.Args[0].Reg() load.To.Type = obj.TYPE_REG load.To.Reg = v.Reg0() - gc.AddAux(&load.From, v) + ssagen.AddAux(&load.From, v) // CS{,G} ret, arg1, arg0 cs := s.Prog(v.Op.Asm()) @@ -867,15 +836,15 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { cs.Reg = v.Args[1].Reg() // new cs.To.Type = obj.TYPE_MEM cs.To.Reg = v.Args[0].Reg() - gc.AddAux(&cs.To, v) + ssagen.AddAux(&cs.To, v) // BNE cs bne := s.Prog(s390x.ABNE) bne.To.Type = obj.TYPE_BRANCH - gc.Patch(bne, cs) + bne.To.SetTarget(cs) case ssa.OpS390XSYNC: s.Prog(s390x.ASYNC) - case ssa.OpClobber: + case ssa.OpClobber, ssa.OpClobberReg: // TODO: implement for clobberdead experiment. Nop is ok for now. default: v.Fatalf("genValue not implemented: %s", v.LongString()) @@ -907,14 +876,14 @@ func blockAsm(b *ssa.Block) obj.As { panic("unreachable") } -func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { // Handle generic blocks first. switch b.Kind { case ssa.BlockPlain: if b.Succs[0].Block() != next { p := s.Prog(s390x.ABR) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } return case ssa.BlockDefer: @@ -925,7 +894,7 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.From.Type = obj.TYPE_CONST p.From.Offset = int64(s390x.NotEqual & s390x.NotUnordered) // unordered is not possible p.Reg = s390x.REG_R3 - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: 0}) + p.SetFrom3Const(0) if b.Succs[0].Block() != next { s.Br(s390x.ABR, b.Succs[0].Block()) } @@ -968,17 +937,17 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.From.Type = obj.TYPE_CONST p.From.Offset = int64(mask & s390x.NotUnordered) // unordered is not possible p.Reg = b.Controls[0].Reg() - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: b.Controls[1].Reg()}) + p.SetFrom3Reg(b.Controls[1].Reg()) case ssa.BlockS390XCGIJ, ssa.BlockS390XCIJ: p.From.Type = obj.TYPE_CONST p.From.Offset = int64(mask & s390x.NotUnordered) // unordered is not possible p.Reg = b.Controls[0].Reg() - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: int64(int8(b.AuxInt))}) + p.SetFrom3Const(int64(int8(b.AuxInt))) case ssa.BlockS390XCLGIJ, ssa.BlockS390XCLIJ: p.From.Type = obj.TYPE_CONST p.From.Offset = int64(mask & s390x.NotUnordered) // unordered is not possible p.Reg = b.Controls[0].Reg() - p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: int64(uint8(b.AuxInt))}) + p.SetFrom3Const(int64(uint8(b.AuxInt))) default: b.Fatalf("branch not implemented: %s", b.LongString()) } diff --git a/src/cmd/compile/internal/ssa/README.md b/src/cmd/compile/internal/ssa/README.md index 4483c2c85f3a9b05b2f35bc5cd80ec8d5a96ea56..833bf1ddc9fa770fc2fd75a311716d617fb82e62 100644 --- a/src/cmd/compile/internal/ssa/README.md +++ b/src/cmd/compile/internal/ssa/README.md @@ -184,6 +184,19 @@ compile passes, making it easy to see what each pass does to a particular program. You can also click on values and blocks to highlight them, to help follow the control flow and values. +The value specified in GOSSAFUNC can also be a package-qualified function +name, e.g. + + GOSSAFUNC=blah.Foo go build + +This will match any function named "Foo" within a package whose final +suffix is "blah" (e.g. something/blah.Foo, anotherthing/extra/blah.Foo). + +If non-HTML dumps are needed, append a "+" to the GOSSAFUNC value +and dumps will be written to stdout: + + GOSSAFUNC=Bar+ go build + diff --git a/src/cmd/compile/internal/ssa/bench_test.go b/src/cmd/compile/internal/ssa/bench_test.go new file mode 100644 index 0000000000000000000000000000000000000000..09716675071ee0e11c609a1c90134569054727fe --- /dev/null +++ b/src/cmd/compile/internal/ssa/bench_test.go @@ -0,0 +1,32 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package ssa + +import ( + "math/rand" + "testing" +) + +var d int + +//go:noinline +func fn(a, b int) bool { + c := false + if a > 0 { + if b < 0 { + d = d + 1 + } + c = true + } + return c +} + +func BenchmarkPhioptPass(b *testing.B) { + for i := 0; i < b.N; i++ { + a := rand.Perm(i/10 + 10) + for i := 1; i < len(a)/2; i++ { + fn(a[i]-a[i-1], a[i+len(a)/2-2]-a[i+len(a)/2-1]) + } + } +} diff --git a/src/cmd/compile/internal/ssa/block.go b/src/cmd/compile/internal/ssa/block.go index 519ac214caef220ddae40f6cd7b49048fba4fc3e..71ca774431e33cb99a4344d1aaa6e341a052997a 100644 --- a/src/cmd/compile/internal/ssa/block.go +++ b/src/cmd/compile/internal/ssa/block.go @@ -52,7 +52,7 @@ type Block struct { Controls [2]*Value // Auxiliary info for the block. Its value depends on the Kind. - Aux interface{} + Aux Aux AuxInt int64 // The unordered set of Values that define the operation of this block. @@ -358,6 +358,22 @@ func (b *Block) AuxIntString() string { } } +// likelyBranch reports whether block b is the likely branch of all of its predecessors. +func (b *Block) likelyBranch() bool { + if len(b.Preds) == 0 { + return false + } + for _, e := range b.Preds { + p := e.b + if len(p.Succs) == 1 || len(p.Succs) == 2 && (p.Likely == BranchLikely && p.Succs[0].b == b || + p.Likely == BranchUnlikely && p.Succs[1].b == b) { + continue + } + return false + } + return true +} + func (b *Block) Logf(msg string, args ...interface{}) { b.Func.Logf(msg, args...) } func (b *Block) Log() bool { return b.Func.Log() } func (b *Block) Fatalf(msg string, args ...interface{}) { b.Func.Fatalf(msg, args...) } diff --git a/src/cmd/compile/internal/ssa/check.go b/src/cmd/compile/internal/ssa/check.go index 2dade7a88d9470009e7e489df52924052541dd6f..969fd96dbf573cf67483a1f740f5880fb5d8940e 100644 --- a/src/cmd/compile/internal/ssa/check.go +++ b/src/cmd/compile/internal/ssa/check.go @@ -166,7 +166,7 @@ func checkFunc(f *Func) { f.Fatalf("value %v has an AuxInt that encodes a NaN", v) } case auxString: - if _, ok := v.Aux.(string); !ok { + if _, ok := v.Aux.(stringAux); !ok { f.Fatalf("value %v has Aux type %T, want string", v, v.Aux) } canHaveAux = true @@ -182,6 +182,12 @@ func checkFunc(f *Func) { f.Fatalf("value %v has Aux type %T, want *AuxCall", v, v.Aux) } canHaveAux = true + case auxNameOffsetInt8: + if _, ok := v.Aux.(*AuxNameOffset); !ok { + f.Fatalf("value %v has Aux type %T, want *AuxNameOffset", v, v.Aux) + } + canHaveAux = true + canHaveAuxInt = true case auxSym, auxTyp: canHaveAux = true case auxSymOff, auxSymValAndOff, auxTypSize: diff --git a/src/cmd/compile/internal/ssa/compile.go b/src/cmd/compile/internal/ssa/compile.go index 63994d1778ed029a9d59f8042db4af96837c2ac0..cd8eba405d5c7bc95fa23175aca1652bde7b13db 100644 --- a/src/cmd/compile/internal/ssa/compile.go +++ b/src/cmd/compile/internal/ssa/compile.go @@ -6,10 +6,10 @@ package ssa import ( "bytes" - "cmd/internal/objabi" "cmd/internal/src" "fmt" "hash/crc32" + "internal/buildcfg" "log" "math/rand" "os" @@ -297,6 +297,11 @@ enables time reporting for all phases -d=ssa/prove/debug=2 sets debugging level to 2 in the prove pass +Be aware that when "/debug=X" is applied to a pass, some passes +will emit debug output for all functions, and other passes will +only emit debug output for functions that match the current +GOSSAFUNC value. + Multiple flags can be passed at once, by separating them with commas. For example: @@ -431,7 +436,6 @@ var passes = [...]pass{ {name: "early copyelim", fn: copyelim}, {name: "early deadcode", fn: deadcode}, // remove generated dead code to avoid doing pointless work during opt {name: "short circuit", fn: shortcircuit}, - {name: "decompose args", fn: decomposeArgs, required: !go116lateCallExpansion, disabled: go116lateCallExpansion}, // handled by late call lowering {name: "decompose user", fn: decomposeUser, required: true}, {name: "pre-opt deadcode", fn: deadcode}, {name: "opt", fn: opt, required: true}, // NB: some generic rules know the name of the opt pass. TODO: split required rules and optimizing rules @@ -455,7 +459,7 @@ var passes = [...]pass{ {name: "dse", fn: dse}, {name: "writebarrier", fn: writebarrier, required: true}, // expand write barrier ops {name: "insert resched checks", fn: insertLoopReschedChecks, - disabled: objabi.Preemptibleloops_enabled == 0}, // insert resched checks in loops. + disabled: !buildcfg.Experiment.PreemptibleLoops}, // insert resched checks in loops. {name: "lower", fn: lower, required: true}, {name: "addressing modes", fn: addressingModes, required: false}, {name: "lowered deadcode for cse", fn: deadcode}, // deadcode immediately before CSE avoids CSE making dead values live again diff --git a/src/cmd/compile/internal/ssa/config.go b/src/cmd/compile/internal/ssa/config.go index 0fe0337ddfa309c4d6bab1538fd31faf5a5bfaef..a8393a19995a5f922a419fdd899acedf76a12bef 100644 --- a/src/cmd/compile/internal/ssa/config.go +++ b/src/cmd/compile/internal/ssa/config.go @@ -5,10 +5,12 @@ package ssa import ( + "cmd/compile/internal/abi" + "cmd/compile/internal/ir" "cmd/compile/internal/types" "cmd/internal/obj" - "cmd/internal/objabi" "cmd/internal/src" + "internal/buildcfg" ) // A Config holds readonly compilation information. @@ -19,30 +21,33 @@ type Config struct { PtrSize int64 // 4 or 8; copy of cmd/internal/sys.Arch.PtrSize RegSize int64 // 4 or 8; copy of cmd/internal/sys.Arch.RegSize Types Types - lowerBlock blockRewriter // lowering function - lowerValue valueRewriter // lowering function - splitLoad valueRewriter // function for splitting merged load ops; only used on some architectures - registers []Register // machine registers - gpRegMask regMask // general purpose integer register mask - fpRegMask regMask // floating point register mask - fp32RegMask regMask // floating point register mask - fp64RegMask regMask // floating point register mask - specialRegMask regMask // special register mask - GCRegMap []*Register // garbage collector register map, by GC register index - FPReg int8 // register number of frame pointer, -1 if not used - LinkReg int8 // register number of link register if it is a general purpose register, -1 if not used - hasGReg bool // has hardware g register - ctxt *obj.Link // Generic arch information - optimize bool // Do optimization - noDuffDevice bool // Don't use Duff's device - useSSE bool // Use SSE for non-float operations - useAvg bool // Use optimizations that need Avg* operations - useHmul bool // Use optimizations that need Hmul* operations - SoftFloat bool // - Race bool // race detector enabled - NeedsFpScratch bool // No direct move between GP and FP register sets - BigEndian bool // - UseFMA bool // Use hardware FMA operation + lowerBlock blockRewriter // lowering function + lowerValue valueRewriter // lowering function + splitLoad valueRewriter // function for splitting merged load ops; only used on some architectures + registers []Register // machine registers + gpRegMask regMask // general purpose integer register mask + fpRegMask regMask // floating point register mask + fp32RegMask regMask // floating point register mask + fp64RegMask regMask // floating point register mask + specialRegMask regMask // special register mask + intParamRegs []int8 // register numbers of integer param (in/out) registers + floatParamRegs []int8 // register numbers of floating param (in/out) registers + ABI1 *abi.ABIConfig // "ABIInternal" under development // TODO change comment when this becomes current + ABI0 *abi.ABIConfig + GCRegMap []*Register // garbage collector register map, by GC register index + FPReg int8 // register number of frame pointer, -1 if not used + LinkReg int8 // register number of link register if it is a general purpose register, -1 if not used + hasGReg bool // has hardware g register + ctxt *obj.Link // Generic arch information + optimize bool // Do optimization + noDuffDevice bool // Don't use Duff's device + useSSE bool // Use SSE for non-float operations + useAvg bool // Use optimizations that need Avg* operations + useHmul bool // Use optimizations that need Hmul* operations + SoftFloat bool // + Race bool // race detector enabled + BigEndian bool // + UseFMA bool // Use hardware FMA operation } type ( @@ -138,17 +143,10 @@ type Frontend interface { // Auto returns a Node for an auto variable of the given type. // The SSA compiler uses this function to allocate space for spills. - Auto(src.XPos, *types.Type) GCNode + Auto(src.XPos, *types.Type) *ir.Name // Given the name for a compound type, returns the name we should use // for the parts of that compound type. - SplitString(LocalSlot) (LocalSlot, LocalSlot) - SplitInterface(LocalSlot) (LocalSlot, LocalSlot) - SplitSlice(LocalSlot) (LocalSlot, LocalSlot, LocalSlot) - SplitComplex(LocalSlot) (LocalSlot, LocalSlot) - SplitStruct(LocalSlot, int) LocalSlot - SplitArray(LocalSlot) LocalSlot // array must be length 1 - SplitInt64(LocalSlot) (LocalSlot, LocalSlot) // returns (hi, lo) SplitSlot(parent *LocalSlot, suffix string, offset int64, t *types.Type) LocalSlot // DerefItab dereferences an itab function @@ -178,32 +176,6 @@ type Frontend interface { MyImportPath() string } -// interface used to hold a *gc.Node (a stack variable). -// We'd use *gc.Node directly but that would lead to an import cycle. -type GCNode interface { - Typ() *types.Type - String() string - IsSynthetic() bool - IsAutoTmp() bool - StorageClass() StorageClass -} - -type StorageClass uint8 - -const ( - ClassAuto StorageClass = iota // local stack variable - ClassParam // argument - ClassParamOut // return value -) - -const go116lateCallExpansion = true - -// LateCallExpansionEnabledWithin returns true if late call expansion should be tested -// within compilation of a function/method. -func LateCallExpansionEnabledWithin(f *Func) bool { - return go116lateCallExpansion -} - // NewConfig returns a new configuration object for the given architecture. func NewConfig(arch string, types Types, ctxt *obj.Link, optimize bool) *Config { c := &Config{arch: arch, Types: types} @@ -219,9 +191,12 @@ func NewConfig(arch string, types Types, ctxt *obj.Link, optimize bool) *Config c.registers = registersAMD64[:] c.gpRegMask = gpRegMaskAMD64 c.fpRegMask = fpRegMaskAMD64 + c.specialRegMask = specialRegMaskAMD64 + c.intParamRegs = paramIntRegAMD64 + c.floatParamRegs = paramFloatRegAMD64 c.FPReg = framepointerRegAMD64 c.LinkReg = linkRegAMD64 - c.hasGReg = false + c.hasGReg = buildcfg.Experiment.RegabiG case "386": c.PtrSize = 4 c.RegSize = 4 @@ -256,7 +231,7 @@ func NewConfig(arch string, types Types, ctxt *obj.Link, optimize bool) *Config c.FPReg = framepointerRegARM64 c.LinkReg = linkRegARM64 c.hasGReg = true - c.noDuffDevice = objabi.GOOS == "darwin" || objabi.GOOS == "ios" // darwin linker cannot handle BR26 reloc with non-zero addend + c.noDuffDevice = buildcfg.GOOS == "darwin" || buildcfg.GOOS == "ios" // darwin linker cannot handle BR26 reloc with non-zero addend case "ppc64": c.BigEndian = true fallthrough @@ -350,8 +325,11 @@ func NewConfig(arch string, types Types, ctxt *obj.Link, optimize bool) *Config c.useSSE = true c.UseFMA = true + c.ABI0 = abi.NewABIConfig(0, 0, ctxt.FixedFrameSize()) + c.ABI1 = abi.NewABIConfig(len(c.intParamRegs), len(c.floatParamRegs), ctxt.FixedFrameSize()) + // On Plan 9, floating point operations are not allowed in note handler. - if objabi.GOOS == "plan9" { + if buildcfg.GOOS == "plan9" { // Don't use FMA on Plan 9 c.UseFMA = false diff --git a/src/cmd/compile/internal/ssa/copyelim.go b/src/cmd/compile/internal/ssa/copyelim.go index 5954d3bec8ef8298a58fa90fe2008767eddb93df..17f65127ee0da8d105e7dedafce5098aa3269146 100644 --- a/src/cmd/compile/internal/ssa/copyelim.go +++ b/src/cmd/compile/internal/ssa/copyelim.go @@ -26,7 +26,7 @@ func copyelim(f *Func) { // Update named values. for _, name := range f.Names { - values := f.NamedValues[name] + values := f.NamedValues[*name] for i, v := range values { if v.Op == OpCopy { values[i] = v.Args[0] diff --git a/src/cmd/compile/internal/ssa/cse.go b/src/cmd/compile/internal/ssa/cse.go index 3b4f2be37e7beabe44ec6e569be52d4660604056..ade5e0648e78d8ed992747e4865b49ffa7715e1e 100644 --- a/src/cmd/compile/internal/ssa/cse.go +++ b/src/cmd/compile/internal/ssa/cse.go @@ -275,7 +275,7 @@ func lt2Cmp(isLt bool) types.Cmp { return types.CMPgt } -type auxmap map[interface{}]int32 +type auxmap map[Aux]int32 func cmpVal(v, w *Value, auxIDs auxmap) types.Cmp { // Try to order these comparison by cost (cheaper first) @@ -299,7 +299,7 @@ func cmpVal(v, w *Value, auxIDs auxmap) types.Cmp { // OpSelect is a pseudo-op. We need to be more aggressive // regarding CSE to keep multiple OpSelect's of the same // argument from existing. - if v.Op != OpSelect0 && v.Op != OpSelect1 { + if v.Op != OpSelect0 && v.Op != OpSelect1 && v.Op != OpSelectN { if tc := v.Type.Compare(w.Type); tc != types.CMPeq { return tc } diff --git a/src/cmd/compile/internal/ssa/cse_test.go b/src/cmd/compile/internal/ssa/cse_test.go index 9e76645f54fa5bf77df40eaecaff0f19ddcc98b8..8052016f3af6928fde492839c8b521b1d814aa63 100644 --- a/src/cmd/compile/internal/ssa/cse_test.go +++ b/src/cmd/compile/internal/ssa/cse_test.go @@ -14,6 +14,8 @@ type tstAux struct { s string } +func (*tstAux) CanBeAnSSAAux() {} + // This tests for a bug found when partitioning, but not sorting by the Aux value. func TestCSEAuxPartitionBug(t *testing.T) { c := testConfig(t) diff --git a/src/cmd/compile/internal/ssa/deadcode.go b/src/cmd/compile/internal/ssa/deadcode.go index 96b552ecf3bfa4d3f361aca2ccf01389921c7622..5d10dfe025e97ccd2dcad6af9500eeb04f7df40f 100644 --- a/src/cmd/compile/internal/ssa/deadcode.go +++ b/src/cmd/compile/internal/ssa/deadcode.go @@ -223,7 +223,7 @@ func deadcode(f *Func) { for _, name := range f.Names { j := 0 s.clear() - values := f.NamedValues[name] + values := f.NamedValues[*name] for _, v := range values { if live[v.ID] && !s.contains(v.ID) { values[j] = v @@ -232,19 +232,19 @@ func deadcode(f *Func) { } } if j == 0 { - delete(f.NamedValues, name) + delete(f.NamedValues, *name) } else { f.Names[i] = name i++ for k := len(values) - 1; k >= j; k-- { values[k] = nil } - f.NamedValues[name] = values[:j] + f.NamedValues[*name] = values[:j] } } clearNames := f.Names[i:] for j := range clearNames { - clearNames[j] = LocalSlot{} + clearNames[j] = nil } f.Names = f.Names[:i] diff --git a/src/cmd/compile/internal/ssa/deadstore.go b/src/cmd/compile/internal/ssa/deadstore.go index 0664013b397ed0668552fe2174d0351cb21623a5..d694133ec3bb8dc17d67e61770026a585846bb62 100644 --- a/src/cmd/compile/internal/ssa/deadstore.go +++ b/src/cmd/compile/internal/ssa/deadstore.go @@ -5,6 +5,7 @@ package ssa import ( + "cmd/compile/internal/ir" "cmd/compile/internal/types" "cmd/internal/src" ) @@ -136,9 +137,9 @@ func dse(f *Func) { // reaches stores then we delete all the stores. The other operations will then // be eliminated by the dead code elimination pass. func elimDeadAutosGeneric(f *Func) { - addr := make(map[*Value]GCNode) // values that the address of the auto reaches - elim := make(map[*Value]GCNode) // values that could be eliminated if the auto is - used := make(map[GCNode]bool) // used autos that must be kept + addr := make(map[*Value]*ir.Name) // values that the address of the auto reaches + elim := make(map[*Value]*ir.Name) // values that could be eliminated if the auto is + var used ir.NameSet // used autos that must be kept // visit the value and report whether any of the maps are updated visit := func(v *Value) (changed bool) { @@ -146,8 +147,8 @@ func elimDeadAutosGeneric(f *Func) { switch v.Op { case OpAddr, OpLocalAddr: // Propagate the address if it points to an auto. - n, ok := v.Aux.(GCNode) - if !ok || n.StorageClass() != ClassAuto { + n, ok := v.Aux.(*ir.Name) + if !ok || n.Class != ir.PAUTO { return } if addr[v] == nil { @@ -157,8 +158,8 @@ func elimDeadAutosGeneric(f *Func) { return case OpVarDef, OpVarKill: // v should be eliminated if we eliminate the auto. - n, ok := v.Aux.(GCNode) - if !ok || n.StorageClass() != ClassAuto { + n, ok := v.Aux.(*ir.Name) + if !ok || n.Class != ir.PAUTO { return } if elim[v] == nil { @@ -173,12 +174,12 @@ func elimDeadAutosGeneric(f *Func) { // for open-coded defers from being removed (since they // may not be used by the inline code, but will be used by // panic processing). - n, ok := v.Aux.(GCNode) - if !ok || n.StorageClass() != ClassAuto { + n, ok := v.Aux.(*ir.Name) + if !ok || n.Class != ir.PAUTO { return } - if !used[n] { - used[n] = true + if !used.Has(n) { + used.Add(n) changed = true } return @@ -200,8 +201,9 @@ func elimDeadAutosGeneric(f *Func) { panic("unhandled op with sym effect") } - if v.Uses == 0 && v.Op != OpNilCheck || len(args) == 0 { + if v.Uses == 0 && v.Op != OpNilCheck && !v.Op.IsCall() && !v.Op.HasSideEffects() || len(args) == 0 { // Nil check has no use, but we need to keep it. + // Also keep calls and values that have side effects. return } @@ -211,8 +213,8 @@ func elimDeadAutosGeneric(f *Func) { if v.Type.IsMemory() || v.Type.IsFlags() || v.Op == OpPhi || v.MemoryArg() != nil { for _, a := range args { if n, ok := addr[a]; ok { - if !used[n] { - used[n] = true + if !used.Has(n) { + used.Add(n) changed = true } } @@ -221,9 +223,9 @@ func elimDeadAutosGeneric(f *Func) { } // Propagate any auto addresses through v. - node := GCNode(nil) + var node *ir.Name for _, a := range args { - if n, ok := addr[a]; ok && !used[n] { + if n, ok := addr[a]; ok && !used.Has(n) { if node == nil { node = n } else if node != n { @@ -232,7 +234,7 @@ func elimDeadAutosGeneric(f *Func) { // multiple pointers (e.g. NeqPtr, Phi etc.). // This is rare, so just propagate the first // value to keep things simple. - used[n] = true + used.Add(n) changed = true } } @@ -248,7 +250,7 @@ func elimDeadAutosGeneric(f *Func) { } if addr[v] != node { // This doesn't happen in practice, but catch it just in case. - used[node] = true + used.Add(node) changed = true } return @@ -268,8 +270,8 @@ func elimDeadAutosGeneric(f *Func) { } // keep the auto if its address reaches a control value for _, c := range b.ControlValues() { - if n, ok := addr[c]; ok && !used[n] { - used[n] = true + if n, ok := addr[c]; ok && !used.Has(n) { + used.Add(n) changed = true } } @@ -281,7 +283,7 @@ func elimDeadAutosGeneric(f *Func) { // Eliminate stores to unread autos. for v, n := range elim { - if used[n] { + if used.Has(n) { continue } // replace with OpCopy @@ -298,15 +300,15 @@ func elimUnreadAutos(f *Func) { // Loop over all ops that affect autos taking note of which // autos we need and also stores that we might be able to // eliminate. - seen := make(map[GCNode]bool) + var seen ir.NameSet var stores []*Value for _, b := range f.Blocks { for _, v := range b.Values { - n, ok := v.Aux.(GCNode) + n, ok := v.Aux.(*ir.Name) if !ok { continue } - if n.StorageClass() != ClassAuto { + if n.Class != ir.PAUTO { continue } @@ -316,7 +318,7 @@ func elimUnreadAutos(f *Func) { // If we haven't seen the auto yet // then this might be a store we can // eliminate. - if !seen[n] { + if !seen.Has(n) { stores = append(stores, v) } default: @@ -326,7 +328,7 @@ func elimUnreadAutos(f *Func) { // because dead loads haven't been // eliminated yet. if v.Uses > 0 { - seen[n] = true + seen.Add(n) } } } @@ -334,8 +336,8 @@ func elimUnreadAutos(f *Func) { // Eliminate stores to unread autos. for _, store := range stores { - n, _ := store.Aux.(GCNode) - if seen[n] { + n, _ := store.Aux.(*ir.Name) + if seen.Has(n) { continue } diff --git a/src/cmd/compile/internal/ssa/debug.go b/src/cmd/compile/internal/ssa/debug.go index 6353f7289785880aa734606e8453f4a398cc1d8a..8e2872363b6edbceaa5a3c1c00c396569092b9bc 100644 --- a/src/cmd/compile/internal/ssa/debug.go +++ b/src/cmd/compile/internal/ssa/debug.go @@ -5,10 +5,15 @@ package ssa import ( + "cmd/compile/internal/abi" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" "cmd/internal/dwarf" "cmd/internal/obj" + "cmd/internal/src" "encoding/hex" "fmt" + "internal/buildcfg" "math/bits" "sort" "strings" @@ -24,7 +29,7 @@ type FuncDebug struct { // Slots is all the slots used in the debug info, indexed by their SlotID. Slots []LocalSlot // The user variables, indexed by VarID. - Vars []GCNode + Vars []*ir.Name // The slots that make up each variable, indexed by VarID. VarSlots [][]SlotID // The location list data, indexed by VarID. Must be processed by PutLocationList. @@ -142,13 +147,19 @@ func (loc VarLoc) absent() bool { var BlockStart = &Value{ ID: -10000, Op: OpInvalid, - Aux: "BlockStart", + Aux: StringToAux("BlockStart"), } var BlockEnd = &Value{ ID: -20000, Op: OpInvalid, - Aux: "BlockEnd", + Aux: StringToAux("BlockEnd"), +} + +var FuncEnd = &Value{ + ID: -30000, + Op: OpInvalid, + Aux: StringToAux("FuncEnd"), } // RegisterSet is a bitmap of registers, indexed by Register.num. @@ -165,7 +176,7 @@ func (s *debugState) logf(msg string, args ...interface{}) { type debugState struct { // See FuncDebug. slots []LocalSlot - vars []GCNode + vars []*ir.Name varSlots [][]SlotID lists [][]byte @@ -189,7 +200,7 @@ type debugState struct { // The pending location list entry for each user variable, indexed by VarID. pendingEntries []pendingEntry - varParts map[GCNode][]SlotID + varParts map[*ir.Name][]SlotID blockDebug []BlockDebug pendingSlotLocs []VarLoc liveSlots []liveSlot @@ -327,6 +338,216 @@ func (s *debugState) stateString(state stateAtPC) string { return strings.Join(strs, "") } +// slotCanonicalizer is a table used to lookup and canonicalize +// LocalSlot's in a type insensitive way (e.g. taking into account the +// base name, offset, and width of the slot, but ignoring the slot +// type). +type slotCanonicalizer struct { + slmap map[slotKey]SlKeyIdx + slkeys []LocalSlot +} + +func newSlotCanonicalizer() *slotCanonicalizer { + return &slotCanonicalizer{ + slmap: make(map[slotKey]SlKeyIdx), + slkeys: []LocalSlot{LocalSlot{N: nil}}, + } +} + +type SlKeyIdx uint32 + +const noSlot = SlKeyIdx(0) + +// slotKey is a type-insensitive encapsulation of a LocalSlot; it +// is used to key a map within slotCanonicalizer. +type slotKey struct { + name *ir.Name + offset int64 + width int64 + splitOf SlKeyIdx // idx in slkeys slice in slotCanonicalizer + splitOffset int64 +} + +// lookup looks up a LocalSlot in the slot canonicalizer "sc", returning +// a canonical index for the slot, and adding it to the table if need +// be. Return value is the canonical slot index, and a boolean indicating +// whether the slot was found in the table already (TRUE => found). +func (sc *slotCanonicalizer) lookup(ls LocalSlot) (SlKeyIdx, bool) { + split := noSlot + if ls.SplitOf != nil { + split, _ = sc.lookup(*ls.SplitOf) + } + k := slotKey{ + name: ls.N, offset: ls.Off, width: ls.Type.Width, + splitOf: split, splitOffset: ls.SplitOffset, + } + if idx, ok := sc.slmap[k]; ok { + return idx, true + } + rv := SlKeyIdx(len(sc.slkeys)) + sc.slkeys = append(sc.slkeys, ls) + sc.slmap[k] = rv + return rv, false +} + +func (sc *slotCanonicalizer) canonSlot(idx SlKeyIdx) LocalSlot { + return sc.slkeys[idx] +} + +// PopulateABIInRegArgOps examines the entry block of the function +// and looks for incoming parameters that have missing or partial +// OpArg{Int,Float}Reg values, inserting additional values in +// cases where they are missing. Example: +// +// func foo(s string, used int, notused int) int { +// return len(s) + used +// } +// +// In the function above, the incoming parameter "used" is fully live, +// "notused" is not live, and "s" is partially live (only the length +// field of the string is used). At the point where debug value +// analysis runs, we might expect to see an entry block with: +// +// b1: +// v4 = ArgIntReg {s+8} [0] : BX +// v5 = ArgIntReg {used} [0] : CX +// +// While this is an accurate picture of the live incoming params, +// we also want to have debug locations for non-live params (or +// their non-live pieces), e.g. something like +// +// b1: +// v9 = ArgIntReg <*uint8> {s+0} [0] : AX +// v4 = ArgIntReg {s+8} [0] : BX +// v5 = ArgIntReg {used} [0] : CX +// v10 = ArgIntReg {unused} [0] : DI +// +// This function examines the live OpArg{Int,Float}Reg values and +// synthesizes new (dead) values for the non-live params or the +// non-live pieces of partially live params. +// +func PopulateABIInRegArgOps(f *Func) { + pri := f.ABISelf.ABIAnalyzeFuncType(f.Type.FuncType()) + + // When manufacturing new slots that correspond to splits of + // composite parameters, we want to avoid creating a new sub-slot + // that differs from some existing sub-slot only by type, since + // the debug location analysis will treat that slot as a separate + // entity. To achieve this, create a lookup table of existing + // slots that is type-insenstitive. + sc := newSlotCanonicalizer() + for _, sl := range f.Names { + sc.lookup(*sl) + } + + // Add slot -> value entry to f.NamedValues if not already present. + addToNV := func(v *Value, sl LocalSlot) { + values, ok := f.NamedValues[sl] + if !ok { + // Haven't seen this slot yet. + sla := f.localSlotAddr(sl) + f.Names = append(f.Names, sla) + } else { + for _, ev := range values { + if v == ev { + return + } + } + } + values = append(values, v) + f.NamedValues[sl] = values + } + + newValues := []*Value{} + + abiRegIndexToRegister := func(reg abi.RegIndex) int8 { + i := f.ABISelf.FloatIndexFor(reg) + if i >= 0 { // float PR + return f.Config.floatParamRegs[i] + } else { + return f.Config.intParamRegs[reg] + } + } + + // Helper to construct a new OpArg{Float,Int}Reg op value. + var pos src.XPos + if len(f.Entry.Values) != 0 { + pos = f.Entry.Values[0].Pos + } + synthesizeOpIntFloatArg := func(n *ir.Name, t *types.Type, reg abi.RegIndex, sl LocalSlot) *Value { + aux := &AuxNameOffset{n, sl.Off} + op, auxInt := ArgOpAndRegisterFor(reg, f.ABISelf) + v := f.newValueNoBlock(op, t, pos) + v.AuxInt = auxInt + v.Aux = aux + v.Args = nil + v.Block = f.Entry + newValues = append(newValues, v) + addToNV(v, sl) + f.setHome(v, &f.Config.registers[abiRegIndexToRegister(reg)]) + return v + } + + // Make a pass through the entry block looking for + // OpArg{Int,Float}Reg ops. Record the slots they use in a table + // ("sc"). We use a type-insensitive lookup for the slot table, + // since the type we get from the ABI analyzer won't always match + // what the compiler uses when creating OpArg{Int,Float}Reg ops. + for _, v := range f.Entry.Values { + if v.Op == OpArgIntReg || v.Op == OpArgFloatReg { + aux := v.Aux.(*AuxNameOffset) + sl := LocalSlot{N: aux.Name, Type: v.Type, Off: aux.Offset} + // install slot in lookup table + idx, _ := sc.lookup(sl) + // add to f.NamedValues if not already present + addToNV(v, sc.canonSlot(idx)) + } else if v.Op.IsCall() { + // if we hit a call, we've gone too far. + break + } + } + + // Now make a pass through the ABI in-params, looking for params + // or pieces of params that we didn't encounter in the loop above. + for _, inp := range pri.InParams() { + if !isNamedRegParam(inp) { + continue + } + n := inp.Name.(*ir.Name) + + // Param is spread across one or more registers. Walk through + // each piece to see whether we've seen an arg reg op for it. + types, offsets := inp.RegisterTypesAndOffsets() + for k, t := range types { + // Note: this recipe for creating a LocalSlot is designed + // to be compatible with the one used in expand_calls.go + // as opposed to decompose.go. The expand calls code just + // takes the base name and creates an offset into it, + // without using the SplitOf/SplitOffset fields. The code + // in decompose.go does the opposite -- it creates a + // LocalSlot object with "Off" set to zero, but with + // SplitOf pointing to a parent slot, and SplitOffset + // holding the offset into the parent object. + pieceSlot := LocalSlot{N: n, Type: t, Off: offsets[k]} + + // Look up this piece to see if we've seen a reg op + // for it. If not, create one. + _, found := sc.lookup(pieceSlot) + if !found { + // This slot doesn't appear in the map, meaning it + // corresponds to an in-param that is not live, or + // a portion of an in-param that is not live/used. + // Add a new dummy OpArg{Int,Float}Reg for it. + synthesizeOpIntFloatArg(n, t, inp.Registers[k], + pieceSlot) + } + } + } + + // Insert the new values into the head of the block. + f.Entry.Values = append(newValues, f.Entry.Values...) +} + // BuildFuncDebug returns debug information for f. // f must be fully processed, so that each Value is where it will be when // machine code is emitted. @@ -341,12 +562,16 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset fu state.stackOffset = stackOffset state.ctxt = ctxt + if buildcfg.Experiment.RegabiArgs { + PopulateABIInRegArgOps(f) + } + if state.loggingEnabled { state.logf("Generating location lists for function %q\n", f.Name) } if state.varParts == nil { - state.varParts = make(map[GCNode][]SlotID) + state.varParts = make(map[*ir.Name][]SlotID) } else { for n := range state.varParts { delete(state.varParts, n) @@ -359,12 +584,12 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset fu state.slots = state.slots[:0] state.vars = state.vars[:0] for i, slot := range f.Names { - state.slots = append(state.slots, slot) - if slot.N.IsSynthetic() { + state.slots = append(state.slots, *slot) + if ir.IsSynthetic(slot.N) { continue } - topSlot := &slot + topSlot := slot for topSlot.SplitOf != nil { topSlot = topSlot.SplitOf } @@ -379,8 +604,8 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset fu for _, b := range f.Blocks { for _, v := range b.Values { if v.Op == OpVarDef || v.Op == OpVarKill { - n := v.Aux.(GCNode) - if n.IsSynthetic() { + n := v.Aux.(*ir.Name) + if ir.IsSynthetic(n) { continue } @@ -425,10 +650,10 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset fu state.initializeCache(f, len(state.varParts), len(state.slots)) for i, slot := range f.Names { - if slot.N.IsSynthetic() { + if ir.IsSynthetic(slot.N) { continue } - for _, value := range f.NamedValues[slot] { + for _, value := range f.NamedValues[*slot] { state.valueNames[value.ID] = append(state.valueNames[value.ID], SlotID(i)) } } @@ -717,8 +942,8 @@ func (state *debugState) processValue(v *Value, vSlots []SlotID, vReg *Register) switch { case v.Op == OpVarDef, v.Op == OpVarKill: - n := v.Aux.(GCNode) - if n.IsSynthetic() { + n := v.Aux.(*ir.Name) + if ir.IsSynthetic(n) { break } @@ -890,8 +1115,14 @@ func (state *debugState) buildLocationLists(blockLocs []*BlockDebug) { continue } + mustBeFirst := func(v *Value) bool { + return v.Op == OpPhi || v.Op.isLoweredGetClosurePtr() || + v.Op == OpArgIntReg || v.Op == OpArgFloatReg + } + zeroWidthPending := false - apcChangedSize := 0 // size of changedVars for leading Args, Phi, ClosurePtr + blockPrologComplete := false // set to true at first non-zero-width op + apcChangedSize := 0 // size of changedVars for leading Args, Phi, ClosurePtr // expect to see values in pattern (apc)* (zerowidth|real)* for _, v := range b.Values { slots := state.valueNames[v.ID] @@ -900,16 +1131,16 @@ func (state *debugState) buildLocationLists(blockLocs []*BlockDebug) { if opcodeTable[v.Op].zeroWidth { if changed { - if v.Op == OpArg || v.Op == OpPhi || v.Op.isLoweredGetClosurePtr() { + if mustBeFirst(v) || v.Op == OpArg { // These ranges begin at true beginning of block, not after first instruction - if zeroWidthPending { - b.Func.Fatalf("Unexpected op mixed with OpArg/OpPhi/OpLoweredGetClosurePtr at beginning of block %s in %s\n%s", b, b.Func.Name, b.Func) + if blockPrologComplete && mustBeFirst(v) { + panic(fmt.Errorf("Unexpected placement of op '%s' appearing after non-pseudo-op at beginning of block %s in %s\n%s", v.LongString(), b, b.Func.Name, b.Func)) } apcChangedSize = len(state.changedVars.contents()) + // Other zero-width ops must wait on a "real" op. + zeroWidthPending = true continue } - // Other zero-width ops must wait on a "real" op. - zeroWidthPending = true } continue } @@ -920,6 +1151,7 @@ func (state *debugState) buildLocationLists(blockLocs []*BlockDebug) { // Not zero-width; i.e., a "real" instruction. zeroWidthPending = false + blockPrologComplete = true for i, varID := range state.changedVars.contents() { if i < apcChangedSize { // buffered true start-of-block changes state.updateVar(VarID(varID), v.Block, BlockStart) @@ -947,7 +1179,7 @@ func (state *debugState) buildLocationLists(blockLocs []*BlockDebug) { // Flush any leftover entries live at the end of the last block. for varID := range state.lists { - state.writePendingEntry(VarID(varID), state.f.Blocks[len(state.f.Blocks)-1].ID, BlockEnd.ID) + state.writePendingEntry(VarID(varID), state.f.Blocks[len(state.f.Blocks)-1].ID, FuncEnd.ID) list := state.lists[varID] if state.loggingEnabled { if len(list) == 0 { @@ -1117,8 +1349,11 @@ func (debugInfo *FuncDebug) PutLocationList(list []byte, ctxt *obj.Link, listSym listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, 0) } -// Pack a value and block ID into an address-sized uint, returning ~0 if they -// don't fit. +// Pack a value and block ID into an address-sized uint, returning encoded +// value and boolean indicating whether the encoding succeeded. For +// 32-bit architectures the process may fail for very large procedures +// (the theory being that it's ok to have degraded debug quality in +// this case). func encodeValue(ctxt *obj.Link, b, v ID) (uint64, bool) { if ctxt.Arch.PtrSize == 8 { result := uint64(b)<<32 | uint64(uint32(v)) @@ -1185,3 +1420,276 @@ func readPtr(ctxt *obj.Link, buf []byte) uint64 { } } + +// setupLocList creates the initial portion of a location list for a +// user variable. It emits the encoded start/end of the range and a +// placeholder for the size. Return value is the new list plus the +// slot in the list holding the size (to be updated later). +func setupLocList(ctxt *obj.Link, f *Func, list []byte, st, en ID) ([]byte, int) { + start, startOK := encodeValue(ctxt, f.Entry.ID, st) + end, endOK := encodeValue(ctxt, f.Entry.ID, en) + if !startOK || !endOK { + // This could happen if someone writes a function that uses + // >65K values on a 32-bit platform. Hopefully a degraded debugging + // experience is ok in that case. + return nil, 0 + } + list = appendPtr(ctxt, list, start) + list = appendPtr(ctxt, list, end) + + // Where to write the length of the location description once + // we know how big it is. + sizeIdx := len(list) + list = list[:len(list)+2] + return list, sizeIdx +} + +// locatePrologEnd walks the entry block of a function with incoming +// register arguments and locates the last instruction in the prolog +// that spills a register arg. It returns the ID of that instruction +// Example: +// +// b1: +// v3 = ArgIntReg {p1+0} [0] : AX +// ... more arg regs .. +// v4 = ArgFloatReg {f1+0} [0] : X0 +// v52 = MOVQstore {p1} v2 v3 v1 +// ... more stores ... +// v68 = MOVSSstore {f4} v2 v67 v66 +// v38 = MOVQstoreconst {blob} [val=0,off=0] v2 v32 +// +// Important: locatePrologEnd is expected to work properly only with +// optimization turned off (e.g. "-N"). If optimization is enabled +// we can't be assured of finding all input arguments spilled in the +// entry block prolog. +func locatePrologEnd(f *Func) ID { + + // returns true if this instruction looks like it moves an ABI + // register to the stack, along with the value being stored. + isRegMoveLike := func(v *Value) (bool, ID) { + n, ok := v.Aux.(*ir.Name) + var r ID + if !ok || n.Class != ir.PPARAM { + return false, r + } + regInputs, memInputs, spInputs := 0, 0, 0 + for _, a := range v.Args { + if a.Op == OpArgIntReg || a.Op == OpArgFloatReg { + regInputs++ + r = a.ID + } else if a.Type.IsMemory() { + memInputs++ + } else if a.Op == OpSP { + spInputs++ + } else { + return false, r + } + } + return v.Type.IsMemory() && memInputs == 1 && + regInputs == 1 && spInputs == 1, r + } + + // OpArg*Reg values we've seen so far on our forward walk, + // for which we have not yet seen a corresponding spill. + regArgs := make([]ID, 0, 32) + + // removeReg tries to remove a value from regArgs, returning true + // if found and removed, or false otherwise. + removeReg := func(r ID) bool { + for i := 0; i < len(regArgs); i++ { + if regArgs[i] == r { + regArgs = append(regArgs[:i], regArgs[i+1:]...) + return true + } + } + return false + } + + // Walk forwards through the block. When we see OpArg*Reg, record + // the value it produces in the regArgs list. When see a store that uses + // the value, remove the entry. When we hit the last store (use) + // then we've arrived at the end of the prolog. + for k, v := range f.Entry.Values { + if v.Op == OpArgIntReg || v.Op == OpArgFloatReg { + regArgs = append(regArgs, v.ID) + continue + } + if ok, r := isRegMoveLike(v); ok { + if removed := removeReg(r); removed { + if len(regArgs) == 0 { + // Found our last spill; return the value after + // it. Note that it is possible that this spill is + // the last instruction in the block. If so, then + // return the "end of block" sentinel. + if k < len(f.Entry.Values)-1 { + return f.Entry.Values[k+1].ID + } + return BlockEnd.ID + } + } + } + if v.Op.IsCall() { + // if we hit a call, we've gone too far. + return v.ID + } + } + // nothing found + return ID(-1) +} + +// isNamedRegParam returns true if the param corresponding to "p" +// is a named, non-blank input parameter assigned to one or more +// registers. +func isNamedRegParam(p abi.ABIParamAssignment) bool { + if p.Name == nil { + return false + } + n := p.Name.(*ir.Name) + if n.Sym() == nil || n.Sym().IsBlank() { + return false + } + if len(p.Registers) == 0 { + return false + } + return true +} + +// BuildFuncDebugNoOptimized constructs a FuncDebug object with +// entries corresponding to the register-resident input parameters for +// the function "f"; it is used when we are compiling without +// optimization but the register ABI is enabled. For each reg param, +// it constructs a 2-element location list: the first element holds +// the input register, and the second element holds the stack location +// of the param (the assumption being that when optimization is off, +// each input param reg will be spilled in the prolog. +func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset func(LocalSlot) int32) *FuncDebug { + fd := FuncDebug{} + + pri := f.ABISelf.ABIAnalyzeFuncType(f.Type.FuncType()) + + // Look to see if we have any named register-promoted parameters. + // If there are none, bail early and let the caller sort things + // out for the remainder of the params/locals. + numRegParams := 0 + for _, inp := range pri.InParams() { + if isNamedRegParam(inp) { + numRegParams++ + } + } + if numRegParams == 0 { + return &fd + } + + state := debugState{f: f} + + if loggingEnabled { + state.logf("generating -N reg param loc lists for func %q\n", f.Name) + } + + // Allocate location lists. + fd.LocationLists = make([][]byte, numRegParams) + + // Locate the value corresponding to the last spill of + // an input register. + afterPrologVal := locatePrologEnd(f) + + // Walk the input params again and process the register-resident elements. + pidx := 0 + for _, inp := range pri.InParams() { + if !isNamedRegParam(inp) { + // will be sorted out elsewhere + continue + } + + n := inp.Name.(*ir.Name) + sl := LocalSlot{N: n, Type: inp.Type, Off: 0} + fd.Vars = append(fd.Vars, n) + fd.Slots = append(fd.Slots, sl) + slid := len(fd.VarSlots) + fd.VarSlots = append(fd.VarSlots, []SlotID{SlotID(slid)}) + + if afterPrologVal == ID(-1) { + // This can happen for degenerate functions with infinite + // loops such as that in issue 45948. In such cases, leave + // the var/slot set up for the param, but don't try to + // emit a location list. + if loggingEnabled { + state.logf("locatePrologEnd failed, skipping %v\n", n) + } + pidx++ + continue + } + + // Param is arriving in one or more registers. We need a 2-element + // location expression for it. First entry in location list + // will correspond to lifetime in input registers. + list, sizeIdx := setupLocList(ctxt, f, fd.LocationLists[pidx], + BlockStart.ID, afterPrologVal) + if list == nil { + pidx++ + continue + } + if loggingEnabled { + state.logf("param %v:\n [, %d]:\n", n, afterPrologVal) + } + rtypes, _ := inp.RegisterTypesAndOffsets() + padding := make([]uint64, 0, 32) + padding = inp.ComputePadding(padding) + for k, r := range inp.Registers { + reg := ObjRegForAbiReg(r, f.Config) + dwreg := ctxt.Arch.DWARFRegisters[reg] + if dwreg < 32 { + list = append(list, dwarf.DW_OP_reg0+byte(dwreg)) + } else { + list = append(list, dwarf.DW_OP_regx) + list = dwarf.AppendUleb128(list, uint64(dwreg)) + } + if loggingEnabled { + state.logf(" piece %d -> dwreg %d", k, dwreg) + } + if len(inp.Registers) > 1 { + list = append(list, dwarf.DW_OP_piece) + ts := rtypes[k].Width + list = dwarf.AppendUleb128(list, uint64(ts)) + if padding[k] > 0 { + if loggingEnabled { + state.logf(" [pad %d bytes]", padding[k]) + } + list = append(list, dwarf.DW_OP_piece) + list = dwarf.AppendUleb128(list, padding[k]) + } + } + if loggingEnabled { + state.logf("\n") + } + } + // fill in length of location expression element + ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2)) + + // Second entry in the location list will be the stack home + // of the param, once it has been spilled. Emit that now. + list, sizeIdx = setupLocList(ctxt, f, list, + afterPrologVal, FuncEnd.ID) + if list == nil { + pidx++ + continue + } + soff := stackOffset(sl) + if soff == 0 { + list = append(list, dwarf.DW_OP_call_frame_cfa) + } else { + list = append(list, dwarf.DW_OP_fbreg) + list = dwarf.AppendSleb128(list, int64(soff)) + } + if loggingEnabled { + state.logf(" [%d, ): stackOffset=%d\n", afterPrologVal, soff) + } + + // fill in size + ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2)) + + fd.LocationLists[pidx] = list + pidx++ + } + return &fd +} diff --git a/src/cmd/compile/internal/ssa/decompose.go b/src/cmd/compile/internal/ssa/decompose.go index bf7f1e826b17ee66fae6d219d54cb037be25d496..753d69cebcd1a3b32eae9dbee113d943de009ade 100644 --- a/src/cmd/compile/internal/ssa/decompose.go +++ b/src/cmd/compile/internal/ssa/decompose.go @@ -24,7 +24,7 @@ func decomposeBuiltIn(f *Func) { } // Decompose other values - // Note: deadcode is false because we need to keep the original + // Note: Leave dead values because we need to keep the original // values around so the name component resolution below can still work. applyRewrite(f, rewriteBlockdec, rewriteValuedec, leaveDeadValues) if f.Config.RegSize == 4 { @@ -36,64 +36,65 @@ func decomposeBuiltIn(f *Func) { // accumulate new LocalSlots in newNames for addition after the iteration. This decomposition is for // builtin types with leaf components, and thus there is no need to reprocess the newly create LocalSlots. var toDelete []namedVal - var newNames []LocalSlot + var newNames []*LocalSlot for i, name := range f.Names { t := name.Type switch { case t.IsInteger() && t.Size() > f.Config.RegSize: - hiName, loName := f.fe.SplitInt64(name) - newNames = append(newNames, hiName, loName) - for j, v := range f.NamedValues[name] { + hiName, loName := f.SplitInt64(name) + newNames = maybeAppend2(f, newNames, hiName, loName) + for j, v := range f.NamedValues[*name] { if v.Op != OpInt64Make { continue } - f.NamedValues[hiName] = append(f.NamedValues[hiName], v.Args[0]) - f.NamedValues[loName] = append(f.NamedValues[loName], v.Args[1]) + f.NamedValues[*hiName] = append(f.NamedValues[*hiName], v.Args[0]) + f.NamedValues[*loName] = append(f.NamedValues[*loName], v.Args[1]) toDelete = append(toDelete, namedVal{i, j}) } case t.IsComplex(): - rName, iName := f.fe.SplitComplex(name) - newNames = append(newNames, rName, iName) - for j, v := range f.NamedValues[name] { + rName, iName := f.SplitComplex(name) + newNames = maybeAppend2(f, newNames, rName, iName) + for j, v := range f.NamedValues[*name] { if v.Op != OpComplexMake { continue } - f.NamedValues[rName] = append(f.NamedValues[rName], v.Args[0]) - f.NamedValues[iName] = append(f.NamedValues[iName], v.Args[1]) + f.NamedValues[*rName] = append(f.NamedValues[*rName], v.Args[0]) + f.NamedValues[*iName] = append(f.NamedValues[*iName], v.Args[1]) toDelete = append(toDelete, namedVal{i, j}) } case t.IsString(): - ptrName, lenName := f.fe.SplitString(name) - newNames = append(newNames, ptrName, lenName) - for j, v := range f.NamedValues[name] { + ptrName, lenName := f.SplitString(name) + newNames = maybeAppend2(f, newNames, ptrName, lenName) + for j, v := range f.NamedValues[*name] { if v.Op != OpStringMake { continue } - f.NamedValues[ptrName] = append(f.NamedValues[ptrName], v.Args[0]) - f.NamedValues[lenName] = append(f.NamedValues[lenName], v.Args[1]) + f.NamedValues[*ptrName] = append(f.NamedValues[*ptrName], v.Args[0]) + f.NamedValues[*lenName] = append(f.NamedValues[*lenName], v.Args[1]) toDelete = append(toDelete, namedVal{i, j}) } case t.IsSlice(): - ptrName, lenName, capName := f.fe.SplitSlice(name) - newNames = append(newNames, ptrName, lenName, capName) - for j, v := range f.NamedValues[name] { + ptrName, lenName, capName := f.SplitSlice(name) + newNames = maybeAppend2(f, newNames, ptrName, lenName) + newNames = maybeAppend(f, newNames, capName) + for j, v := range f.NamedValues[*name] { if v.Op != OpSliceMake { continue } - f.NamedValues[ptrName] = append(f.NamedValues[ptrName], v.Args[0]) - f.NamedValues[lenName] = append(f.NamedValues[lenName], v.Args[1]) - f.NamedValues[capName] = append(f.NamedValues[capName], v.Args[2]) + f.NamedValues[*ptrName] = append(f.NamedValues[*ptrName], v.Args[0]) + f.NamedValues[*lenName] = append(f.NamedValues[*lenName], v.Args[1]) + f.NamedValues[*capName] = append(f.NamedValues[*capName], v.Args[2]) toDelete = append(toDelete, namedVal{i, j}) } case t.IsInterface(): - typeName, dataName := f.fe.SplitInterface(name) - newNames = append(newNames, typeName, dataName) - for j, v := range f.NamedValues[name] { + typeName, dataName := f.SplitInterface(name) + newNames = maybeAppend2(f, newNames, typeName, dataName) + for j, v := range f.NamedValues[*name] { if v.Op != OpIMake { continue } - f.NamedValues[typeName] = append(f.NamedValues[typeName], v.Args[0]) - f.NamedValues[dataName] = append(f.NamedValues[dataName], v.Args[1]) + f.NamedValues[*typeName] = append(f.NamedValues[*typeName], v.Args[0]) + f.NamedValues[*dataName] = append(f.NamedValues[*dataName], v.Args[1]) toDelete = append(toDelete, namedVal{i, j}) } case t.IsFloat(): @@ -107,6 +108,18 @@ func decomposeBuiltIn(f *Func) { f.Names = append(f.Names, newNames...) } +func maybeAppend(f *Func, ss []*LocalSlot, s *LocalSlot) []*LocalSlot { + if _, ok := f.NamedValues[*s]; !ok { + f.NamedValues[*s] = nil + return append(ss, s) + } + return ss +} + +func maybeAppend2(f *Func, ss []*LocalSlot, s1, s2 *LocalSlot) []*LocalSlot { + return maybeAppend(f, maybeAppend(f, ss, s1), s2) +} + func decomposeBuiltInPhi(v *Value) { switch { case v.Type.IsInteger() && v.Type.Size() > v.Block.Func.Config.RegSize: @@ -219,10 +232,6 @@ func decomposeInterfacePhi(v *Value) { v.AddArg(data) } -func decomposeArgs(f *Func) { - applyRewrite(f, rewriteBlockdecArgs, rewriteValuedecArgs, removeDeadValues) -} - func decomposeUser(f *Func) { for _, b := range f.Blocks { for _, v := range b.Values { @@ -234,7 +243,7 @@ func decomposeUser(f *Func) { } // Split up named values into their components. i := 0 - var newNames []LocalSlot + var newNames []*LocalSlot for _, name := range f.Names { t := name.Type switch { @@ -254,7 +263,7 @@ func decomposeUser(f *Func) { // decomposeUserArrayInto creates names for the element(s) of arrays referenced // by name where possible, and appends those new names to slots, which is then // returned. -func decomposeUserArrayInto(f *Func, name LocalSlot, slots []LocalSlot) []LocalSlot { +func decomposeUserArrayInto(f *Func, name *LocalSlot, slots []*LocalSlot) []*LocalSlot { t := name.Type if t.NumElem() == 0 { // TODO(khr): Not sure what to do here. Probably nothing. @@ -265,20 +274,20 @@ func decomposeUserArrayInto(f *Func, name LocalSlot, slots []LocalSlot) []LocalS // shouldn't get here due to CanSSA f.Fatalf("array not of size 1") } - elemName := f.fe.SplitArray(name) + elemName := f.SplitArray(name) var keep []*Value - for _, v := range f.NamedValues[name] { + for _, v := range f.NamedValues[*name] { if v.Op != OpArrayMake1 { keep = append(keep, v) continue } - f.NamedValues[elemName] = append(f.NamedValues[elemName], v.Args[0]) + f.NamedValues[*elemName] = append(f.NamedValues[*elemName], v.Args[0]) } if len(keep) == 0 { // delete the name for the array as a whole - delete(f.NamedValues, name) + delete(f.NamedValues, *name) } else { - f.NamedValues[name] = keep + f.NamedValues[*name] = keep } if t.Elem().IsArray() { @@ -293,38 +302,38 @@ func decomposeUserArrayInto(f *Func, name LocalSlot, slots []LocalSlot) []LocalS // decomposeUserStructInto creates names for the fields(s) of structs referenced // by name where possible, and appends those new names to slots, which is then // returned. -func decomposeUserStructInto(f *Func, name LocalSlot, slots []LocalSlot) []LocalSlot { - fnames := []LocalSlot{} // slots for struct in name +func decomposeUserStructInto(f *Func, name *LocalSlot, slots []*LocalSlot) []*LocalSlot { + fnames := []*LocalSlot{} // slots for struct in name t := name.Type n := t.NumFields() for i := 0; i < n; i++ { - fs := f.fe.SplitStruct(name, i) + fs := f.SplitStruct(name, i) fnames = append(fnames, fs) // arrays and structs will be decomposed further, so // there's no need to record a name if !fs.Type.IsArray() && !fs.Type.IsStruct() { - slots = append(slots, fs) + slots = maybeAppend(f, slots, fs) } } makeOp := StructMakeOp(n) var keep []*Value // create named values for each struct field - for _, v := range f.NamedValues[name] { + for _, v := range f.NamedValues[*name] { if v.Op != makeOp { keep = append(keep, v) continue } for i := 0; i < len(fnames); i++ { - f.NamedValues[fnames[i]] = append(f.NamedValues[fnames[i]], v.Args[i]) + f.NamedValues[*fnames[i]] = append(f.NamedValues[*fnames[i]], v.Args[i]) } } if len(keep) == 0 { // delete the name for the struct as a whole - delete(f.NamedValues, name) + delete(f.NamedValues, *name) } else { - f.NamedValues[name] = keep + f.NamedValues[*name] = keep } // now that this f.NamedValues contains values for the struct @@ -332,10 +341,10 @@ func decomposeUserStructInto(f *Func, name LocalSlot, slots []LocalSlot) []Local for i := 0; i < n; i++ { if name.Type.FieldType(i).IsStruct() { slots = decomposeUserStructInto(f, fnames[i], slots) - delete(f.NamedValues, fnames[i]) + delete(f.NamedValues, *fnames[i]) } else if name.Type.FieldType(i).IsArray() { slots = decomposeUserArrayInto(f, fnames[i], slots) - delete(f.NamedValues, fnames[i]) + delete(f.NamedValues, *fnames[i]) } } return slots @@ -420,9 +429,10 @@ type namedVal struct { locIndex, valIndex int // f.NamedValues[f.Names[locIndex]][valIndex] = key } -// deleteNamedVals removes particular values with debugger names from f's naming data structures +// deleteNamedVals removes particular values with debugger names from f's naming data structures, +// removes all values with OpInvalid, and re-sorts the list of Names. func deleteNamedVals(f *Func, toDelete []namedVal) { - // Arrange to delete from larger indices to smaller, to ensure swap-with-end deletion does not invalid pending indices. + // Arrange to delete from larger indices to smaller, to ensure swap-with-end deletion does not invalidate pending indices. sort.Slice(toDelete, func(i, j int) bool { if toDelete[i].locIndex != toDelete[j].locIndex { return toDelete[i].locIndex > toDelete[j].locIndex @@ -434,16 +444,36 @@ func deleteNamedVals(f *Func, toDelete []namedVal) { // Get rid of obsolete names for _, d := range toDelete { loc := f.Names[d.locIndex] - vals := f.NamedValues[loc] + vals := f.NamedValues[*loc] l := len(vals) - 1 if l > 0 { vals[d.valIndex] = vals[l] - f.NamedValues[loc] = vals[:l] - } else { - delete(f.NamedValues, loc) - l = len(f.Names) - 1 - f.Names[d.locIndex] = f.Names[l] - f.Names = f.Names[:l] + } + vals[l] = nil + f.NamedValues[*loc] = vals[:l] + } + // Delete locations with no values attached. + end := len(f.Names) + for i := len(f.Names) - 1; i >= 0; i-- { + loc := f.Names[i] + vals := f.NamedValues[*loc] + last := len(vals) + for j := len(vals) - 1; j >= 0; j-- { + if vals[j].Op == OpInvalid { + last-- + vals[j] = vals[last] + vals[last] = nil + } + } + if last < len(vals) { + f.NamedValues[*loc] = vals[:last] + } + if len(vals) == 0 { + delete(f.NamedValues, *loc) + end-- + f.Names[i] = f.Names[end] + f.Names[end] = nil } } + f.Names = f.Names[:end] } diff --git a/src/cmd/compile/internal/ssa/expand_calls.go b/src/cmd/compile/internal/ssa/expand_calls.go index 679ee8ad1650d71ee663a9c11229ede0f467212e..7e973ab20591fb8ae42eddcc542f6ca6c1c67451 100644 --- a/src/cmd/compile/internal/ssa/expand_calls.go +++ b/src/cmd/compile/internal/ssa/expand_calls.go @@ -5,6 +5,9 @@ package ssa import ( + "cmd/compile/internal/abi" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/types" "cmd/internal/src" "fmt" @@ -12,261 +15,446 @@ import ( ) type selKey struct { - from *Value - offset int64 - size int64 - typ *types.Type + from *Value // what is selected from + offsetOrIndex int64 // whatever is appropriate for the selector + size int64 + typ *types.Type } -type offsetKey struct { - from *Value - offset int64 - pt *types.Type -} +type Abi1RO uint8 // An offset within a parameter's slice of register indices, for abi1. -// expandCalls converts LE (Late Expansion) calls that act like they receive value args into a lower-level form -// that is more oriented to a platform's ABI. The SelectN operations that extract results are rewritten into -// more appropriate forms, and any StructMake or ArrayMake inputs are decomposed until non-struct values are -// reached. On the callee side, OpArg nodes are not decomposed until this phase is run. -// TODO results should not be lowered until this phase. -func expandCalls(f *Func) { - // Calls that need lowering have some number of inputs, including a memory input, - // and produce a tuple of (value1, value2, ..., mem) where valueK may or may not be SSA-able. +func isBlockMultiValueExit(b *Block) bool { + return (b.Kind == BlockRet || b.Kind == BlockRetJmp) && len(b.Controls) > 0 && b.Controls[0].Op == OpMakeResult +} - // With the current ABI those inputs need to be converted into stores to memory, - // rethreading the call's memory input to the first, and the new call now receiving the last. +func badVal(s string, v *Value) error { + return fmt.Errorf("%s %s", s, v.LongString()) +} - // With the current ABI, the outputs need to be converted to loads, which will all use the call's - // memory output as their input. - if !LateCallExpansionEnabledWithin(f) { - return +// removeTrivialWrapperTypes unwraps layers of +// struct { singleField SomeType } and [1]SomeType +// until a non-wrapper type is reached. This is useful +// for working with assignments to/from interface data +// fields (either second operand to OpIMake or OpIData) +// where the wrapping or type conversion can be elided +// because of type conversions/assertions in source code +// that do not appear in SSA. +func removeTrivialWrapperTypes(t *types.Type) *types.Type { + for { + if t.IsStruct() && t.NumFields() == 1 { + t = t.Field(0).Type + continue + } + if t.IsArray() && t.NumElem() == 1 { + t = t.Elem() + continue + } + break } - debug := f.pass.debug > 0 + return t +} - if debug { - fmt.Printf("\nexpandsCalls(%s)\n", f.Name) - } +// A registerCursor tracks which register is used for an Arg or regValues, or a piece of such. +type registerCursor struct { + // TODO(register args) convert this to a generalized target cursor. + storeDest *Value // if there are no register targets, then this is the base of the store. + regsLen int // the number of registers available for this Arg/result (which is all in registers or not at all) + nextSlice Abi1RO // the next register/register-slice offset + config *abi.ABIConfig + regValues *[]*Value // values assigned to registers accumulate here +} - canSSAType := f.fe.CanSSA - regSize := f.Config.RegSize - sp, _ := f.spSb() - typ := &f.Config.Types - ptrSize := f.Config.PtrSize +func (rc *registerCursor) String() string { + dest := "" + if rc.storeDest != nil { + dest = rc.storeDest.String() + } + regs := "" + if rc.regValues != nil { + regs = "" + for i, x := range *rc.regValues { + if i > 0 { + regs = regs + "; " + } + regs = regs + x.LongString() + } + } + // not printing the config because that has not been useful + return fmt.Sprintf("RCSR{storeDest=%v, regsLen=%d, nextSlice=%d, regValues=[%s]}", dest, rc.regsLen, rc.nextSlice, regs) +} - // For 32-bit, need to deal with decomposition of 64-bit integers, which depends on endianness. - var hiOffset, lowOffset int64 - if f.Config.BigEndian { - lowOffset = 4 - } else { - hiOffset = 4 +// next effectively post-increments the register cursor; the receiver is advanced, +// the old value is returned. +func (c *registerCursor) next(t *types.Type) registerCursor { + rc := *c + if int(c.nextSlice) < c.regsLen { + w := c.config.NumParamRegs(t) + c.nextSlice += Abi1RO(w) } + return rc +} - namedSelects := make(map[*Value][]namedVal) +// plus returns a register cursor offset from the original, without modifying the original. +func (c *registerCursor) plus(regWidth Abi1RO) registerCursor { + rc := *c + rc.nextSlice += regWidth + return rc +} - sdom := f.Sdom() +const ( + // Register offsets for fields of built-in aggregate types; the ones not listed are zero. + RO_complex_imag = 1 + RO_string_len = 1 + RO_slice_len = 1 + RO_slice_cap = 2 + RO_iface_data = 1 +) - common := make(map[selKey]*Value) +func (x *expandState) regWidth(t *types.Type) Abi1RO { + return Abi1RO(x.abi1.NumParamRegs(t)) +} - // intPairTypes returns the pair of 32-bit int types needed to encode a 64-bit integer type on a target - // that has no 64-bit integer registers. - intPairTypes := func(et types.EType) (tHi, tLo *types.Type) { - tHi = typ.UInt32 - if et == types.TINT64 { - tHi = typ.Int32 +// regOffset returns the register offset of the i'th element of type t +func (x *expandState) regOffset(t *types.Type, i int) Abi1RO { + // TODO maybe cache this in a map if profiling recommends. + if i == 0 { + return 0 + } + if t.IsArray() { + return Abi1RO(i) * x.regWidth(t.Elem()) + } + if t.IsStruct() { + k := Abi1RO(0) + for j := 0; j < i; j++ { + k += x.regWidth(t.FieldType(j)) } - tLo = typ.UInt32 - return + return k } + panic("Haven't implemented this case yet, do I need to?") +} - // isAlreadyExpandedAggregateType returns whether a type is an SSA-able "aggregate" (multiple register) type - // that was expanded in an earlier phase (currently, expand_calls is intended to run after decomposeBuiltin, - // so this is all aggregate types -- small struct and array, complex, interface, string, slice, and 64-bit - // integer on 32-bit). - isAlreadyExpandedAggregateType := func(t *types.Type) bool { - if !canSSAType(t) { - return false +// at returns the register cursor for component i of t, where the first +// component is numbered 0. +func (c *registerCursor) at(t *types.Type, i int) registerCursor { + rc := *c + if i == 0 || c.regsLen == 0 { + return rc + } + if t.IsArray() { + w := c.config.NumParamRegs(t.Elem()) + rc.nextSlice += Abi1RO(i * w) + return rc + } + if t.IsStruct() { + for j := 0; j < i; j++ { + rc.next(t.FieldType(j)) } - return t.IsStruct() || t.IsArray() || t.IsComplex() || t.IsInterface() || t.IsString() || t.IsSlice() || - t.Size() > regSize && t.IsInteger() + return rc + } + panic("Haven't implemented this case yet, do I need to?") +} + +func (c *registerCursor) init(regs []abi.RegIndex, info *abi.ABIParamResultInfo, result *[]*Value, storeDest *Value) { + c.regsLen = len(regs) + c.nextSlice = 0 + if len(regs) == 0 { + c.storeDest = storeDest // only save this if there are no registers, will explode if misused. + return } + c.config = info.Config() + c.regValues = result +} - offsets := make(map[offsetKey]*Value) +func (c *registerCursor) addArg(v *Value) { + *c.regValues = append(*c.regValues, v) +} + +func (c *registerCursor) hasRegs() bool { + return c.regsLen > 0 +} - // offsetFrom creates an offset from a pointer, simplifying chained offsets and offsets from SP - // TODO should also optimize offsets from SB? - offsetFrom := func(from *Value, offset int64, pt *types.Type) *Value { - if offset == 0 && from.Type == pt { // this is not actually likely +type expandState struct { + f *Func + abi1 *abi.ABIConfig + debug bool + canSSAType func(*types.Type) bool + regSize int64 + sp *Value + typs *Types + ptrSize int64 + hiOffset int64 + lowOffset int64 + hiRo Abi1RO + loRo Abi1RO + namedSelects map[*Value][]namedVal + sdom SparseTree + commonSelectors map[selKey]*Value // used to de-dupe selectors + commonArgs map[selKey]*Value // used to de-dupe OpArg/OpArgIntReg/OpArgFloatReg + memForCall map[ID]*Value // For a call, need to know the unique selector that gets the mem. + transformedSelects map[ID]bool // OpSelectN after rewriting, either created or renumbered. + indentLevel int // Indentation for debugging recursion +} + +// intPairTypes returns the pair of 32-bit int types needed to encode a 64-bit integer type on a target +// that has no 64-bit integer registers. +func (x *expandState) intPairTypes(et types.Kind) (tHi, tLo *types.Type) { + tHi = x.typs.UInt32 + if et == types.TINT64 { + tHi = x.typs.Int32 + } + tLo = x.typs.UInt32 + return +} + +// isAlreadyExpandedAggregateType returns whether a type is an SSA-able "aggregate" (multiple register) type +// that was expanded in an earlier phase (currently, expand_calls is intended to run after decomposeBuiltin, +// so this is all aggregate types -- small struct and array, complex, interface, string, slice, and 64-bit +// integer on 32-bit). +func (x *expandState) isAlreadyExpandedAggregateType(t *types.Type) bool { + if !x.canSSAType(t) { + return false + } + return t.IsStruct() || t.IsArray() || t.IsComplex() || t.IsInterface() || t.IsString() || t.IsSlice() || + t.Size() > x.regSize && t.IsInteger() +} + +// offsetFrom creates an offset from a pointer, simplifying chained offsets and offsets from SP +// TODO should also optimize offsets from SB? +func (x *expandState) offsetFrom(b *Block, from *Value, offset int64, pt *types.Type) *Value { + ft := from.Type + if offset == 0 { + if ft == pt { return from } - // Simplify, canonicalize - for from.Op == OpOffPtr { - offset += from.AuxInt - from = from.Args[0] - } - if from == sp { - return f.ConstOffPtrSP(pt, offset, sp) - } - key := offsetKey{from, offset, pt} - v := offsets[key] - if v != nil { - return v - } - v = from.Block.NewValue1I(from.Pos.WithNotStmt(), OpOffPtr, pt, offset, from) - offsets[key] = v - return v - } - - // splitSlots splits one "field" (specified by sfx, offset, and ty) out of the LocalSlots in ls and returns the new LocalSlots this generates. - splitSlots := func(ls []LocalSlot, sfx string, offset int64, ty *types.Type) []LocalSlot { - var locs []LocalSlot - for i := range ls { - locs = append(locs, f.fe.SplitSlot(&ls[i], sfx, offset, ty)) - } - return locs - } - - // removeTrivialWrapperTypes unwraps layers of - // struct { singleField SomeType } and [1]SomeType - // until a non-wrapper type is reached. This is useful - // for working with assignments to/from interface data - // fields (either second operand to OpIMake or OpIData) - // where the wrapping or type conversion can be elided - // because of type conversions/assertions in source code - // that do not appear in SSA. - removeTrivialWrapperTypes := func(t *types.Type) *types.Type { - for { - if t.IsStruct() && t.NumFields() == 1 { - t = t.Field(0).Type - continue - } - if t.IsArray() && t.NumElem() == 1 { - t = t.Elem() - continue - } - break + // This captures common, (apparently) safe cases. The unsafe cases involve ft == uintptr + if (ft.IsPtr() || ft.IsUnsafePtr()) && pt.IsPtr() { + return from } - return t } + // Simplify, canonicalize + for from.Op == OpOffPtr { + offset += from.AuxInt + from = from.Args[0] + } + if from == x.sp { + return x.f.ConstOffPtrSP(pt, offset, x.sp) + } + return b.NewValue1I(from.Pos.WithNotStmt(), OpOffPtr, pt, offset, from) +} - // Calls that need lowering have some number of inputs, including a memory input, - // and produce a tuple of (value1, value2, ..., mem) where valueK may or may not be SSA-able. +// splitSlots splits one "field" (specified by sfx, offset, and ty) out of the LocalSlots in ls and returns the new LocalSlots this generates. +func (x *expandState) splitSlots(ls []*LocalSlot, sfx string, offset int64, ty *types.Type) []*LocalSlot { + var locs []*LocalSlot + for i := range ls { + locs = append(locs, x.f.SplitSlot(ls[i], sfx, offset, ty)) + } + return locs +} - // With the current ABI those inputs need to be converted into stores to memory, - // rethreading the call's memory input to the first, and the new call now receiving the last. +// prAssignForArg returns the ABIParamAssignment for v, assumed to be an OpArg. +func (x *expandState) prAssignForArg(v *Value) *abi.ABIParamAssignment { + if v.Op != OpArg { + panic(badVal("Wanted OpArg, instead saw", v)) + } + return ParamAssignmentForArgName(x.f, v.Aux.(*ir.Name)) +} - // With the current ABI, the outputs need to be converted to loads, which will all use the call's - // memory output as their input. +// ParamAssignmentForArgName returns the ABIParamAssignment for f's arg with matching name. +func ParamAssignmentForArgName(f *Func, name *ir.Name) *abi.ABIParamAssignment { + abiInfo := f.OwnAux.abiInfo + ip := abiInfo.InParams() + for i, a := range ip { + if a.Name == name { + return &ip[i] + } + } + panic(fmt.Errorf("Did not match param %v in prInfo %+v", name, abiInfo.InParams())) +} - // rewriteSelect recursively walks from leaf selector to a root (OpSelectN, OpLoad, OpArg) - // through a chain of Struct/Array/builtin Select operations. If the chain of selectors does not - // end in an expected root, it does nothing (this can happen depending on compiler phase ordering). - // The "leaf" provides the type, the root supplies the container, and the leaf-to-root path - // accumulates the offset. - // It emits the code necessary to implement the leaf select operation that leads to the root. - // - // TODO when registers really arrive, must also decompose anything split across two registers or registers and memory. - var rewriteSelect func(leaf *Value, selector *Value, offset int64) []LocalSlot - rewriteSelect = func(leaf *Value, selector *Value, offset int64) []LocalSlot { - if debug { - fmt.Printf("rewriteSelect(%s, %s, %d)\n", leaf.LongString(), selector.LongString(), offset) - } - var locs []LocalSlot - leafType := leaf.Type - if len(selector.Args) > 0 { - w := selector.Args[0] - if w.Op == OpCopy { - for w.Op == OpCopy { - w = w.Args[0] - } - selector.SetArg(0, w) +// indent increments (or decrements) the indentation. +func (x *expandState) indent(n int) { + x.indentLevel += n +} + +// Printf does an indented fmt.Printf on te format and args. +func (x *expandState) Printf(format string, a ...interface{}) (n int, err error) { + if x.indentLevel > 0 { + fmt.Printf("%[1]*s", x.indentLevel, "") + } + return fmt.Printf(format, a...) +} + +// Calls that need lowering have some number of inputs, including a memory input, +// and produce a tuple of (value1, value2, ..., mem) where valueK may or may not be SSA-able. + +// With the current ABI those inputs need to be converted into stores to memory, +// rethreading the call's memory input to the first, and the new call now receiving the last. + +// With the current ABI, the outputs need to be converted to loads, which will all use the call's +// memory output as their input. + +// rewriteSelect recursively walks from leaf selector to a root (OpSelectN, OpLoad, OpArg) +// through a chain of Struct/Array/builtin Select operations. If the chain of selectors does not +// end in an expected root, it does nothing (this can happen depending on compiler phase ordering). +// The "leaf" provides the type, the root supplies the container, and the leaf-to-root path +// accumulates the offset. +// It emits the code necessary to implement the leaf select operation that leads to the root. +// +// TODO when registers really arrive, must also decompose anything split across two registers or registers and memory. +func (x *expandState) rewriteSelect(leaf *Value, selector *Value, offset int64, regOffset Abi1RO) []*LocalSlot { + if x.debug { + x.indent(3) + defer x.indent(-3) + x.Printf("rewriteSelect(%s; %s; memOff=%d; regOff=%d)\n", leaf.LongString(), selector.LongString(), offset, regOffset) + } + var locs []*LocalSlot + leafType := leaf.Type + if len(selector.Args) > 0 { + w := selector.Args[0] + if w.Op == OpCopy { + for w.Op == OpCopy { + w = w.Args[0] } + selector.SetArg(0, w) } - switch selector.Op { - case OpArg: - if !isAlreadyExpandedAggregateType(selector.Type) { - if leafType == selector.Type { // OpIData leads us here, sometimes. - leaf.copyOf(selector) - } else { - f.Fatalf("Unexpected OpArg type, selector=%s, leaf=%s\n", selector.LongString(), leaf.LongString()) - } - if debug { - fmt.Printf("\tOpArg, break\n") - } - break - } - switch leaf.Op { - case OpIData, OpStructSelect, OpArraySelect: - leafType = removeTrivialWrapperTypes(leaf.Type) - } - aux := selector.Aux - auxInt := selector.AuxInt + offset - if leaf.Block == selector.Block { - leaf.reset(OpArg) - leaf.Aux = aux - leaf.AuxInt = auxInt - leaf.Type = leafType + } + switch selector.Op { + case OpArgIntReg, OpArgFloatReg: + if leafType == selector.Type { // OpIData leads us here, sometimes. + leaf.copyOf(selector) + } else { + x.f.Fatalf("Unexpected %s type, selector=%s, leaf=%s\n", selector.Op.String(), selector.LongString(), leaf.LongString()) + } + if x.debug { + x.Printf("---%s, break\n", selector.Op.String()) + } + case OpArg: + if !x.isAlreadyExpandedAggregateType(selector.Type) { + if leafType == selector.Type { // OpIData leads us here, sometimes. + x.newArgToMemOrRegs(selector, leaf, offset, regOffset, leafType, leaf.Pos) } else { - w := selector.Block.NewValue0IA(leaf.Pos, OpArg, leafType, auxInt, aux) - leaf.copyOf(w) - if debug { - fmt.Printf("\tnew %s\n", w.LongString()) - } + x.f.Fatalf("Unexpected OpArg type, selector=%s, leaf=%s\n", selector.LongString(), leaf.LongString()) } - for _, s := range namedSelects[selector] { - locs = append(locs, f.Names[s.locIndex]) - } - - case OpLoad: // We end up here because of IData of immediate structures. - // Failure case: - // (note the failure case is very rare; w/o this case, make.bash and run.bash both pass, as well as - // the hard cases of building {syscall,math,math/cmplx,math/bits,go/constant} on ppc64le and mips-softfloat). - // - // GOSSAFUNC='(*dumper).dump' go build -gcflags=-l -tags=math_big_pure_go cmd/compile/internal/gc - // cmd/compile/internal/gc/dump.go:136:14: internal compiler error: '(*dumper).dump': not lowered: v827, StructSelect PTR PTR - // b2: ← b1 - // v20 (+142) = StaticLECall {AuxCall{reflect.Value.Interface([reflect.Value,0])[interface {},24]}} [40] v8 v1 - // v21 (142) = SelectN [1] v20 - // v22 (142) = SelectN [0] v20 - // b15: ← b8 - // v71 (+143) = IData v22 (v[Nodes]) - // v73 (+146) = StaticLECall <[]*Node,mem> {AuxCall{"".Nodes.Slice([Nodes,0])[[]*Node,8]}} [32] v71 v21 - // - // translates (w/o the "case OpLoad:" above) to: - // - // b2: ← b1 - // v20 (+142) = StaticCall {AuxCall{reflect.Value.Interface([reflect.Value,0])[interface {},24]}} [40] v715 - // v23 (142) = Load <*uintptr> v19 v20 - // v823 (142) = IsNonNil v23 - // v67 (+143) = Load <*[]*Node> v880 v20 - // b15: ← b8 - // v827 (146) = StructSelect <*[]*Node> [0] v67 - // v846 (146) = Store {*[]*Node} v769 v827 v20 - // v73 (+146) = StaticCall {AuxCall{"".Nodes.Slice([Nodes,0])[[]*Node,8]}} [32] v846 - // i.e., the struct select is generated and remains in because it is not applied to an actual structure. - // The OpLoad was created to load the single field of the IData - // This case removes that StructSelect. - if leafType != selector.Type { - f.Fatalf("Unexpected Load as selector, leaf=%s, selector=%s\n", leaf.LongString(), selector.LongString()) + if x.debug { + x.Printf("---OpArg, break\n") } + break + } + switch leaf.Op { + case OpIData, OpStructSelect, OpArraySelect: + leafType = removeTrivialWrapperTypes(leaf.Type) + } + x.newArgToMemOrRegs(selector, leaf, offset, regOffset, leafType, leaf.Pos) + + for _, s := range x.namedSelects[selector] { + locs = append(locs, x.f.Names[s.locIndex]) + } + + case OpLoad: // We end up here because of IData of immediate structures. + // Failure case: + // (note the failure case is very rare; w/o this case, make.bash and run.bash both pass, as well as + // the hard cases of building {syscall,math,math/cmplx,math/bits,go/constant} on ppc64le and mips-softfloat). + // + // GOSSAFUNC='(*dumper).dump' go build -gcflags=-l -tags=math_big_pure_go cmd/compile/internal/gc + // cmd/compile/internal/gc/dump.go:136:14: internal compiler error: '(*dumper).dump': not lowered: v827, StructSelect PTR PTR + // b2: ← b1 + // v20 (+142) = StaticLECall {AuxCall{reflect.Value.Interface([reflect.Value,0])[interface {},24]}} [40] v8 v1 + // v21 (142) = SelectN [1] v20 + // v22 (142) = SelectN [0] v20 + // b15: ← b8 + // v71 (+143) = IData v22 (v[Nodes]) + // v73 (+146) = StaticLECall <[]*Node,mem> {AuxCall{"".Nodes.Slice([Nodes,0])[[]*Node,8]}} [32] v71 v21 + // + // translates (w/o the "case OpLoad:" above) to: + // + // b2: ← b1 + // v20 (+142) = StaticCall {AuxCall{reflect.Value.Interface([reflect.Value,0])[interface {},24]}} [40] v715 + // v23 (142) = Load <*uintptr> v19 v20 + // v823 (142) = IsNonNil v23 + // v67 (+143) = Load <*[]*Node> v880 v20 + // b15: ← b8 + // v827 (146) = StructSelect <*[]*Node> [0] v67 + // v846 (146) = Store {*[]*Node} v769 v827 v20 + // v73 (+146) = StaticCall {AuxCall{"".Nodes.Slice([Nodes,0])[[]*Node,8]}} [32] v846 + // i.e., the struct select is generated and remains in because it is not applied to an actual structure. + // The OpLoad was created to load the single field of the IData + // This case removes that StructSelect. + if leafType != selector.Type { + x.f.Fatalf("Unexpected Load as selector, leaf=%s, selector=%s\n", leaf.LongString(), selector.LongString()) + } + leaf.copyOf(selector) + for _, s := range x.namedSelects[selector] { + locs = append(locs, x.f.Names[s.locIndex]) + } + + case OpSelectN: + // TODO(register args) result case + // if applied to Op-mumble-call, the Aux tells us which result, regOffset specifies offset within result. If a register, should rewrite to OpSelectN for new call. + // TODO these may be duplicated. Should memoize. Intermediate selectors will go dead, no worries there. + call := selector.Args[0] + call0 := call + aux := call.Aux.(*AuxCall) + which := selector.AuxInt + if x.transformedSelects[selector.ID] { + // This is a minor hack. Either this select has had its operand adjusted (mem) or + // it is some other intermediate node that was rewritten to reference a register (not a generic arg). + // This can occur with chains of selection/indexing from single field/element aggregates. leaf.copyOf(selector) - for _, s := range namedSelects[selector] { - locs = append(locs, f.Names[s.locIndex]) + break + } + if which == aux.NResults() { // mem is after the results. + // rewrite v as a Copy of call -- the replacement call will produce a mem. + if leaf != selector { + panic(fmt.Errorf("Unexpected selector of memory, selector=%s, call=%s, leaf=%s", selector.LongString(), call.LongString(), leaf.LongString())) } - - case OpSelectN: - // TODO these may be duplicated. Should memoize. Intermediate selectors will go dead, no worries there. - call := selector.Args[0] - aux := call.Aux.(*AuxCall) - which := selector.AuxInt - if which == aux.NResults() { // mem is after the results. - // rewrite v as a Copy of call -- the replacement call will produce a mem. - leaf.copyOf(call) + if aux.abiInfo == nil { + panic(badVal("aux.abiInfo nil for call", call)) + } + if existing := x.memForCall[call.ID]; existing == nil { + selector.AuxInt = int64(aux.abiInfo.OutRegistersUsed()) + x.memForCall[call.ID] = selector + x.transformedSelects[selector.ID] = true // operand adjusted } else { - leafType := removeTrivialWrapperTypes(leaf.Type) - if canSSAType(leafType) { - pt := types.NewPtr(leafType) - off := offsetFrom(sp, offset+aux.OffsetOfResult(which), pt) - // Any selection right out of the arg area/registers has to be same Block as call, use call as mem input. + selector.copyOf(existing) + } + + } else { + leafType := removeTrivialWrapperTypes(leaf.Type) + if x.canSSAType(leafType) { + pt := types.NewPtr(leafType) + // Any selection right out of the arg area/registers has to be same Block as call, use call as mem input. + // Create a "mem" for any loads that need to occur. + if mem := x.memForCall[call.ID]; mem != nil { + if mem.Block != call.Block { + panic(fmt.Errorf("selector and call need to be in same block, selector=%s; call=%s", selector.LongString(), call.LongString())) + } + call = mem + } else { + mem = call.Block.NewValue1I(call.Pos.WithNotStmt(), OpSelectN, types.TypeMem, int64(aux.abiInfo.OutRegistersUsed()), call) + x.transformedSelects[mem.ID] = true // select uses post-expansion indexing + x.memForCall[call.ID] = mem + call = mem + } + outParam := aux.abiInfo.OutParam(int(which)) + if len(outParam.Registers) > 0 { + firstReg := uint32(0) + for i := 0; i < int(which); i++ { + firstReg += uint32(len(aux.abiInfo.OutParam(i).Registers)) + } + reg := int64(regOffset + Abi1RO(firstReg)) + if leaf.Block == call.Block { + leaf.reset(OpSelectN) + leaf.SetArgs1(call0) + leaf.Type = leafType + leaf.AuxInt = reg + x.transformedSelects[leaf.ID] = true // leaf, rewritten to use post-expansion indexing. + } else { + w := call.Block.NewValue1I(leaf.Pos, OpSelectN, leafType, reg, call0) + x.transformedSelects[w.ID] = true // select, using post-expansion indexing. + leaf.copyOf(w) + } + } else { + off := x.offsetFrom(x.f.Entry, x.sp, offset+aux.OffsetOfResult(which), pt) if leaf.Block == call.Block { leaf.reset(OpLoad) leaf.SetArgs2(off, call) @@ -274,436 +462,805 @@ func expandCalls(f *Func) { } else { w := call.Block.NewValue2(leaf.Pos, OpLoad, leafType, off, call) leaf.copyOf(w) - if debug { - fmt.Printf("\tnew %s\n", w.LongString()) + if x.debug { + x.Printf("---new %s\n", w.LongString()) } } - for _, s := range namedSelects[selector] { - locs = append(locs, f.Names[s.locIndex]) - } - } else { - f.Fatalf("Should not have non-SSA-able OpSelectN, selector=%s", selector.LongString()) } + for _, s := range x.namedSelects[selector] { + locs = append(locs, x.f.Names[s.locIndex]) + } + } else { + x.f.Fatalf("Should not have non-SSA-able OpSelectN, selector=%s", selector.LongString()) } + } - case OpStructSelect: - w := selector.Args[0] - var ls []LocalSlot - if w.Type.Etype != types.TSTRUCT { // IData artifact - ls = rewriteSelect(leaf, w, offset) - } else { - ls = rewriteSelect(leaf, w, offset+w.Type.FieldOff(int(selector.AuxInt))) - if w.Op != OpIData { - for _, l := range ls { - locs = append(locs, f.fe.SplitStruct(l, int(selector.AuxInt))) - } + case OpStructSelect: + w := selector.Args[0] + var ls []*LocalSlot + if w.Type.Kind() != types.TSTRUCT { // IData artifact + ls = x.rewriteSelect(leaf, w, offset, regOffset) + } else { + fldi := int(selector.AuxInt) + ls = x.rewriteSelect(leaf, w, offset+w.Type.FieldOff(fldi), regOffset+x.regOffset(w.Type, fldi)) + if w.Op != OpIData { + for _, l := range ls { + locs = append(locs, x.f.SplitStruct(l, int(selector.AuxInt))) } } + } - case OpArraySelect: - w := selector.Args[0] - rewriteSelect(leaf, w, offset+selector.Type.Size()*selector.AuxInt) - - case OpInt64Hi: - w := selector.Args[0] - ls := rewriteSelect(leaf, w, offset+hiOffset) - locs = splitSlots(ls, ".hi", hiOffset, leafType) - - case OpInt64Lo: - w := selector.Args[0] - ls := rewriteSelect(leaf, w, offset+lowOffset) - locs = splitSlots(ls, ".lo", lowOffset, leafType) - - case OpStringPtr: - ls := rewriteSelect(leaf, selector.Args[0], offset) - locs = splitSlots(ls, ".ptr", 0, typ.BytePtr) - - case OpSlicePtr: - w := selector.Args[0] - ls := rewriteSelect(leaf, w, offset) - locs = splitSlots(ls, ".ptr", 0, types.NewPtr(w.Type.Elem())) + case OpArraySelect: + w := selector.Args[0] + index := selector.AuxInt + x.rewriteSelect(leaf, w, offset+selector.Type.Size()*index, regOffset+x.regOffset(w.Type, int(index))) + + case OpInt64Hi: + w := selector.Args[0] + ls := x.rewriteSelect(leaf, w, offset+x.hiOffset, regOffset+x.hiRo) + locs = x.splitSlots(ls, ".hi", x.hiOffset, leafType) + + case OpInt64Lo: + w := selector.Args[0] + ls := x.rewriteSelect(leaf, w, offset+x.lowOffset, regOffset+x.loRo) + locs = x.splitSlots(ls, ".lo", x.lowOffset, leafType) + + case OpStringPtr: + ls := x.rewriteSelect(leaf, selector.Args[0], offset, regOffset) + locs = x.splitSlots(ls, ".ptr", 0, x.typs.BytePtr) + + case OpSlicePtr, OpSlicePtrUnchecked: + w := selector.Args[0] + ls := x.rewriteSelect(leaf, w, offset, regOffset) + locs = x.splitSlots(ls, ".ptr", 0, types.NewPtr(w.Type.Elem())) + + case OpITab: + w := selector.Args[0] + ls := x.rewriteSelect(leaf, w, offset, regOffset) + sfx := ".itab" + if w.Type.IsEmptyInterface() { + sfx = ".type" + } + locs = x.splitSlots(ls, sfx, 0, x.typs.Uintptr) - case OpITab: - w := selector.Args[0] - ls := rewriteSelect(leaf, w, offset) - sfx := ".itab" - if w.Type.IsEmptyInterface() { - sfx = ".type" - } - locs = splitSlots(ls, sfx, 0, typ.Uintptr) + case OpComplexReal: + ls := x.rewriteSelect(leaf, selector.Args[0], offset, regOffset) + locs = x.splitSlots(ls, ".real", 0, leafType) - case OpComplexReal: - ls := rewriteSelect(leaf, selector.Args[0], offset) - locs = splitSlots(ls, ".real", 0, leafType) + case OpComplexImag: + ls := x.rewriteSelect(leaf, selector.Args[0], offset+leafType.Width, regOffset+RO_complex_imag) // result is FloatNN, width of result is offset of imaginary part. + locs = x.splitSlots(ls, ".imag", leafType.Width, leafType) - case OpComplexImag: - ls := rewriteSelect(leaf, selector.Args[0], offset+leafType.Width) // result is FloatNN, width of result is offset of imaginary part. - locs = splitSlots(ls, ".imag", leafType.Width, leafType) + case OpStringLen, OpSliceLen: + ls := x.rewriteSelect(leaf, selector.Args[0], offset+x.ptrSize, regOffset+RO_slice_len) + locs = x.splitSlots(ls, ".len", x.ptrSize, leafType) - case OpStringLen, OpSliceLen: - ls := rewriteSelect(leaf, selector.Args[0], offset+ptrSize) - locs = splitSlots(ls, ".len", ptrSize, leafType) + case OpIData: + ls := x.rewriteSelect(leaf, selector.Args[0], offset+x.ptrSize, regOffset+RO_iface_data) + locs = x.splitSlots(ls, ".data", x.ptrSize, leafType) - case OpIData: - ls := rewriteSelect(leaf, selector.Args[0], offset+ptrSize) - locs = splitSlots(ls, ".data", ptrSize, leafType) + case OpSliceCap: + ls := x.rewriteSelect(leaf, selector.Args[0], offset+2*x.ptrSize, regOffset+RO_slice_cap) + locs = x.splitSlots(ls, ".cap", 2*x.ptrSize, leafType) - case OpSliceCap: - ls := rewriteSelect(leaf, selector.Args[0], offset+2*ptrSize) - locs = splitSlots(ls, ".cap", 2*ptrSize, leafType) + case OpCopy: // If it's an intermediate result, recurse + locs = x.rewriteSelect(leaf, selector.Args[0], offset, regOffset) + for _, s := range x.namedSelects[selector] { + // this copy may have had its own name, preserve that, too. + locs = append(locs, x.f.Names[s.locIndex]) + } - case OpCopy: // If it's an intermediate result, recurse - locs = rewriteSelect(leaf, selector.Args[0], offset) - for _, s := range namedSelects[selector] { - // this copy may have had its own name, preserve that, too. - locs = append(locs, f.Names[s.locIndex]) - } + default: + // Ignore dead ends. These can occur if this phase is run before decompose builtin (which is not intended, but allowed). + } - default: - // Ignore dead ends. These can occur if this phase is run before decompose builtin (which is not intended, but allowed). - } + return locs +} - return locs +func (x *expandState) rewriteDereference(b *Block, base, a, mem *Value, offset, size int64, typ *types.Type, pos src.XPos) *Value { + source := a.Args[0] + dst := x.offsetFrom(b, base, offset, source.Type) + if a.Uses == 1 && a.Block == b { + a.reset(OpMove) + a.Pos = pos + a.Type = types.TypeMem + a.Aux = typ + a.AuxInt = size + a.SetArgs3(dst, source, mem) + mem = a + } else { + mem = b.NewValue3A(pos, OpMove, types.TypeMem, typ, dst, source, mem) + mem.AuxInt = size } + return mem +} - // storeArgOrLoad converts stores of SSA-able aggregate arguments (passed to a call) into a series of primitive-typed - // stores of non-aggregate types. It recursively walks up a chain of selectors until it reaches a Load or an Arg. - // If it does not reach a Load or an Arg, nothing happens; this allows a little freedom in phase ordering. - var storeArgOrLoad func(pos src.XPos, b *Block, base, source, mem *Value, t *types.Type, offset int64) *Value +var indexNames [1]string = [1]string{"[0]"} - // decomposeArgOrLoad is a helper for storeArgOrLoad. - // It decomposes a Load or an Arg into smaller parts, parameterized by the decomposeOne and decomposeTwo functions - // passed to it, and returns the new mem. If the type does not match one of the expected aggregate types, it returns nil instead. - decomposeArgOrLoad := func(pos src.XPos, b *Block, base, source, mem *Value, t *types.Type, offset int64, - decomposeOne func(pos src.XPos, b *Block, base, source, mem *Value, t1 *types.Type, offArg, offStore int64) *Value, - decomposeTwo func(pos src.XPos, b *Block, base, source, mem *Value, t1, t2 *types.Type, offArg, offStore int64) *Value) *Value { - u := source.Type - switch u.Etype { +// pathTo returns the selection path to the leaf type at offset within container. +// e.g. len(thing.field[0]) => ".field[0].len" +// this is for purposes of generating names ultimately fed to a debugger. +func (x *expandState) pathTo(container, leaf *types.Type, offset int64) string { + if container == leaf || offset == 0 && container.Size() == leaf.Size() { + return "" + } + path := "" +outer: + for { + switch container.Kind() { case types.TARRAY: - elem := u.Elem() - for i := int64(0); i < u.NumElem(); i++ { - elemOff := i * elem.Size() - mem = decomposeOne(pos, b, base, source, mem, elem, source.AuxInt+elemOff, offset+elemOff) - pos = pos.WithNotStmt() + container = container.Elem() + if container.Size() == 0 { + return path } - return mem + i := offset / container.Size() + offset = offset % container.Size() + // If a future compiler/ABI supports larger SSA/Arg-able arrays, expand indexNames. + path = path + indexNames[i] + continue case types.TSTRUCT: - for i := 0; i < u.NumFields(); i++ { - fld := u.Field(i) - mem = decomposeOne(pos, b, base, source, mem, fld.Type, source.AuxInt+fld.Offset, offset+fld.Offset) - pos = pos.WithNotStmt() + for i := 0; i < container.NumFields(); i++ { + fld := container.Field(i) + if fld.Offset+fld.Type.Size() > offset { + offset -= fld.Offset + path += "." + fld.Sym.Name + container = fld.Type + continue outer + } } - return mem + return path case types.TINT64, types.TUINT64: - if t.Width == regSize { - break + if container.Width == x.regSize { + return path } - tHi, tLo := intPairTypes(t.Etype) - mem = decomposeOne(pos, b, base, source, mem, tHi, source.AuxInt+hiOffset, offset+hiOffset) - pos = pos.WithNotStmt() - return decomposeOne(pos, b, base, source, mem, tLo, source.AuxInt+lowOffset, offset+lowOffset) + if offset == x.hiOffset { + return path + ".hi" + } + return path + ".lo" case types.TINTER: - return decomposeTwo(pos, b, base, source, mem, typ.Uintptr, typ.BytePtr, source.AuxInt, offset) - case types.TSTRING: - return decomposeTwo(pos, b, base, source, mem, typ.BytePtr, typ.Int, source.AuxInt, offset) - case types.TCOMPLEX64: - return decomposeTwo(pos, b, base, source, mem, typ.Float32, typ.Float32, source.AuxInt, offset) - case types.TCOMPLEX128: - return decomposeTwo(pos, b, base, source, mem, typ.Float64, typ.Float64, source.AuxInt, offset) + if offset != 0 { + return path + ".data" + } + if container.IsEmptyInterface() { + return path + ".type" + } + return path + ".itab" + case types.TSLICE: - mem = decomposeTwo(pos, b, base, source, mem, typ.BytePtr, typ.Int, source.AuxInt, offset) - return decomposeOne(pos, b, base, source, mem, typ.Int, source.AuxInt+2*ptrSize, offset+2*ptrSize) + if offset == 2*x.regSize { + return path + ".cap" + } + fallthrough + case types.TSTRING: + if offset == 0 { + return path + ".ptr" + } + return path + ".len" + case types.TCOMPLEX64, types.TCOMPLEX128: + if offset == 0 { + return path + ".real" + } + return path + ".imag" } - return nil + return path } +} - // storeOneArg creates a decomposed (one step) arg that is then stored. - // pos and b locate the store instruction, base is the base of the store target, source is the "base" of the value input, - // mem is the input mem, t is the type in question, and offArg and offStore are the offsets from the respective bases. - storeOneArg := func(pos src.XPos, b *Block, base, source, mem *Value, t *types.Type, offArg, offStore int64) *Value { - w := common[selKey{source, offArg, t.Width, t}] - if w == nil { - w = source.Block.NewValue0IA(source.Pos, OpArg, t, offArg, source.Aux) - common[selKey{source, offArg, t.Width, t}] = w - } - return storeArgOrLoad(pos, b, base, w, mem, t, offStore) +// decomposeArg is a helper for storeArgOrLoad. +// It decomposes a Load or an Arg into smaller parts and returns the new mem. +// If the type does not match one of the expected aggregate types, it returns nil instead. +// Parameters: +// pos -- the location of any generated code. +// b -- the block into which any generated code should normally be placed +// source -- the value, possibly an aggregate, to be stored. +// mem -- the mem flowing into this decomposition (loads depend on it, stores updated it) +// t -- the type of the value to be stored +// storeOffset -- if the value is stored in memory, it is stored at base (see storeRc) + storeOffset +// loadRegOffset -- regarding source as a value in registers, the register offset in ABI1. Meaningful only if source is OpArg. +// storeRc -- storeRC; if the value is stored in registers, this specifies the registers. +// StoreRc also identifies whether the target is registers or memory, and has the base for the store operation. +func (x *expandState) decomposeArg(pos src.XPos, b *Block, source, mem *Value, t *types.Type, storeOffset int64, loadRegOffset Abi1RO, storeRc registerCursor) *Value { + + pa := x.prAssignForArg(source) + var locs []*LocalSlot + for _, s := range x.namedSelects[source] { + locs = append(locs, x.f.Names[s.locIndex]) } - // storeOneLoad creates a decomposed (one step) load that is then stored. - storeOneLoad := func(pos src.XPos, b *Block, base, source, mem *Value, t *types.Type, offArg, offStore int64) *Value { - from := offsetFrom(source.Args[0], offArg, types.NewPtr(t)) - w := source.Block.NewValue2(source.Pos, OpLoad, t, from, mem) - return storeArgOrLoad(pos, b, base, w, mem, t, offStore) + if len(pa.Registers) > 0 { + // Handle the in-registers case directly + rts, offs := pa.RegisterTypesAndOffsets() + last := loadRegOffset + x.regWidth(t) + if offs[loadRegOffset] != 0 { + // Document the problem before panicking. + for i := 0; i < len(rts); i++ { + rt := rts[i] + off := offs[i] + fmt.Printf("rt=%s, off=%d, rt.Width=%d, rt.Align=%d\n", rt.String(), off, rt.Width, rt.Align) + } + panic(fmt.Errorf("offset %d of requested register %d should be zero, source=%s", offs[loadRegOffset], loadRegOffset, source.LongString())) + } + + if x.debug { + x.Printf("decompose arg %s has %d locs\n", source.LongString(), len(locs)) + } + + for i := loadRegOffset; i < last; i++ { + rt := rts[i] + off := offs[i] + w := x.commonArgs[selKey{source, off, rt.Width, rt}] + if w == nil { + w = x.newArgToMemOrRegs(source, w, off, i, rt, pos) + suffix := x.pathTo(source.Type, rt, off) + if suffix != "" { + x.splitSlotsIntoNames(locs, suffix, off, rt, w) + } + } + if t.IsPtrShaped() { + // Preserve the original store type. This ensures pointer type + // properties aren't discarded (e.g, notinheap). + if rt.Width != t.Width || len(pa.Registers) != 1 || i != loadRegOffset { + b.Func.Fatalf("incompatible store type %v and %v, i=%d", t, rt, i) + } + rt = t + } + mem = x.storeArgOrLoad(pos, b, w, mem, rt, storeOffset+off, i, storeRc.next(rt)) + } + return mem } - storeTwoArg := func(pos src.XPos, b *Block, base, source, mem *Value, t1, t2 *types.Type, offArg, offStore int64) *Value { - mem = storeOneArg(pos, b, base, source, mem, t1, offArg, offStore) + u := source.Type + switch u.Kind() { + case types.TARRAY: + elem := u.Elem() + elemRO := x.regWidth(elem) + for i := int64(0); i < u.NumElem(); i++ { + elemOff := i * elem.Size() + mem = storeOneArg(x, pos, b, locs, indexNames[i], source, mem, elem, elemOff, storeOffset+elemOff, loadRegOffset, storeRc.next(elem)) + loadRegOffset += elemRO + pos = pos.WithNotStmt() + } + return mem + case types.TSTRUCT: + for i := 0; i < u.NumFields(); i++ { + fld := u.Field(i) + mem = storeOneArg(x, pos, b, locs, "."+fld.Sym.Name, source, mem, fld.Type, fld.Offset, storeOffset+fld.Offset, loadRegOffset, storeRc.next(fld.Type)) + loadRegOffset += x.regWidth(fld.Type) + pos = pos.WithNotStmt() + } + return mem + case types.TINT64, types.TUINT64: + if t.Width == x.regSize { + break + } + tHi, tLo := x.intPairTypes(t.Kind()) + mem = storeOneArg(x, pos, b, locs, ".hi", source, mem, tHi, x.hiOffset, storeOffset+x.hiOffset, loadRegOffset+x.hiRo, storeRc.plus(x.hiRo)) pos = pos.WithNotStmt() - t1Size := t1.Size() - return storeOneArg(pos, b, base, source, mem, t2, offArg+t1Size, offStore+t1Size) + return storeOneArg(x, pos, b, locs, ".lo", source, mem, tLo, x.lowOffset, storeOffset+x.lowOffset, loadRegOffset+x.loRo, storeRc.plus(x.loRo)) + case types.TINTER: + sfx := ".itab" + if u.IsEmptyInterface() { + sfx = ".type" + } + return storeTwoArg(x, pos, b, locs, sfx, ".idata", source, mem, x.typs.Uintptr, x.typs.BytePtr, 0, storeOffset, loadRegOffset, storeRc) + case types.TSTRING: + return storeTwoArg(x, pos, b, locs, ".ptr", ".len", source, mem, x.typs.BytePtr, x.typs.Int, 0, storeOffset, loadRegOffset, storeRc) + case types.TCOMPLEX64: + return storeTwoArg(x, pos, b, locs, ".real", ".imag", source, mem, x.typs.Float32, x.typs.Float32, 0, storeOffset, loadRegOffset, storeRc) + case types.TCOMPLEX128: + return storeTwoArg(x, pos, b, locs, ".real", ".imag", source, mem, x.typs.Float64, x.typs.Float64, 0, storeOffset, loadRegOffset, storeRc) + case types.TSLICE: + mem = storeOneArg(x, pos, b, locs, ".ptr", source, mem, x.typs.BytePtr, 0, storeOffset, loadRegOffset, storeRc.next(x.typs.BytePtr)) + return storeTwoArg(x, pos, b, locs, ".len", ".cap", source, mem, x.typs.Int, x.typs.Int, x.ptrSize, storeOffset+x.ptrSize, loadRegOffset+RO_slice_len, storeRc) } + return nil +} - storeTwoLoad := func(pos src.XPos, b *Block, base, source, mem *Value, t1, t2 *types.Type, offArg, offStore int64) *Value { - mem = storeOneLoad(pos, b, base, source, mem, t1, offArg, offStore) - pos = pos.WithNotStmt() - t1Size := t1.Size() - return storeOneLoad(pos, b, base, source, mem, t2, offArg+t1Size, offStore+t1Size) +func (x *expandState) splitSlotsIntoNames(locs []*LocalSlot, suffix string, off int64, rt *types.Type, w *Value) { + wlocs := x.splitSlots(locs, suffix, off, rt) + for _, l := range wlocs { + old, ok := x.f.NamedValues[*l] + x.f.NamedValues[*l] = append(old, w) + if !ok { + x.f.Names = append(x.f.Names, l) + } } +} - storeArgOrLoad = func(pos src.XPos, b *Block, base, source, mem *Value, t *types.Type, offset int64) *Value { - if debug { - fmt.Printf("\tstoreArgOrLoad(%s; %s; %s; %s; %d)\n", base.LongString(), source.LongString(), mem.String(), t.String(), offset) +// decomposeLoad is a helper for storeArgOrLoad. +// It decomposes a Load into smaller parts and returns the new mem. +// If the type does not match one of the expected aggregate types, it returns nil instead. +// Parameters: +// pos -- the location of any generated code. +// b -- the block into which any generated code should normally be placed +// source -- the value, possibly an aggregate, to be stored. +// mem -- the mem flowing into this decomposition (loads depend on it, stores updated it) +// t -- the type of the value to be stored +// storeOffset -- if the value is stored in memory, it is stored at base (see storeRc) + offset +// loadRegOffset -- regarding source as a value in registers, the register offset in ABI1. Meaningful only if source is OpArg. +// storeRc -- storeRC; if the value is stored in registers, this specifies the registers. +// StoreRc also identifies whether the target is registers or memory, and has the base for the store operation. +// +// TODO -- this needs cleanup; it just works for SSA-able aggregates, and won't fully generalize to register-args aggregates. +func (x *expandState) decomposeLoad(pos src.XPos, b *Block, source, mem *Value, t *types.Type, storeOffset int64, loadRegOffset Abi1RO, storeRc registerCursor) *Value { + u := source.Type + switch u.Kind() { + case types.TARRAY: + elem := u.Elem() + elemRO := x.regWidth(elem) + for i := int64(0); i < u.NumElem(); i++ { + elemOff := i * elem.Size() + mem = storeOneLoad(x, pos, b, source, mem, elem, elemOff, storeOffset+elemOff, loadRegOffset, storeRc.next(elem)) + loadRegOffset += elemRO + pos = pos.WithNotStmt() + } + return mem + case types.TSTRUCT: + for i := 0; i < u.NumFields(); i++ { + fld := u.Field(i) + mem = storeOneLoad(x, pos, b, source, mem, fld.Type, fld.Offset, storeOffset+fld.Offset, loadRegOffset, storeRc.next(fld.Type)) + loadRegOffset += x.regWidth(fld.Type) + pos = pos.WithNotStmt() } + return mem + case types.TINT64, types.TUINT64: + if t.Width == x.regSize { + break + } + tHi, tLo := x.intPairTypes(t.Kind()) + mem = storeOneLoad(x, pos, b, source, mem, tHi, x.hiOffset, storeOffset+x.hiOffset, loadRegOffset+x.hiRo, storeRc.plus(x.hiRo)) + pos = pos.WithNotStmt() + return storeOneLoad(x, pos, b, source, mem, tLo, x.lowOffset, storeOffset+x.lowOffset, loadRegOffset+x.loRo, storeRc.plus(x.loRo)) + case types.TINTER: + return storeTwoLoad(x, pos, b, source, mem, x.typs.Uintptr, x.typs.BytePtr, 0, storeOffset, loadRegOffset, storeRc) + case types.TSTRING: + return storeTwoLoad(x, pos, b, source, mem, x.typs.BytePtr, x.typs.Int, 0, storeOffset, loadRegOffset, storeRc) + case types.TCOMPLEX64: + return storeTwoLoad(x, pos, b, source, mem, x.typs.Float32, x.typs.Float32, 0, storeOffset, loadRegOffset, storeRc) + case types.TCOMPLEX128: + return storeTwoLoad(x, pos, b, source, mem, x.typs.Float64, x.typs.Float64, 0, storeOffset, loadRegOffset, storeRc) + case types.TSLICE: + mem = storeOneLoad(x, pos, b, source, mem, x.typs.BytePtr, 0, storeOffset, loadRegOffset, storeRc.next(x.typs.BytePtr)) + return storeTwoLoad(x, pos, b, source, mem, x.typs.Int, x.typs.Int, x.ptrSize, storeOffset+x.ptrSize, loadRegOffset+RO_slice_len, storeRc) + } + return nil +} - switch source.Op { - case OpCopy: - return storeArgOrLoad(pos, b, base, source.Args[0], mem, t, offset) +// storeOneArg creates a decomposed (one step) arg that is then stored. +// pos and b locate the store instruction, source is the "base" of the value input, +// mem is the input mem, t is the type in question, and offArg and offStore are the offsets from the respective bases. +func storeOneArg(x *expandState, pos src.XPos, b *Block, locs []*LocalSlot, suffix string, source, mem *Value, t *types.Type, argOffset, storeOffset int64, loadRegOffset Abi1RO, storeRc registerCursor) *Value { + if x.debug { + x.indent(3) + defer x.indent(-3) + x.Printf("storeOneArg(%s; %s; %s; aO=%d; sO=%d; lrO=%d; %s)\n", source.LongString(), mem.String(), t.String(), argOffset, storeOffset, loadRegOffset, storeRc.String()) + } - case OpLoad: - ret := decomposeArgOrLoad(pos, b, base, source, mem, t, offset, storeOneLoad, storeTwoLoad) - if ret != nil { - return ret - } + w := x.commonArgs[selKey{source, argOffset, t.Width, t}] + if w == nil { + w = x.newArgToMemOrRegs(source, w, argOffset, loadRegOffset, t, pos) + x.splitSlotsIntoNames(locs, suffix, argOffset, t, w) + } + return x.storeArgOrLoad(pos, b, w, mem, t, storeOffset, loadRegOffset, storeRc) +} - case OpArg: - ret := decomposeArgOrLoad(pos, b, base, source, mem, t, offset, storeOneArg, storeTwoArg) - if ret != nil { - return ret - } +// storeOneLoad creates a decomposed (one step) load that is then stored. +func storeOneLoad(x *expandState, pos src.XPos, b *Block, source, mem *Value, t *types.Type, offArg, offStore int64, loadRegOffset Abi1RO, storeRc registerCursor) *Value { + from := x.offsetFrom(b, source.Args[0], offArg, types.NewPtr(t)) + w := source.Block.NewValue2(source.Pos, OpLoad, t, from, mem) + return x.storeArgOrLoad(pos, b, w, mem, t, offStore, loadRegOffset, storeRc) +} - case OpArrayMake0, OpStructMake0: - return mem +func storeTwoArg(x *expandState, pos src.XPos, b *Block, locs []*LocalSlot, suffix1 string, suffix2 string, source, mem *Value, t1, t2 *types.Type, offArg, offStore int64, loadRegOffset Abi1RO, storeRc registerCursor) *Value { + mem = storeOneArg(x, pos, b, locs, suffix1, source, mem, t1, offArg, offStore, loadRegOffset, storeRc.next(t1)) + pos = pos.WithNotStmt() + t1Size := t1.Size() + return storeOneArg(x, pos, b, locs, suffix2, source, mem, t2, offArg+t1Size, offStore+t1Size, loadRegOffset+1, storeRc) +} - case OpStructMake1, OpStructMake2, OpStructMake3, OpStructMake4: - for i := 0; i < t.NumFields(); i++ { - fld := t.Field(i) - mem = storeArgOrLoad(pos, b, base, source.Args[i], mem, fld.Type, offset+fld.Offset) - pos = pos.WithNotStmt() - } - return mem +// storeTwoLoad creates a pair of decomposed (one step) loads that are then stored. +// the elements of the pair must not require any additional alignment. +func storeTwoLoad(x *expandState, pos src.XPos, b *Block, source, mem *Value, t1, t2 *types.Type, offArg, offStore int64, loadRegOffset Abi1RO, storeRc registerCursor) *Value { + mem = storeOneLoad(x, pos, b, source, mem, t1, offArg, offStore, loadRegOffset, storeRc.next(t1)) + pos = pos.WithNotStmt() + t1Size := t1.Size() + return storeOneLoad(x, pos, b, source, mem, t2, offArg+t1Size, offStore+t1Size, loadRegOffset+1, storeRc) +} - case OpArrayMake1: - return storeArgOrLoad(pos, b, base, source.Args[0], mem, t.Elem(), offset) +// storeArgOrLoad converts stores of SSA-able potentially aggregatable arguments (passed to a call) into a series of primitive-typed +// stores of non-aggregate types. It recursively walks up a chain of selectors until it reaches a Load or an Arg. +// If it does not reach a Load or an Arg, nothing happens; this allows a little freedom in phase ordering. +func (x *expandState) storeArgOrLoad(pos src.XPos, b *Block, source, mem *Value, t *types.Type, storeOffset int64, loadRegOffset Abi1RO, storeRc registerCursor) *Value { + if x.debug { + x.indent(3) + defer x.indent(-3) + x.Printf("storeArgOrLoad(%s; %s; %s; %d; %s)\n", source.LongString(), mem.String(), t.String(), storeOffset, storeRc.String()) + } - case OpInt64Make: - tHi, tLo := intPairTypes(t.Etype) - mem = storeArgOrLoad(pos, b, base, source.Args[0], mem, tHi, offset+hiOffset) - pos = pos.WithNotStmt() - return storeArgOrLoad(pos, b, base, source.Args[1], mem, tLo, offset+lowOffset) + // Start with Opcodes that can be disassembled + switch source.Op { + case OpCopy: + return x.storeArgOrLoad(pos, b, source.Args[0], mem, t, storeOffset, loadRegOffset, storeRc) - case OpComplexMake: - tPart := typ.Float32 - wPart := t.Width / 2 - if wPart == 8 { - tPart = typ.Float64 - } - mem = storeArgOrLoad(pos, b, base, source.Args[0], mem, tPart, offset) - pos = pos.WithNotStmt() - return storeArgOrLoad(pos, b, base, source.Args[1], mem, tPart, offset+wPart) + case OpLoad, OpDereference: + ret := x.decomposeLoad(pos, b, source, mem, t, storeOffset, loadRegOffset, storeRc) + if ret != nil { + return ret + } - case OpIMake: - mem = storeArgOrLoad(pos, b, base, source.Args[0], mem, typ.Uintptr, offset) - pos = pos.WithNotStmt() - return storeArgOrLoad(pos, b, base, source.Args[1], mem, typ.BytePtr, offset+ptrSize) + case OpArg: + ret := x.decomposeArg(pos, b, source, mem, t, storeOffset, loadRegOffset, storeRc) + if ret != nil { + return ret + } - case OpStringMake: - mem = storeArgOrLoad(pos, b, base, source.Args[0], mem, typ.BytePtr, offset) - pos = pos.WithNotStmt() - return storeArgOrLoad(pos, b, base, source.Args[1], mem, typ.Int, offset+ptrSize) + case OpArrayMake0, OpStructMake0: + // TODO(register args) is this correct for registers? + return mem - case OpSliceMake: - mem = storeArgOrLoad(pos, b, base, source.Args[0], mem, typ.BytePtr, offset) + case OpStructMake1, OpStructMake2, OpStructMake3, OpStructMake4: + for i := 0; i < t.NumFields(); i++ { + fld := t.Field(i) + mem = x.storeArgOrLoad(pos, b, source.Args[i], mem, fld.Type, storeOffset+fld.Offset, 0, storeRc.next(fld.Type)) pos = pos.WithNotStmt() - mem = storeArgOrLoad(pos, b, base, source.Args[1], mem, typ.Int, offset+ptrSize) - return storeArgOrLoad(pos, b, base, source.Args[2], mem, typ.Int, offset+2*ptrSize) } + return mem - // For nodes that cannot be taken apart -- OpSelectN, other structure selectors. - switch t.Etype { - case types.TARRAY: - elt := t.Elem() - if source.Type != t && t.NumElem() == 1 && elt.Width == t.Width && t.Width == regSize { - t = removeTrivialWrapperTypes(t) - // it could be a leaf type, but the "leaf" could be complex64 (for example) - return storeArgOrLoad(pos, b, base, source, mem, t, offset) - } - for i := int64(0); i < t.NumElem(); i++ { - sel := source.Block.NewValue1I(pos, OpArraySelect, elt, i, source) - mem = storeArgOrLoad(pos, b, base, sel, mem, elt, offset+i*elt.Width) - pos = pos.WithNotStmt() - } - return mem + case OpArrayMake1: + return x.storeArgOrLoad(pos, b, source.Args[0], mem, t.Elem(), storeOffset, 0, storeRc.at(t, 0)) - case types.TSTRUCT: - if source.Type != t && t.NumFields() == 1 && t.Field(0).Type.Width == t.Width && t.Width == regSize { - // This peculiar test deals with accesses to immediate interface data. - // It works okay because everything is the same size. - // Example code that triggers this can be found in go/constant/value.go, function ToComplex - // v119 (+881) = IData v6 - // v121 (+882) = StaticLECall {AuxCall{"".itof([intVal,0])[floatVal,8]}} [16] v119 v1 - // This corresponds to the generic rewrite rule "(StructSelect [0] (IData x)) => (IData x)" - // Guard against "struct{struct{*foo}}" - // Other rewriting phases create minor glitches when they transform IData, for instance the - // interface-typed Arg "x" of ToFloat in go/constant/value.go - // v6 (858) = Arg {x} (x[Value], x[Value]) - // is rewritten by decomposeArgs into - // v141 (858) = Arg {x} - // v139 (858) = Arg <*uint8> {x} [8] - // because of a type case clause on line 862 of go/constant/value.go - // case intVal: - // return itof(x) - // v139 is later stored as an intVal == struct{val *big.Int} which naively requires the fields of - // of a *uint8, which does not succeed. - t = removeTrivialWrapperTypes(t) - // it could be a leaf type, but the "leaf" could be complex64 (for example) - return storeArgOrLoad(pos, b, base, source, mem, t, offset) - } - - for i := 0; i < t.NumFields(); i++ { - fld := t.Field(i) - sel := source.Block.NewValue1I(pos, OpStructSelect, fld.Type, int64(i), source) - mem = storeArgOrLoad(pos, b, base, sel, mem, fld.Type, offset+fld.Offset) - pos = pos.WithNotStmt() - } - return mem + case OpInt64Make: + tHi, tLo := x.intPairTypes(t.Kind()) + mem = x.storeArgOrLoad(pos, b, source.Args[0], mem, tHi, storeOffset+x.hiOffset, 0, storeRc.next(tHi)) + pos = pos.WithNotStmt() + return x.storeArgOrLoad(pos, b, source.Args[1], mem, tLo, storeOffset+x.lowOffset, 0, storeRc) - case types.TINT64, types.TUINT64: - if t.Width == regSize { - break - } - tHi, tLo := intPairTypes(t.Etype) - sel := source.Block.NewValue1(pos, OpInt64Hi, tHi, source) - mem = storeArgOrLoad(pos, b, base, sel, mem, tHi, offset+hiOffset) - pos = pos.WithNotStmt() - sel = source.Block.NewValue1(pos, OpInt64Lo, tLo, source) - return storeArgOrLoad(pos, b, base, sel, mem, tLo, offset+lowOffset) + case OpComplexMake: + tPart := x.typs.Float32 + wPart := t.Width / 2 + if wPart == 8 { + tPart = x.typs.Float64 + } + mem = x.storeArgOrLoad(pos, b, source.Args[0], mem, tPart, storeOffset, 0, storeRc.next(tPart)) + pos = pos.WithNotStmt() + return x.storeArgOrLoad(pos, b, source.Args[1], mem, tPart, storeOffset+wPart, 0, storeRc) - case types.TINTER: - sel := source.Block.NewValue1(pos, OpITab, typ.BytePtr, source) - mem = storeArgOrLoad(pos, b, base, sel, mem, typ.BytePtr, offset) - pos = pos.WithNotStmt() - sel = source.Block.NewValue1(pos, OpIData, typ.BytePtr, source) - return storeArgOrLoad(pos, b, base, sel, mem, typ.BytePtr, offset+ptrSize) + case OpIMake: + mem = x.storeArgOrLoad(pos, b, source.Args[0], mem, x.typs.Uintptr, storeOffset, 0, storeRc.next(x.typs.Uintptr)) + pos = pos.WithNotStmt() + return x.storeArgOrLoad(pos, b, source.Args[1], mem, x.typs.BytePtr, storeOffset+x.ptrSize, 0, storeRc) - case types.TSTRING: - sel := source.Block.NewValue1(pos, OpStringPtr, typ.BytePtr, source) - mem = storeArgOrLoad(pos, b, base, sel, mem, typ.BytePtr, offset) - pos = pos.WithNotStmt() - sel = source.Block.NewValue1(pos, OpStringLen, typ.Int, source) - return storeArgOrLoad(pos, b, base, sel, mem, typ.Int, offset+ptrSize) + case OpStringMake: + mem = x.storeArgOrLoad(pos, b, source.Args[0], mem, x.typs.BytePtr, storeOffset, 0, storeRc.next(x.typs.BytePtr)) + pos = pos.WithNotStmt() + return x.storeArgOrLoad(pos, b, source.Args[1], mem, x.typs.Int, storeOffset+x.ptrSize, 0, storeRc) - case types.TSLICE: - et := types.NewPtr(t.Elem()) - sel := source.Block.NewValue1(pos, OpSlicePtr, et, source) - mem = storeArgOrLoad(pos, b, base, sel, mem, et, offset) - pos = pos.WithNotStmt() - sel = source.Block.NewValue1(pos, OpSliceLen, typ.Int, source) - mem = storeArgOrLoad(pos, b, base, sel, mem, typ.Int, offset+ptrSize) - sel = source.Block.NewValue1(pos, OpSliceCap, typ.Int, source) - return storeArgOrLoad(pos, b, base, sel, mem, typ.Int, offset+2*ptrSize) - - case types.TCOMPLEX64: - sel := source.Block.NewValue1(pos, OpComplexReal, typ.Float32, source) - mem = storeArgOrLoad(pos, b, base, sel, mem, typ.Float32, offset) + case OpSliceMake: + mem = x.storeArgOrLoad(pos, b, source.Args[0], mem, x.typs.BytePtr, storeOffset, 0, storeRc.next(x.typs.BytePtr)) + pos = pos.WithNotStmt() + mem = x.storeArgOrLoad(pos, b, source.Args[1], mem, x.typs.Int, storeOffset+x.ptrSize, 0, storeRc.next(x.typs.Int)) + return x.storeArgOrLoad(pos, b, source.Args[2], mem, x.typs.Int, storeOffset+2*x.ptrSize, 0, storeRc) + } + + // For nodes that cannot be taken apart -- OpSelectN, other structure selectors. + switch t.Kind() { + case types.TARRAY: + elt := t.Elem() + if source.Type != t && t.NumElem() == 1 && elt.Width == t.Width && t.Width == x.regSize { + t = removeTrivialWrapperTypes(t) + // it could be a leaf type, but the "leaf" could be complex64 (for example) + return x.storeArgOrLoad(pos, b, source, mem, t, storeOffset, loadRegOffset, storeRc) + } + eltRO := x.regWidth(elt) + for i := int64(0); i < t.NumElem(); i++ { + sel := source.Block.NewValue1I(pos, OpArraySelect, elt, i, source) + mem = x.storeArgOrLoad(pos, b, sel, mem, elt, storeOffset+i*elt.Width, loadRegOffset, storeRc.at(t, 0)) + loadRegOffset += eltRO pos = pos.WithNotStmt() - sel = source.Block.NewValue1(pos, OpComplexImag, typ.Float32, source) - return storeArgOrLoad(pos, b, base, sel, mem, typ.Float32, offset+4) + } + return mem - case types.TCOMPLEX128: - sel := source.Block.NewValue1(pos, OpComplexReal, typ.Float64, source) - mem = storeArgOrLoad(pos, b, base, sel, mem, typ.Float64, offset) + case types.TSTRUCT: + if source.Type != t && t.NumFields() == 1 && t.Field(0).Type.Width == t.Width && t.Width == x.regSize { + // This peculiar test deals with accesses to immediate interface data. + // It works okay because everything is the same size. + // Example code that triggers this can be found in go/constant/value.go, function ToComplex + // v119 (+881) = IData v6 + // v121 (+882) = StaticLECall {AuxCall{"".itof([intVal,0])[floatVal,8]}} [16] v119 v1 + // This corresponds to the generic rewrite rule "(StructSelect [0] (IData x)) => (IData x)" + // Guard against "struct{struct{*foo}}" + // Other rewriting phases create minor glitches when they transform IData, for instance the + // interface-typed Arg "x" of ToFloat in go/constant/value.go + // v6 (858) = Arg {x} (x[Value], x[Value]) + // is rewritten by decomposeArgs into + // v141 (858) = Arg {x} + // v139 (858) = Arg <*uint8> {x} [8] + // because of a type case clause on line 862 of go/constant/value.go + // case intVal: + // return itof(x) + // v139 is later stored as an intVal == struct{val *big.Int} which naively requires the fields of + // of a *uint8, which does not succeed. + t = removeTrivialWrapperTypes(t) + // it could be a leaf type, but the "leaf" could be complex64 (for example) + return x.storeArgOrLoad(pos, b, source, mem, t, storeOffset, loadRegOffset, storeRc) + } + + for i := 0; i < t.NumFields(); i++ { + fld := t.Field(i) + sel := source.Block.NewValue1I(pos, OpStructSelect, fld.Type, int64(i), source) + mem = x.storeArgOrLoad(pos, b, sel, mem, fld.Type, storeOffset+fld.Offset, loadRegOffset, storeRc.next(fld.Type)) + loadRegOffset += x.regWidth(fld.Type) pos = pos.WithNotStmt() - sel = source.Block.NewValue1(pos, OpComplexImag, typ.Float64, source) - return storeArgOrLoad(pos, b, base, sel, mem, typ.Float64, offset+8) } + return mem - dst := offsetFrom(base, offset, types.NewPtr(t)) - x := b.NewValue3A(pos, OpStore, types.TypeMem, t, dst, source, mem) - if debug { - fmt.Printf("\t\tstoreArg returns %s\n", x.LongString()) + case types.TINT64, types.TUINT64: + if t.Width == x.regSize { + break } - return x + tHi, tLo := x.intPairTypes(t.Kind()) + sel := source.Block.NewValue1(pos, OpInt64Hi, tHi, source) + mem = x.storeArgOrLoad(pos, b, sel, mem, tHi, storeOffset+x.hiOffset, loadRegOffset+x.hiRo, storeRc.plus(x.hiRo)) + pos = pos.WithNotStmt() + sel = source.Block.NewValue1(pos, OpInt64Lo, tLo, source) + return x.storeArgOrLoad(pos, b, sel, mem, tLo, storeOffset+x.lowOffset, loadRegOffset+x.loRo, storeRc.plus(x.hiRo)) + + case types.TINTER: + sel := source.Block.NewValue1(pos, OpITab, x.typs.BytePtr, source) + mem = x.storeArgOrLoad(pos, b, sel, mem, x.typs.BytePtr, storeOffset, loadRegOffset, storeRc.next(x.typs.BytePtr)) + pos = pos.WithNotStmt() + sel = source.Block.NewValue1(pos, OpIData, x.typs.BytePtr, source) + return x.storeArgOrLoad(pos, b, sel, mem, x.typs.BytePtr, storeOffset+x.ptrSize, loadRegOffset+RO_iface_data, storeRc) + + case types.TSTRING: + sel := source.Block.NewValue1(pos, OpStringPtr, x.typs.BytePtr, source) + mem = x.storeArgOrLoad(pos, b, sel, mem, x.typs.BytePtr, storeOffset, loadRegOffset, storeRc.next(x.typs.BytePtr)) + pos = pos.WithNotStmt() + sel = source.Block.NewValue1(pos, OpStringLen, x.typs.Int, source) + return x.storeArgOrLoad(pos, b, sel, mem, x.typs.Int, storeOffset+x.ptrSize, loadRegOffset+RO_string_len, storeRc) + + case types.TSLICE: + et := types.NewPtr(t.Elem()) + sel := source.Block.NewValue1(pos, OpSlicePtr, et, source) + mem = x.storeArgOrLoad(pos, b, sel, mem, et, storeOffset, loadRegOffset, storeRc.next(et)) + pos = pos.WithNotStmt() + sel = source.Block.NewValue1(pos, OpSliceLen, x.typs.Int, source) + mem = x.storeArgOrLoad(pos, b, sel, mem, x.typs.Int, storeOffset+x.ptrSize, loadRegOffset+RO_slice_len, storeRc.next(x.typs.Int)) + sel = source.Block.NewValue1(pos, OpSliceCap, x.typs.Int, source) + return x.storeArgOrLoad(pos, b, sel, mem, x.typs.Int, storeOffset+2*x.ptrSize, loadRegOffset+RO_slice_cap, storeRc) + + case types.TCOMPLEX64: + sel := source.Block.NewValue1(pos, OpComplexReal, x.typs.Float32, source) + mem = x.storeArgOrLoad(pos, b, sel, mem, x.typs.Float32, storeOffset, loadRegOffset, storeRc.next(x.typs.Float32)) + pos = pos.WithNotStmt() + sel = source.Block.NewValue1(pos, OpComplexImag, x.typs.Float32, source) + return x.storeArgOrLoad(pos, b, sel, mem, x.typs.Float32, storeOffset+4, loadRegOffset+RO_complex_imag, storeRc) + + case types.TCOMPLEX128: + sel := source.Block.NewValue1(pos, OpComplexReal, x.typs.Float64, source) + mem = x.storeArgOrLoad(pos, b, sel, mem, x.typs.Float64, storeOffset, loadRegOffset, storeRc.next(x.typs.Float64)) + pos = pos.WithNotStmt() + sel = source.Block.NewValue1(pos, OpComplexImag, x.typs.Float64, source) + return x.storeArgOrLoad(pos, b, sel, mem, x.typs.Float64, storeOffset+8, loadRegOffset+RO_complex_imag, storeRc) } - // rewriteArgs removes all the Args from a call and converts the call args into appropriate - // stores (or later, register movement). Extra args for interface and closure calls are ignored, - // but removed. - rewriteArgs := func(v *Value, firstArg int) *Value { - // Thread the stores on the memory arg - aux := v.Aux.(*AuxCall) - pos := v.Pos.WithNotStmt() - m0 := v.Args[len(v.Args)-1] - mem := m0 - for i, a := range v.Args { - if i < firstArg { - continue - } - if a == m0 { // mem is last. - break + s := mem + if source.Op == OpDereference { + source.Op = OpLoad // For purposes of parameter passing expansion, a Dereference is a Load. + } + if storeRc.hasRegs() { + storeRc.addArg(source) + } else { + dst := x.offsetFrom(b, storeRc.storeDest, storeOffset, types.NewPtr(t)) + s = b.NewValue3A(pos, OpStore, types.TypeMem, t, dst, source, mem) + } + if x.debug { + x.Printf("-->storeArg returns %s, storeRc=%s\n", s.LongString(), storeRc.String()) + } + return s +} + +// rewriteArgs replaces all the call-parameter Args to a call with their register translation (if any). +// Preceding parameters (code pointers, closure pointer) are preserved, and the memory input is modified +// to account for any parameter stores required. +// Any of the old Args that have their use count fall to zero are marked OpInvalid. +func (x *expandState) rewriteArgs(v *Value, firstArg int) { + if x.debug { + x.indent(3) + defer x.indent(-3) + x.Printf("rewriteArgs(%s; %d)\n", v.LongString(), firstArg) + } + // Thread the stores on the memory arg + aux := v.Aux.(*AuxCall) + pos := v.Pos.WithNotStmt() + m0 := v.MemoryArg() + mem := m0 + newArgs := []*Value{} + oldArgs := []*Value{} + for i, a := range v.Args[firstArg : len(v.Args)-1] { // skip leading non-parameter SSA Args and trailing mem SSA Arg. + oldArgs = append(oldArgs, a) + auxI := int64(i) + aRegs := aux.RegsOfArg(auxI) + aType := aux.TypeOfArg(auxI) + if len(aRegs) == 0 && a.Op == OpDereference { + aOffset := aux.OffsetOfArg(auxI) + if a.MemoryArg() != m0 { + x.f.Fatalf("Op...LECall and OpDereference have mismatched mem, %s and %s", v.LongString(), a.LongString()) } - auxI := int64(i - firstArg) - if a.Op == OpDereference { - if a.MemoryArg() != m0 { - f.Fatalf("Op...LECall and OpDereference have mismatched mem, %s and %s", v.LongString(), a.LongString()) - } - // "Dereference" of addressed (probably not-SSA-eligible) value becomes Move - // TODO this will be more complicated with registers in the picture. - source := a.Args[0] - dst := f.ConstOffPtrSP(source.Type, aux.OffsetOfArg(auxI), sp) - if a.Uses == 1 && a.Block == v.Block { - a.reset(OpMove) - a.Pos = pos - a.Type = types.TypeMem - a.Aux = aux.TypeOfArg(auxI) - a.AuxInt = aux.SizeOfArg(auxI) - a.SetArgs3(dst, source, mem) - mem = a - } else { - mem = v.Block.NewValue3A(pos, OpMove, types.TypeMem, aux.TypeOfArg(auxI), dst, source, mem) - mem.AuxInt = aux.SizeOfArg(auxI) - } + // "Dereference" of addressed (probably not-SSA-eligible) value becomes Move + // TODO(register args) this will be more complicated with registers in the picture. + mem = x.rewriteDereference(v.Block, x.sp, a, mem, aOffset, aux.SizeOfArg(auxI), aType, pos) + } else { + var rc registerCursor + var result *[]*Value + var aOffset int64 + if len(aRegs) > 0 { + result = &newArgs } else { - if debug { - fmt.Printf("storeArg %s, %v, %d\n", a.LongString(), aux.TypeOfArg(auxI), aux.OffsetOfArg(auxI)) + aOffset = aux.OffsetOfArg(auxI) + } + if x.debug { + x.Printf("...storeArg %s, %v, %d\n", a.LongString(), aType, aOffset) + } + rc.init(aRegs, aux.abiInfo, result, x.sp) + mem = x.storeArgOrLoad(pos, v.Block, a, mem, aType, aOffset, 0, rc) + } + } + var preArgStore [2]*Value + preArgs := append(preArgStore[:0], v.Args[0:firstArg]...) + v.resetArgs() + v.AddArgs(preArgs...) + v.AddArgs(newArgs...) + v.AddArg(mem) + for _, a := range oldArgs { + if a.Uses == 0 { + if x.debug { + x.Printf("...marking %v unused\n", a.LongString()) + } + a.invalidateRecursively() + } + } + + return +} + +// expandCalls converts LE (Late Expansion) calls that act like they receive value args into a lower-level form +// that is more oriented to a platform's ABI. The SelectN operations that extract results are rewritten into +// more appropriate forms, and any StructMake or ArrayMake inputs are decomposed until non-struct values are +// reached. On the callee side, OpArg nodes are not decomposed until this phase is run. +// TODO results should not be lowered until this phase. +func expandCalls(f *Func) { + // Calls that need lowering have some number of inputs, including a memory input, + // and produce a tuple of (value1, value2, ..., mem) where valueK may or may not be SSA-able. + + // With the current ABI those inputs need to be converted into stores to memory, + // rethreading the call's memory input to the first, and the new call now receiving the last. + + // With the current ABI, the outputs need to be converted to loads, which will all use the call's + // memory output as their input. + sp, _ := f.spSb() + x := &expandState{ + f: f, + abi1: f.ABI1, + debug: f.pass.debug > 0, + canSSAType: f.fe.CanSSA, + regSize: f.Config.RegSize, + sp: sp, + typs: &f.Config.Types, + ptrSize: f.Config.PtrSize, + namedSelects: make(map[*Value][]namedVal), + sdom: f.Sdom(), + commonArgs: make(map[selKey]*Value), + memForCall: make(map[ID]*Value), + transformedSelects: make(map[ID]bool), + } + + // For 32-bit, need to deal with decomposition of 64-bit integers, which depends on endianness. + if f.Config.BigEndian { + x.lowOffset, x.hiOffset = 4, 0 + x.loRo, x.hiRo = 1, 0 + } else { + x.lowOffset, x.hiOffset = 0, 4 + x.loRo, x.hiRo = 0, 1 + } + + if x.debug { + x.Printf("\nexpandsCalls(%s)\n", f.Name) + } + + for i, name := range f.Names { + t := name.Type + if x.isAlreadyExpandedAggregateType(t) { + for j, v := range f.NamedValues[*name] { + if v.Op == OpSelectN || v.Op == OpArg && x.isAlreadyExpandedAggregateType(v.Type) { + ns := x.namedSelects[v] + x.namedSelects[v] = append(ns, namedVal{locIndex: i, valIndex: j}) } - mem = storeArgOrLoad(pos, v.Block, sp, a, mem, aux.TypeOfArg(auxI), aux.OffsetOfArg(auxI)) } } - v.resetArgs() - return mem } // TODO if too slow, whole program iteration can be replaced w/ slices of appropriate values, accumulated in first loop here. - // Step 0: rewrite the calls to convert incoming args to stores. + // Step 0: rewrite the calls to convert args to calls into stores/register movement. for _, b := range f.Blocks { for _, v := range b.Values { + firstArg := 0 switch v.Op { case OpStaticLECall: - mem := rewriteArgs(v, 0) - v.SetArgs1(mem) - case OpClosureLECall: - code := v.Args[0] - context := v.Args[1] - mem := rewriteArgs(v, 2) - v.SetArgs3(code, context, mem) case OpInterLECall: - code := v.Args[0] - mem := rewriteArgs(v, 1) - v.SetArgs2(code, mem) + firstArg = 1 + case OpClosureLECall: + firstArg = 2 + default: + continue } + x.rewriteArgs(v, firstArg) } - } - - for i, name := range f.Names { - t := name.Type - if isAlreadyExpandedAggregateType(t) { - for j, v := range f.NamedValues[name] { - if v.Op == OpSelectN || v.Op == OpArg && isAlreadyExpandedAggregateType(v.Type) { - ns := namedSelects[v] - namedSelects[v] = append(ns, namedVal{locIndex: i, valIndex: j}) + if isBlockMultiValueExit(b) { + x.indent(3) + // Very similar to code in rewriteArgs, but results instead of args. + v := b.Controls[0] + m0 := v.MemoryArg() + mem := m0 + aux := f.OwnAux + pos := v.Pos.WithNotStmt() + allResults := []*Value{} + if x.debug { + x.Printf("multiValueExit rewriting %s\n", v.LongString()) + } + var oldArgs []*Value + for j, a := range v.Args[:len(v.Args)-1] { + oldArgs = append(oldArgs, a) + i := int64(j) + auxType := aux.TypeOfResult(i) + auxBase := b.NewValue2A(v.Pos, OpLocalAddr, types.NewPtr(auxType), aux.NameOfResult(i), x.sp, mem) + auxOffset := int64(0) + auxSize := aux.SizeOfResult(i) + aRegs := aux.RegsOfResult(int64(j)) + if len(aRegs) == 0 && a.Op == OpDereference { + // Avoid a self-move, and if one is detected try to remove the already-inserted VarDef for the assignment that won't happen. + if dAddr, dMem := a.Args[0], a.Args[1]; dAddr.Op == OpLocalAddr && dAddr.Args[0].Op == OpSP && + dAddr.Args[1] == dMem && dAddr.Aux == aux.NameOfResult(i) { + if dMem.Op == OpVarDef && dMem.Aux == dAddr.Aux { + dMem.copyOf(dMem.MemoryArg()) // elide the VarDef + } + continue + } + mem = x.rewriteDereference(v.Block, auxBase, a, mem, auxOffset, auxSize, auxType, pos) + } else { + if a.Op == OpLoad && a.Args[0].Op == OpLocalAddr { + addr := a.Args[0] // This is a self-move. // TODO(register args) do what here for registers? + if addr.MemoryArg() == a.MemoryArg() && addr.Aux == aux.NameOfResult(i) { + continue + } + } + var rc registerCursor + var result *[]*Value + if len(aRegs) > 0 { + result = &allResults + } + rc.init(aRegs, aux.abiInfo, result, auxBase) + mem = x.storeArgOrLoad(v.Pos, b, a, mem, aux.TypeOfResult(i), auxOffset, 0, rc) + } + } + v.resetArgs() + v.AddArgs(allResults...) + v.AddArg(mem) + v.Type = types.NewResults(append(abi.RegisterTypes(aux.abiInfo.OutParams()), types.TypeMem)) + b.SetControl(v) + for _, a := range oldArgs { + if a.Uses == 0 { + if x.debug { + x.Printf("...marking %v unused\n", a.LongString()) + } + a.invalidateRecursively() } } + if x.debug { + x.Printf("...multiValueExit new result %s\n", v.LongString()) + } + x.indent(-3) } } @@ -715,24 +1272,19 @@ func expandCalls(f *Func) { t := v.Aux.(*types.Type) source := v.Args[1] tSrc := source.Type - iAEATt := isAlreadyExpandedAggregateType(t) + iAEATt := x.isAlreadyExpandedAggregateType(t) if !iAEATt { // guarding against store immediate struct into interface data field -- store type is *uint8 // TODO can this happen recursively? - iAEATt = isAlreadyExpandedAggregateType(tSrc) + iAEATt = x.isAlreadyExpandedAggregateType(tSrc) if iAEATt { t = tSrc } } - if iAEATt { - if debug { - fmt.Printf("Splitting store %s\n", v.LongString()) - } - dst, mem := v.Args[0], v.Args[2] - mem = storeArgOrLoad(v.Pos, b, dst, source, mem, t, 0) - v.copyOf(mem) - } + dst, mem := v.Args[0], v.Args[2] + mem = x.storeArgOrLoad(v.Pos, b, source, mem, t, 0, 0, registerCursor{storeDest: dst}) + v.copyOf(mem) } } } @@ -752,15 +1304,15 @@ func expandCalls(f *Func) { case OpStructSelect, OpArraySelect, OpIData, OpITab, OpStringPtr, OpStringLen, - OpSlicePtr, OpSliceLen, OpSliceCap, + OpSlicePtr, OpSliceLen, OpSliceCap, OpSlicePtrUnchecked, OpComplexReal, OpComplexImag, OpInt64Hi, OpInt64Lo: w := v.Args[0] switch w.Op { case OpStructSelect, OpArraySelect, OpSelectN, OpArg: val2Preds[w] += 1 - if debug { - fmt.Printf("v2p[%s] = %d\n", w.LongString(), val2Preds[w]) + if x.debug { + x.Printf("v2p[%s] = %d\n", w.LongString(), val2Preds[w]) } } fallthrough @@ -768,19 +1320,19 @@ func expandCalls(f *Func) { case OpSelectN: if _, ok := val2Preds[v]; !ok { val2Preds[v] = 0 - if debug { - fmt.Printf("v2p[%s] = %d\n", v.LongString(), val2Preds[v]) + if x.debug { + x.Printf("v2p[%s] = %d\n", v.LongString(), val2Preds[v]) } } case OpArg: - if !isAlreadyExpandedAggregateType(v.Type) { + if !x.isAlreadyExpandedAggregateType(v.Type) { continue } if _, ok := val2Preds[v]; !ok { val2Preds[v] = 0 - if debug { - fmt.Printf("v2p[%s] = %d\n", v.LongString(), val2Preds[v]) + if x.debug { + x.Printf("v2p[%s] = %d\n", v.LongString(), val2Preds[v]) } } @@ -790,7 +1342,7 @@ func expandCalls(f *Func) { which := v.AuxInt aux := call.Aux.(*AuxCall) pt := v.Type - off := offsetFrom(sp, aux.OffsetOfResult(which), pt) + off := x.offsetFrom(x.f.Entry, x.sp, aux.OffsetOfResult(which), pt) v.copyOf(off) } } @@ -812,7 +1364,7 @@ func expandCalls(f *Func) { if bi == bj { return vi.ID < vj.ID } - return sdom.domorder(bi) > sdom.domorder(bj) // reverse the order to put dominators last. + return x.sdom.domorder(bi) > x.sdom.domorder(bj) // reverse the order to put dominators last. } // Accumulate order in allOrdered @@ -846,7 +1398,7 @@ func expandCalls(f *Func) { } } - common = make(map[selKey]*Value) + x.commonSelectors = make(map[selKey]*Value) // Rewrite duplicate selectors as copies where possible. for i := len(allOrdered) - 1; i >= 0; i-- { v := allOrdered[i] @@ -868,7 +1420,7 @@ func expandCalls(f *Func) { offset := int64(0) switch v.Op { case OpStructSelect: - if w.Type.Etype == types.TSTRUCT { + if w.Type.Kind() == types.TSTRUCT { offset = w.Type.FieldOff(int(v.AuxInt)) } else { // Immediate interface data artifact, offset is zero. f.Fatalf("Expand calls interface data problem, func %s, v=%s, w=%s\n", f.Name, v.LongString(), w.LongString()) @@ -876,28 +1428,31 @@ func expandCalls(f *Func) { case OpArraySelect: offset = size * v.AuxInt case OpSelectN: - offset = w.Aux.(*AuxCall).OffsetOfResult(v.AuxInt) + offset = v.AuxInt // offset is just a key, really. case OpInt64Hi: - offset = hiOffset + offset = x.hiOffset case OpInt64Lo: - offset = lowOffset + offset = x.lowOffset case OpStringLen, OpSliceLen, OpIData: - offset = ptrSize + offset = x.ptrSize case OpSliceCap: - offset = 2 * ptrSize + offset = 2 * x.ptrSize case OpComplexImag: offset = size } - sk := selKey{from: w, size: size, offset: offset, typ: typ} - dupe := common[sk] + sk := selKey{from: w, size: size, offsetOrIndex: offset, typ: typ} + dupe := x.commonSelectors[sk] if dupe == nil { - common[sk] = v - } else if sdom.IsAncestorEq(dupe.Block, v.Block) { + x.commonSelectors[sk] = v + } else if x.sdom.IsAncestorEq(dupe.Block, v.Block) { + if x.debug { + x.Printf("Duplicate, make %s copy of %s\n", v, dupe) + } v.copyOf(dupe) } else { // Because values are processed in dominator order, the old common[s] will never dominate after a miss is seen. // Installing the new value might match some future values. - common[sk] = v + x.commonSelectors[sk] = v } } @@ -906,56 +1461,112 @@ func expandCalls(f *Func) { // Rewrite selectors. for i, v := range allOrdered { - if debug { + if x.debug { b := v.Block - fmt.Printf("allOrdered[%d] = b%d, %s, uses=%d\n", i, b.ID, v.LongString(), v.Uses) + x.Printf("allOrdered[%d] = b%d, %s, uses=%d\n", i, b.ID, v.LongString(), v.Uses) } if v.Uses == 0 { - v.reset(OpInvalid) + v.invalidateRecursively() continue } if v.Op == OpCopy { continue } - locs := rewriteSelect(v, v, 0) + locs := x.rewriteSelect(v, v, 0, 0) // Install new names. if v.Type.IsMemory() { continue } // Leaf types may have debug locations - if !isAlreadyExpandedAggregateType(v.Type) { + if !x.isAlreadyExpandedAggregateType(v.Type) { for _, l := range locs { - f.NamedValues[l] = append(f.NamedValues[l], v) + if _, ok := f.NamedValues[*l]; !ok { + f.Names = append(f.Names, l) + } + f.NamedValues[*l] = append(f.NamedValues[*l], v) } - f.Names = append(f.Names, locs...) continue } - // Not-leaf types that had debug locations need to lose them. - if ns, ok := namedSelects[v]; ok { + if ns, ok := x.namedSelects[v]; ok { + // Not-leaf types that had debug locations need to lose them. + toDelete = append(toDelete, ns...) } } deleteNamedVals(f, toDelete) - // Step 4: rewrite the calls themselves, correcting the type + // Step 4: rewrite the calls themselves, correcting the type. for _, b := range f.Blocks { for _, v := range b.Values { switch v.Op { + case OpArg: + x.rewriteArgToMemOrRegs(v) case OpStaticLECall: v.Op = OpStaticCall - v.Type = types.TypeMem + rts := abi.RegisterTypes(v.Aux.(*AuxCall).abiInfo.OutParams()) + v.Type = types.NewResults(append(rts, types.TypeMem)) case OpClosureLECall: v.Op = OpClosureCall - v.Type = types.TypeMem + rts := abi.RegisterTypes(v.Aux.(*AuxCall).abiInfo.OutParams()) + v.Type = types.NewResults(append(rts, types.TypeMem)) case OpInterLECall: v.Op = OpInterCall - v.Type = types.TypeMem + rts := abi.RegisterTypes(v.Aux.(*AuxCall).abiInfo.OutParams()) + v.Type = types.NewResults(append(rts, types.TypeMem)) + } + } + } + + // Step 5: dedup OpArgXXXReg values. Mostly it is already dedup'd by commonArgs, + // but there are cases that we have same OpArgXXXReg values with different types. + // E.g. string is sometimes decomposed as { *int8, int }, sometimes as { unsafe.Pointer, uintptr }. + // (Can we avoid that?) + var IArg, FArg [32]*Value + for _, v := range f.Entry.Values { + switch v.Op { + case OpArgIntReg: + i := v.AuxInt + if w := IArg[i]; w != nil { + if w.Type.Width != v.Type.Width { + f.Fatalf("incompatible OpArgIntReg [%d]: %s and %s", i, v.LongString(), w.LongString()) + } + if w.Type.IsUnsafePtr() && !v.Type.IsUnsafePtr() { + // Update unsafe.Pointer type if we know the actual pointer type. + w.Type = v.Type + } + // TODO: don't dedup pointer and scalar? Rewrite to OpConvert? Can it happen? + v.copyOf(w) + } else { + IArg[i] = v + } + case OpArgFloatReg: + i := v.AuxInt + if w := FArg[i]; w != nil { + if w.Type.Width != v.Type.Width { + f.Fatalf("incompatible OpArgFloatReg [%d]: %v and %v", i, v, w) + } + v.copyOf(w) + } else { + FArg[i] = v } } } - // Step 5: elide any copies introduced. + // Step 6: elide any copies introduced. + // Update named values. + for _, name := range f.Names { + values := f.NamedValues[*name] + for i, v := range values { + if v.Op == OpCopy { + a := v.Args[0] + for a.Op == OpCopy { + a = a.Args[0] + } + values[i] = a + } + } + } for _, b := range f.Blocks { for _, v := range b.Values { for i, a := range v.Args { @@ -966,10 +1577,163 @@ func expandCalls(f *Func) { v.SetArg(i, aa) for a.Uses == 0 { b := a.Args[0] - a.reset(OpInvalid) + a.invalidateRecursively() a = b } } } } + + // Rewriting can attach lines to values that are unlikely to survive code generation, so move them to a use. + for _, b := range f.Blocks { + for _, v := range b.Values { + for _, a := range v.Args { + if a.Pos.IsStmt() != src.PosIsStmt { + continue + } + if a.Type.IsMemory() { + continue + } + if a.Pos.Line() != v.Pos.Line() { + continue + } + if !a.Pos.SameFile(v.Pos) { + continue + } + switch a.Op { + case OpArgIntReg, OpArgFloatReg, OpSelectN: + v.Pos = v.Pos.WithIsStmt() + a.Pos = a.Pos.WithDefaultStmt() + } + } + } + } +} + +// rewriteArgToMemOrRegs converts OpArg v in-place into the register version of v, +// if that is appropriate. +func (x *expandState) rewriteArgToMemOrRegs(v *Value) *Value { + if x.debug { + x.indent(3) + defer x.indent(-3) + x.Printf("rewriteArgToMemOrRegs(%s)\n", v.LongString()) + } + pa := x.prAssignForArg(v) + switch len(pa.Registers) { + case 0: + frameOff := v.Aux.(*ir.Name).FrameOffset() + if pa.Offset() != int32(frameOff+x.f.ABISelf.LocalsOffset()) { + panic(fmt.Errorf("Parameter assignment %d and OpArg.Aux frameOffset %d disagree, op=%s", + pa.Offset(), frameOff, v.LongString())) + } + case 1: + t := v.Type + key := selKey{v, 0, t.Width, t} + w := x.commonArgs[key] + if w != nil { + v.copyOf(w) + break + } + r := pa.Registers[0] + var i int64 + v.Op, i = ArgOpAndRegisterFor(r, x.f.ABISelf) + v.Aux = &AuxNameOffset{v.Aux.(*ir.Name), 0} + v.AuxInt = i + x.commonArgs[key] = v + + default: + panic(badVal("Saw unexpanded OpArg", v)) + } + if x.debug { + x.Printf("-->%s\n", v.LongString()) + } + return v +} + +// newArgToMemOrRegs either rewrites toReplace into an OpArg referencing memory or into an OpArgXXXReg to a register, +// or rewrites it into a copy of the appropriate OpArgXXX. The actual OpArgXXX is determined by combining baseArg (an OpArg) +// with offset, regOffset, and t to determine which portion of it to reference (either all or a part, in memory or in registers). +func (x *expandState) newArgToMemOrRegs(baseArg, toReplace *Value, offset int64, regOffset Abi1RO, t *types.Type, pos src.XPos) *Value { + if x.debug { + x.indent(3) + defer x.indent(-3) + x.Printf("newArgToMemOrRegs(base=%s; toReplace=%s; t=%s; memOff=%d; regOff=%d)\n", baseArg.String(), toReplace.LongString(), t.String(), offset, regOffset) + } + key := selKey{baseArg, offset, t.Width, t} + w := x.commonArgs[key] + if w != nil { + if toReplace != nil { + toReplace.copyOf(w) + } + return w + } + + pa := x.prAssignForArg(baseArg) + if len(pa.Registers) == 0 { // Arg is on stack + frameOff := baseArg.Aux.(*ir.Name).FrameOffset() + if pa.Offset() != int32(frameOff+x.f.ABISelf.LocalsOffset()) { + panic(fmt.Errorf("Parameter assignment %d and OpArg.Aux frameOffset %d disagree, op=%s", + pa.Offset(), frameOff, baseArg.LongString())) + } + aux := baseArg.Aux + auxInt := baseArg.AuxInt + offset + if toReplace != nil && toReplace.Block == baseArg.Block { + toReplace.reset(OpArg) + toReplace.Aux = aux + toReplace.AuxInt = auxInt + toReplace.Type = t + w = toReplace + } else { + w = baseArg.Block.NewValue0IA(pos, OpArg, t, auxInt, aux) + } + x.commonArgs[key] = w + if toReplace != nil { + toReplace.copyOf(w) + } + if x.debug { + x.Printf("-->%s\n", w.LongString()) + } + return w + } + // Arg is in registers + r := pa.Registers[regOffset] + op, auxInt := ArgOpAndRegisterFor(r, x.f.ABISelf) + if op == OpArgIntReg && t.IsFloat() || op == OpArgFloatReg && t.IsInteger() { + fmt.Printf("pa=%v\nx.f.OwnAux.abiInfo=%s\n", + pa.ToString(x.f.ABISelf, true), + x.f.OwnAux.abiInfo.String()) + panic(fmt.Errorf("Op/Type mismatch, op=%s, type=%s", op.String(), t.String())) + } + if baseArg.AuxInt != 0 { + base.Fatalf("BaseArg %s bound to registers has non-zero AuxInt", baseArg.LongString()) + } + aux := &AuxNameOffset{baseArg.Aux.(*ir.Name), offset} + if toReplace != nil && toReplace.Block == baseArg.Block { + toReplace.reset(op) + toReplace.Aux = aux + toReplace.AuxInt = auxInt + toReplace.Type = t + w = toReplace + } else { + w = baseArg.Block.NewValue0IA(pos, op, t, auxInt, aux) + } + x.commonArgs[key] = w + if toReplace != nil { + toReplace.copyOf(w) + } + if x.debug { + x.Printf("-->%s\n", w.LongString()) + } + return w + +} + +// argOpAndRegisterFor converts an abi register index into an ssa Op and corresponding +// arg register index. +func ArgOpAndRegisterFor(r abi.RegIndex, abiConfig *abi.ABIConfig) (Op, int64) { + i := abiConfig.FloatIndexFor(r) + if i >= 0 { // float PR + return OpArgFloatReg, i + } + return OpArgIntReg, int64(r) } diff --git a/src/cmd/compile/internal/ssa/export_test.go b/src/cmd/compile/internal/ssa/export_test.go index b4c3e5cfdfb73a6a3965caff7e7a67ff3f3d5809..8ed8a0c4a6e3b725f5f621b0ea8855a3a6b9bac7 100644 --- a/src/cmd/compile/internal/ssa/export_test.go +++ b/src/cmd/compile/internal/ssa/export_test.go @@ -5,13 +5,13 @@ package ssa import ( + "cmd/compile/internal/ir" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/arm64" "cmd/internal/obj/s390x" "cmd/internal/obj/x86" "cmd/internal/src" - "fmt" "testing" ) @@ -36,10 +36,10 @@ func testConfigArch(tb testing.TB, arch string) *Conf { tb.Fatalf("unknown arch %s", arch) } if ctxt.Arch.PtrSize != 8 { - tb.Fatal("dummyTypes is 64-bit only") + tb.Fatal("testTypes is 64-bit only") } c := &Conf{ - config: NewConfig(arch, dummyTypes, ctxt, true), + config: NewConfig(arch, testTypes, ctxt, true), tb: tb, } return c @@ -53,131 +53,64 @@ type Conf struct { func (c *Conf) Frontend() Frontend { if c.fe == nil { - c.fe = DummyFrontend{t: c.tb, ctxt: c.config.ctxt} + c.fe = TestFrontend{t: c.tb, ctxt: c.config.ctxt} } return c.fe } -// DummyFrontend is a test-only frontend. +// TestFrontend is a test-only frontend. // It assumes 64 bit integers and pointers. -type DummyFrontend struct { +type TestFrontend struct { t testing.TB ctxt *obj.Link } -type DummyAuto struct { - t *types.Type - s string -} - -func (d *DummyAuto) Typ() *types.Type { - return d.t -} - -func (d *DummyAuto) String() string { - return d.s -} - -func (d *DummyAuto) StorageClass() StorageClass { - return ClassAuto -} - -func (d *DummyAuto) IsSynthetic() bool { - return false -} - -func (d *DummyAuto) IsAutoTmp() bool { - return true -} - -func (DummyFrontend) StringData(s string) *obj.LSym { +func (TestFrontend) StringData(s string) *obj.LSym { return nil } -func (DummyFrontend) Auto(pos src.XPos, t *types.Type) GCNode { - return &DummyAuto{t: t, s: "aDummyAuto"} -} -func (d DummyFrontend) SplitString(s LocalSlot) (LocalSlot, LocalSlot) { - return LocalSlot{N: s.N, Type: dummyTypes.BytePtr, Off: s.Off}, LocalSlot{N: s.N, Type: dummyTypes.Int, Off: s.Off + 8} +func (TestFrontend) Auto(pos src.XPos, t *types.Type) *ir.Name { + n := ir.NewNameAt(pos, &types.Sym{Name: "aFakeAuto"}) + n.Class = ir.PAUTO + return n } -func (d DummyFrontend) SplitInterface(s LocalSlot) (LocalSlot, LocalSlot) { - return LocalSlot{N: s.N, Type: dummyTypes.BytePtr, Off: s.Off}, LocalSlot{N: s.N, Type: dummyTypes.BytePtr, Off: s.Off + 8} -} -func (d DummyFrontend) SplitSlice(s LocalSlot) (LocalSlot, LocalSlot, LocalSlot) { - return LocalSlot{N: s.N, Type: s.Type.Elem().PtrTo(), Off: s.Off}, - LocalSlot{N: s.N, Type: dummyTypes.Int, Off: s.Off + 8}, - LocalSlot{N: s.N, Type: dummyTypes.Int, Off: s.Off + 16} -} -func (d DummyFrontend) SplitComplex(s LocalSlot) (LocalSlot, LocalSlot) { - if s.Type.Size() == 16 { - return LocalSlot{N: s.N, Type: dummyTypes.Float64, Off: s.Off}, LocalSlot{N: s.N, Type: dummyTypes.Float64, Off: s.Off + 8} - } - return LocalSlot{N: s.N, Type: dummyTypes.Float32, Off: s.Off}, LocalSlot{N: s.N, Type: dummyTypes.Float32, Off: s.Off + 4} -} -func (d DummyFrontend) SplitInt64(s LocalSlot) (LocalSlot, LocalSlot) { - if s.Type.IsSigned() { - return LocalSlot{N: s.N, Type: dummyTypes.Int32, Off: s.Off + 4}, LocalSlot{N: s.N, Type: dummyTypes.UInt32, Off: s.Off} - } - return LocalSlot{N: s.N, Type: dummyTypes.UInt32, Off: s.Off + 4}, LocalSlot{N: s.N, Type: dummyTypes.UInt32, Off: s.Off} -} -func (d DummyFrontend) SplitStruct(s LocalSlot, i int) LocalSlot { - return LocalSlot{N: s.N, Type: s.Type.FieldType(i), Off: s.Off + s.Type.FieldOff(i)} -} -func (d DummyFrontend) SplitArray(s LocalSlot) LocalSlot { - return LocalSlot{N: s.N, Type: s.Type.Elem(), Off: s.Off} -} - -func (d DummyFrontend) SplitSlot(parent *LocalSlot, suffix string, offset int64, t *types.Type) LocalSlot { +func (d TestFrontend) SplitSlot(parent *LocalSlot, suffix string, offset int64, t *types.Type) LocalSlot { return LocalSlot{N: parent.N, Type: t, Off: offset} } -func (DummyFrontend) Line(_ src.XPos) string { +func (TestFrontend) Line(_ src.XPos) string { return "unknown.go:0" } -func (DummyFrontend) AllocFrame(f *Func) { +func (TestFrontend) AllocFrame(f *Func) { } -func (d DummyFrontend) Syslook(s string) *obj.LSym { +func (d TestFrontend) Syslook(s string) *obj.LSym { return d.ctxt.Lookup(s) } -func (DummyFrontend) UseWriteBarrier() bool { +func (TestFrontend) UseWriteBarrier() bool { return true // only writebarrier_test cares } -func (DummyFrontend) SetWBPos(pos src.XPos) { +func (TestFrontend) SetWBPos(pos src.XPos) { } -func (d DummyFrontend) Logf(msg string, args ...interface{}) { d.t.Logf(msg, args...) } -func (d DummyFrontend) Log() bool { return true } +func (d TestFrontend) Logf(msg string, args ...interface{}) { d.t.Logf(msg, args...) } +func (d TestFrontend) Log() bool { return true } -func (d DummyFrontend) Fatalf(_ src.XPos, msg string, args ...interface{}) { d.t.Fatalf(msg, args...) } -func (d DummyFrontend) Warnl(_ src.XPos, msg string, args ...interface{}) { d.t.Logf(msg, args...) } -func (d DummyFrontend) Debug_checknil() bool { return false } +func (d TestFrontend) Fatalf(_ src.XPos, msg string, args ...interface{}) { d.t.Fatalf(msg, args...) } +func (d TestFrontend) Warnl(_ src.XPos, msg string, args ...interface{}) { d.t.Logf(msg, args...) } +func (d TestFrontend) Debug_checknil() bool { return false } -func (d DummyFrontend) MyImportPath() string { +func (d TestFrontend) MyImportPath() string { return "my/import/path" } -var dummyTypes Types +var testTypes Types func init() { // Initialize just enough of the universe and the types package to make our tests function. // TODO(josharian): move universe initialization to the types package, // so this test setup can share it. - types.Tconv = func(t *types.Type, flag, mode int) string { - return t.Etype.String() - } - types.Sconv = func(s *types.Sym, flag, mode int) string { - return "sym" - } - types.FormatSym = func(sym *types.Sym, s fmt.State, verb rune, mode int) { - fmt.Fprintf(s, "sym") - } - types.FormatType = func(t *types.Type, s fmt.State, verb rune, mode int) { - fmt.Fprintf(s, "%v", t.Etype) - } - types.Dowidth = func(t *types.Type) {} - for _, typ := range [...]struct { width int64 - et types.EType + et types.Kind }{ {1, types.TINT8}, {1, types.TUINT8}, @@ -198,12 +131,12 @@ func init() { t.Align = uint8(typ.width) types.Types[typ.et] = t } - dummyTypes.SetTypPtrs() + testTypes.SetTypPtrs() } -func (d DummyFrontend) DerefItab(sym *obj.LSym, off int64) *obj.LSym { return nil } +func (d TestFrontend) DerefItab(sym *obj.LSym, off int64) *obj.LSym { return nil } -func (d DummyFrontend) CanSSA(t *types.Type) bool { - // There are no un-SSAable types in dummy land. +func (d TestFrontend) CanSSA(t *types.Type) bool { + // There are no un-SSAable types in test land. return true } diff --git a/src/cmd/compile/internal/ssa/flags_amd64_test.s b/src/cmd/compile/internal/ssa/flags_amd64_test.s index 8bd87019af71164182b068b2883ebad7e399dc47..7402f6badb1d8254bd879a4d7e92118860ca2b81 100644 --- a/src/cmd/compile/internal/ssa/flags_amd64_test.s +++ b/src/cmd/compile/internal/ssa/flags_amd64_test.s @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build amd64 - #include "textflag.h" TEXT ·asmAddFlags(SB),NOSPLIT,$0-24 diff --git a/src/cmd/compile/internal/ssa/flags_arm64_test.s b/src/cmd/compile/internal/ssa/flags_arm64_test.s index f201bcc9943502b620995d5341a4fd9cfa010465..639d7e3aedc299ece9b9a0a27debc8b566eee922 100644 --- a/src/cmd/compile/internal/ssa/flags_arm64_test.s +++ b/src/cmd/compile/internal/ssa/flags_arm64_test.s @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build arm64 - #include "textflag.h" TEXT ·asmAddFlags(SB),NOSPLIT,$0-24 diff --git a/src/cmd/compile/internal/ssa/flags_test.go b/src/cmd/compile/internal/ssa/flags_test.go index d64abf652cdfe0f9ca515cf018ae48158c0492fe..0bc1097199464992994a2562a4f61795f2eb0f32 100644 --- a/src/cmd/compile/internal/ssa/flags_test.go +++ b/src/cmd/compile/internal/ssa/flags_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build amd64 || arm64 // +build amd64 arm64 package ssa diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index e6f899a2c77b1e098166ef08592bec620e6917f2..fac876c23ebc2eede4fabfc5a085a962bb99f11b 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -5,6 +5,8 @@ package ssa import ( + "cmd/compile/internal/abi" + "cmd/compile/internal/base" "cmd/compile/internal/types" "cmd/internal/src" "crypto/sha1" @@ -43,6 +45,10 @@ type Func struct { DebugTest bool // default true unless $GOSSAHASH != ""; as a debugging aid, make new code conditional on this and use GOSSAHASH to binary search for failing cases PrintOrHtmlSSA bool // true if GOSSAFUNC matches, true even if fe.Log() (spew phase results to stdout) is false. ruleMatches map[string]int // number of times countRule was called during compilation for any given string + ABI0 *abi.ABIConfig // A copy, for no-sync access + ABI1 *abi.ABIConfig // A copy, for no-sync access + ABISelf *abi.ABIConfig // ABI for function being compiled + ABIDefault *abi.ABIConfig // ABI for rtcall and other no-parsed-signature/pragma functions. scheduled bool // Values in Blocks are in final order laidout bool // Blocks are ordered @@ -56,7 +62,16 @@ type Func struct { NamedValues map[LocalSlot][]*Value // Names is a copy of NamedValues.Keys. We keep a separate list // of keys to make iteration order deterministic. - Names []LocalSlot + Names []*LocalSlot + // Canonicalize root/top-level local slots, and canonicalize their pieces. + // Because LocalSlot pieces refer to their parents with a pointer, this ensures that equivalent slots really are equal. + CanonicalLocalSlots map[LocalSlot]*LocalSlot + CanonicalLocalSplits map[LocalSlotSplitKey]*LocalSlot + + // RegArgs is a slice of register-memory pairs that must be spilled and unspilled in the uncommon path of function entry. + RegArgs []Spill + // AuxCall describing parameters and results for this function. + OwnAux *AuxCall // WBLoads is a list of Blocks that branch on the write // barrier flag. Safe-points are disabled from the OpLoad that @@ -77,10 +92,16 @@ type Func struct { constants map[int64][]*Value // constants cache, keyed by constant value; users must check value's Op and Type } +type LocalSlotSplitKey struct { + parent *LocalSlot + Off int64 // offset of slot in N + Type *types.Type // type of slot +} + // NewFunc returns a new, empty function object. // Caller must set f.Config and f.Cache before using f. func NewFunc(fe Frontend) *Func { - return &Func{fe: fe, NamedValues: make(map[LocalSlot][]*Value)} + return &Func{fe: fe, NamedValues: make(map[LocalSlot][]*Value), CanonicalLocalSlots: make(map[LocalSlot]*LocalSlot), CanonicalLocalSplits: make(map[LocalSlotSplitKey]*LocalSlot)} } // NumBlocks returns an integer larger than the id of any Block in the Func. @@ -183,6 +204,101 @@ func (f *Func) retDeadcodeLiveOrderStmts(liveOrderStmts []*Value) { f.Cache.deadcode.liveOrderStmts = liveOrderStmts } +func (f *Func) localSlotAddr(slot LocalSlot) *LocalSlot { + a, ok := f.CanonicalLocalSlots[slot] + if !ok { + a = new(LocalSlot) + *a = slot // don't escape slot + f.CanonicalLocalSlots[slot] = a + } + return a +} + +func (f *Func) SplitString(name *LocalSlot) (*LocalSlot, *LocalSlot) { + ptrType := types.NewPtr(types.Types[types.TUINT8]) + lenType := types.Types[types.TINT] + // Split this string up into two separate variables. + p := f.SplitSlot(name, ".ptr", 0, ptrType) + l := f.SplitSlot(name, ".len", ptrType.Size(), lenType) + return p, l +} + +func (f *Func) SplitInterface(name *LocalSlot) (*LocalSlot, *LocalSlot) { + n := name.N + u := types.Types[types.TUINTPTR] + t := types.NewPtr(types.Types[types.TUINT8]) + // Split this interface up into two separate variables. + sfx := ".itab" + if n.Type().IsEmptyInterface() { + sfx = ".type" + } + c := f.SplitSlot(name, sfx, 0, u) // see comment in typebits.Set + d := f.SplitSlot(name, ".data", u.Size(), t) + return c, d +} + +func (f *Func) SplitSlice(name *LocalSlot) (*LocalSlot, *LocalSlot, *LocalSlot) { + ptrType := types.NewPtr(name.Type.Elem()) + lenType := types.Types[types.TINT] + p := f.SplitSlot(name, ".ptr", 0, ptrType) + l := f.SplitSlot(name, ".len", ptrType.Size(), lenType) + c := f.SplitSlot(name, ".cap", ptrType.Size()+lenType.Size(), lenType) + return p, l, c +} + +func (f *Func) SplitComplex(name *LocalSlot) (*LocalSlot, *LocalSlot) { + s := name.Type.Size() / 2 + var t *types.Type + if s == 8 { + t = types.Types[types.TFLOAT64] + } else { + t = types.Types[types.TFLOAT32] + } + r := f.SplitSlot(name, ".real", 0, t) + i := f.SplitSlot(name, ".imag", t.Size(), t) + return r, i +} + +func (f *Func) SplitInt64(name *LocalSlot) (*LocalSlot, *LocalSlot) { + var t *types.Type + if name.Type.IsSigned() { + t = types.Types[types.TINT32] + } else { + t = types.Types[types.TUINT32] + } + if f.Config.BigEndian { + return f.SplitSlot(name, ".hi", 0, t), f.SplitSlot(name, ".lo", t.Size(), types.Types[types.TUINT32]) + } + return f.SplitSlot(name, ".hi", t.Size(), t), f.SplitSlot(name, ".lo", 0, types.Types[types.TUINT32]) +} + +func (f *Func) SplitStruct(name *LocalSlot, i int) *LocalSlot { + st := name.Type + return f.SplitSlot(name, st.FieldName(i), st.FieldOff(i), st.FieldType(i)) +} +func (f *Func) SplitArray(name *LocalSlot) *LocalSlot { + n := name.N + at := name.Type + if at.NumElem() != 1 { + base.FatalfAt(n.Pos(), "bad array size") + } + et := at.Elem() + return f.SplitSlot(name, "[0]", 0, et) +} + +func (f *Func) SplitSlot(name *LocalSlot, sfx string, offset int64, t *types.Type) *LocalSlot { + lssk := LocalSlotSplitKey{name, offset, t} + if als, ok := f.CanonicalLocalSplits[lssk]; ok { + return als + } + // Note: the _ field may appear several times. But + // have no fear, identically-named but distinct Autos are + // ok, albeit maybe confusing for a debugger. + ls := f.fe.SplitSlot(name, sfx, offset, t) + f.CanonicalLocalSplits[lssk] = &ls + return &ls +} + // newValue allocates a new Value with the given fields and places it at the end of b.Values. func (f *Func) newValue(op Op, t *types.Type, b *Block, pos src.XPos) *Value { var v *Value @@ -377,13 +493,7 @@ func (b *Block) NewValue0I(pos src.XPos, op Op, t *types.Type, auxint int64) *Va } // NewValue returns a new value in the block with no arguments and an aux value. -func (b *Block) NewValue0A(pos src.XPos, op Op, t *types.Type, aux interface{}) *Value { - if _, ok := aux.(int64); ok { - // Disallow int64 aux values. They should be in the auxint field instead. - // Maybe we want to allow this at some point, but for now we disallow it - // to prevent errors like using NewValue1A instead of NewValue1I. - b.Fatalf("aux field has int64 type op=%s type=%s aux=%v", op, t, aux) - } +func (b *Block) NewValue0A(pos src.XPos, op Op, t *types.Type, aux Aux) *Value { v := b.Func.newValue(op, t, b, pos) v.AuxInt = 0 v.Aux = aux @@ -392,7 +502,7 @@ func (b *Block) NewValue0A(pos src.XPos, op Op, t *types.Type, aux interface{}) } // NewValue returns a new value in the block with no arguments and both an auxint and aux values. -func (b *Block) NewValue0IA(pos src.XPos, op Op, t *types.Type, auxint int64, aux interface{}) *Value { +func (b *Block) NewValue0IA(pos src.XPos, op Op, t *types.Type, auxint int64, aux Aux) *Value { v := b.Func.newValue(op, t, b, pos) v.AuxInt = auxint v.Aux = aux @@ -421,7 +531,7 @@ func (b *Block) NewValue1I(pos src.XPos, op Op, t *types.Type, auxint int64, arg } // NewValue1A returns a new value in the block with one argument and an aux value. -func (b *Block) NewValue1A(pos src.XPos, op Op, t *types.Type, aux interface{}, arg *Value) *Value { +func (b *Block) NewValue1A(pos src.XPos, op Op, t *types.Type, aux Aux, arg *Value) *Value { v := b.Func.newValue(op, t, b, pos) v.AuxInt = 0 v.Aux = aux @@ -432,7 +542,7 @@ func (b *Block) NewValue1A(pos src.XPos, op Op, t *types.Type, aux interface{}, } // NewValue1IA returns a new value in the block with one argument and both an auxint and aux values. -func (b *Block) NewValue1IA(pos src.XPos, op Op, t *types.Type, auxint int64, aux interface{}, arg *Value) *Value { +func (b *Block) NewValue1IA(pos src.XPos, op Op, t *types.Type, auxint int64, aux Aux, arg *Value) *Value { v := b.Func.newValue(op, t, b, pos) v.AuxInt = auxint v.Aux = aux @@ -455,7 +565,7 @@ func (b *Block) NewValue2(pos src.XPos, op Op, t *types.Type, arg0, arg1 *Value) } // NewValue2A returns a new value in the block with two arguments and one aux values. -func (b *Block) NewValue2A(pos src.XPos, op Op, t *types.Type, aux interface{}, arg0, arg1 *Value) *Value { +func (b *Block) NewValue2A(pos src.XPos, op Op, t *types.Type, aux Aux, arg0, arg1 *Value) *Value { v := b.Func.newValue(op, t, b, pos) v.AuxInt = 0 v.Aux = aux @@ -480,7 +590,7 @@ func (b *Block) NewValue2I(pos src.XPos, op Op, t *types.Type, auxint int64, arg } // NewValue2IA returns a new value in the block with two arguments and both an auxint and aux values. -func (b *Block) NewValue2IA(pos src.XPos, op Op, t *types.Type, auxint int64, aux interface{}, arg0, arg1 *Value) *Value { +func (b *Block) NewValue2IA(pos src.XPos, op Op, t *types.Type, auxint int64, aux Aux, arg0, arg1 *Value) *Value { v := b.Func.newValue(op, t, b, pos) v.AuxInt = auxint v.Aux = aux @@ -521,7 +631,7 @@ func (b *Block) NewValue3I(pos src.XPos, op Op, t *types.Type, auxint int64, arg } // NewValue3A returns a new value in the block with three argument and an aux value. -func (b *Block) NewValue3A(pos src.XPos, op Op, t *types.Type, aux interface{}, arg0, arg1, arg2 *Value) *Value { +func (b *Block) NewValue3A(pos src.XPos, op Op, t *types.Type, aux Aux, arg0, arg1, arg2 *Value) *Value { v := b.Func.newValue(op, t, b, pos) v.AuxInt = 0 v.Aux = aux @@ -547,7 +657,7 @@ func (b *Block) NewValue4(pos src.XPos, op Op, t *types.Type, arg0, arg1, arg2, return v } -// NewValue4I returns a new value in the block with four arguments and and auxint value. +// NewValue4I returns a new value in the block with four arguments and auxint value. func (b *Block) NewValue4I(pos src.XPos, op Op, t *types.Type, auxint int64, arg0, arg1, arg2, arg3 *Value) *Value { v := b.Func.newValue(op, t, b, pos) v.AuxInt = auxint @@ -633,7 +743,7 @@ func (f *Func) ConstNil(t *types.Type) *Value { } func (f *Func) ConstEmptyString(t *types.Type) *Value { v := f.constVal(OpConstString, t, constEmptyStringMagic, false) - v.Aux = "" + v.Aux = StringToAux("") return v } func (f *Func) ConstOffPtrSP(t *types.Type, c int64, sp *Value) *Value { @@ -649,7 +759,19 @@ func (f *Func) Frontend() Frontend { return f.f func (f *Func) Warnl(pos src.XPos, msg string, args ...interface{}) { f.fe.Warnl(pos, msg, args...) } func (f *Func) Logf(msg string, args ...interface{}) { f.fe.Logf(msg, args...) } func (f *Func) Log() bool { return f.fe.Log() } -func (f *Func) Fatalf(msg string, args ...interface{}) { f.fe.Fatalf(f.Entry.Pos, msg, args...) } + +func (f *Func) Fatalf(msg string, args ...interface{}) { + stats := "crashed" + if f.Log() { + f.Logf(" pass %s end %s\n", f.pass.name, stats) + printFunc(f) + } + if f.HTMLWriter != nil { + f.HTMLWriter.WritePhase(f.pass.name, fmt.Sprintf("%s %s", f.pass.name, stats)) + f.HTMLWriter.flushPhases() + } + f.fe.Fatalf(f.Entry.Pos, msg, args...) +} // postorder returns the reachable blocks in f in a postorder traversal. func (f *Func) postorder() []*Block { @@ -777,7 +899,7 @@ func DebugNameMatch(evname, name string) bool { } func (f *Func) spSb() (sp, sb *Value) { - initpos := f.Entry.Pos + initpos := src.NoXPos // These are originally created with no position in ssa.go; if they are optimized out then recreated, should be the same. for _, v := range f.Entry.Values { if v.Op == OpSB { sb = v @@ -786,7 +908,7 @@ func (f *Func) spSb() (sp, sb *Value) { sp = v } if sb != nil && sp != nil { - break + return } } if sb == nil { diff --git a/src/cmd/compile/internal/ssa/func_test.go b/src/cmd/compile/internal/ssa/func_test.go index 568c6436f5bb5f767e56a50600399d49950d9d1e..276c444b9a88f01a3eeff5824c3550ebe205af03 100644 --- a/src/cmd/compile/internal/ssa/func_test.go +++ b/src/cmd/compile/internal/ssa/func_test.go @@ -232,7 +232,7 @@ func Bloc(name string, entries ...interface{}) bloc { } // Valu defines a value in a block. -func Valu(name string, op Op, t *types.Type, auxint int64, aux interface{}, args ...string) valu { +func Valu(name string, op Op, t *types.Type, auxint int64, aux Aux, args ...string) valu { return valu{name, op, t, auxint, aux, args} } @@ -277,7 +277,7 @@ type valu struct { op Op t *types.Type auxint int64 - aux interface{} + aux Aux args []string } @@ -402,12 +402,12 @@ func TestEquiv(t *testing.T) { cfg.Fun("entry", Bloc("entry", Valu("mem", OpInitMem, types.TypeMem, 0, nil), - Valu("a", OpConst64, cfg.config.Types.Int64, 0, 14), + Valu("a", OpConstString, cfg.config.Types.String, 0, StringToAux("foo")), Exit("mem"))), cfg.Fun("entry", Bloc("entry", Valu("mem", OpInitMem, types.TypeMem, 0, nil), - Valu("a", OpConst64, cfg.config.Types.Int64, 0, 26), + Valu("a", OpConstString, cfg.config.Types.String, 0, StringToAux("bar")), Exit("mem"))), }, // value args different diff --git a/src/cmd/compile/internal/ssa/fuse.go b/src/cmd/compile/internal/ssa/fuse.go index c51461cbfffedd375ed7a8f07761d1a9d8e9f2bb..fec2ba877372eedc8be7a2b6262a187567f3b9c5 100644 --- a/src/cmd/compile/internal/ssa/fuse.go +++ b/src/cmd/compile/internal/ssa/fuse.go @@ -11,8 +11,8 @@ import ( // fuseEarly runs fuse(f, fuseTypePlain|fuseTypeIntInRange). func fuseEarly(f *Func) { fuse(f, fuseTypePlain|fuseTypeIntInRange) } -// fuseLate runs fuse(f, fuseTypePlain|fuseTypeIf). -func fuseLate(f *Func) { fuse(f, fuseTypePlain|fuseTypeIf) } +// fuseLate runs fuse(f, fuseTypePlain|fuseTypeIf|fuseTypeBranchRedirect). +func fuseLate(f *Func) { fuse(f, fuseTypePlain|fuseTypeIf|fuseTypeBranchRedirect) } type fuseType uint8 @@ -20,6 +20,7 @@ const ( fuseTypePlain fuseType = 1 << iota fuseTypeIf fuseTypeIntInRange + fuseTypeBranchRedirect fuseTypeShortCircuit ) @@ -43,6 +44,9 @@ func fuse(f *Func, typ fuseType) { changed = shortcircuitBlock(b) || changed } } + if typ&fuseTypeBranchRedirect != 0 { + changed = fuseBranchRedirect(f) || changed + } if changed { f.invalidateCFG() } @@ -51,11 +55,11 @@ func fuse(f *Func, typ fuseType) { // fuseBlockIf handles the following cases where s0 and s1 are empty blocks. // -// b b b b -// / \ | \ / | | | -// s0 s1 | s1 s0 | | | -// \ / | / \ | | | -// ss ss ss ss +// b b b b +// \ / \ / | \ / \ / | | | +// s0 s1 | s1 s0 | | | +// \ / | / \ | | | +// ss ss ss ss // // If all Phi ops in ss have identical variables for slots corresponding to // s0, s1 and b then the branch can be dropped. @@ -69,11 +73,11 @@ func fuseBlockIf(b *Block) bool { if b.Kind != BlockIf { return false } - + // It doesn't matter how much Preds does s0 or s1 have. var ss0, ss1 *Block s0 := b.Succs[0].b i0 := b.Succs[0].i - if s0.Kind != BlockPlain || len(s0.Preds) != 1 || !isEmpty(s0) { + if s0.Kind != BlockPlain || !isEmpty(s0) { s0, ss0 = b, s0 } else { ss0 = s0.Succs[0].b @@ -81,15 +85,25 @@ func fuseBlockIf(b *Block) bool { } s1 := b.Succs[1].b i1 := b.Succs[1].i - if s1.Kind != BlockPlain || len(s1.Preds) != 1 || !isEmpty(s1) { + if s1.Kind != BlockPlain || !isEmpty(s1) { s1, ss1 = b, s1 } else { ss1 = s1.Succs[0].b i1 = s1.Succs[0].i } - if ss0 != ss1 { - return false + if s0.Kind == BlockPlain && isEmpty(s0) && s1.Kind == BlockPlain && isEmpty(s1) { + // Two special cases where both s0, s1 and ss are empty blocks. + if s0 == ss1 { + s0, ss0 = b, ss1 + } else if ss0 == s1 { + s1, ss1 = b, ss0 + } else { + return false + } + } else { + return false + } } ss := ss0 @@ -102,48 +116,45 @@ func fuseBlockIf(b *Block) bool { } } - // Now we have two of following b->ss, b->s0->ss and b->s1->ss, - // with s0 and s1 empty if exist. - // We can replace it with b->ss without if all OpPhis in ss - // have identical predecessors (verified above). - // No critical edge is introduced because b will have one successor. - if s0 != b && s1 != b { - // Replace edge b->s0->ss with b->ss. - // We need to keep a slot for Phis corresponding to b. - b.Succs[0] = Edge{ss, i0} - ss.Preds[i0] = Edge{b, 0} - b.removeEdge(1) - s1.removeEdge(0) - } else if s0 != b { - b.removeEdge(0) + // We do not need to redirect the Preds of s0 and s1 to ss, + // the following optimization will do this. + b.removeEdge(0) + if s0 != b && len(s0.Preds) == 0 { s0.removeEdge(0) - } else if s1 != b { - b.removeEdge(1) - s1.removeEdge(0) - } else { - b.removeEdge(1) + // Move any (dead) values in s0 to b, + // where they will be eliminated by the next deadcode pass. + for _, v := range s0.Values { + v.Block = b + } + b.Values = append(b.Values, s0.Values...) + // Clear s0. + s0.Kind = BlockInvalid + s0.Values = nil + s0.Succs = nil + s0.Preds = nil } + b.Kind = BlockPlain b.Likely = BranchUnknown b.ResetControls() - - // Trash the empty blocks s0 and s1. - blocks := [...]*Block{s0, s1} - for _, s := range &blocks { - if s == b { - continue + // The values in b may be dead codes, and clearing them in time may + // obtain new optimization opportunities. + // First put dead values that can be deleted into a slice walkValues. + // Then put their arguments in walkValues before resetting the dead values + // in walkValues, because the arguments may also become dead values. + walkValues := []*Value{} + for _, v := range b.Values { + if v.Uses == 0 && v.removeable() { + walkValues = append(walkValues, v) } - // Move any (dead) values in s0 or s1 to b, - // where they will be eliminated by the next deadcode pass. - for _, v := range s.Values { - v.Block = b + } + for len(walkValues) != 0 { + v := walkValues[len(walkValues)-1] + walkValues = walkValues[:len(walkValues)-1] + if v.Uses == 0 && v.removeable() { + walkValues = append(walkValues, v.Args...) + v.reset(OpInvalid) } - b.Values = append(b.Values, s.Values...) - // Clear s. - s.Kind = BlockInvalid - s.Values = nil - s.Succs = nil - s.Preds = nil } return true } diff --git a/src/cmd/compile/internal/ssa/fuse_branchredirect.go b/src/cmd/compile/internal/ssa/fuse_branchredirect.go new file mode 100644 index 0000000000000000000000000000000000000000..1b8b307bcac7a213880b25ca64f71eff41639dc7 --- /dev/null +++ b/src/cmd/compile/internal/ssa/fuse_branchredirect.go @@ -0,0 +1,110 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssa + +// fuseBranchRedirect checks for a CFG in which the outbound branch +// of an If block can be derived from its predecessor If block, in +// some such cases, we can redirect the predecessor If block to the +// corresponding successor block directly. For example: +// p: +// v11 = Less64 v10 v8 +// If v11 goto b else u +// b: <- p ... +// v17 = Leq64 v10 v8 +// If v17 goto s else o +// We can redirect p to s directly. +// +// The implementation here borrows the framework of the prove pass. +// 1, Traverse all blocks of function f to find If blocks. +// 2, For any If block b, traverse all its predecessors to find If blocks. +// 3, For any If block predecessor p, update relationship p->b. +// 4, Traverse all successors of b. +// 5, For any successor s of b, try to update relationship b->s, if a +// contradiction is found then redirect p to another successor of b. +func fuseBranchRedirect(f *Func) bool { + ft := newFactsTable(f) + ft.checkpoint() + + changed := false + for i := len(f.Blocks) - 1; i >= 0; i-- { + b := f.Blocks[i] + if b.Kind != BlockIf { + continue + } + // b is either empty or only contains the control value. + // TODO: if b contains only OpCopy or OpNot related to b.Controls, + // such as Copy(Not(Copy(Less64(v1, v2)))), perhaps it can be optimized. + bCtl := b.Controls[0] + if bCtl.Block != b && len(b.Values) != 0 || (len(b.Values) != 1 || bCtl.Uses != 1) && bCtl.Block == b { + continue + } + + for k := 0; k < len(b.Preds); k++ { + pk := b.Preds[k] + p := pk.b + if p.Kind != BlockIf || p == b { + continue + } + pbranch := positive + if pk.i == 1 { + pbranch = negative + } + ft.checkpoint() + // Assume branch p->b is taken. + addBranchRestrictions(ft, p, pbranch) + // Check if any outgoing branch is unreachable based on the above condition. + parent := b + for j, bbranch := range [...]branch{positive, negative} { + ft.checkpoint() + // Try to update relationship b->child, and check if the contradiction occurs. + addBranchRestrictions(ft, parent, bbranch) + unsat := ft.unsat + ft.restore() + if !unsat { + continue + } + // This branch is impossible,so redirect p directly to another branch. + out := 1 ^ j + child := parent.Succs[out].b + if child == b { + continue + } + b.removePred(k) + p.Succs[pk.i] = Edge{child, len(child.Preds)} + // Fix up Phi value in b to have one less argument. + for _, v := range b.Values { + if v.Op != OpPhi { + continue + } + v.RemoveArg(k) + phielimValue(v) + } + // Fix up child to have one more predecessor. + child.Preds = append(child.Preds, Edge{p, pk.i}) + ai := b.Succs[out].i + for _, v := range child.Values { + if v.Op != OpPhi { + continue + } + v.AddArg(v.Args[ai]) + } + if b.Func.pass.debug > 0 { + b.Func.Warnl(b.Controls[0].Pos, "Redirect %s based on %s", b.Controls[0].Op, p.Controls[0].Op) + } + changed = true + k-- + break + } + ft.restore() + } + if len(b.Preds) == 0 && b != f.Entry { + // Block is now dead. + b.Kind = BlockInvalid + } + } + ft.restore() + ft.cleanup(f) + return changed +} diff --git a/src/cmd/compile/internal/ssa/fuse_test.go b/src/cmd/compile/internal/ssa/fuse_test.go index 15190997f2262f779e2d8bd818e358745c10d637..27a14b17819c5d6c1626a4c242b36cb2f861ce0b 100644 --- a/src/cmd/compile/internal/ssa/fuse_test.go +++ b/src/cmd/compile/internal/ssa/fuse_test.go @@ -104,6 +104,18 @@ func TestFuseHandlesPhis(t *testing.T) { func TestFuseEliminatesEmptyBlocks(t *testing.T) { c := testConfig(t) + // Case 1, plain type empty blocks z0 ~ z3 will be eliminated. + // entry + // | + // z0 + // | + // z1 + // | + // z2 + // | + // z3 + // | + // exit fun := c.Fun("entry", Bloc("entry", Valu("mem", OpInitMem, types.TypeMem, 0, nil), @@ -126,16 +138,77 @@ func TestFuseEliminatesEmptyBlocks(t *testing.T) { for k, b := range fun.blocks { if k[:1] == "z" && b.Kind != BlockInvalid { - t.Errorf("%s was not eliminated, but should have", k) + t.Errorf("case1 %s was not eliminated, but should have", k) + } + } + + // Case 2, empty blocks with If branch, z0 and z1 will be eliminated. + // entry + // / \ + // z0 z1 + // \ / + // exit + fun = c.Fun("entry", + Bloc("entry", + Valu("mem", OpInitMem, types.TypeMem, 0, nil), + Valu("c", OpArg, c.config.Types.Bool, 0, nil), + If("c", "z0", "z1")), + Bloc("z0", + Goto("exit")), + Bloc("z1", + Goto("exit")), + Bloc("exit", + Exit("mem"), + )) + + CheckFunc(fun.f) + fuseLate(fun.f) + + for k, b := range fun.blocks { + if k[:1] == "z" && b.Kind != BlockInvalid { + t.Errorf("case2 %s was not eliminated, but should have", k) + } + } + + // Case 3, empty blocks with multiple predecessors, z0 and z1 will be eliminated. + // entry + // | \ + // | b0 + // | / \ + // z0 z1 + // \ / + // exit + fun = c.Fun("entry", + Bloc("entry", + Valu("mem", OpInitMem, types.TypeMem, 0, nil), + Valu("c1", OpArg, c.config.Types.Bool, 0, nil), + If("c1", "b0", "z0")), + Bloc("b0", + Valu("c2", OpArg, c.config.Types.Bool, 0, nil), + If("c2", "z1", "z0")), + Bloc("z0", + Goto("exit")), + Bloc("z1", + Goto("exit")), + Bloc("exit", + Exit("mem"), + )) + + CheckFunc(fun.f) + fuseLate(fun.f) + + for k, b := range fun.blocks { + if k[:1] == "z" && b.Kind != BlockInvalid { + t.Errorf("case3 %s was not eliminated, but should have", k) } } } func TestFuseSideEffects(t *testing.T) { - // Test that we don't fuse branches that have side effects but + c := testConfig(t) + // Case1, test that we don't fuse branches that have side effects but // have no use (e.g. followed by infinite loop). // See issue #36005. - c := testConfig(t) fun := c.Fun("entry", Bloc("entry", Valu("mem", OpInitMem, types.TypeMem, 0, nil), @@ -163,6 +236,31 @@ func TestFuseSideEffects(t *testing.T) { t.Errorf("else is eliminated, but should not") } } + + // Case2, z0 contains a value that has side effect, z0 shouldn't be eliminated. + // entry + // | \ + // | z0 + // | / + // exit + fun = c.Fun("entry", + Bloc("entry", + Valu("mem", OpInitMem, types.TypeMem, 0, nil), + Valu("c1", OpArg, c.config.Types.Bool, 0, nil), + Valu("p", OpArg, c.config.Types.IntPtr, 0, nil), + If("c1", "z0", "exit")), + Bloc("z0", + Valu("nilcheck", OpNilCheck, types.TypeVoid, 0, nil, "p", "mem"), + Goto("exit")), + Bloc("exit", + Exit("mem"), + )) + CheckFunc(fun.f) + fuseLate(fun.f) + z0, ok := fun.blocks["z0"] + if !ok || z0.Kind == BlockInvalid { + t.Errorf("case2 z0 is eliminated, but should not") + } } func BenchmarkFuse(b *testing.B) { diff --git a/src/cmd/compile/internal/ssa/gen/386.rules b/src/cmd/compile/internal/ssa/gen/386.rules index fbc12fd67219a87050a0c61a9238408044f912b1..199b73c42f436fcfc88e422d196ffd8904780787 100644 --- a/src/cmd/compile/internal/ssa/gen/386.rules +++ b/src/cmd/compile/internal/ssa/gen/386.rules @@ -54,6 +54,7 @@ (Bswap32 ...) => (BSWAPL ...) (Sqrt ...) => (SQRTSD ...) +(Sqrt32 ...) => (SQRTSS ...) (Ctz16 x) => (BSFL (ORLconst [0x10000] x)) (Ctz16NonZero ...) => (BSFL ...) @@ -257,17 +258,17 @@ (Zero [4] destptr mem) => (MOVLstoreconst [0] destptr mem) (Zero [3] destptr mem) => - (MOVBstoreconst [makeValAndOff32(0,2)] destptr - (MOVWstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVBstoreconst [makeValAndOff(0,2)] destptr + (MOVWstoreconst [makeValAndOff(0,0)] destptr mem)) (Zero [5] destptr mem) => - (MOVBstoreconst [makeValAndOff32(0,4)] destptr - (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVBstoreconst [makeValAndOff(0,4)] destptr + (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) (Zero [6] destptr mem) => - (MOVWstoreconst [makeValAndOff32(0,4)] destptr - (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVWstoreconst [makeValAndOff(0,4)] destptr + (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) (Zero [7] destptr mem) => - (MOVLstoreconst [makeValAndOff32(0,3)] destptr - (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVLstoreconst [makeValAndOff(0,3)] destptr + (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) // Strip off any fractional word zeroing. (Zero [s] destptr mem) && s%4 != 0 && s > 4 => @@ -276,17 +277,17 @@ // Zero small numbers of words directly. (Zero [8] destptr mem) => - (MOVLstoreconst [makeValAndOff32(0,4)] destptr - (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVLstoreconst [makeValAndOff(0,4)] destptr + (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) (Zero [12] destptr mem) => - (MOVLstoreconst [makeValAndOff32(0,8)] destptr - (MOVLstoreconst [makeValAndOff32(0,4)] destptr - (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem))) + (MOVLstoreconst [makeValAndOff(0,8)] destptr + (MOVLstoreconst [makeValAndOff(0,4)] destptr + (MOVLstoreconst [makeValAndOff(0,0)] destptr mem))) (Zero [16] destptr mem) => - (MOVLstoreconst [makeValAndOff32(0,12)] destptr - (MOVLstoreconst [makeValAndOff32(0,8)] destptr - (MOVLstoreconst [makeValAndOff32(0,4)] destptr - (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)))) + (MOVLstoreconst [makeValAndOff(0,12)] destptr + (MOVLstoreconst [makeValAndOff(0,8)] destptr + (MOVLstoreconst [makeValAndOff(0,4)] destptr + (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)))) // Medium zeroing uses a duff device. (Zero [s] destptr mem) @@ -475,7 +476,7 @@ (CMPB (MOVLconst [c]) x) => (InvertFlags (CMPBconst x [int8(c)])) // Canonicalize the order of arguments to comparisons - helps with CSE. -(CMP(L|W|B) x y) && x.ID > y.ID => (InvertFlags (CMP(L|W|B) y x)) +(CMP(L|W|B) x y) && canonLessThan(x,y) => (InvertFlags (CMP(L|W|B) y x)) // strength reduction // Assumes that the following costs from https://gmplib.org/~tege/x86-timing.pdf: @@ -620,12 +621,12 @@ ((ADD|AND|OR|XOR)Lconstmodify [valoff1.addOffset32(off2)] {sym} base mem) // Fold constants into stores. -(MOVLstore [off] {sym} ptr (MOVLconst [c]) mem) && validOff(int64(off)) => - (MOVLstoreconst [makeValAndOff32(c,off)] {sym} ptr mem) -(MOVWstore [off] {sym} ptr (MOVLconst [c]) mem) && validOff(int64(off)) => - (MOVWstoreconst [makeValAndOff32(c,off)] {sym} ptr mem) -(MOVBstore [off] {sym} ptr (MOVLconst [c]) mem) && validOff(int64(off)) => - (MOVBstoreconst [makeValAndOff32(c,off)] {sym} ptr mem) +(MOVLstore [off] {sym} ptr (MOVLconst [c]) mem) => + (MOVLstoreconst [makeValAndOff(c,off)] {sym} ptr mem) +(MOVWstore [off] {sym} ptr (MOVLconst [c]) mem) => + (MOVWstoreconst [makeValAndOff(c,off)] {sym} ptr mem) +(MOVBstore [off] {sym} ptr (MOVLconst [c]) mem) => + (MOVBstoreconst [makeValAndOff(c,off)] {sym} ptr mem) // Fold address offsets into constant stores. (MOV(L|W|B)storeconst [sc] {s} (ADDLconst [off] ptr) mem) && sc.canAdd32(off) => @@ -675,8 +676,8 @@ (MOVLstore {sym} [off] ptr y:((ADD|SUB|AND|OR|XOR)L l:(MOVLload [off] {sym} ptr mem) x) mem) && y.Uses==1 && l.Uses==1 && clobber(y, l) => ((ADD|SUB|AND|OR|XOR)Lmodify [off] {sym} ptr x mem) (MOVLstore {sym} [off] ptr y:((ADD|AND|OR|XOR)Lconst [c] l:(MOVLload [off] {sym} ptr mem)) mem) - && y.Uses==1 && l.Uses==1 && clobber(y, l) && validValAndOff(int64(c),int64(off)) => - ((ADD|AND|OR|XOR)Lconstmodify [makeValAndOff32(c,off)] {sym} ptr mem) + && y.Uses==1 && l.Uses==1 && clobber(y, l) => + ((ADD|AND|OR|XOR)Lconstmodify [makeValAndOff(c,off)] {sym} ptr mem) // fold LEALs together (LEAL [off1] {sym1} (LEAL [off2] {sym2} x)) && is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) => @@ -994,49 +995,49 @@ && x.Uses == 1 && a.Off() + 1 == c.Off() && clobber(x) - => (MOVWstoreconst [makeValAndOff32(int32(a.Val()&0xff | c.Val()<<8), a.Off32())] {s} p mem) + => (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) (MOVBstoreconst [a] {s} p x:(MOVBstoreconst [c] {s} p mem)) && x.Uses == 1 && a.Off() + 1 == c.Off() && clobber(x) - => (MOVWstoreconst [makeValAndOff32(int32(a.Val()&0xff | c.Val()<<8), a.Off32())] {s} p mem) + => (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) (MOVBstoreconst [c] {s} p1 x:(MOVBstoreconst [a] {s} p0 mem)) && x.Uses == 1 && a.Off() == c.Off() && sequentialAddresses(p0, p1, 1) && clobber(x) - => (MOVWstoreconst [makeValAndOff32(int32(a.Val()&0xff | c.Val()<<8), a.Off32())] {s} p0 mem) + => (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p0 mem) (MOVBstoreconst [a] {s} p0 x:(MOVBstoreconst [c] {s} p1 mem)) && x.Uses == 1 && a.Off() == c.Off() && sequentialAddresses(p0, p1, 1) && clobber(x) - => (MOVWstoreconst [makeValAndOff32(int32(a.Val()&0xff | c.Val()<<8), a.Off32())] {s} p0 mem) + => (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p0 mem) (MOVWstoreconst [c] {s} p x:(MOVWstoreconst [a] {s} p mem)) && x.Uses == 1 && a.Off() + 2 == c.Off() && clobber(x) - => (MOVLstoreconst [makeValAndOff32(int32(a.Val()&0xffff | c.Val()<<16), a.Off32())] {s} p mem) + => (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) (MOVWstoreconst [a] {s} p x:(MOVWstoreconst [c] {s} p mem)) && x.Uses == 1 && ValAndOff(a).Off() + 2 == ValAndOff(c).Off() && clobber(x) - => (MOVLstoreconst [makeValAndOff32(int32(a.Val()&0xffff | c.Val()<<16), a.Off32())] {s} p mem) + => (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) (MOVWstoreconst [c] {s} p1 x:(MOVWstoreconst [a] {s} p0 mem)) && x.Uses == 1 && a.Off() == c.Off() && sequentialAddresses(p0, p1, 2) && clobber(x) - => (MOVLstoreconst [makeValAndOff32(int32(a.Val()&0xffff | c.Val()<<16), a.Off32())] {s} p0 mem) + => (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p0 mem) (MOVWstoreconst [a] {s} p0 x:(MOVWstoreconst [c] {s} p1 mem)) && x.Uses == 1 && a.Off() == c.Off() && sequentialAddresses(p0, p1, 2) && clobber(x) - => (MOVLstoreconst [makeValAndOff32(int32(a.Val()&0xffff | c.Val()<<16), a.Off32())] {s} p0 mem) + => (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p0 mem) // Combine stores into larger (unaligned) stores. (MOVBstore [i] {s} p (SHR(W|L)const [8] w) x:(MOVBstore [i-1] {s} p w mem)) @@ -1098,13 +1099,12 @@ (CMP(L|W|B)const l:(MOV(L|W|B)load {sym} [off] ptr mem) [c]) && l.Uses == 1 - && validValAndOff(int64(c), int64(off)) && clobber(l) => - @l.Block (CMP(L|W|B)constload {sym} [makeValAndOff32(int32(c),int32(off))] ptr mem) + @l.Block (CMP(L|W|B)constload {sym} [makeValAndOff(int32(c),off)] ptr mem) -(CMPLload {sym} [off] ptr (MOVLconst [c]) mem) && validValAndOff(int64(c),int64(off)) => (CMPLconstload {sym} [makeValAndOff32(c,off)] ptr mem) -(CMPWload {sym} [off] ptr (MOVLconst [c]) mem) && validValAndOff(int64(int16(c)),int64(off)) => (CMPWconstload {sym} [makeValAndOff32(int32(int16(c)),off)] ptr mem) -(CMPBload {sym} [off] ptr (MOVLconst [c]) mem) && validValAndOff(int64(int8(c)),int64(off)) => (CMPBconstload {sym} [makeValAndOff32(int32(int8(c)),off)] ptr mem) +(CMPLload {sym} [off] ptr (MOVLconst [c]) mem) => (CMPLconstload {sym} [makeValAndOff(c,off)] ptr mem) +(CMPWload {sym} [off] ptr (MOVLconst [c]) mem) => (CMPWconstload {sym} [makeValAndOff(int32(int16(c)),off)] ptr mem) +(CMPBload {sym} [off] ptr (MOVLconst [c]) mem) => (CMPBconstload {sym} [makeValAndOff(int32(int8(c)),off)] ptr mem) (MOVBload [off] {sym} (SB) _) && symIsRO(sym) => (MOVLconst [int32(read8(sym, int64(off)))]) (MOVWload [off] {sym} (SB) _) && symIsRO(sym) => (MOVLconst [int32(read16(sym, int64(off), config.ctxt.Arch.ByteOrder))]) diff --git a/src/cmd/compile/internal/ssa/gen/386Ops.go b/src/cmd/compile/internal/ssa/gen/386Ops.go index 737b99c3716c8894f45b764419f7d1040cdf1e78..c4b49fbb23067d4e52e5940a25e6351b7adc4060 100644 --- a/src/cmd/compile/internal/ssa/gen/386Ops.go +++ b/src/cmd/compile/internal/ssa/gen/386Ops.go @@ -146,14 +146,14 @@ func init() { var _386ops = []opData{ // fp ops - {name: "ADDSS", argLength: 2, reg: fp21, asm: "ADDSS", commutative: true, resultInArg0: true, usesScratch: true}, // fp32 add - {name: "ADDSD", argLength: 2, reg: fp21, asm: "ADDSD", commutative: true, resultInArg0: true}, // fp64 add - {name: "SUBSS", argLength: 2, reg: fp21, asm: "SUBSS", resultInArg0: true, usesScratch: true}, // fp32 sub - {name: "SUBSD", argLength: 2, reg: fp21, asm: "SUBSD", resultInArg0: true}, // fp64 sub - {name: "MULSS", argLength: 2, reg: fp21, asm: "MULSS", commutative: true, resultInArg0: true, usesScratch: true}, // fp32 mul - {name: "MULSD", argLength: 2, reg: fp21, asm: "MULSD", commutative: true, resultInArg0: true}, // fp64 mul - {name: "DIVSS", argLength: 2, reg: fp21, asm: "DIVSS", resultInArg0: true, usesScratch: true}, // fp32 div - {name: "DIVSD", argLength: 2, reg: fp21, asm: "DIVSD", resultInArg0: true}, // fp64 div + {name: "ADDSS", argLength: 2, reg: fp21, asm: "ADDSS", commutative: true, resultInArg0: true}, // fp32 add + {name: "ADDSD", argLength: 2, reg: fp21, asm: "ADDSD", commutative: true, resultInArg0: true}, // fp64 add + {name: "SUBSS", argLength: 2, reg: fp21, asm: "SUBSS", resultInArg0: true}, // fp32 sub + {name: "SUBSD", argLength: 2, reg: fp21, asm: "SUBSD", resultInArg0: true}, // fp64 sub + {name: "MULSS", argLength: 2, reg: fp21, asm: "MULSS", commutative: true, resultInArg0: true}, // fp32 mul + {name: "MULSD", argLength: 2, reg: fp21, asm: "MULSD", commutative: true, resultInArg0: true}, // fp64 mul + {name: "DIVSS", argLength: 2, reg: fp21, asm: "DIVSS", resultInArg0: true}, // fp32 div + {name: "DIVSD", argLength: 2, reg: fp21, asm: "DIVSD", resultInArg0: true}, // fp64 div {name: "MOVSSload", argLength: 2, reg: fpload, asm: "MOVSS", aux: "SymOff", faultOnNilArg0: true, symEffect: "Read"}, // fp32 load {name: "MOVSDload", argLength: 2, reg: fpload, asm: "MOVSD", aux: "SymOff", faultOnNilArg0: true, symEffect: "Read"}, // fp64 load @@ -246,8 +246,8 @@ func init() { {name: "CMPWconstload", argLength: 2, reg: gp0flagsLoad, asm: "CMPW", aux: "SymValAndOff", typ: "Flags", symEffect: "Read", faultOnNilArg0: true}, {name: "CMPBconstload", argLength: 2, reg: gp0flagsLoad, asm: "CMPB", aux: "SymValAndOff", typ: "Flags", symEffect: "Read", faultOnNilArg0: true}, - {name: "UCOMISS", argLength: 2, reg: fp2flags, asm: "UCOMISS", typ: "Flags", usesScratch: true}, // arg0 compare to arg1, f32 - {name: "UCOMISD", argLength: 2, reg: fp2flags, asm: "UCOMISD", typ: "Flags", usesScratch: true}, // arg0 compare to arg1, f64 + {name: "UCOMISS", argLength: 2, reg: fp2flags, asm: "UCOMISS", typ: "Flags"}, // arg0 compare to arg1, f32 + {name: "UCOMISD", argLength: 2, reg: fp2flags, asm: "UCOMISD", typ: "Flags"}, // arg0 compare to arg1, f64 {name: "TESTL", argLength: 2, reg: gp2flags, commutative: true, asm: "TESTL", typ: "Flags"}, // (arg0 & arg1) compare to 0 {name: "TESTW", argLength: 2, reg: gp2flags, commutative: true, asm: "TESTW", typ: "Flags"}, // (arg0 & arg1) compare to 0 @@ -308,6 +308,7 @@ func init() { {name: "BSWAPL", argLength: 1, reg: gp11, asm: "BSWAPL", resultInArg0: true, clobberFlags: true}, // arg0 swap bytes {name: "SQRTSD", argLength: 1, reg: fp11, asm: "SQRTSD"}, // sqrt(arg0) + {name: "SQRTSS", argLength: 1, reg: fp11, asm: "SQRTSS"}, // sqrt(arg0), float32 {name: "SBBLcarrymask", argLength: 1, reg: flagsgp, asm: "SBBL"}, // (int32)(-1) if carry is set, 0 if carry is clear. // Note: SBBW and SBBB are subsumed by SBBL @@ -341,12 +342,12 @@ func init() { {name: "MOVLconst", reg: gp01, asm: "MOVL", typ: "UInt32", aux: "Int32", rematerializeable: true}, // 32 low bits of auxint - {name: "CVTTSD2SL", argLength: 1, reg: fpgp, asm: "CVTTSD2SL", usesScratch: true}, // convert float64 to int32 - {name: "CVTTSS2SL", argLength: 1, reg: fpgp, asm: "CVTTSS2SL", usesScratch: true}, // convert float32 to int32 - {name: "CVTSL2SS", argLength: 1, reg: gpfp, asm: "CVTSL2SS", usesScratch: true}, // convert int32 to float32 - {name: "CVTSL2SD", argLength: 1, reg: gpfp, asm: "CVTSL2SD", usesScratch: true}, // convert int32 to float64 - {name: "CVTSD2SS", argLength: 1, reg: fp11, asm: "CVTSD2SS", usesScratch: true}, // convert float64 to float32 - {name: "CVTSS2SD", argLength: 1, reg: fp11, asm: "CVTSS2SD"}, // convert float32 to float64 + {name: "CVTTSD2SL", argLength: 1, reg: fpgp, asm: "CVTTSD2SL"}, // convert float64 to int32 + {name: "CVTTSS2SL", argLength: 1, reg: fpgp, asm: "CVTTSS2SL"}, // convert float32 to int32 + {name: "CVTSL2SS", argLength: 1, reg: gpfp, asm: "CVTSL2SS"}, // convert int32 to float32 + {name: "CVTSL2SD", argLength: 1, reg: gpfp, asm: "CVTSL2SD"}, // convert int32 to float64 + {name: "CVTSD2SS", argLength: 1, reg: fp11, asm: "CVTSD2SS"}, // convert float64 to float32 + {name: "CVTSS2SD", argLength: 1, reg: fp11, asm: "CVTSS2SD"}, // convert float32 to float64 {name: "PXOR", argLength: 2, reg: fp21, asm: "PXOR", commutative: true, resultInArg0: true}, // exclusive or, applied to X regs for float negation. diff --git a/src/cmd/compile/internal/ssa/gen/386splitload.rules b/src/cmd/compile/internal/ssa/gen/386splitload.rules index ed93b90b73361e351691e489ad861dc03bf77711..29d4f8c227f550e89d1b53cefed08ad10c32cb9b 100644 --- a/src/cmd/compile/internal/ssa/gen/386splitload.rules +++ b/src/cmd/compile/internal/ssa/gen/386splitload.rules @@ -6,6 +6,6 @@ (CMP(L|W|B)load {sym} [off] ptr x mem) => (CMP(L|W|B) (MOV(L|W|B)load {sym} [off] ptr mem) x) -(CMPLconstload {sym} [vo] ptr mem) => (CMPLconst (MOVLload {sym} [vo.Off32()] ptr mem) [vo.Val32()]) -(CMPWconstload {sym} [vo] ptr mem) => (CMPWconst (MOVWload {sym} [vo.Off32()] ptr mem) [vo.Val16()]) -(CMPBconstload {sym} [vo] ptr mem) => (CMPBconst (MOVBload {sym} [vo.Off32()] ptr mem) [vo.Val8()]) +(CMPLconstload {sym} [vo] ptr mem) => (CMPLconst (MOVLload {sym} [vo.Off()] ptr mem) [vo.Val()]) +(CMPWconstload {sym} [vo] ptr mem) => (CMPWconst (MOVWload {sym} [vo.Off()] ptr mem) [vo.Val16()]) +(CMPBconstload {sym} [vo] ptr mem) => (CMPBconst (MOVBload {sym} [vo.Off()] ptr mem) [vo.Val8()]) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index 5de1e1ec3123b1f94a89fd84fc1394297b86ccbe..4cd00732fc3f5e5f8a4098f6a0b0261f01c9a4a8 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -104,6 +104,7 @@ (PopCount8 x) => (POPCNTL (MOVBQZX x)) (Sqrt ...) => (SQRTSD ...) +(Sqrt32 ...) => (SQRTSS ...) (RoundToEven x) => (ROUNDSD [0] x) (Floor x) => (ROUNDSD [1] x) @@ -317,75 +318,75 @@ // Lowering Zero instructions (Zero [0] _ mem) => mem -(Zero [1] destptr mem) => (MOVBstoreconst [makeValAndOff32(0,0)] destptr mem) -(Zero [2] destptr mem) => (MOVWstoreconst [makeValAndOff32(0,0)] destptr mem) -(Zero [4] destptr mem) => (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem) -(Zero [8] destptr mem) => (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem) +(Zero [1] destptr mem) => (MOVBstoreconst [makeValAndOff(0,0)] destptr mem) +(Zero [2] destptr mem) => (MOVWstoreconst [makeValAndOff(0,0)] destptr mem) +(Zero [4] destptr mem) => (MOVLstoreconst [makeValAndOff(0,0)] destptr mem) +(Zero [8] destptr mem) => (MOVQstoreconst [makeValAndOff(0,0)] destptr mem) (Zero [3] destptr mem) => - (MOVBstoreconst [makeValAndOff32(0,2)] destptr - (MOVWstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVBstoreconst [makeValAndOff(0,2)] destptr + (MOVWstoreconst [makeValAndOff(0,0)] destptr mem)) (Zero [5] destptr mem) => - (MOVBstoreconst [makeValAndOff32(0,4)] destptr - (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVBstoreconst [makeValAndOff(0,4)] destptr + (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) (Zero [6] destptr mem) => - (MOVWstoreconst [makeValAndOff32(0,4)] destptr - (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVWstoreconst [makeValAndOff(0,4)] destptr + (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) (Zero [7] destptr mem) => - (MOVLstoreconst [makeValAndOff32(0,3)] destptr - (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVLstoreconst [makeValAndOff(0,3)] destptr + (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) // Strip off any fractional word zeroing. (Zero [s] destptr mem) && s%8 != 0 && s > 8 && !config.useSSE => (Zero [s-s%8] (OffPtr destptr [s%8]) - (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVQstoreconst [makeValAndOff(0,0)] destptr mem)) // Zero small numbers of words directly. (Zero [16] destptr mem) && !config.useSSE => - (MOVQstoreconst [makeValAndOff32(0,8)] destptr - (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVQstoreconst [makeValAndOff(0,8)] destptr + (MOVQstoreconst [makeValAndOff(0,0)] destptr mem)) (Zero [24] destptr mem) && !config.useSSE => - (MOVQstoreconst [makeValAndOff32(0,16)] destptr - (MOVQstoreconst [makeValAndOff32(0,8)] destptr - (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem))) + (MOVQstoreconst [makeValAndOff(0,16)] destptr + (MOVQstoreconst [makeValAndOff(0,8)] destptr + (MOVQstoreconst [makeValAndOff(0,0)] destptr mem))) (Zero [32] destptr mem) && !config.useSSE => - (MOVQstoreconst [makeValAndOff32(0,24)] destptr - (MOVQstoreconst [makeValAndOff32(0,16)] destptr - (MOVQstoreconst [makeValAndOff32(0,8)] destptr - (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem)))) + (MOVQstoreconst [makeValAndOff(0,24)] destptr + (MOVQstoreconst [makeValAndOff(0,16)] destptr + (MOVQstoreconst [makeValAndOff(0,8)] destptr + (MOVQstoreconst [makeValAndOff(0,0)] destptr mem)))) (Zero [s] destptr mem) && s > 8 && s < 16 && config.useSSE => - (MOVQstoreconst [makeValAndOff32(0,int32(s-8))] destptr - (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVQstoreconst [makeValAndOff(0,int32(s-8))] destptr + (MOVQstoreconst [makeValAndOff(0,0)] destptr mem)) // Adjust zeros to be a multiple of 16 bytes. (Zero [s] destptr mem) && s%16 != 0 && s > 16 && s%16 > 8 && config.useSSE => (Zero [s-s%16] (OffPtr destptr [s%16]) - (MOVOstore destptr (MOVOconst [0]) mem)) + (MOVOstorezero destptr mem)) (Zero [s] destptr mem) && s%16 != 0 && s > 16 && s%16 <= 8 && config.useSSE => (Zero [s-s%16] (OffPtr destptr [s%16]) - (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem)) + (MOVQstoreconst [makeValAndOff(0,0)] destptr mem)) (Zero [16] destptr mem) && config.useSSE => - (MOVOstore destptr (MOVOconst [0]) mem) + (MOVOstorezero destptr mem) (Zero [32] destptr mem) && config.useSSE => - (MOVOstore (OffPtr destptr [16]) (MOVOconst [0]) - (MOVOstore destptr (MOVOconst [0]) mem)) + (MOVOstorezero (OffPtr destptr [16]) + (MOVOstorezero destptr mem)) (Zero [48] destptr mem) && config.useSSE => - (MOVOstore (OffPtr destptr [32]) (MOVOconst [0]) - (MOVOstore (OffPtr destptr [16]) (MOVOconst [0]) - (MOVOstore destptr (MOVOconst [0]) mem))) + (MOVOstorezero (OffPtr destptr [32]) + (MOVOstorezero (OffPtr destptr [16]) + (MOVOstorezero destptr mem))) (Zero [64] destptr mem) && config.useSSE => - (MOVOstore (OffPtr destptr [48]) (MOVOconst [0]) - (MOVOstore (OffPtr destptr [32]) (MOVOconst [0]) - (MOVOstore (OffPtr destptr [16]) (MOVOconst [0]) - (MOVOstore destptr (MOVOconst [0]) mem)))) + (MOVOstorezero (OffPtr destptr [48]) + (MOVOstorezero (OffPtr destptr [32]) + (MOVOstorezero (OffPtr destptr [16]) + (MOVOstorezero destptr mem)))) // Medium zeroing uses a duff device. (Zero [s] destptr mem) && s > 64 && s <= 1024 && s%16 == 0 && !config.noDuffDevice => - (DUFFZERO [s] destptr (MOVOconst [0]) mem) + (DUFFZERO [s] destptr mem) // Large zeroing uses REP STOSQ. (Zero [s] destptr mem) @@ -459,7 +460,7 @@ (IsInBounds idx len) => (SETB (CMPQ idx len)) (IsSliceInBounds idx len) => (SETBE (CMPQ idx len)) (NilCheck ...) => (LoweredNilCheck ...) -(GetG ...) => (LoweredGetG ...) +(GetG mem) && !(buildcfg.Experiment.RegabiG && v.Block.Func.OwnAux.Fn.ABI() == obj.ABIInternal) => (LoweredGetG mem) // only lower in old ABI. in new ABI we have a G register. (GetClosurePtr ...) => (LoweredGetClosurePtr ...) (GetCallerPC ...) => (LoweredGetCallerPC ...) (GetCallerSP ...) => (LoweredGetCallerSP ...) @@ -623,14 +624,6 @@ // Recognize bit setting (a |= 1< (BTS(Q|L) x y) (XOR(Q|L) (SHL(Q|L) (MOV(Q|L)const [1]) y) x) => (BTC(Q|L) x y) -(ORLmodify [off] {sym} ptr s:(SHLL (MOVLconst [1]) x) mem) => - (BTSLmodify [off] {sym} ptr (ANDLconst [31] x) mem) -(ORQmodify [off] {sym} ptr s:(SHLQ (MOVQconst [1]) x) mem) => - (BTSQmodify [off] {sym} ptr (ANDQconst [63] x) mem) -(XORLmodify [off] {sym} ptr s:(SHLL (MOVLconst [1]) x) mem) => - (BTCLmodify [off] {sym} ptr (ANDLconst [31] x) mem) -(XORQmodify [off] {sym} ptr s:(SHLQ (MOVQconst [1]) x) mem) => - (BTCQmodify [off] {sym} ptr (ANDQconst [63] x) mem) // Convert ORconst into BTS, if the code gets smaller, with boundary being // (ORL $40,AX is 3 bytes, ORL $80,AX is 6 bytes). @@ -653,10 +646,6 @@ => (BTRQconst [int8(log64(^c))] x) (ANDL (MOVLconst [c]) x) && isUint32PowerOfTwo(int64(^c)) && uint64(^c) >= 128 => (BTRLconst [int8(log32(^c))] x) -(ANDLmodify [off] {sym} ptr (NOTL s:(SHLL (MOVLconst [1]) x)) mem) => - (BTRLmodify [off] {sym} ptr (ANDLconst [31] x) mem) -(ANDQmodify [off] {sym} ptr (NOTQ s:(SHLQ (MOVQconst [1]) x)) mem) => - (BTRQmodify [off] {sym} ptr (ANDQconst [63] x) mem) // Special-case bit patterns on first/last bit. // generic.rules changes ANDs of high-part/low-part masks into a couple of shifts, @@ -913,6 +902,9 @@ ((SHRB|SARB)const x [0]) => x ((ROLQ|ROLL|ROLW|ROLB)const x [0]) => x +// Multi-register shifts +(ORQ (SH(R|L)Q lo bits) (SH(L|R)Q hi (NEGQ bits))) => (SH(R|L)DQ lo hi bits) + // Note: the word and byte shifts keep the low 5 bits (not the low 4 or 3 bits) // because the x86 instructions are defined to use all 5 bits of the shift even // for the small shifts. I don't think we'll ever generate a weird shift (e.g. @@ -928,7 +920,7 @@ (CMPB (MOVLconst [c]) x) => (InvertFlags (CMPBconst x [int8(c)])) // Canonicalize the order of arguments to comparisons - helps with CSE. -(CMP(Q|L|W|B) x y) && x.ID > y.ID => (InvertFlags (CMP(Q|L|W|B) y x)) +(CMP(Q|L|W|B) x y) && canonLessThan(x,y) => (InvertFlags (CMP(Q|L|W|B) y x)) // Using MOVZX instead of AND is cheaper. (AND(Q|L)const [ 0xFF] x) => (MOVBQZX x) @@ -1122,24 +1114,24 @@ ((ADD|SUB|MUL|DIV)SSload [off1+off2] {sym} val base mem) ((ADD|SUB|MUL|DIV)SDload [off1] {sym} val (ADDQconst [off2] base) mem) && is32Bit(int64(off1)+int64(off2)) => ((ADD|SUB|MUL|DIV)SDload [off1+off2] {sym} val base mem) -((ADD|AND|OR|XOR|BTC|BTR|BTS)Qconstmodify [valoff1] {sym} (ADDQconst [off2] base) mem) && ValAndOff(valoff1).canAdd32(off2) => - ((ADD|AND|OR|XOR|BTC|BTR|BTS)Qconstmodify [ValAndOff(valoff1).addOffset32(off2)] {sym} base mem) -((ADD|AND|OR|XOR|BTC|BTR|BTS)Lconstmodify [valoff1] {sym} (ADDQconst [off2] base) mem) && ValAndOff(valoff1).canAdd32(off2) => - ((ADD|AND|OR|XOR|BTC|BTR|BTS)Lconstmodify [ValAndOff(valoff1).addOffset32(off2)] {sym} base mem) -((ADD|SUB|AND|OR|XOR|BTC|BTR|BTS)Qmodify [off1] {sym} (ADDQconst [off2] base) val mem) && is32Bit(int64(off1)+int64(off2)) => - ((ADD|SUB|AND|OR|XOR|BTC|BTR|BTS)Qmodify [off1+off2] {sym} base val mem) -((ADD|SUB|AND|OR|XOR|BTC|BTR|BTS)Lmodify [off1] {sym} (ADDQconst [off2] base) val mem) && is32Bit(int64(off1)+int64(off2)) => - ((ADD|SUB|AND|OR|XOR|BTC|BTR|BTS)Lmodify [off1+off2] {sym} base val mem) +((ADD|AND|OR|XOR)Qconstmodify [valoff1] {sym} (ADDQconst [off2] base) mem) && ValAndOff(valoff1).canAdd32(off2) => + ((ADD|AND|OR|XOR)Qconstmodify [ValAndOff(valoff1).addOffset32(off2)] {sym} base mem) +((ADD|AND|OR|XOR)Lconstmodify [valoff1] {sym} (ADDQconst [off2] base) mem) && ValAndOff(valoff1).canAdd32(off2) => + ((ADD|AND|OR|XOR)Lconstmodify [ValAndOff(valoff1).addOffset32(off2)] {sym} base mem) +((ADD|SUB|AND|OR|XOR)Qmodify [off1] {sym} (ADDQconst [off2] base) val mem) && is32Bit(int64(off1)+int64(off2)) => + ((ADD|SUB|AND|OR|XOR)Qmodify [off1+off2] {sym} base val mem) +((ADD|SUB|AND|OR|XOR)Lmodify [off1] {sym} (ADDQconst [off2] base) val mem) && is32Bit(int64(off1)+int64(off2)) => + ((ADD|SUB|AND|OR|XOR)Lmodify [off1+off2] {sym} base val mem) // Fold constants into stores. (MOVQstore [off] {sym} ptr (MOVQconst [c]) mem) && validVal(c) => - (MOVQstoreconst [makeValAndOff32(int32(c),off)] {sym} ptr mem) + (MOVQstoreconst [makeValAndOff(int32(c),off)] {sym} ptr mem) (MOVLstore [off] {sym} ptr (MOV(L|Q)const [c]) mem) => - (MOVLstoreconst [makeValAndOff32(int32(c),off)] {sym} ptr mem) + (MOVLstoreconst [makeValAndOff(int32(c),off)] {sym} ptr mem) (MOVWstore [off] {sym} ptr (MOV(L|Q)const [c]) mem) => - (MOVWstoreconst [makeValAndOff32(int32(int16(c)),off)] {sym} ptr mem) + (MOVWstoreconst [makeValAndOff(int32(int16(c)),off)] {sym} ptr mem) (MOVBstore [off] {sym} ptr (MOV(L|Q)const [c]) mem) => - (MOVBstoreconst [makeValAndOff32(int32(int8(c)),off)] {sym} ptr mem) + (MOVBstoreconst [makeValAndOff(int32(int8(c)),off)] {sym} ptr mem) // Fold address offsets into constant stores. (MOV(Q|L|W|B)storeconst [sc] {s} (ADDQconst [off] ptr) mem) && ValAndOff(sc).canAdd32(off) => @@ -1177,18 +1169,18 @@ ((ADD|SUB|MUL|DIV)SDload [off1] {sym1} val (LEAQ [off2] {sym2} base) mem) && is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) => ((ADD|SUB|MUL|DIV)SDload [off1+off2] {mergeSym(sym1,sym2)} val base mem) -((ADD|AND|OR|XOR|BTC|BTR|BTS)Qconstmodify [valoff1] {sym1} (LEAQ [off2] {sym2} base) mem) +((ADD|AND|OR|XOR)Qconstmodify [valoff1] {sym1} (LEAQ [off2] {sym2} base) mem) && ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2) => - ((ADD|AND|OR|XOR|BTC|BTR|BTS)Qconstmodify [ValAndOff(valoff1).addOffset32(off2)] {mergeSym(sym1,sym2)} base mem) -((ADD|AND|OR|XOR|BTC|BTR|BTS)Lconstmodify [valoff1] {sym1} (LEAQ [off2] {sym2} base) mem) + ((ADD|AND|OR|XOR)Qconstmodify [ValAndOff(valoff1).addOffset32(off2)] {mergeSym(sym1,sym2)} base mem) +((ADD|AND|OR|XOR)Lconstmodify [valoff1] {sym1} (LEAQ [off2] {sym2} base) mem) && ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2) => - ((ADD|AND|OR|XOR|BTC|BTR|BTS)Lconstmodify [ValAndOff(valoff1).addOffset32(off2)] {mergeSym(sym1,sym2)} base mem) -((ADD|SUB|AND|OR|XOR|BTC|BTR|BTS)Qmodify [off1] {sym1} (LEAQ [off2] {sym2} base) val mem) + ((ADD|AND|OR|XOR)Lconstmodify [ValAndOff(valoff1).addOffset32(off2)] {mergeSym(sym1,sym2)} base mem) +((ADD|SUB|AND|OR|XOR)Qmodify [off1] {sym1} (LEAQ [off2] {sym2} base) val mem) && is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) => - ((ADD|SUB|AND|OR|XOR|BTC|BTR|BTS)Qmodify [off1+off2] {mergeSym(sym1,sym2)} base val mem) -((ADD|SUB|AND|OR|XOR|BTC|BTR|BTS)Lmodify [off1] {sym1} (LEAQ [off2] {sym2} base) val mem) + ((ADD|SUB|AND|OR|XOR)Qmodify [off1+off2] {mergeSym(sym1,sym2)} base val mem) +((ADD|SUB|AND|OR|XOR)Lmodify [off1] {sym1} (LEAQ [off2] {sym2} base) val mem) && is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) => - ((ADD|SUB|AND|OR|XOR|BTC|BTR|BTS)Lmodify [off1+off2] {mergeSym(sym1,sym2)} base val mem) + ((ADD|SUB|AND|OR|XOR)Lmodify [off1+off2] {mergeSym(sym1,sym2)} base val mem) // fold LEAQs together (LEAQ [off1] {sym1} (LEAQ [off2] {sym2} x)) && is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) => @@ -1879,32 +1871,32 @@ && x.Uses == 1 && a.Off() + 1 == c.Off() && clobber(x) - => (MOVWstoreconst [makeValAndOff64(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) + => (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) (MOVBstoreconst [a] {s} p x:(MOVBstoreconst [c] {s} p mem)) && x.Uses == 1 && a.Off() + 1 == c.Off() && clobber(x) - => (MOVWstoreconst [makeValAndOff64(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) + => (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) (MOVWstoreconst [c] {s} p x:(MOVWstoreconst [a] {s} p mem)) && x.Uses == 1 && a.Off() + 2 == c.Off() && clobber(x) - => (MOVLstoreconst [makeValAndOff64(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) + => (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) (MOVWstoreconst [a] {s} p x:(MOVWstoreconst [c] {s} p mem)) && x.Uses == 1 && a.Off() + 2 == c.Off() && clobber(x) - => (MOVLstoreconst [makeValAndOff64(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) + => (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) (MOVLstoreconst [c] {s} p x:(MOVLstoreconst [a] {s} p mem)) && x.Uses == 1 && a.Off() + 4 == c.Off() && clobber(x) - => (MOVQstore [a.Off32()] {s} p (MOVQconst [a.Val()&0xffffffff | c.Val()<<32]) mem) + => (MOVQstore [a.Off()] {s} p (MOVQconst [a.Val64()&0xffffffff | c.Val64()<<32]) mem) (MOVLstoreconst [a] {s} p x:(MOVLstoreconst [c] {s} p mem)) && x.Uses == 1 && a.Off() + 4 == c.Off() && clobber(x) - => (MOVQstore [a.Off32()] {s} p (MOVQconst [a.Val()&0xffffffff | c.Val()<<32]) mem) + => (MOVQstore [a.Off()] {s} p (MOVQconst [a.Val64()&0xffffffff | c.Val64()<<32]) mem) (MOVQstoreconst [c] {s} p x:(MOVQstoreconst [c2] {s} p mem)) && config.useSSE && x.Uses == 1 @@ -1912,7 +1904,7 @@ && c.Val() == 0 && c2.Val() == 0 && clobber(x) - => (MOVOstore [c2.Off32()] {s} p (MOVOconst [0]) mem) + => (MOVOstorezero [c2.Off()] {s} p mem) // Combine stores into larger (unaligned) stores. Little endian. (MOVBstore [i] {s} p (SHR(W|L|Q)const [8] w) x:(MOVBstore [i-1] {s} p w mem)) @@ -1981,6 +1973,16 @@ && clobber(x) => (MOVQstore [i] {s} p0 w0 mem) +(MOVBstore [7] {s} p1 (SHRQconst [56] w) + x1:(MOVWstore [5] {s} p1 (SHRQconst [40] w) + x2:(MOVLstore [1] {s} p1 (SHRQconst [8] w) + x3:(MOVBstore [0] {s} p1 w mem)))) + && x1.Uses == 1 + && x2.Uses == 1 + && x3.Uses == 1 + && clobber(x1, x2, x3) + => (MOVQstore {s} p1 w mem) + (MOVBstore [i] {s} p x1:(MOVBload [j] {s2} p2 mem) mem2:(MOVBstore [i-1] {s} p @@ -2064,13 +2066,9 @@ (MOVLstore {sym} [off] ptr y:((ADD|AND|OR|XOR)Lload x [off] {sym} ptr mem) mem) && y.Uses==1 && clobber(y) => ((ADD|AND|OR|XOR)Lmodify [off] {sym} ptr x mem) (MOVLstore {sym} [off] ptr y:((ADD|SUB|AND|OR|XOR)L l:(MOVLload [off] {sym} ptr mem) x) mem) && y.Uses==1 && l.Uses==1 && clobber(y, l) => ((ADD|SUB|AND|OR|XOR)Lmodify [off] {sym} ptr x mem) -(MOVLstore {sym} [off] ptr y:((BTC|BTR|BTS)L l:(MOVLload [off] {sym} ptr mem) x) mem) && y.Uses==1 && l.Uses==1 && clobber(y, l) => - ((BTC|BTR|BTS)Lmodify [off] {sym} ptr (ANDLconst [31] x) mem) (MOVQstore {sym} [off] ptr y:((ADD|AND|OR|XOR)Qload x [off] {sym} ptr mem) mem) && y.Uses==1 && clobber(y) => ((ADD|AND|OR|XOR)Qmodify [off] {sym} ptr x mem) (MOVQstore {sym} [off] ptr y:((ADD|SUB|AND|OR|XOR)Q l:(MOVQload [off] {sym} ptr mem) x) mem) && y.Uses==1 && l.Uses==1 && clobber(y, l) => ((ADD|SUB|AND|OR|XOR)Qmodify [off] {sym} ptr x mem) -(MOVQstore {sym} [off] ptr y:((BTC|BTR|BTS)Q l:(MOVQload [off] {sym} ptr mem) x) mem) && y.Uses==1 && l.Uses==1 && clobber(y, l) => - ((BTC|BTR|BTS)Qmodify [off] {sym} ptr (ANDQconst [63] x) mem) // Merge ADDQconst and LEAQ into atomic loads. (MOV(Q|L|B)atomicload [off1] {sym} (ADDQconst [off2] ptr) mem) && is32Bit(int64(off1)+int64(off2)) => @@ -2124,12 +2122,12 @@ (MOVWQZX (MOVBQZX x)) => (MOVBQZX x) (MOVBQZX (MOVBQZX x)) => (MOVBQZX x) -(MOVQstore [off] {sym} ptr a:((ADD|AND|OR|XOR|BTC|BTR|BTS)Qconst [c] l:(MOVQload [off] {sym} ptr2 mem)) mem) - && isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) => - ((ADD|AND|OR|XOR|BTC|BTR|BTS)Qconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) -(MOVLstore [off] {sym} ptr a:((ADD|AND|OR|XOR|BTC|BTR|BTS)Lconst [c] l:(MOVLload [off] {sym} ptr2 mem)) mem) - && isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) => - ((ADD|AND|OR|XOR|BTC|BTR|BTS)Lconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) +(MOVQstore [off] {sym} ptr a:((ADD|AND|OR|XOR)Qconst [c] l:(MOVQload [off] {sym} ptr2 mem)) mem) + && isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a) => + ((ADD|AND|OR|XOR)Qconstmodify {sym} [makeValAndOff(int32(c),off)] ptr mem) +(MOVLstore [off] {sym} ptr a:((ADD|AND|OR|XOR)Lconst [c] l:(MOVLload [off] {sym} ptr2 mem)) mem) + && isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a) => + ((ADD|AND|OR|XOR)Lconstmodify {sym} [makeValAndOff(int32(c),off)] ptr mem) // float <-> int register moves, with no conversion. // These come up when compiling math.{Float{32,64}bits,Float{32,64}frombits}. @@ -2189,23 +2187,27 @@ (CMP(Q|L)const l:(MOV(Q|L)load {sym} [off] ptr mem) [c]) && l.Uses == 1 && clobber(l) => -@l.Block (CMP(Q|L)constload {sym} [makeValAndOff32(c,off)] ptr mem) +@l.Block (CMP(Q|L)constload {sym} [makeValAndOff(c,off)] ptr mem) (CMP(W|B)const l:(MOV(W|B)load {sym} [off] ptr mem) [c]) && l.Uses == 1 && clobber(l) => -@l.Block (CMP(W|B)constload {sym} [makeValAndOff32(int32(c),off)] ptr mem) +@l.Block (CMP(W|B)constload {sym} [makeValAndOff(int32(c),off)] ptr mem) -(CMPQload {sym} [off] ptr (MOVQconst [c]) mem) && validValAndOff(c,int64(off)) => (CMPQconstload {sym} [makeValAndOff64(c,int64(off))] ptr mem) -(CMPLload {sym} [off] ptr (MOVLconst [c]) mem) && validValAndOff(int64(c),int64(off)) => (CMPLconstload {sym} [makeValAndOff32(c,off)] ptr mem) -(CMPWload {sym} [off] ptr (MOVLconst [c]) mem) && validValAndOff(int64(int16(c)),int64(off)) => (CMPWconstload {sym} [makeValAndOff32(int32(int16(c)),off)] ptr mem) -(CMPBload {sym} [off] ptr (MOVLconst [c]) mem) && validValAndOff(int64(int8(c)),int64(off)) => (CMPBconstload {sym} [makeValAndOff32(int32(int8(c)),off)] ptr mem) +(CMPQload {sym} [off] ptr (MOVQconst [c]) mem) && validVal(c) => (CMPQconstload {sym} [makeValAndOff(int32(c),off)] ptr mem) +(CMPLload {sym} [off] ptr (MOVLconst [c]) mem) => (CMPLconstload {sym} [makeValAndOff(c,off)] ptr mem) +(CMPWload {sym} [off] ptr (MOVLconst [c]) mem) => (CMPWconstload {sym} [makeValAndOff(int32(int16(c)),off)] ptr mem) +(CMPBload {sym} [off] ptr (MOVLconst [c]) mem) => (CMPBconstload {sym} [makeValAndOff(int32(int8(c)),off)] ptr mem) (TEST(Q|L|W|B) l:(MOV(Q|L|W|B)load {sym} [off] ptr mem) l2) && l == l2 && l.Uses == 2 - && validValAndOff(0, int64(off)) && clobber(l) => - @l.Block (CMP(Q|L|W|B)constload {sym} [makeValAndOff64(0, int64(off))] ptr mem) + @l.Block (CMP(Q|L|W|B)constload {sym} [makeValAndOff(0, off)] ptr mem) + +// Convert ANDload to MOVload when we can do the AND in a containing TEST op. +// Only do when it's within the same block, so we don't have flags live across basic block boundaries. +// See issue 44228. +(TEST(Q|L) a:(AND(Q|L)load [off] {sym} x ptr mem) a) && a.Uses == 2 && a.Block == v.Block && clobber(a) => (TEST(Q|L) (MOV(Q|L)load [off] {sym} ptr mem) x) (MOVBload [off] {sym} (SB) _) && symIsRO(sym) => (MOVLconst [int32(read8(sym, int64(off)))]) (MOVWload [off] {sym} (SB) _) && symIsRO(sym) => (MOVLconst [int32(read16(sym, int64(off), config.ctxt.Arch.ByteOrder))]) @@ -2214,3 +2216,22 @@ (MOVOstore [dstOff] {dstSym} ptr (MOVOload [srcOff] {srcSym} (SB) _) mem) && symIsRO(srcSym) => (MOVQstore [dstOff+8] {dstSym} ptr (MOVQconst [int64(read64(srcSym, int64(srcOff)+8, config.ctxt.Arch.ByteOrder))]) (MOVQstore [dstOff] {dstSym} ptr (MOVQconst [int64(read64(srcSym, int64(srcOff), config.ctxt.Arch.ByteOrder))]) mem)) + +// Arch-specific inlining for small or disjoint runtime.memmove +// Match post-lowering calls, memory version. +(SelectN [0] call:(CALLstatic {sym} s1:(MOVQstoreconst _ [sc] s2:(MOVQstore _ src s3:(MOVQstore _ dst mem))))) + && sc.Val64() >= 0 + && isSameCall(sym, "runtime.memmove") + && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 + && isInlinableMemmove(dst, src, sc.Val64(), config) + && clobber(s1, s2, s3, call) + => (Move [sc.Val64()] dst src mem) + +// Match post-lowering calls, register version. +(SelectN [0] call:(CALLstatic {sym} dst src (MOVQconst [sz]) mem)) + && sz >= 0 + && isSameCall(sym, "runtime.memmove") + && call.Uses == 1 + && isInlinableMemmove(dst, src, sz, config) + && clobber(call) + => (Move [sz] dst src mem) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go index a87581b68fe33e05a115ea412becc88f1025e5e2..67b3293903cd915f5ef2e3839b806fe0230370fd 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore package main @@ -44,7 +45,7 @@ var regNamesAMD64 = []string{ "R11", "R12", "R13", - "R14", + "g", // a.k.a. R14 "R15", "X0", "X1", @@ -61,7 +62,7 @@ var regNamesAMD64 = []string{ "X12", "X13", "X14", - "X15", + "X15", // constant 0 in ABIInternal // If you add registers, update asyncPreempt in runtime @@ -96,11 +97,14 @@ func init() { cx = buildReg("CX") dx = buildReg("DX") bx = buildReg("BX") - gp = buildReg("AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15") - fp = buildReg("X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15") + gp = buildReg("AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15") + g = buildReg("g") + fp = buildReg("X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14") + x15 = buildReg("X15") gpsp = gp | buildReg("SP") gpspsb = gpsp | buildReg("SB") - callerSave = gp | fp + gpspsbg = gpspsb | g + callerSave = gp | fp | g // runtime.setg (and anything calling it) may clobber g ) // Common slices of register masks var ( @@ -113,11 +117,12 @@ func init() { gp01 = regInfo{inputs: nil, outputs: gponly} gp11 = regInfo{inputs: []regMask{gp}, outputs: gponly} gp11sp = regInfo{inputs: []regMask{gpsp}, outputs: gponly} - gp11sb = regInfo{inputs: []regMask{gpspsb}, outputs: gponly} + gp11sb = regInfo{inputs: []regMask{gpspsbg}, outputs: gponly} gp21 = regInfo{inputs: []regMask{gp, gp}, outputs: gponly} gp21sp = regInfo{inputs: []regMask{gpsp, gp}, outputs: gponly} - gp21sb = regInfo{inputs: []regMask{gpspsb, gpsp}, outputs: gponly} + gp21sb = regInfo{inputs: []regMask{gpspsbg, gpsp}, outputs: gponly} gp21shift = regInfo{inputs: []regMask{gp, cx}, outputs: []regMask{gp}} + gp31shift = regInfo{inputs: []regMask{gp, gp, cx}, outputs: []regMask{gp}} gp11div = regInfo{inputs: []regMask{ax, gpsp &^ dx}, outputs: []regMask{ax, dx}} gp21hmul = regInfo{inputs: []regMask{ax, gpsp}, outputs: []regMask{dx}, clobbers: ax} gp21flags = regInfo{inputs: []regMask{gp, gp}, outputs: []regMask{gp, 0}} @@ -125,9 +130,9 @@ func init() { gp2flags = regInfo{inputs: []regMask{gpsp, gpsp}} gp1flags = regInfo{inputs: []regMask{gpsp}} - gp0flagsLoad = regInfo{inputs: []regMask{gpspsb, 0}} - gp1flagsLoad = regInfo{inputs: []regMask{gpspsb, gpsp, 0}} - gp2flagsLoad = regInfo{inputs: []regMask{gpspsb, gpsp, gpsp, 0}} + gp0flagsLoad = regInfo{inputs: []regMask{gpspsbg, 0}} + gp1flagsLoad = regInfo{inputs: []regMask{gpspsbg, gpsp, 0}} + gp2flagsLoad = regInfo{inputs: []regMask{gpspsbg, gpsp, gpsp, 0}} flagsgp = regInfo{inputs: nil, outputs: gponly} gp11flags = regInfo{inputs: []regMask{gp}, outputs: []regMask{gp, 0}} @@ -136,24 +141,24 @@ func init() { readflags = regInfo{inputs: nil, outputs: gponly} flagsgpax = regInfo{inputs: nil, clobbers: ax, outputs: []regMask{gp &^ ax}} - gpload = regInfo{inputs: []regMask{gpspsb, 0}, outputs: gponly} - gp21load = regInfo{inputs: []regMask{gp, gpspsb, 0}, outputs: gponly} - gploadidx = regInfo{inputs: []regMask{gpspsb, gpsp, 0}, outputs: gponly} - gp21loadidx = regInfo{inputs: []regMask{gp, gpspsb, gpsp, 0}, outputs: gponly} + gpload = regInfo{inputs: []regMask{gpspsbg, 0}, outputs: gponly} + gp21load = regInfo{inputs: []regMask{gp, gpspsbg, 0}, outputs: gponly} + gploadidx = regInfo{inputs: []regMask{gpspsbg, gpsp, 0}, outputs: gponly} + gp21loadidx = regInfo{inputs: []regMask{gp, gpspsbg, gpsp, 0}, outputs: gponly} gp21pax = regInfo{inputs: []regMask{gp &^ ax, gp}, outputs: []regMask{gp &^ ax}, clobbers: ax} - gpstore = regInfo{inputs: []regMask{gpspsb, gpsp, 0}} - gpstoreconst = regInfo{inputs: []regMask{gpspsb, 0}} - gpstoreidx = regInfo{inputs: []regMask{gpspsb, gpsp, gpsp, 0}} - gpstoreconstidx = regInfo{inputs: []regMask{gpspsb, gpsp, 0}} - gpstorexchg = regInfo{inputs: []regMask{gp, gpspsb, 0}, outputs: []regMask{gp}} + gpstore = regInfo{inputs: []regMask{gpspsbg, gpsp, 0}} + gpstoreconst = regInfo{inputs: []regMask{gpspsbg, 0}} + gpstoreidx = regInfo{inputs: []regMask{gpspsbg, gpsp, gpsp, 0}} + gpstoreconstidx = regInfo{inputs: []regMask{gpspsbg, gpsp, 0}} + gpstorexchg = regInfo{inputs: []regMask{gp, gpspsbg, 0}, outputs: []regMask{gp}} cmpxchg = regInfo{inputs: []regMask{gp, ax, gp, 0}, outputs: []regMask{gp, 0}, clobbers: ax} fp01 = regInfo{inputs: nil, outputs: fponly} fp21 = regInfo{inputs: []regMask{fp, fp}, outputs: fponly} fp31 = regInfo{inputs: []regMask{fp, fp, fp}, outputs: fponly} - fp21load = regInfo{inputs: []regMask{fp, gpspsb, 0}, outputs: fponly} - fp21loadidx = regInfo{inputs: []regMask{fp, gpspsb, gpspsb, 0}, outputs: fponly} + fp21load = regInfo{inputs: []regMask{fp, gpspsbg, 0}, outputs: fponly} + fp21loadidx = regInfo{inputs: []regMask{fp, gpspsbg, gpspsb, 0}, outputs: fponly} fpgp = regInfo{inputs: fponly, outputs: gponly} gpfp = regInfo{inputs: gponly, outputs: fponly} fp11 = regInfo{inputs: fponly, outputs: fponly} @@ -357,25 +362,6 @@ func init() { {name: "BTSLconst", argLength: 1, reg: gp11, asm: "BTSL", resultInArg0: true, clobberFlags: true, aux: "Int8"}, // set bit auxint in arg0, 0 <= auxint < 32 {name: "BTSQconst", argLength: 1, reg: gp11, asm: "BTSQ", resultInArg0: true, clobberFlags: true, aux: "Int8"}, // set bit auxint in arg0, 0 <= auxint < 64 - // direct bit operation on memory operand - // - // Note that these operations do not mask the bit offset (arg1), and will write beyond their expected - // bounds if that argument is larger than 64/32 (for BT*Q and BT*L, respectively). If the compiler - // cannot prove that arg1 is in range, it must be explicitly masked (see e.g. the patterns that produce - // BT*modify from (MOVstore (BT* (MOVLload ptr mem) x) mem)). - {name: "BTCQmodify", argLength: 3, reg: gpstore, asm: "BTCQ", aux: "SymOff", typ: "Mem", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // complement bit arg1 in 64-bit arg0+auxint+aux, arg2=mem - {name: "BTCLmodify", argLength: 3, reg: gpstore, asm: "BTCL", aux: "SymOff", typ: "Mem", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // complement bit arg1 in 32-bit arg0+auxint+aux, arg2=mem - {name: "BTSQmodify", argLength: 3, reg: gpstore, asm: "BTSQ", aux: "SymOff", typ: "Mem", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // set bit arg1 in 64-bit arg0+auxint+aux, arg2=mem - {name: "BTSLmodify", argLength: 3, reg: gpstore, asm: "BTSL", aux: "SymOff", typ: "Mem", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // set bit arg1 in 32-bit arg0+auxint+aux, arg2=mem - {name: "BTRQmodify", argLength: 3, reg: gpstore, asm: "BTRQ", aux: "SymOff", typ: "Mem", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // reset bit arg1 in 64-bit arg0+auxint+aux, arg2=mem - {name: "BTRLmodify", argLength: 3, reg: gpstore, asm: "BTRL", aux: "SymOff", typ: "Mem", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // reset bit arg1 in 32-bit arg0+auxint+aux, arg2=mem - {name: "BTCQconstmodify", argLength: 2, reg: gpstoreconst, asm: "BTCQ", aux: "SymValAndOff", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // complement bit ValAndOff(AuxInt).Val() in 64-bit arg0+ValAndOff(AuxInt).Off()+aux, arg1=mem - {name: "BTCLconstmodify", argLength: 2, reg: gpstoreconst, asm: "BTCL", aux: "SymValAndOff", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // complement bit ValAndOff(AuxInt).Val() in 32-bit arg0+ValAndOff(AuxInt).Off()+aux, arg1=mem - {name: "BTSQconstmodify", argLength: 2, reg: gpstoreconst, asm: "BTSQ", aux: "SymValAndOff", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // set bit ValAndOff(AuxInt).Val() in 64-bit arg0+ValAndOff(AuxInt).Off()+aux, arg1=mem - {name: "BTSLconstmodify", argLength: 2, reg: gpstoreconst, asm: "BTSL", aux: "SymValAndOff", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // set bit ValAndOff(AuxInt).Val() in 32-bit arg0+ValAndOff(AuxInt).Off()+aux, arg1=mem - {name: "BTRQconstmodify", argLength: 2, reg: gpstoreconst, asm: "BTRQ", aux: "SymValAndOff", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // reset bit ValAndOff(AuxInt).Val() in 64-bit arg0+ValAndOff(AuxInt).Off()+aux, arg1=mem - {name: "BTRLconstmodify", argLength: 2, reg: gpstoreconst, asm: "BTRL", aux: "SymValAndOff", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, // reset bit ValAndOff(AuxInt).Val() in 32-bit arg0+ValAndOff(AuxInt).Off()+aux, arg1=mem - {name: "TESTQ", argLength: 2, reg: gp2flags, commutative: true, asm: "TESTQ", typ: "Flags"}, // (arg0 & arg1) compare to 0 {name: "TESTL", argLength: 2, reg: gp2flags, commutative: true, asm: "TESTL", typ: "Flags"}, // (arg0 & arg1) compare to 0 {name: "TESTW", argLength: 2, reg: gp2flags, commutative: true, asm: "TESTW", typ: "Flags"}, // (arg0 & arg1) compare to 0 @@ -409,6 +395,9 @@ func init() { {name: "SARWconst", argLength: 1, reg: gp11, asm: "SARW", aux: "Int8", resultInArg0: true, clobberFlags: true}, // signed int16(arg0) >> auxint, shift amount 0-15 {name: "SARBconst", argLength: 1, reg: gp11, asm: "SARB", aux: "Int8", resultInArg0: true, clobberFlags: true}, // signed int8(arg0) >> auxint, shift amount 0-7 + {name: "SHRDQ", argLength: 3, reg: gp31shift, asm: "SHRQ", resultInArg0: true, clobberFlags: true}, // unsigned arg0 >> arg2, shifting in bits from arg1 (==(arg1<<64+arg0)>>arg2, keeping low 64 bits), shift amount is mod 64 + {name: "SHLDQ", argLength: 3, reg: gp31shift, asm: "SHLQ", resultInArg0: true, clobberFlags: true}, // unsigned arg0 << arg2, shifting in bits from arg1 (==(arg0<<64+arg1)< (CMP(Q|L|W|B) (MOV(Q|L|W|B)load {sym} [off] ptr mem) x) -(CMP(Q|L|W|B)constload {sym} [vo] ptr mem) && vo.Val() == 0 => (TEST(Q|L|W|B) x:(MOV(Q|L|W|B)load {sym} [vo.Off32()] ptr mem) x) +(CMP(Q|L|W|B)constload {sym} [vo] ptr mem) && vo.Val() == 0 => (TEST(Q|L|W|B) x:(MOV(Q|L|W|B)load {sym} [vo.Off()] ptr mem) x) -(CMPQconstload {sym} [vo] ptr mem) && vo.Val() != 0 => (CMPQconst (MOVQload {sym} [vo.Off32()] ptr mem) [vo.Val32()]) -(CMPLconstload {sym} [vo] ptr mem) && vo.Val() != 0 => (CMPLconst (MOVLload {sym} [vo.Off32()] ptr mem) [vo.Val32()]) -(CMPWconstload {sym} [vo] ptr mem) && vo.Val() != 0 => (CMPWconst (MOVWload {sym} [vo.Off32()] ptr mem) [vo.Val16()]) -(CMPBconstload {sym} [vo] ptr mem) && vo.Val() != 0 => (CMPBconst (MOVBload {sym} [vo.Off32()] ptr mem) [vo.Val8()]) +(CMPQconstload {sym} [vo] ptr mem) && vo.Val() != 0 => (CMPQconst (MOVQload {sym} [vo.Off()] ptr mem) [vo.Val()]) +(CMPLconstload {sym} [vo] ptr mem) && vo.Val() != 0 => (CMPLconst (MOVLload {sym} [vo.Off()] ptr mem) [vo.Val()]) +(CMPWconstload {sym} [vo] ptr mem) && vo.Val() != 0 => (CMPWconst (MOVWload {sym} [vo.Off()] ptr mem) [vo.Val16()]) +(CMPBconstload {sym} [vo] ptr mem) && vo.Val() != 0 => (CMPBconst (MOVBload {sym} [vo.Off()] ptr mem) [vo.Val8()]) (CMP(Q|L|W|B)loadidx1 {sym} [off] ptr idx x mem) => (CMP(Q|L|W|B) (MOV(Q|L|W|B)loadidx1 {sym} [off] ptr idx mem) x) (CMPQloadidx8 {sym} [off] ptr idx x mem) => (CMPQ (MOVQloadidx8 {sym} [off] ptr idx mem) x) (CMPLloadidx4 {sym} [off] ptr idx x mem) => (CMPL (MOVLloadidx4 {sym} [off] ptr idx mem) x) (CMPWloadidx2 {sym} [off] ptr idx x mem) => (CMPW (MOVWloadidx2 {sym} [off] ptr idx mem) x) -(CMP(Q|L|W|B)constloadidx1 {sym} [vo] ptr idx mem) && vo.Val() == 0 => (TEST(Q|L|W|B) x:(MOV(Q|L|W|B)loadidx1 {sym} [vo.Off32()] ptr idx mem) x) -(CMPQconstloadidx8 {sym} [vo] ptr idx mem) && vo.Val() == 0 => (TESTQ x:(MOVQloadidx8 {sym} [vo.Off32()] ptr idx mem) x) -(CMPLconstloadidx4 {sym} [vo] ptr idx mem) && vo.Val() == 0 => (TESTL x:(MOVLloadidx4 {sym} [vo.Off32()] ptr idx mem) x) -(CMPWconstloadidx2 {sym} [vo] ptr idx mem) && vo.Val() == 0 => (TESTW x:(MOVWloadidx2 {sym} [vo.Off32()] ptr idx mem) x) +(CMP(Q|L|W|B)constloadidx1 {sym} [vo] ptr idx mem) && vo.Val() == 0 => (TEST(Q|L|W|B) x:(MOV(Q|L|W|B)loadidx1 {sym} [vo.Off()] ptr idx mem) x) +(CMPQconstloadidx8 {sym} [vo] ptr idx mem) && vo.Val() == 0 => (TESTQ x:(MOVQloadidx8 {sym} [vo.Off()] ptr idx mem) x) +(CMPLconstloadidx4 {sym} [vo] ptr idx mem) && vo.Val() == 0 => (TESTL x:(MOVLloadidx4 {sym} [vo.Off()] ptr idx mem) x) +(CMPWconstloadidx2 {sym} [vo] ptr idx mem) && vo.Val() == 0 => (TESTW x:(MOVWloadidx2 {sym} [vo.Off()] ptr idx mem) x) -(CMPQconstloadidx1 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPQconst (MOVQloadidx1 {sym} [vo.Off32()] ptr idx mem) [vo.Val32()]) -(CMPLconstloadidx1 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPLconst (MOVLloadidx1 {sym} [vo.Off32()] ptr idx mem) [vo.Val32()]) -(CMPWconstloadidx1 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPWconst (MOVWloadidx1 {sym} [vo.Off32()] ptr idx mem) [vo.Val16()]) -(CMPBconstloadidx1 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPBconst (MOVBloadidx1 {sym} [vo.Off32()] ptr idx mem) [vo.Val8()]) +(CMPQconstloadidx1 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPQconst (MOVQloadidx1 {sym} [vo.Off()] ptr idx mem) [vo.Val()]) +(CMPLconstloadidx1 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPLconst (MOVLloadidx1 {sym} [vo.Off()] ptr idx mem) [vo.Val()]) +(CMPWconstloadidx1 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPWconst (MOVWloadidx1 {sym} [vo.Off()] ptr idx mem) [vo.Val16()]) +(CMPBconstloadidx1 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPBconst (MOVBloadidx1 {sym} [vo.Off()] ptr idx mem) [vo.Val8()]) -(CMPQconstloadidx8 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPQconst (MOVQloadidx8 {sym} [vo.Off32()] ptr idx mem) [vo.Val32()]) -(CMPLconstloadidx4 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPLconst (MOVLloadidx4 {sym} [vo.Off32()] ptr idx mem) [vo.Val32()]) -(CMPWconstloadidx2 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPWconst (MOVWloadidx2 {sym} [vo.Off32()] ptr idx mem) [vo.Val16()]) +(CMPQconstloadidx8 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPQconst (MOVQloadidx8 {sym} [vo.Off()] ptr idx mem) [vo.Val()]) +(CMPLconstloadidx4 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPLconst (MOVLloadidx4 {sym} [vo.Off()] ptr idx mem) [vo.Val()]) +(CMPWconstloadidx2 {sym} [vo] ptr idx mem) && vo.Val() != 0 => (CMPWconst (MOVWloadidx2 {sym} [vo.Off()] ptr idx mem) [vo.Val16()]) diff --git a/src/cmd/compile/internal/ssa/gen/ARM.rules b/src/cmd/compile/internal/ssa/gen/ARM.rules index 11c36b5da3567ed6728cdfc191d5676e28e401e6..bcacbafe3a5f0299dd47e45962a46170bd9896d4 100644 --- a/src/cmd/compile/internal/ssa/gen/ARM.rules +++ b/src/cmd/compile/internal/ssa/gen/ARM.rules @@ -56,6 +56,7 @@ (Com(32|16|8) ...) => (MVN ...) (Sqrt ...) => (SQRTD ...) +(Sqrt32 ...) => (SQRTF ...) (Abs ...) => (ABSD ...) // TODO: optimize this for ARMv5 and ARMv6 @@ -65,17 +66,17 @@ // count trailing zero for ARMv5 and ARMv6 // 32 - CLZ(x&-x - 1) -(Ctz32 x) && objabi.GOARM<=6 => +(Ctz32 x) && buildcfg.GOARM<=6 => (RSBconst [32] (CLZ (SUBconst (AND x (RSBconst [0] x)) [1]))) -(Ctz16 x) && objabi.GOARM<=6 => +(Ctz16 x) && buildcfg.GOARM<=6 => (RSBconst [32] (CLZ (SUBconst (AND (ORconst [0x10000] x) (RSBconst [0] (ORconst [0x10000] x))) [1]))) -(Ctz8 x) && objabi.GOARM<=6 => +(Ctz8 x) && buildcfg.GOARM<=6 => (RSBconst [32] (CLZ (SUBconst (AND (ORconst [0x100] x) (RSBconst [0] (ORconst [0x100] x))) [1]))) // count trailing zero for ARMv7 -(Ctz32 x) && objabi.GOARM==7 => (CLZ (RBIT x)) -(Ctz16 x) && objabi.GOARM==7 => (CLZ (RBIT (ORconst [0x10000] x))) -(Ctz8 x) && objabi.GOARM==7 => (CLZ (RBIT (ORconst [0x100] x))) +(Ctz32 x) && buildcfg.GOARM==7 => (CLZ (RBIT x)) +(Ctz16 x) && buildcfg.GOARM==7 => (CLZ (RBIT (ORconst [0x10000] x))) +(Ctz8 x) && buildcfg.GOARM==7 => (CLZ (RBIT (ORconst [0x100] x))) // bit length (BitLen32 x) => (RSBconst [32] (CLZ x)) @@ -89,13 +90,13 @@ // t5 = x right rotate 8 bits -- (d, a, b, c ) // result = t4 ^ t5 -- (d, c, b, a ) // using shifted ops this can be done in 4 instructions. -(Bswap32 x) && objabi.GOARM==5 => +(Bswap32 x) && buildcfg.GOARM==5 => (XOR (SRLconst (BICconst (XOR x (SRRconst [16] x)) [0xff0000]) [8]) (SRRconst x [8])) // byte swap for ARMv6 and above -(Bswap32 x) && objabi.GOARM>=6 => (REV x) +(Bswap32 x) && buildcfg.GOARM>=6 => (REV x) // boolean ops -- booleans are represented with 0=false, 1=true (AndB ...) => (AND ...) @@ -172,7 +173,7 @@ (Const(8|16|32) [val]) => (MOVWconst [int32(val)]) (Const(32|64)F [val]) => (MOV(F|D)const [float64(val)]) (ConstNil) => (MOVWconst [0]) -(ConstBool [b]) => (MOVWconst [b2i32(b)]) +(ConstBool [t]) => (MOVWconst [b2i32(t)]) // truncations // Because we ignore high parts of registers, truncates are just copies. @@ -507,7 +508,7 @@ (TEQ x (MOVWconst [c])) => (TEQconst [c] x) // Canonicalize the order of arguments to comparisons - helps with CSE. -(CMP x y) && x.ID > y.ID => (InvertFlags (CMP y x)) +(CMP x y) && canonLessThan(x,y) => (InvertFlags (CMP y x)) // don't extend after proper load // MOVWreg instruction is not emitted if src and dst registers are same, but it ensures the type. @@ -546,6 +547,10 @@ // MOVWnop doesn't emit instruction, only for ensuring the type. (MOVWreg x) && x.Uses == 1 => (MOVWnop x) +// TODO: we should be able to get rid of MOVWnop all together. +// But for now, this is enough to get rid of lots of them. +(MOVWnop (MOVWconst [c])) => (MOVWconst [c]) + // mul by constant (MUL x (MOVWconst [c])) && int32(c) == -1 => (RSBconst [0] x) (MUL _ (MOVWconst [0])) => (MOVWconst [0]) @@ -732,10 +737,10 @@ (SUBconst [c] x) && !isARMImmRot(uint32(c)) && isARMImmRot(uint32(-c)) => (ADDconst [-c] x) (ANDconst [c] x) && !isARMImmRot(uint32(c)) && isARMImmRot(^uint32(c)) => (BICconst [int32(^uint32(c))] x) (BICconst [c] x) && !isARMImmRot(uint32(c)) && isARMImmRot(^uint32(c)) => (ANDconst [int32(^uint32(c))] x) -(ADDconst [c] x) && objabi.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && uint32(-c)<=0xffff => (SUBconst [-c] x) -(SUBconst [c] x) && objabi.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && uint32(-c)<=0xffff => (ADDconst [-c] x) -(ANDconst [c] x) && objabi.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && ^uint32(c)<=0xffff => (BICconst [int32(^uint32(c))] x) -(BICconst [c] x) && objabi.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && ^uint32(c)<=0xffff => (ANDconst [int32(^uint32(c))] x) +(ADDconst [c] x) && buildcfg.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && uint32(-c)<=0xffff => (SUBconst [-c] x) +(SUBconst [c] x) && buildcfg.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && uint32(-c)<=0xffff => (ADDconst [-c] x) +(ANDconst [c] x) && buildcfg.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && ^uint32(c)<=0xffff => (BICconst [int32(^uint32(c))] x) +(BICconst [c] x) && buildcfg.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && ^uint32(c)<=0xffff => (ANDconst [int32(^uint32(c))] x) (ADDconst [c] (MOVWconst [d])) => (MOVWconst [c+d]) (ADDconst [c] (ADDconst [d] x)) => (ADDconst [c+d] x) (ADDconst [c] (SUBconst [d] x)) => (ADDconst [c-d] x) @@ -1139,7 +1144,7 @@ // UBFX instruction is supported by ARMv6T2, ARMv7 and above versions, REV16 is supported by // ARMv6 and above versions. So for ARMv6, we need to match SLLconst, SRLconst and ORshiftLL. ((ADDshiftLL|ORshiftLL|XORshiftLL) [8] (BFXU [int32(armBFAuxInt(8, 8))] x) x) => (REV16 x) -((ADDshiftLL|ORshiftLL|XORshiftLL) [8] (SRLconst [24] (SLLconst [16] x)) x) && objabi.GOARM>=6 => (REV16 x) +((ADDshiftLL|ORshiftLL|XORshiftLL) [8] (SRLconst [24] (SLLconst [16] x)) x) && buildcfg.GOARM>=6 => (REV16 x) // use indexed loads and stores (MOVWload [0] {sym} (ADD ptr idx) mem) && sym == nil => (MOVWloadidx ptr idx mem) @@ -1209,25 +1214,25 @@ (BIC x x) => (MOVWconst [0]) (ADD (MUL x y) a) => (MULA x y a) -(SUB a (MUL x y)) && objabi.GOARM == 7 => (MULS x y a) -(RSB (MUL x y) a) && objabi.GOARM == 7 => (MULS x y a) +(SUB a (MUL x y)) && buildcfg.GOARM == 7 => (MULS x y a) +(RSB (MUL x y) a) && buildcfg.GOARM == 7 => (MULS x y a) -(NEGF (MULF x y)) && objabi.GOARM >= 6 => (NMULF x y) -(NEGD (MULD x y)) && objabi.GOARM >= 6 => (NMULD x y) -(MULF (NEGF x) y) && objabi.GOARM >= 6 => (NMULF x y) -(MULD (NEGD x) y) && objabi.GOARM >= 6 => (NMULD x y) +(NEGF (MULF x y)) && buildcfg.GOARM >= 6 => (NMULF x y) +(NEGD (MULD x y)) && buildcfg.GOARM >= 6 => (NMULD x y) +(MULF (NEGF x) y) && buildcfg.GOARM >= 6 => (NMULF x y) +(MULD (NEGD x) y) && buildcfg.GOARM >= 6 => (NMULD x y) (NMULF (NEGF x) y) => (MULF x y) (NMULD (NEGD x) y) => (MULD x y) // the result will overwrite the addend, since they are in the same register -(ADDF a (MULF x y)) && a.Uses == 1 && objabi.GOARM >= 6 => (MULAF a x y) -(ADDF a (NMULF x y)) && a.Uses == 1 && objabi.GOARM >= 6 => (MULSF a x y) -(ADDD a (MULD x y)) && a.Uses == 1 && objabi.GOARM >= 6 => (MULAD a x y) -(ADDD a (NMULD x y)) && a.Uses == 1 && objabi.GOARM >= 6 => (MULSD a x y) -(SUBF a (MULF x y)) && a.Uses == 1 && objabi.GOARM >= 6 => (MULSF a x y) -(SUBF a (NMULF x y)) && a.Uses == 1 && objabi.GOARM >= 6 => (MULAF a x y) -(SUBD a (MULD x y)) && a.Uses == 1 && objabi.GOARM >= 6 => (MULSD a x y) -(SUBD a (NMULD x y)) && a.Uses == 1 && objabi.GOARM >= 6 => (MULAD a x y) +(ADDF a (MULF x y)) && a.Uses == 1 && buildcfg.GOARM >= 6 => (MULAF a x y) +(ADDF a (NMULF x y)) && a.Uses == 1 && buildcfg.GOARM >= 6 => (MULSF a x y) +(ADDD a (MULD x y)) && a.Uses == 1 && buildcfg.GOARM >= 6 => (MULAD a x y) +(ADDD a (NMULD x y)) && a.Uses == 1 && buildcfg.GOARM >= 6 => (MULSD a x y) +(SUBF a (MULF x y)) && a.Uses == 1 && buildcfg.GOARM >= 6 => (MULSF a x y) +(SUBF a (NMULF x y)) && a.Uses == 1 && buildcfg.GOARM >= 6 => (MULAF a x y) +(SUBD a (MULD x y)) && a.Uses == 1 && buildcfg.GOARM >= 6 => (MULSD a x y) +(SUBD a (NMULD x y)) && a.Uses == 1 && buildcfg.GOARM >= 6 => (MULAD a x y) (AND x (MVN y)) => (BIC x y) @@ -1259,8 +1264,8 @@ (CMPD x (MOVDconst [0])) => (CMPD0 x) // bit extraction -(SRAconst (SLLconst x [c]) [d]) && objabi.GOARM==7 && uint64(d)>=uint64(c) && uint64(d)<=31 => (BFX [(d-c)|(32-d)<<8] x) -(SRLconst (SLLconst x [c]) [d]) && objabi.GOARM==7 && uint64(d)>=uint64(c) && uint64(d)<=31 => (BFXU [(d-c)|(32-d)<<8] x) +(SRAconst (SLLconst x [c]) [d]) && buildcfg.GOARM==7 && uint64(d)>=uint64(c) && uint64(d)<=31 => (BFX [(d-c)|(32-d)<<8] x) +(SRLconst (SLLconst x [c]) [d]) && buildcfg.GOARM==7 && uint64(d)>=uint64(c) && uint64(d)<=31 => (BFXU [(d-c)|(32-d)<<8] x) // comparison simplification ((LT|LE|EQ|NE|GE|GT) (CMP x (RSBconst [0] y))) => ((LT|LE|EQ|NE|GE|GT) (CMN x y)) // sense of carry bit not preserved diff --git a/src/cmd/compile/internal/ssa/gen/ARM64.rules b/src/cmd/compile/internal/ssa/gen/ARM64.rules index 3f4d0c1c52769e33091e92312800b77bda669617..62699f290c2149e6308c4f0d3d82b9b20e3d7738 100644 --- a/src/cmd/compile/internal/ssa/gen/ARM64.rules +++ b/src/cmd/compile/internal/ssa/gen/ARM64.rules @@ -60,6 +60,8 @@ (Trunc ...) => (FRINTZD ...) (FMA x y z) => (FMADDD z x y) +(Sqrt32 ...) => (FSQRTS ...) + // lowering rotates (RotateLeft8 x (MOVDconst [c])) => (Or8 (Lsh8x64 x (MOVDconst [c&7])) (Rsh8Ux64 x (MOVDconst [-c&7]))) (RotateLeft16 x (MOVDconst [c])) => (Or16 (Lsh16x64 x (MOVDconst [c&15])) (Rsh16Ux64 x (MOVDconst [-c&15]))) @@ -196,7 +198,7 @@ (Const(64|32|16|8) [val]) => (MOVDconst [int64(val)]) (Const(32F|64F) [val]) => (FMOV(S|D)const [float64(val)]) (ConstNil) => (MOVDconst [0]) -(ConstBool [b]) => (MOVDconst [b2i(b)]) +(ConstBool [t]) => (MOVDconst [b2i(t)]) (Slicemask x) => (SRAconst (NEG x) [63]) @@ -792,6 +794,15 @@ (MOVHUloadidx2 ptr (MOVDconst [c]) mem) && is32Bit(c<<1) => (MOVHUload [int32(c)<<1] ptr mem) (MOVHloadidx2 ptr (MOVDconst [c]) mem) && is32Bit(c<<1) => (MOVHload [int32(c)<<1] ptr mem) +(FMOVDload [off] {sym} (ADDshiftLL [3] ptr idx) mem) && off == 0 && sym == nil => (FMOVDloadidx8 ptr idx mem) +(FMOVSload [off] {sym} (ADDshiftLL [2] ptr idx) mem) && off == 0 && sym == nil => (FMOVSloadidx4 ptr idx mem) +(FMOVDloadidx ptr (SLLconst [3] idx) mem) => (FMOVDloadidx8 ptr idx mem) +(FMOVSloadidx ptr (SLLconst [2] idx) mem) => (FMOVSloadidx4 ptr idx mem) +(FMOVDloadidx (SLLconst [3] idx) ptr mem) => (FMOVDloadidx8 ptr idx mem) +(FMOVSloadidx (SLLconst [2] idx) ptr mem) => (FMOVSloadidx4 ptr idx mem) +(FMOVDloadidx8 ptr (MOVDconst [c]) mem) && is32Bit(c<<3) => (FMOVDload ptr [int32(c)<<3] mem) +(FMOVSloadidx4 ptr (MOVDconst [c]) mem) && is32Bit(c<<2) => (FMOVSload ptr [int32(c)<<2] mem) + (MOVBstore [off1] {sym} (ADDconst [off2] ptr) val mem) && is32Bit(int64(off1)+off2) && (ptr.Op != OpSB || !config.ctxt.Flag_shared) => (MOVBstore [off1+int32(off2)] {sym} ptr val mem) @@ -865,6 +876,15 @@ (MOVWstoreidx4 ptr (MOVDconst [c]) val mem) && is32Bit(c<<2) => (MOVWstore [int32(c)<<2] ptr val mem) (MOVHstoreidx2 ptr (MOVDconst [c]) val mem) && is32Bit(c<<1) => (MOVHstore [int32(c)<<1] ptr val mem) +(FMOVDstore [off] {sym} (ADDshiftLL [3] ptr idx) val mem) && off == 0 && sym == nil => (FMOVDstoreidx8 ptr idx val mem) +(FMOVSstore [off] {sym} (ADDshiftLL [2] ptr idx) val mem) && off == 0 && sym == nil => (FMOVSstoreidx4 ptr idx val mem) +(FMOVDstoreidx ptr (SLLconst [3] idx) val mem) => (FMOVDstoreidx8 ptr idx val mem) +(FMOVSstoreidx ptr (SLLconst [2] idx) val mem) => (FMOVSstoreidx4 ptr idx val mem) +(FMOVDstoreidx (SLLconst [3] idx) ptr val mem) => (FMOVDstoreidx8 ptr idx val mem) +(FMOVSstoreidx (SLLconst [2] idx) ptr val mem) => (FMOVSstoreidx4 ptr idx val mem) +(FMOVDstoreidx8 ptr (MOVDconst [c]) val mem) && is32Bit(c<<3) => (FMOVDstore [int32(c)<<3] ptr val mem) +(FMOVSstoreidx4 ptr (MOVDconst [c]) val mem) && is32Bit(c<<2) => (FMOVSstore [int32(c)<<2] ptr val mem) + (MOVBload [off1] {sym1} (MOVDaddr [off2] {sym2} ptr) mem) && canMergeSym(sym1,sym2) && is32Bit(int64(off1)+int64(off2)) && (ptr.Op != OpSB || !config.ctxt.Flag_shared) => @@ -1127,6 +1147,10 @@ // MOVDnop doesn't emit instruction, only for ensuring the type. (MOVDreg x) && x.Uses == 1 => (MOVDnop x) +// TODO: we should be able to get rid of MOVDnop all together. +// But for now, this is enough to get rid of lots of them. +(MOVDnop (MOVDconst [c])) => (MOVDconst [c]) + // fold constant into arithmatic ops (ADD x (MOVDconst [c])) => (ADDconst [c] x) (SUB x (MOVDconst [c])) => (SUBconst [c] x) @@ -1151,7 +1175,7 @@ (CMPW (MOVDconst [c]) x) => (InvertFlags (CMPWconst [int32(c)] x)) // Canonicalize the order of arguments to comparisons - helps with CSE. -((CMP|CMPW) x y) && x.ID > y.ID => (InvertFlags ((CMP|CMPW) y x)) +((CMP|CMPW) x y) && canonLessThan(x,y) => (InvertFlags ((CMP|CMPW) y x)) // mul-neg => mneg (NEG (MUL x y)) => (MNEG x y) @@ -1335,8 +1359,18 @@ (XOR x (MVN y)) => (EON x y) (OR x (MVN y)) => (ORN x y) (MVN (XOR x y)) => (EON x y) + +(CSEL [cc] (MOVDconst [-1]) (MOVDconst [0]) flag) => (CSETM [cc] flag) +(CSEL [cc] (MOVDconst [0]) (MOVDconst [-1]) flag) => (CSETM [arm64Negate(cc)] flag) (CSEL [cc] x (MOVDconst [0]) flag) => (CSEL0 [cc] x flag) (CSEL [cc] (MOVDconst [0]) y flag) => (CSEL0 [arm64Negate(cc)] y flag) +(CSEL [cc] x (ADDconst [1] a) flag) => (CSINC [cc] x a flag) +(CSEL [cc] (ADDconst [1] a) x flag) => (CSINC [arm64Negate(cc)] x a flag) +(CSEL [cc] x (MVN a) flag) => (CSINV [cc] x a flag) +(CSEL [cc] (MVN a) x flag) => (CSINV [arm64Negate(cc)] x a flag) +(CSEL [cc] x (NEG a) flag) => (CSNEG [cc] x a flag) +(CSEL [cc] (NEG a) x flag) => (CSNEG [arm64Negate(cc)] x a flag) + (SUB x (SUB y z)) => (SUB (ADD x z) y) (SUB (SUB x y) z) => (SUB x (ADD y z)) @@ -1491,9 +1525,13 @@ (LEnoov (InvertFlags cmp) yes no) => (GEnoov cmp yes no) (GTnoov (InvertFlags cmp) yes no) => (LTnoov cmp yes no) -// absorb InvertFlags into CSEL(0) +// absorb InvertFlags into conditional instructions (CSEL [cc] x y (InvertFlags cmp)) => (CSEL [arm64Invert(cc)] x y cmp) (CSEL0 [cc] x (InvertFlags cmp)) => (CSEL0 [arm64Invert(cc)] x cmp) +(CSETM [cc] (InvertFlags cmp)) => (CSETM [arm64Invert(cc)] cmp) +(CSINC [cc] x y (InvertFlags cmp)) => (CSINC [arm64Invert(cc)] x y cmp) +(CSINV [cc] x y (InvertFlags cmp)) => (CSINV [arm64Invert(cc)] x y cmp) +(CSNEG [cc] x y (InvertFlags cmp)) => (CSNEG [arm64Invert(cc)] x y cmp) // absorb flag constants into boolean values (Equal (FlagConstant [fc])) => (MOVDconst [b2i(fc.eq())]) @@ -1532,6 +1570,14 @@ (CSEL [cc] _ y flag) && ccARM64Eval(cc, flag) < 0 => y (CSEL0 [cc] x flag) && ccARM64Eval(cc, flag) > 0 => x (CSEL0 [cc] _ flag) && ccARM64Eval(cc, flag) < 0 => (MOVDconst [0]) +(CSNEG [cc] x _ flag) && ccARM64Eval(cc, flag) > 0 => x +(CSNEG [cc] _ y flag) && ccARM64Eval(cc, flag) < 0 => (NEG y) +(CSINV [cc] x _ flag) && ccARM64Eval(cc, flag) > 0 => x +(CSINV [cc] _ y flag) && ccARM64Eval(cc, flag) < 0 => (Not y) +(CSINC [cc] x _ flag) && ccARM64Eval(cc, flag) > 0 => x +(CSINC [cc] _ y flag) && ccARM64Eval(cc, flag) < 0 => (ADDconst [1] y) +(CSETM [cc] flag) && ccARM64Eval(cc, flag) > 0 => (MOVDconst [-1]) +(CSETM [cc] flag) && ccARM64Eval(cc, flag) < 0 => (MOVDconst [0]) // absorb flags back into boolean CSEL (CSEL [cc] x y (CMPWconst [0] boolval)) && cc == OpARM64NotEqual && flagArg(boolval) != nil => @@ -1724,9 +1770,25 @@ (CMPconst [64] (SUB (MOVDconst [32]) (ANDconst [31] y))))) && cc == OpARM64LessThanU => (RORW x y) +// rev16w | rev16 // ((x>>8) | (x<<8)) => (REV16W x), the type of x is uint16, "|" can also be "^" or "+". ((ADDshiftLL|ORshiftLL|XORshiftLL) [8] (UBFX [armBFAuxInt(8, 8)] x) x) => (REV16W x) +// ((x & 0xff00ff00)>>8) | ((x & 0x00ff00ff)<<8), "|" can also be "^" or "+". +((ADDshiftLL|ORshiftLL|XORshiftLL) [8] (UBFX [armBFAuxInt(8, 24)] (ANDconst [c1] x)) (ANDconst [c2] x)) + && uint32(c1) == 0xff00ff00 && uint32(c2) == 0x00ff00ff + => (REV16W x) + +// ((x & 0xff00ff00ff00ff00)>>8) | ((x & 0x00ff00ff00ff00ff)<<8), "|" can also be "^" or "+". +((ADDshiftLL|ORshiftLL|XORshiftLL) [8] (SRLconst [8] (ANDconst [c1] x)) (ANDconst [c2] x)) + && (uint64(c1) == 0xff00ff00ff00ff00 && uint64(c2) == 0x00ff00ff00ff00ff) + => (REV16 x) + +// ((x & 0xff00ff00)>>8) | ((x & 0x00ff00ff)<<8), "|" can also be "^" or "+". +((ADDshiftLL|ORshiftLL|XORshiftLL) [8] (SRLconst [8] (ANDconst [c1] x)) (ANDconst [c2] x)) + && (uint64(c1) == 0xff00ff00 && uint64(c2) == 0x00ff00ff) + => (REV16 (ANDconst [0xffffffff] x)) + // Extract from reg pair (ADDshiftLL [c] (SRLconst x [64-c]) x2) => (EXTRconst [64-c] x2 x) ( ORshiftLL [c] (SRLconst x [64-c]) x2) => (EXTRconst [64-c] x2 x) @@ -1747,6 +1809,16 @@ // Special case setting bit as 1. An example is math.Copysign(c,-1) (ORconst [c1] (ANDconst [c2] x)) && c2|c1 == ^0 => (ORconst [c1] x) +// If the shift amount is larger than the datasize(32, 16, 8), we can optimize to constant 0. +(MOVWUreg (SLLconst [lc] x)) && lc >= 32 => (MOVDconst [0]) +(MOVHUreg (SLLconst [lc] x)) && lc >= 16 => (MOVDconst [0]) +(MOVBUreg (SLLconst [lc] x)) && lc >= 8 => (MOVDconst [0]) + +// After zero extension, the upper (64-datasize(32|16|8)) bits are zero, we can optimiza to constant 0. +(SRLconst [rc] (MOVWUreg x)) && rc >= 32 => (MOVDconst [0]) +(SRLconst [rc] (MOVHUreg x)) && rc >= 16 => (MOVDconst [0]) +(SRLconst [rc] (MOVBUreg x)) && rc >= 8 => (MOVDconst [0]) + // bitfield ops // sbfiz @@ -2787,3 +2859,12 @@ (MOVHUload [off] {sym} (SB) _) && symIsRO(sym) => (MOVDconst [int64(read16(sym, int64(off), config.ctxt.Arch.ByteOrder))]) (MOVWUload [off] {sym} (SB) _) && symIsRO(sym) => (MOVDconst [int64(read32(sym, int64(off), config.ctxt.Arch.ByteOrder))]) (MOVDload [off] {sym} (SB) _) && symIsRO(sym) => (MOVDconst [int64(read64(sym, int64(off), config.ctxt.Arch.ByteOrder))]) + +// Arch-specific inlining for small or disjoint runtime.memmove +(SelectN [0] call:(CALLstatic {sym} s1:(MOVDstore _ (MOVDconst [sz]) s2:(MOVDstore _ src s3:(MOVDstore {t} _ dst mem))))) + && sz >= 0 + && isSameCall(sym, "runtime.memmove") + && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 + && isInlinableMemmove(dst, src, sz, config) + && clobber(s1, s2, s3, call) + => (Move [sz] dst src mem) diff --git a/src/cmd/compile/internal/ssa/gen/ARM64Ops.go b/src/cmd/compile/internal/ssa/gen/ARM64Ops.go index b0bc9c78ff6ca2dc8c42a4209b8844254062226d..18a5666b40f0dfcce916f12efb228ee6b2ec1100 100644 --- a/src/cmd/compile/internal/ssa/gen/ARM64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/ARM64Ops.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore package main @@ -236,8 +237,10 @@ func init() { {name: "FNEGS", argLength: 1, reg: fp11, asm: "FNEGS"}, // -arg0, float32 {name: "FNEGD", argLength: 1, reg: fp11, asm: "FNEGD"}, // -arg0, float64 {name: "FSQRTD", argLength: 1, reg: fp11, asm: "FSQRTD"}, // sqrt(arg0), float64 + {name: "FSQRTS", argLength: 1, reg: fp11, asm: "FSQRTS"}, // sqrt(arg0), float32 {name: "REV", argLength: 1, reg: gp11, asm: "REV"}, // byte reverse, 64-bit {name: "REVW", argLength: 1, reg: gp11, asm: "REVW"}, // byte reverse, 32-bit + {name: "REV16", argLength: 1, reg: gp11, asm: "REV16"}, // byte reverse in each 16-bit halfword, 64-bit {name: "REV16W", argLength: 1, reg: gp11, asm: "REV16W"}, // byte reverse in each 16-bit halfword, 32-bit {name: "RBIT", argLength: 1, reg: gp11, asm: "RBIT"}, // bit reverse, 64-bit {name: "RBITW", argLength: 1, reg: gp11, asm: "RBITW"}, // bit reverse, 32-bit @@ -264,17 +267,17 @@ func init() { // shifts {name: "SLL", argLength: 2, reg: gp21, asm: "LSL"}, // arg0 << arg1, shift amount is mod 64 - {name: "SLLconst", argLength: 1, reg: gp11, asm: "LSL", aux: "Int64"}, // arg0 << auxInt + {name: "SLLconst", argLength: 1, reg: gp11, asm: "LSL", aux: "Int64"}, // arg0 << auxInt, auxInt should be in the range 0 to 63. {name: "SRL", argLength: 2, reg: gp21, asm: "LSR"}, // arg0 >> arg1, unsigned, shift amount is mod 64 - {name: "SRLconst", argLength: 1, reg: gp11, asm: "LSR", aux: "Int64"}, // arg0 >> auxInt, unsigned + {name: "SRLconst", argLength: 1, reg: gp11, asm: "LSR", aux: "Int64"}, // arg0 >> auxInt, unsigned, auxInt should be in the range 0 to 63. {name: "SRA", argLength: 2, reg: gp21, asm: "ASR"}, // arg0 >> arg1, signed, shift amount is mod 64 - {name: "SRAconst", argLength: 1, reg: gp11, asm: "ASR", aux: "Int64"}, // arg0 >> auxInt, signed + {name: "SRAconst", argLength: 1, reg: gp11, asm: "ASR", aux: "Int64"}, // arg0 >> auxInt, signed, auxInt should be in the range 0 to 63. {name: "ROR", argLength: 2, reg: gp21, asm: "ROR"}, // arg0 right rotate by (arg1 mod 64) bits {name: "RORW", argLength: 2, reg: gp21, asm: "RORW"}, // arg0 right rotate by (arg1 mod 32) bits - {name: "RORconst", argLength: 1, reg: gp11, asm: "ROR", aux: "Int64"}, // arg0 right rotate by auxInt bits - {name: "RORWconst", argLength: 1, reg: gp11, asm: "RORW", aux: "Int64"}, // uint32(arg0) right rotate by auxInt bits - {name: "EXTRconst", argLength: 2, reg: gp21, asm: "EXTR", aux: "Int64"}, // extract 64 bits from arg0:arg1 starting at lsb auxInt - {name: "EXTRWconst", argLength: 2, reg: gp21, asm: "EXTRW", aux: "Int64"}, // extract 32 bits from arg0[31:0]:arg1[31:0] starting at lsb auxInt and zero top 32 bits + {name: "RORconst", argLength: 1, reg: gp11, asm: "ROR", aux: "Int64"}, // arg0 right rotate by auxInt bits, auxInt should be in the range 0 to 63. + {name: "RORWconst", argLength: 1, reg: gp11, asm: "RORW", aux: "Int64"}, // uint32(arg0) right rotate by auxInt bits, auxInt should be in the range 0 to 31. + {name: "EXTRconst", argLength: 2, reg: gp21, asm: "EXTR", aux: "Int64"}, // extract 64 bits from arg0:arg1 starting at lsb auxInt, auxInt should be in the range 0 to 63. + {name: "EXTRWconst", argLength: 2, reg: gp21, asm: "EXTRW", aux: "Int64"}, // extract 32 bits from arg0[31:0]:arg1[31:0] starting at lsb auxInt and zero top 32 bits, auxInt should be in the range 0 to 31. // comparisons {name: "CMP", argLength: 2, reg: gp2flags, asm: "CMP", typ: "Flags"}, // arg0 compare to arg1 @@ -295,45 +298,45 @@ func init() { {name: "FCMPD0", argLength: 1, reg: fp1flags, asm: "FCMPD", typ: "Flags"}, // arg0 compare to 0, float64 // shifted ops - {name: "MVNshiftLL", argLength: 1, reg: gp11, asm: "MVN", aux: "Int64"}, // ^(arg0<>auxInt), unsigned shift - {name: "MVNshiftRA", argLength: 1, reg: gp11, asm: "MVN", aux: "Int64"}, // ^(arg0>>auxInt), signed shift - {name: "NEGshiftLL", argLength: 1, reg: gp11, asm: "NEG", aux: "Int64"}, // -(arg0<>auxInt), unsigned shift - {name: "NEGshiftRA", argLength: 1, reg: gp11, asm: "NEG", aux: "Int64"}, // -(arg0>>auxInt), signed shift - {name: "ADDshiftLL", argLength: 2, reg: gp21, asm: "ADD", aux: "Int64"}, // arg0 + arg1<>auxInt, unsigned shift - {name: "ADDshiftRA", argLength: 2, reg: gp21, asm: "ADD", aux: "Int64"}, // arg0 + arg1>>auxInt, signed shift - {name: "SUBshiftLL", argLength: 2, reg: gp21, asm: "SUB", aux: "Int64"}, // arg0 - arg1<>auxInt, unsigned shift - {name: "SUBshiftRA", argLength: 2, reg: gp21, asm: "SUB", aux: "Int64"}, // arg0 - arg1>>auxInt, signed shift - {name: "ANDshiftLL", argLength: 2, reg: gp21, asm: "AND", aux: "Int64"}, // arg0 & (arg1<>auxInt), unsigned shift - {name: "ANDshiftRA", argLength: 2, reg: gp21, asm: "AND", aux: "Int64"}, // arg0 & (arg1>>auxInt), signed shift - {name: "ORshiftLL", argLength: 2, reg: gp21, asm: "ORR", aux: "Int64"}, // arg0 | arg1<>auxInt, unsigned shift - {name: "ORshiftRA", argLength: 2, reg: gp21, asm: "ORR", aux: "Int64"}, // arg0 | arg1>>auxInt, signed shift - {name: "XORshiftLL", argLength: 2, reg: gp21, asm: "EOR", aux: "Int64"}, // arg0 ^ arg1<>auxInt, unsigned shift - {name: "XORshiftRA", argLength: 2, reg: gp21, asm: "EOR", aux: "Int64"}, // arg0 ^ arg1>>auxInt, signed shift - {name: "BICshiftLL", argLength: 2, reg: gp21, asm: "BIC", aux: "Int64"}, // arg0 &^ (arg1<>auxInt), unsigned shift - {name: "BICshiftRA", argLength: 2, reg: gp21, asm: "BIC", aux: "Int64"}, // arg0 &^ (arg1>>auxInt), signed shift - {name: "EONshiftLL", argLength: 2, reg: gp21, asm: "EON", aux: "Int64"}, // arg0 ^ ^(arg1<>auxInt), unsigned shift - {name: "EONshiftRA", argLength: 2, reg: gp21, asm: "EON", aux: "Int64"}, // arg0 ^ ^(arg1>>auxInt), signed shift - {name: "ORNshiftLL", argLength: 2, reg: gp21, asm: "ORN", aux: "Int64"}, // arg0 | ^(arg1<>auxInt), unsigned shift - {name: "ORNshiftRA", argLength: 2, reg: gp21, asm: "ORN", aux: "Int64"}, // arg0 | ^(arg1>>auxInt), signed shift - {name: "CMPshiftLL", argLength: 2, reg: gp2flags, asm: "CMP", aux: "Int64", typ: "Flags"}, // arg0 compare to arg1<>auxInt, unsigned shift - {name: "CMPshiftRA", argLength: 2, reg: gp2flags, asm: "CMP", aux: "Int64", typ: "Flags"}, // arg0 compare to arg1>>auxInt, signed shift - {name: "CMNshiftLL", argLength: 2, reg: gp2flags, asm: "CMN", aux: "Int64", typ: "Flags"}, // (arg0 + arg1<>auxInt) compare to 0, unsigned shift - {name: "CMNshiftRA", argLength: 2, reg: gp2flags, asm: "CMN", aux: "Int64", typ: "Flags"}, // (arg0 + arg1>>auxInt) compare to 0, signed shift - {name: "TSTshiftLL", argLength: 2, reg: gp2flags, asm: "TST", aux: "Int64", typ: "Flags"}, // (arg0 & arg1<>auxInt) compare to 0, unsigned shift - {name: "TSTshiftRA", argLength: 2, reg: gp2flags, asm: "TST", aux: "Int64", typ: "Flags"}, // (arg0 & arg1>>auxInt) compare to 0, signed shift + {name: "MVNshiftLL", argLength: 1, reg: gp11, asm: "MVN", aux: "Int64"}, // ^(arg0<>auxInt), unsigned shift, auxInt should be in the range 0 to 63. + {name: "MVNshiftRA", argLength: 1, reg: gp11, asm: "MVN", aux: "Int64"}, // ^(arg0>>auxInt), signed shift, auxInt should be in the range 0 to 63. + {name: "NEGshiftLL", argLength: 1, reg: gp11, asm: "NEG", aux: "Int64"}, // -(arg0<>auxInt), unsigned shift, auxInt should be in the range 0 to 63. + {name: "NEGshiftRA", argLength: 1, reg: gp11, asm: "NEG", aux: "Int64"}, // -(arg0>>auxInt), signed shift, auxInt should be in the range 0 to 63. + {name: "ADDshiftLL", argLength: 2, reg: gp21, asm: "ADD", aux: "Int64"}, // arg0 + arg1<>auxInt, unsigned shift, auxInt should be in the range 0 to 63. + {name: "ADDshiftRA", argLength: 2, reg: gp21, asm: "ADD", aux: "Int64"}, // arg0 + arg1>>auxInt, signed shift, auxInt should be in the range 0 to 63. + {name: "SUBshiftLL", argLength: 2, reg: gp21, asm: "SUB", aux: "Int64"}, // arg0 - arg1<>auxInt, unsigned shift, auxInt should be in the range 0 to 63. + {name: "SUBshiftRA", argLength: 2, reg: gp21, asm: "SUB", aux: "Int64"}, // arg0 - arg1>>auxInt, signed shift, auxInt should be in the range 0 to 63. + {name: "ANDshiftLL", argLength: 2, reg: gp21, asm: "AND", aux: "Int64"}, // arg0 & (arg1<>auxInt), unsigned shift, auxInt should be in the range 0 to 63. + {name: "ANDshiftRA", argLength: 2, reg: gp21, asm: "AND", aux: "Int64"}, // arg0 & (arg1>>auxInt), signed shift, auxInt should be in the range 0 to 63. + {name: "ORshiftLL", argLength: 2, reg: gp21, asm: "ORR", aux: "Int64"}, // arg0 | arg1<>auxInt, unsigned shift, auxInt should be in the range 0 to 63. + {name: "ORshiftRA", argLength: 2, reg: gp21, asm: "ORR", aux: "Int64"}, // arg0 | arg1>>auxInt, signed shift, auxInt should be in the range 0 to 63. + {name: "XORshiftLL", argLength: 2, reg: gp21, asm: "EOR", aux: "Int64"}, // arg0 ^ arg1<>auxInt, unsigned shift, auxInt should be in the range 0 to 63. + {name: "XORshiftRA", argLength: 2, reg: gp21, asm: "EOR", aux: "Int64"}, // arg0 ^ arg1>>auxInt, signed shift, auxInt should be in the range 0 to 63. + {name: "BICshiftLL", argLength: 2, reg: gp21, asm: "BIC", aux: "Int64"}, // arg0 &^ (arg1<>auxInt), unsigned shift, auxInt should be in the range 0 to 63. + {name: "BICshiftRA", argLength: 2, reg: gp21, asm: "BIC", aux: "Int64"}, // arg0 &^ (arg1>>auxInt), signed shift, auxInt should be in the range 0 to 63. + {name: "EONshiftLL", argLength: 2, reg: gp21, asm: "EON", aux: "Int64"}, // arg0 ^ ^(arg1<>auxInt), unsigned shift, auxInt should be in the range 0 to 63. + {name: "EONshiftRA", argLength: 2, reg: gp21, asm: "EON", aux: "Int64"}, // arg0 ^ ^(arg1>>auxInt), signed shift, auxInt should be in the range 0 to 63. + {name: "ORNshiftLL", argLength: 2, reg: gp21, asm: "ORN", aux: "Int64"}, // arg0 | ^(arg1<>auxInt), unsigned shift, auxInt should be in the range 0 to 63. + {name: "ORNshiftRA", argLength: 2, reg: gp21, asm: "ORN", aux: "Int64"}, // arg0 | ^(arg1>>auxInt), signed shift, auxInt should be in the range 0 to 63. + {name: "CMPshiftLL", argLength: 2, reg: gp2flags, asm: "CMP", aux: "Int64", typ: "Flags"}, // arg0 compare to arg1<>auxInt, unsigned shift, auxInt should be in the range 0 to 63. + {name: "CMPshiftRA", argLength: 2, reg: gp2flags, asm: "CMP", aux: "Int64", typ: "Flags"}, // arg0 compare to arg1>>auxInt, signed shift, auxInt should be in the range 0 to 63. + {name: "CMNshiftLL", argLength: 2, reg: gp2flags, asm: "CMN", aux: "Int64", typ: "Flags"}, // (arg0 + arg1<>auxInt) compare to 0, unsigned shift, auxInt should be in the range 0 to 63. + {name: "CMNshiftRA", argLength: 2, reg: gp2flags, asm: "CMN", aux: "Int64", typ: "Flags"}, // (arg0 + arg1>>auxInt) compare to 0, signed shift, auxInt should be in the range 0 to 63. + {name: "TSTshiftLL", argLength: 2, reg: gp2flags, asm: "TST", aux: "Int64", typ: "Flags"}, // (arg0 & arg1<>auxInt) compare to 0, unsigned shift, auxInt should be in the range 0 to 63. + {name: "TSTshiftRA", argLength: 2, reg: gp2flags, asm: "TST", aux: "Int64", typ: "Flags"}, // (arg0 & arg1>>auxInt) compare to 0, signed shift, auxInt should be in the range 0 to 63. // bitfield ops // for all bitfield ops lsb is auxInt>>8, width is auxInt&0xff @@ -379,11 +382,13 @@ func init() { {name: "FMOVDloadidx", argLength: 3, reg: fp2load, asm: "FMOVD", typ: "Float64"}, // load 64-bit float from arg0 + arg1, arg2=mem. // shifted register indexed load - {name: "MOVHloadidx2", argLength: 3, reg: gp2load, asm: "MOVH", typ: "Int16"}, // load 16-bit half-word from arg0 + arg1*2, sign-extended to 64-bit, arg2=mem. - {name: "MOVHUloadidx2", argLength: 3, reg: gp2load, asm: "MOVHU", typ: "UInt16"}, // load 16-bit half-word from arg0 + arg1*2, zero-extended to 64-bit, arg2=mem. - {name: "MOVWloadidx4", argLength: 3, reg: gp2load, asm: "MOVW", typ: "Int32"}, // load 32-bit word from arg0 + arg1*4, sign-extended to 64-bit, arg2=mem. - {name: "MOVWUloadidx4", argLength: 3, reg: gp2load, asm: "MOVWU", typ: "UInt32"}, // load 32-bit word from arg0 + arg1*4, zero-extended to 64-bit, arg2=mem. - {name: "MOVDloadidx8", argLength: 3, reg: gp2load, asm: "MOVD", typ: "UInt64"}, // load 64-bit double-word from arg0 + arg1*8, arg2 = mem. + {name: "MOVHloadidx2", argLength: 3, reg: gp2load, asm: "MOVH", typ: "Int16"}, // load 16-bit half-word from arg0 + arg1*2, sign-extended to 64-bit, arg2=mem. + {name: "MOVHUloadidx2", argLength: 3, reg: gp2load, asm: "MOVHU", typ: "UInt16"}, // load 16-bit half-word from arg0 + arg1*2, zero-extended to 64-bit, arg2=mem. + {name: "MOVWloadidx4", argLength: 3, reg: gp2load, asm: "MOVW", typ: "Int32"}, // load 32-bit word from arg0 + arg1*4, sign-extended to 64-bit, arg2=mem. + {name: "MOVWUloadidx4", argLength: 3, reg: gp2load, asm: "MOVWU", typ: "UInt32"}, // load 32-bit word from arg0 + arg1*4, zero-extended to 64-bit, arg2=mem. + {name: "MOVDloadidx8", argLength: 3, reg: gp2load, asm: "MOVD", typ: "UInt64"}, // load 64-bit double-word from arg0 + arg1*8, arg2 = mem. + {name: "FMOVSloadidx4", argLength: 3, reg: fp2load, asm: "FMOVS", typ: "Float32"}, // load 32-bit float from arg0 + arg1*4, arg2 = mem. + {name: "FMOVDloadidx8", argLength: 3, reg: fp2load, asm: "FMOVD", typ: "Float64"}, // load 64-bit float from arg0 + arg1*8, arg2 = mem. {name: "MOVBstore", argLength: 3, reg: gpstore, aux: "SymOff", asm: "MOVB", typ: "Mem", faultOnNilArg0: true, symEffect: "Write"}, // store 1 byte of arg1 to arg0 + auxInt + aux. arg2=mem. {name: "MOVHstore", argLength: 3, reg: gpstore, aux: "SymOff", asm: "MOVH", typ: "Mem", faultOnNilArg0: true, symEffect: "Write"}, // store 2 bytes of arg1 to arg0 + auxInt + aux. arg2=mem. @@ -402,9 +407,11 @@ func init() { {name: "FMOVDstoreidx", argLength: 4, reg: fpstore2, asm: "FMOVD", typ: "Mem"}, // store 64-bit float of arg2 to arg0 + arg1, arg3=mem. // shifted register indexed store - {name: "MOVHstoreidx2", argLength: 4, reg: gpstore2, asm: "MOVH", typ: "Mem"}, // store 2 bytes of arg2 to arg0 + arg1*2, arg3 = mem. - {name: "MOVWstoreidx4", argLength: 4, reg: gpstore2, asm: "MOVW", typ: "Mem"}, // store 4 bytes of arg2 to arg0 + arg1*4, arg3 = mem. - {name: "MOVDstoreidx8", argLength: 4, reg: gpstore2, asm: "MOVD", typ: "Mem"}, // store 8 bytes of arg2 to arg0 + arg1*8, arg3 = mem. + {name: "MOVHstoreidx2", argLength: 4, reg: gpstore2, asm: "MOVH", typ: "Mem"}, // store 2 bytes of arg2 to arg0 + arg1*2, arg3 = mem. + {name: "MOVWstoreidx4", argLength: 4, reg: gpstore2, asm: "MOVW", typ: "Mem"}, // store 4 bytes of arg2 to arg0 + arg1*4, arg3 = mem. + {name: "MOVDstoreidx8", argLength: 4, reg: gpstore2, asm: "MOVD", typ: "Mem"}, // store 8 bytes of arg2 to arg0 + arg1*8, arg3 = mem. + {name: "FMOVSstoreidx4", argLength: 4, reg: fpstore2, asm: "FMOVS", typ: "Mem"}, // store 32-bit float of arg2 to arg0 + arg1*4, arg3=mem. + {name: "FMOVDstoreidx8", argLength: 4, reg: fpstore2, asm: "FMOVD", typ: "Mem"}, // store 64-bit float of arg2 to arg0 + arg1*8, arg3=mem. {name: "MOVBstorezero", argLength: 2, reg: gpstore0, aux: "SymOff", asm: "MOVB", typ: "Mem", faultOnNilArg0: true, symEffect: "Write"}, // store 1 byte of zero to arg0 + auxInt + aux. arg1=mem. {name: "MOVHstorezero", argLength: 2, reg: gpstore0, aux: "SymOff", asm: "MOVH", typ: "Mem", faultOnNilArg0: true, symEffect: "Write"}, // store 2 bytes of zero to arg0 + auxInt + aux. arg1=mem. @@ -467,8 +474,12 @@ func init() { // conditional instructions; auxint is // one of the arm64 comparison pseudo-ops (LessThan, LessThanU, etc.) - {name: "CSEL", argLength: 3, reg: gp2flags1, asm: "CSEL", aux: "CCop"}, // auxint(flags) ? arg0 : arg1 - {name: "CSEL0", argLength: 2, reg: gp1flags1, asm: "CSEL", aux: "CCop"}, // auxint(flags) ? arg0 : 0 + {name: "CSEL", argLength: 3, reg: gp2flags1, asm: "CSEL", aux: "CCop"}, // auxint(flags) ? arg0 : arg1 + {name: "CSEL0", argLength: 2, reg: gp1flags1, asm: "CSEL", aux: "CCop"}, // auxint(flags) ? arg0 : 0 + {name: "CSINC", argLength: 3, reg: gp2flags1, asm: "CSINC", aux: "CCop"}, // auxint(flags) ? arg0 : arg1 + 1 + {name: "CSINV", argLength: 3, reg: gp2flags1, asm: "CSINV", aux: "CCop"}, // auxint(flags) ? arg0 : ^arg1 + {name: "CSNEG", argLength: 3, reg: gp2flags1, asm: "CSNEG", aux: "CCop"}, // auxint(flags) ? arg0 : -arg1 + {name: "CSETM", argLength: 1, reg: readflags, asm: "CSETM", aux: "CCop"}, // auxint(flags) ? -1 : 0 // function calls {name: "CALLstatic", argLength: 1, reg: regInfo{clobbers: callerSave}, aux: "CallOff", clobberFlags: true, call: true}, // call static function aux.(*obj.LSym). arg0=mem, auxint=argsize, returns mem @@ -502,13 +513,14 @@ func init() { // auxint = offset into duffzero code to start executing // returns mem // R20 changed as side effect + // R16 and R17 may be clobbered by linker trampoline. { name: "DUFFZERO", aux: "Int64", argLength: 2, reg: regInfo{ inputs: []regMask{buildReg("R20")}, - clobbers: buildReg("R20 R30"), + clobbers: buildReg("R16 R17 R20 R30"), }, faultOnNilArg0: true, unsafePoint: true, // FP maintenance around DUFFZERO can be clobbered by interrupts @@ -542,13 +554,14 @@ func init() { // auxint = offset into duffcopy code to start executing // returns mem // R20, R21 changed as side effect + // R16 and R17 may be clobbered by linker trampoline. { name: "DUFFCOPY", aux: "Int64", argLength: 3, reg: regInfo{ inputs: []regMask{buildReg("R21"), buildReg("R20")}, - clobbers: buildReg("R20 R21 R26 R30"), + clobbers: buildReg("R16 R17 R20 R21 R26 R30"), }, faultOnNilArg0: true, faultOnNilArg1: true, @@ -707,7 +720,8 @@ func init() { // LoweredWB invokes runtime.gcWriteBarrier. arg0=destptr, arg1=srcptr, arg2=mem, aux=runtime.gcWriteBarrier // It saves all GP registers if necessary, // but clobbers R30 (LR) because it's a call. - {name: "LoweredWB", argLength: 3, reg: regInfo{inputs: []regMask{buildReg("R2"), buildReg("R3")}, clobbers: (callerSave &^ gpg) | buildReg("R30")}, clobberFlags: true, aux: "Sym", symEffect: "None"}, + // R16 and R17 may be clobbered by linker trampoline. + {name: "LoweredWB", argLength: 3, reg: regInfo{inputs: []regMask{buildReg("R2"), buildReg("R3")}, clobbers: (callerSave &^ gpg) | buildReg("R16 R17 R30")}, clobberFlags: true, aux: "Sym", symEffect: "None"}, // There are three of these functions so that they can have three different register inputs. // When we check 0 <= c <= cap (A), then 0 <= b <= c (B), then 0 <= a <= b (C), we want the diff --git a/src/cmd/compile/internal/ssa/gen/ARMOps.go b/src/cmd/compile/internal/ssa/gen/ARMOps.go index 70c789937aa9606fd8e5719acaf07cb0c75c6a5e..d1f86039a36e5c98ab7d6681928d67ea50e66302 100644 --- a/src/cmd/compile/internal/ssa/gen/ARMOps.go +++ b/src/cmd/compile/internal/ssa/gen/ARMOps.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore package main @@ -156,7 +157,7 @@ func init() { reg: regInfo{ inputs: []regMask{buildReg("R1"), buildReg("R0")}, outputs: []regMask{buildReg("R0"), buildReg("R1")}, - clobbers: buildReg("R2 R3 R14"), + clobbers: buildReg("R2 R3 R12 R14"), // R14 is LR, R12 is linker trampoline scratch register }, clobberFlags: true, typ: "(UInt32,UInt32)", @@ -217,6 +218,7 @@ func init() { {name: "NEGF", argLength: 1, reg: fp11, asm: "NEGF"}, // -arg0, float32 {name: "NEGD", argLength: 1, reg: fp11, asm: "NEGD"}, // -arg0, float64 {name: "SQRTD", argLength: 1, reg: fp11, asm: "SQRTD"}, // sqrt(arg0), float64 + {name: "SQRTF", argLength: 1, reg: fp11, asm: "SQRTF"}, // sqrt(arg0), float32 {name: "ABSD", argLength: 1, reg: fp11, asm: "ABSD"}, // abs(arg0), float64 {name: "CLZ", argLength: 1, reg: gp11, asm: "CLZ"}, // count leading zero @@ -458,7 +460,7 @@ func init() { argLength: 3, reg: regInfo{ inputs: []regMask{buildReg("R1"), buildReg("R0")}, - clobbers: buildReg("R1 R14"), + clobbers: buildReg("R1 R12 R14"), // R14 is LR, R12 is linker trampoline scratch register }, faultOnNilArg0: true, }, @@ -475,7 +477,7 @@ func init() { argLength: 3, reg: regInfo{ inputs: []regMask{buildReg("R2"), buildReg("R1")}, - clobbers: buildReg("R0 R1 R2 R14"), + clobbers: buildReg("R0 R1 R2 R12 R14"), // R14 is LR, R12 is linker trampoline scratch register }, faultOnNilArg0: true, faultOnNilArg1: true, @@ -563,8 +565,8 @@ func init() { // LoweredWB invokes runtime.gcWriteBarrier. arg0=destptr, arg1=srcptr, arg2=mem, aux=runtime.gcWriteBarrier // It saves all GP registers if necessary, - // but clobbers R14 (LR) because it's a call. - {name: "LoweredWB", argLength: 3, reg: regInfo{inputs: []regMask{buildReg("R2"), buildReg("R3")}, clobbers: (callerSave &^ gpg) | buildReg("R14")}, clobberFlags: true, aux: "Sym", symEffect: "None"}, + // but clobbers R14 (LR) because it's a call, and R12 which is linker trampoline scratch register. + {name: "LoweredWB", argLength: 3, reg: regInfo{inputs: []regMask{buildReg("R2"), buildReg("R3")}, clobbers: (callerSave &^ gpg) | buildReg("R12 R14")}, clobberFlags: true, aux: "Sym", symEffect: "None"}, } blocks := []blockData{ diff --git a/src/cmd/compile/internal/ssa/gen/MIPS.rules b/src/cmd/compile/internal/ssa/gen/MIPS.rules index 8ad2c90ac33c59c6d9ff7e1a9fd8ca9a19babdf9..4ac9668ea9cb566154a506ba1c3c0ecac7a144e7 100644 --- a/src/cmd/compile/internal/ssa/gen/MIPS.rules +++ b/src/cmd/compile/internal/ssa/gen/MIPS.rules @@ -121,6 +121,7 @@ (Com(32|16|8) x) => (NORconst [0] x) (Sqrt ...) => (SQRTD ...) +(Sqrt32 ...) => (SQRTF ...) // TODO: optimize this case? (Ctz32NonZero ...) => (Ctz32 ...) @@ -143,7 +144,7 @@ (Const(32|16|8) [val]) => (MOVWconst [int32(val)]) (Const(32|64)F ...) => (MOV(F|D)const ...) (ConstNil) => (MOVWconst [0]) -(ConstBool [b]) => (MOVWconst [b2i32(b)]) +(ConstBool [t]) => (MOVWconst [b2i32(t)]) // truncations // Because we ignore high parts of registers, truncates are just copies. @@ -559,6 +560,10 @@ // MOVWnop doesn't emit instruction, only for ensuring the type. (MOVWreg x) && x.Uses == 1 => (MOVWnop x) +// TODO: we should be able to get rid of MOVWnop all together. +// But for now, this is enough to get rid of lots of them. +(MOVWnop (MOVWconst [c])) => (MOVWconst [c]) + // fold constant into arithmatic ops (ADD x (MOVWconst [c])) => (ADDconst [c] x) (SUB x (MOVWconst [c])) => (SUBconst [c] x) diff --git a/src/cmd/compile/internal/ssa/gen/MIPS64.rules b/src/cmd/compile/internal/ssa/gen/MIPS64.rules index 088c9b1ac44c148ecd910e2d2b5e4aaf4e12efd5..fd04a6c3a85c7ae768fa36d3a4a9bafb65b53012 100644 --- a/src/cmd/compile/internal/ssa/gen/MIPS64.rules +++ b/src/cmd/compile/internal/ssa/gen/MIPS64.rules @@ -121,6 +121,7 @@ (Com(64|32|16|8) x) => (NOR (MOVVconst [0]) x) (Sqrt ...) => (SQRTD ...) +(Sqrt32 ...) => (SQRTF ...) // boolean ops -- booleans are represented with 0=false, 1=true (AndB ...) => (AND ...) @@ -133,7 +134,7 @@ (Const(64|32|16|8) [val]) => (MOVVconst [int64(val)]) (Const(32|64)F [val]) => (MOV(F|D)const [float64(val)]) (ConstNil) => (MOVVconst [0]) -(ConstBool [b]) => (MOVVconst [int64(b2i(b))]) +(ConstBool [t]) => (MOVVconst [int64(b2i(t))]) (Slicemask x) => (SRAVconst (NEGV x) [63]) @@ -558,6 +559,10 @@ // MOVVnop doesn't emit instruction, only for ensuring the type. (MOVVreg x) && x.Uses == 1 => (MOVVnop x) +// TODO: we should be able to get rid of MOVVnop all together. +// But for now, this is enough to get rid of lots of them. +(MOVVnop (MOVVconst [c])) => (MOVVconst [c]) + // fold constant into arithmatic ops (ADDV x (MOVVconst [c])) && is32Bit(c) => (ADDVconst [c] x) (SUBV x (MOVVconst [c])) && is32Bit(c) => (SUBVconst [c] x) @@ -676,3 +681,9 @@ (GTZ (MOVVconst [c]) yes no) && c <= 0 => (First no yes) (GEZ (MOVVconst [c]) yes no) && c >= 0 => (First yes no) (GEZ (MOVVconst [c]) yes no) && c < 0 => (First no yes) + +// fold readonly sym load +(MOVBload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(read8(sym, int64(off)))]) +(MOVHload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(read16(sym, int64(off), config.ctxt.Arch.ByteOrder))]) +(MOVWload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(read32(sym, int64(off), config.ctxt.Arch.ByteOrder))]) +(MOVVload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(read64(sym, int64(off), config.ctxt.Arch.ByteOrder))]) diff --git a/src/cmd/compile/internal/ssa/gen/MIPS64Ops.go b/src/cmd/compile/internal/ssa/gen/MIPS64Ops.go index e1e393350262c875310992fcf15c7ef44746092f..77f251c0d3f920ed3c1e43fefe781e5257b3748e 100644 --- a/src/cmd/compile/internal/ssa/gen/MIPS64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/MIPS64Ops.go @@ -199,6 +199,7 @@ func init() { {name: "NEGF", argLength: 1, reg: fp11, asm: "NEGF"}, // -arg0, float32 {name: "NEGD", argLength: 1, reg: fp11, asm: "NEGD"}, // -arg0, float64 {name: "SQRTD", argLength: 1, reg: fp11, asm: "SQRTD"}, // sqrt(arg0), float64 + {name: "SQRTF", argLength: 1, reg: fp11, asm: "SQRTF"}, // sqrt(arg0), float32 // shifts {name: "SLLV", argLength: 2, reg: gp21, asm: "SLLV"}, // arg0 << arg1, shift amount is mod 64 diff --git a/src/cmd/compile/internal/ssa/gen/MIPSOps.go b/src/cmd/compile/internal/ssa/gen/MIPSOps.go index 75ab99ea26c254be35710518fc14b280a64227d6..b92e8cb9f1ee64b38db0a24a78cc2058ddb7b0da 100644 --- a/src/cmd/compile/internal/ssa/gen/MIPSOps.go +++ b/src/cmd/compile/internal/ssa/gen/MIPSOps.go @@ -182,6 +182,7 @@ func init() { {name: "NEGF", argLength: 1, reg: fp11, asm: "NEGF"}, // -arg0, float32 {name: "NEGD", argLength: 1, reg: fp11, asm: "NEGD"}, // -arg0, float64 {name: "SQRTD", argLength: 1, reg: fp11, asm: "SQRTD"}, // sqrt(arg0), float64 + {name: "SQRTF", argLength: 1, reg: fp11, asm: "SQRTF"}, // sqrt(arg0), float32 // shifts {name: "SLL", argLength: 2, reg: gp21, asm: "SLL"}, // arg0 << arg1, shift amount is mod 32 diff --git a/src/cmd/compile/internal/ssa/gen/PPC64.rules b/src/cmd/compile/internal/ssa/gen/PPC64.rules index c06404617297a5877b81d186811c43e37ff775e2..ce4b324b5e10227bef0406b69f33b2bdef3652c9 100644 --- a/src/cmd/compile/internal/ssa/gen/PPC64.rules +++ b/src/cmd/compile/internal/ssa/gen/PPC64.rules @@ -12,20 +12,20 @@ (Sub64F ...) => (FSUB ...) // Combine 64 bit integer multiply and adds -(ADD l:(MULLD x y) z) && objabi.GOPPC64 >= 9 && l.Uses == 1 && clobber(l) => (MADDLD x y z) +(ADD l:(MULLD x y) z) && buildcfg.GOPPC64 >= 9 && l.Uses == 1 && clobber(l) => (MADDLD x y z) (Mod16 x y) => (Mod32 (SignExt16to32 x) (SignExt16to32 y)) (Mod16u x y) => (Mod32u (ZeroExt16to32 x) (ZeroExt16to32 y)) (Mod8 x y) => (Mod32 (SignExt8to32 x) (SignExt8to32 y)) (Mod8u x y) => (Mod32u (ZeroExt8to32 x) (ZeroExt8to32 y)) -(Mod64 x y) && objabi.GOPPC64 >=9 => (MODSD x y) -(Mod64 x y) && objabi.GOPPC64 <=8 => (SUB x (MULLD y (DIVD x y))) -(Mod64u x y) && objabi.GOPPC64 >= 9 => (MODUD x y) -(Mod64u x y) && objabi.GOPPC64 <= 8 => (SUB x (MULLD y (DIVDU x y))) -(Mod32 x y) && objabi.GOPPC64 >= 9 => (MODSW x y) -(Mod32 x y) && objabi.GOPPC64 <= 8 => (SUB x (MULLW y (DIVW x y))) -(Mod32u x y) && objabi.GOPPC64 >= 9 => (MODUW x y) -(Mod32u x y) && objabi.GOPPC64 <= 8 => (SUB x (MULLW y (DIVWU x y))) +(Mod64 x y) && buildcfg.GOPPC64 >=9 => (MODSD x y) +(Mod64 x y) && buildcfg.GOPPC64 <=8 => (SUB x (MULLD y (DIVD x y))) +(Mod64u x y) && buildcfg.GOPPC64 >= 9 => (MODUD x y) +(Mod64u x y) && buildcfg.GOPPC64 <= 8 => (SUB x (MULLD y (DIVDU x y))) +(Mod32 x y) && buildcfg.GOPPC64 >= 9 => (MODSW x y) +(Mod32 x y) && buildcfg.GOPPC64 <= 8 => (SUB x (MULLW y (DIVW x y))) +(Mod32u x y) && buildcfg.GOPPC64 >= 9 => (MODUW x y) +(Mod32u x y) && buildcfg.GOPPC64 <= 8 => (SUB x (MULLW y (DIVWU x y))) // (x + y) / 2 with x>=y => (x - y) / 2 + y (Avg64u x y) => (ADD (SRDconst (SUB x y) [1]) y) @@ -71,6 +71,7 @@ (Round(32|64)F ...) => (LoweredRound(32|64)F ...) (Sqrt ...) => (FSQRT ...) +(Sqrt32 ...) => (FSQRTS ...) (Floor ...) => (FFLOOR ...) (Ceil ...) => (FCEIL ...) (Trunc ...) => (FTRUNC ...) @@ -100,7 +101,7 @@ (Const(64|32|16|8) [val]) => (MOVDconst [int64(val)]) (Const(32|64)F ...) => (FMOV(S|D)const ...) (ConstNil) => (MOVDconst [0]) -(ConstBool [b]) => (MOVDconst [b2i(b)]) +(ConstBool [t]) => (MOVDconst [b2i(t)]) // Constant folding (FABS (FMOVDconst [x])) => (FMOVDconst [math.Abs(x)]) @@ -350,9 +351,9 @@ (Ctz32NonZero ...) => (Ctz32 ...) (Ctz64NonZero ...) => (Ctz64 ...) -(Ctz64 x) && objabi.GOPPC64<=8 => (POPCNTD (ANDN (ADDconst [-1] x) x)) +(Ctz64 x) && buildcfg.GOPPC64<=8 => (POPCNTD (ANDN (ADDconst [-1] x) x)) (Ctz64 x) => (CNTTZD x) -(Ctz32 x) && objabi.GOPPC64<=8 => (POPCNTW (MOVWZreg (ANDN (ADDconst [-1] x) x))) +(Ctz32 x) && buildcfg.GOPPC64<=8 => (POPCNTW (MOVWZreg (ANDN (ADDconst [-1] x) x))) (Ctz32 x) => (CNTTZW (MOVWZreg x)) (Ctz16 x) => (POPCNTW (MOVHZreg (ANDN (ADDconst [-1] x) x))) (Ctz8 x) => (POPCNTB (MOVBZreg (ANDN (ADDconst [-1] x) x))) @@ -606,24 +607,18 @@ (MOVHstorezero [4] destptr (MOVWstorezero destptr mem))) -// MOVD for store with DS must have offsets that are multiple of 4 -(Zero [8] {t} destptr mem) && t.Alignment()%4 == 0 => - (MOVDstorezero destptr mem) -(Zero [8] destptr mem) => - (MOVWstorezero [4] destptr - (MOVWstorezero [0] destptr mem)) -// Handle these cases only if aligned properly, otherwise use general case below -(Zero [12] {t} destptr mem) && t.Alignment()%4 == 0 => +(Zero [8] {t} destptr mem) => (MOVDstorezero destptr mem) +(Zero [12] {t} destptr mem) => (MOVWstorezero [8] destptr (MOVDstorezero [0] destptr mem)) -(Zero [16] {t} destptr mem) && t.Alignment()%4 == 0 => +(Zero [16] {t} destptr mem) => (MOVDstorezero [8] destptr (MOVDstorezero [0] destptr mem)) -(Zero [24] {t} destptr mem) && t.Alignment()%4 == 0 => +(Zero [24] {t} destptr mem) => (MOVDstorezero [16] destptr (MOVDstorezero [8] destptr (MOVDstorezero [0] destptr mem))) -(Zero [32] {t} destptr mem) && t.Alignment()%4 == 0 => +(Zero [32] {t} destptr mem) => (MOVDstorezero [24] destptr (MOVDstorezero [16] destptr (MOVDstorezero [8] destptr @@ -632,15 +627,12 @@ // Handle cases not handled above // Lowered Short cases do not generate loops, and as a result don't clobber // the address registers or flags. -(Zero [s] ptr mem) && objabi.GOPPC64 <= 8 && s < 64 => (LoweredZeroShort [s] ptr mem) -(Zero [s] ptr mem) && objabi.GOPPC64 <= 8 => (LoweredZero [s] ptr mem) -(Zero [s] ptr mem) && s < 128 && objabi.GOPPC64 >= 9 => (LoweredQuadZeroShort [s] ptr mem) -(Zero [s] ptr mem) && objabi.GOPPC64 >= 9 => (LoweredQuadZero [s] ptr mem) +(Zero [s] ptr mem) && buildcfg.GOPPC64 <= 8 && s < 64 => (LoweredZeroShort [s] ptr mem) +(Zero [s] ptr mem) && buildcfg.GOPPC64 <= 8 => (LoweredZero [s] ptr mem) +(Zero [s] ptr mem) && s < 128 && buildcfg.GOPPC64 >= 9 => (LoweredQuadZeroShort [s] ptr mem) +(Zero [s] ptr mem) && buildcfg.GOPPC64 >= 9 => (LoweredQuadZero [s] ptr mem) // moves -// Only the MOVD and MOVW instructions require 4 byte -// alignment in the offset field. The other MOVx instructions -// allow any alignment. (Move [0] _ _ mem) => mem (Move [1] dst src mem) => (MOVBstore dst (MOVBZload src mem) mem) (Move [2] dst src mem) => @@ -648,11 +640,8 @@ (Move [4] dst src mem) => (MOVWstore dst (MOVWZload src mem) mem) // MOVD for load and store must have offsets that are multiple of 4 -(Move [8] {t} dst src mem) && t.Alignment()%4 == 0 => +(Move [8] {t} dst src mem) => (MOVDstore dst (MOVDload src mem) mem) -(Move [8] dst src mem) => - (MOVWstore [4] dst (MOVWZload [4] src mem) - (MOVWstore dst (MOVWZload src mem) mem)) (Move [3] dst src mem) => (MOVBstore [2] dst (MOVBZload [2] src mem) (MOVHstore dst (MOVHload src mem) mem)) @@ -669,11 +658,11 @@ // Large move uses a loop. Since the address is computed and the // offset is zero, any alignment can be used. -(Move [s] dst src mem) && s > 8 && objabi.GOPPC64 <= 8 && logLargeCopy(v, s) => +(Move [s] dst src mem) && s > 8 && buildcfg.GOPPC64 <= 8 && logLargeCopy(v, s) => (LoweredMove [s] dst src mem) -(Move [s] dst src mem) && s > 8 && s <= 64 && objabi.GOPPC64 >= 9 => +(Move [s] dst src mem) && s > 8 && s <= 64 && buildcfg.GOPPC64 >= 9 => (LoweredQuadMoveShort [s] dst src mem) -(Move [s] dst src mem) && s > 8 && objabi.GOPPC64 >= 9 && logLargeCopy(v, s) => +(Move [s] dst src mem) && s > 8 && buildcfg.GOPPC64 >= 9 && logLargeCopy(v, s) => (LoweredQuadMove [s] dst src mem) // Calls @@ -874,7 +863,7 @@ (MFVSRD x:(FMOVDload [off] {sym} ptr mem)) && x.Uses == 1 && clobber(x) => @x.Block (MOVDload [off] {sym} ptr mem) // Fold offsets for stores. -(MOVDstore [off1] {sym} (ADDconst [off2] x) val mem) && is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0 => (MOVDstore [off1+int32(off2)] {sym} x val mem) +(MOVDstore [off1] {sym} (ADDconst [off2] x) val mem) && is16Bit(int64(off1)+off2) => (MOVDstore [off1+int32(off2)] {sym} x val mem) (MOVWstore [off1] {sym} (ADDconst [off2] x) val mem) && is16Bit(int64(off1)+off2) => (MOVWstore [off1+int32(off2)] {sym} x val mem) (MOVHstore [off1] {sym} (ADDconst [off2] x) val mem) && is16Bit(int64(off1)+off2) => (MOVHstore [off1+int32(off2)] {sym} x val mem) (MOVBstore [off1] {sym} (ADDconst [off2] x) val mem) && is16Bit(int64(off1)+off2) => (MOVBstore [off1+int32(off2)] {sym} x val mem) @@ -897,7 +886,7 @@ && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) => (MOVWstore [off1+off2] {mergeSym(sym1,sym2)} ptr val mem) (MOVDstore [off1] {sym1} p:(MOVDaddr [off2] {sym2} ptr) val mem) && canMergeSym(sym1,sym2) - && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0 => + && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) => (MOVDstore [off1+off2] {mergeSym(sym1,sym2)} ptr val mem) (FMOVSstore [off1] {sym1} p:(MOVDaddr [off2] {sym2} ptr) val mem) && canMergeSym(sym1,sym2) @@ -917,13 +906,13 @@ && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) => (MOVHZload [off1+off2] {mergeSym(sym1,sym2)} ptr mem) (MOVWload [off1] {sym1} p:(MOVDaddr [off2] {sym2} ptr) mem) && canMergeSym(sym1,sym2) - && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0 => + && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) => (MOVWload [off1+off2] {mergeSym(sym1,sym2)} ptr mem) (MOVWZload [off1] {sym1} p:(MOVDaddr [off2] {sym2} ptr) mem) && canMergeSym(sym1,sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) => (MOVWZload [off1+off2] {mergeSym(sym1,sym2)} ptr mem) (MOVDload [off1] {sym1} p:(MOVDaddr [off2] {sym2} ptr) mem) && canMergeSym(sym1,sym2) - && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0 => + && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) => (MOVDload [off1+off2] {mergeSym(sym1,sym2)} ptr mem) (FMOVSload [off1] {sym1} p:(MOVDaddr [off2] {sym2} ptr) mem) && canMergeSym(sym1,sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) => @@ -936,8 +925,8 @@ (FMOVSload [off1] {sym} (ADDconst [off2] ptr) mem) && is16Bit(int64(off1)+off2) => (FMOVSload [off1+int32(off2)] {sym} ptr mem) (FMOVDload [off1] {sym} (ADDconst [off2] ptr) mem) && is16Bit(int64(off1)+off2) => (FMOVDload [off1+int32(off2)] {sym} ptr mem) -(MOVDload [off1] {sym} (ADDconst [off2] x) mem) && is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0 => (MOVDload [off1+int32(off2)] {sym} x mem) -(MOVWload [off1] {sym} (ADDconst [off2] x) mem) && is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0 => (MOVWload [off1+int32(off2)] {sym} x mem) +(MOVDload [off1] {sym} (ADDconst [off2] x) mem) && is16Bit(int64(off1)+off2) => (MOVDload [off1+int32(off2)] {sym} x mem) +(MOVWload [off1] {sym} (ADDconst [off2] x) mem) && is16Bit(int64(off1)+off2) => (MOVWload [off1+int32(off2)] {sym} x mem) (MOVWZload [off1] {sym} (ADDconst [off2] x) mem) && is16Bit(int64(off1)+off2) => (MOVWZload [off1+int32(off2)] {sym} x mem) (MOVHload [off1] {sym} (ADDconst [off2] x) mem) && is16Bit(int64(off1)+off2) => (MOVHload [off1+int32(off2)] {sym} x mem) (MOVHZload [off1] {sym} (ADDconst [off2] x) mem) && is16Bit(int64(off1)+off2) => (MOVHZload [off1+int32(off2)] {sym} x mem) @@ -946,7 +935,10 @@ // Determine load + addressing that can be done as a register indexed load (MOV(D|W|WZ|H|HZ|BZ)load [0] {sym} p:(ADD ptr idx) mem) && sym == nil && p.Uses == 1 => (MOV(D|W|WZ|H|HZ|BZ)loadidx ptr idx mem) -// Determine indexed loads with constant values that can be done without index +// Determine if there is benefit to using a non-indexed load, since that saves the load +// of the index register. With MOVDload and MOVWload, there is no benefit if the offset +// value is not a multiple of 4, since that results in an extra instruction in the base +// register address computation. (MOV(D|W)loadidx ptr (MOVDconst [c]) mem) && is16Bit(c) && c%4 == 0 => (MOV(D|W)load [int32(c)] ptr mem) (MOV(WZ|H|HZ|BZ)loadidx ptr (MOVDconst [c]) mem) && is16Bit(c) => (MOV(WZ|H|HZ|BZ)load [int32(c)] ptr mem) (MOV(D|W)loadidx (MOVDconst [c]) ptr mem) && is16Bit(c) && c%4 == 0 => (MOV(D|W)load [int32(c)] ptr mem) @@ -959,7 +951,7 @@ (MOVBstore [off] {sym} ptr (MOVDconst [0]) mem) => (MOVBstorezero [off] {sym} ptr mem) // Fold offsets for storezero -(MOVDstorezero [off1] {sym} (ADDconst [off2] x) mem) && is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0 => +(MOVDstorezero [off1] {sym} (ADDconst [off2] x) mem) && is16Bit(int64(off1)+off2) => (MOVDstorezero [off1+int32(off2)] {sym} x mem) (MOVWstorezero [off1] {sym} (ADDconst [off2] x) mem) && is16Bit(int64(off1)+off2) => (MOVWstorezero [off1+int32(off2)] {sym} x mem) @@ -972,6 +964,7 @@ (MOV(D|W|H|B)store [0] {sym} p:(ADD ptr idx) val mem) && sym == nil && p.Uses == 1 => (MOV(D|W|H|B)storeidx ptr idx val mem) // Stores with constant index values can be done without indexed instructions +// No need to lower the idx cases if c%4 is not 0 (MOVDstoreidx ptr (MOVDconst [c]) val mem) && is16Bit(c) && c%4 == 0 => (MOVDstore [int32(c)] ptr val mem) (MOV(W|H|B)storeidx ptr (MOVDconst [c]) val mem) && is16Bit(c) => (MOV(W|H|B)store [int32(c)] ptr val mem) (MOVDstoreidx (MOVDconst [c]) ptr val mem) && is16Bit(c) && c%4 == 0 => (MOVDstore [int32(c)] ptr val mem) @@ -979,7 +972,7 @@ // Fold symbols into storezero (MOVDstorezero [off1] {sym1} p:(MOVDaddr [off2] {sym2} x) mem) && canMergeSym(sym1,sym2) - && (x.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0 => + && (x.Op != OpSB || p.Uses == 1) => (MOVDstorezero [off1+off2] {mergeSym(sym1,sym2)} x mem) (MOVWstorezero [off1] {sym1} p:(MOVDaddr [off2] {sym2} x) mem) && canMergeSym(sym1,sym2) && (x.Op != OpSB || p.Uses == 1) => @@ -1055,7 +1048,7 @@ (SLWconst [c] z:(ANDconst [d] x)) && z.Uses == 1 && isPPC64ValidShiftMask(d) && c<=(32-getPPC64ShiftMaskLength(d)) => (CLRLSLWI [newPPC64ShiftAuxInt(c,32-getPPC64ShiftMaskLength(d),31,32)] x) (SLWconst [c] z:(AND (MOVDconst [d]) x)) && z.Uses == 1 && isPPC64ValidShiftMask(d) && c<=(32-getPPC64ShiftMaskLength(d)) => (CLRLSLWI [newPPC64ShiftAuxInt(c,32-getPPC64ShiftMaskLength(d),31,32)] x) // special case for power9 -(SL(W|D)const [c] z:(MOVWreg x)) && c < 32 && objabi.GOPPC64 >= 9 => (EXTSWSLconst [c] x) +(SL(W|D)const [c] z:(MOVWreg x)) && c < 32 && buildcfg.GOPPC64 >= 9 => (EXTSWSLconst [c] x) // Lose widening ops fed to stores (MOVBstore [off] {sym} ptr (MOV(B|BZ|H|HZ|W|WZ)reg x) mem) => (MOVBstore [off] {sym} ptr x mem) @@ -1088,7 +1081,7 @@ (CMPWU (MOVDconst [c]) y) && isU16Bit(c) => (InvertFlags (CMPWUconst y [int32(c)])) // Canonicalize the order of arguments to comparisons - helps with CSE. -((CMP|CMPW|CMPU|CMPWU) x y) && x.ID > y.ID => (InvertFlags ((CMP|CMPW|CMPU|CMPWU) y x)) +((CMP|CMPW|CMPU|CMPWU) x y) && canonLessThan(x,y) => (InvertFlags ((CMP|CMPW|CMPU|CMPWU) y x)) // ISEL auxInt values 0=LT 1=GT 2=EQ arg2 ? arg0 : arg1 // ISEL auxInt values 4=GE 5=LE 6=NE arg2 ? arg1 : arg0 @@ -1293,7 +1286,6 @@ o3:(OR s3:(SLDconst x4:(MOVBZload [i4] {s} p mem) [32]) x0:(MOVWZload {s} [i0] p mem))))) && !config.BigEndian - && i0%4 == 0 && i4 == i0+4 && i5 == i0+5 && i6 == i0+6 @@ -1430,7 +1422,6 @@ x2:(MOVBstore [i4] {s} p (SRDconst w [32]) x3:(MOVWstore [i0] {s} p w mem))))) && !config.BigEndian - && i0%4 == 0 && x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && i4 == i0+4 && i5 == i0+5 && i6 == i0+6 && i7 == i0+7 && clobber(x0, x1, x2, x3) diff --git a/src/cmd/compile/internal/ssa/gen/RISCV64.rules b/src/cmd/compile/internal/ssa/gen/RISCV64.rules index 4380a5efef719d52601d4858586b90cf57c86932..9cdd62edbe01c004f4b79951d38a4d5cd123a2a3 100644 --- a/src/cmd/compile/internal/ssa/gen/RISCV64.rules +++ b/src/cmd/compile/internal/ssa/gen/RISCV64.rules @@ -92,6 +92,7 @@ (Com8 ...) => (NOT ...) (Sqrt ...) => (FSQRTD ...) +(Sqrt32 ...) => (FSQRTS ...) // Sign and zero extension. @@ -221,9 +222,9 @@ (Rsh64x64 x y) => (SRA x (OR y (ADDI [-1] (SLTIU [64] y)))) // rotates -(RotateLeft8 x (MOVBconst [c])) => (Or8 (Lsh8x64 x (MOVBconst [c&7])) (Rsh8Ux64 x (MOVBconst [-c&7]))) -(RotateLeft16 x (MOVHconst [c])) => (Or16 (Lsh16x64 x (MOVHconst [c&15])) (Rsh16Ux64 x (MOVHconst [-c&15]))) -(RotateLeft32 x (MOVWconst [c])) => (Or32 (Lsh32x64 x (MOVWconst [c&31])) (Rsh32Ux64 x (MOVWconst [-c&31]))) +(RotateLeft8 x (MOVDconst [c])) => (Or8 (Lsh8x64 x (MOVDconst [c&7])) (Rsh8Ux64 x (MOVDconst [-c&7]))) +(RotateLeft16 x (MOVDconst [c])) => (Or16 (Lsh16x64 x (MOVDconst [c&15])) (Rsh16Ux64 x (MOVDconst [-c&15]))) +(RotateLeft32 x (MOVDconst [c])) => (Or32 (Lsh32x64 x (MOVDconst [c&31])) (Rsh32Ux64 x (MOVDconst [-c&31]))) (RotateLeft64 x (MOVDconst [c])) => (Or64 (Lsh64x64 x (MOVDconst [c&63])) (Rsh64Ux64 x (MOVDconst [-c&63]))) (Less64 ...) => (SLT ...) @@ -251,7 +252,7 @@ (EqPtr x y) => (SEQZ (SUB x y)) (Eq64 x y) => (SEQZ (SUB x y)) -(Eq32 x y) => (SEQZ (SUBW x y)) +(Eq32 x y) => (SEQZ (SUB (ZeroExt32to64 x) (ZeroExt32to64 y))) (Eq16 x y) => (SEQZ (SUB (ZeroExt16to64 x) (ZeroExt16to64 y))) (Eq8 x y) => (SEQZ (SUB (ZeroExt8to64 x) (ZeroExt8to64 y))) (Eq64F ...) => (FEQD ...) @@ -259,7 +260,7 @@ (NeqPtr x y) => (SNEZ (SUB x y)) (Neq64 x y) => (SNEZ (SUB x y)) -(Neq32 x y) => (SNEZ (SUBW x y)) +(Neq32 x y) => (SNEZ (SUB (ZeroExt32to64 x) (ZeroExt32to64 y))) (Neq16 x y) => (SNEZ (SUB (ZeroExt16to64 x) (ZeroExt16to64 y))) (Neq8 x y) => (SNEZ (SUB (ZeroExt8to64 x) (ZeroExt8to64 y))) (Neq64F ...) => (FNED ...) @@ -353,45 +354,45 @@ // Small zeroing (Zero [0] _ mem) => mem -(Zero [1] ptr mem) => (MOVBstore ptr (MOVBconst [0]) mem) +(Zero [1] ptr mem) => (MOVBstore ptr (MOVDconst [0]) mem) (Zero [2] {t} ptr mem) && t.Alignment()%2 == 0 => - (MOVHstore ptr (MOVHconst [0]) mem) + (MOVHstore ptr (MOVDconst [0]) mem) (Zero [2] ptr mem) => - (MOVBstore [1] ptr (MOVBconst [0]) - (MOVBstore ptr (MOVBconst [0]) mem)) + (MOVBstore [1] ptr (MOVDconst [0]) + (MOVBstore ptr (MOVDconst [0]) mem)) (Zero [4] {t} ptr mem) && t.Alignment()%4 == 0 => - (MOVWstore ptr (MOVWconst [0]) mem) + (MOVWstore ptr (MOVDconst [0]) mem) (Zero [4] {t} ptr mem) && t.Alignment()%2 == 0 => - (MOVHstore [2] ptr (MOVHconst [0]) - (MOVHstore ptr (MOVHconst [0]) mem)) + (MOVHstore [2] ptr (MOVDconst [0]) + (MOVHstore ptr (MOVDconst [0]) mem)) (Zero [4] ptr mem) => - (MOVBstore [3] ptr (MOVBconst [0]) - (MOVBstore [2] ptr (MOVBconst [0]) - (MOVBstore [1] ptr (MOVBconst [0]) - (MOVBstore ptr (MOVBconst [0]) mem)))) + (MOVBstore [3] ptr (MOVDconst [0]) + (MOVBstore [2] ptr (MOVDconst [0]) + (MOVBstore [1] ptr (MOVDconst [0]) + (MOVBstore ptr (MOVDconst [0]) mem)))) (Zero [8] {t} ptr mem) && t.Alignment()%8 == 0 => (MOVDstore ptr (MOVDconst [0]) mem) (Zero [8] {t} ptr mem) && t.Alignment()%4 == 0 => - (MOVWstore [4] ptr (MOVWconst [0]) - (MOVWstore ptr (MOVWconst [0]) mem)) + (MOVWstore [4] ptr (MOVDconst [0]) + (MOVWstore ptr (MOVDconst [0]) mem)) (Zero [8] {t} ptr mem) && t.Alignment()%2 == 0 => - (MOVHstore [6] ptr (MOVHconst [0]) - (MOVHstore [4] ptr (MOVHconst [0]) - (MOVHstore [2] ptr (MOVHconst [0]) - (MOVHstore ptr (MOVHconst [0]) mem)))) + (MOVHstore [6] ptr (MOVDconst [0]) + (MOVHstore [4] ptr (MOVDconst [0]) + (MOVHstore [2] ptr (MOVDconst [0]) + (MOVHstore ptr (MOVDconst [0]) mem)))) (Zero [3] ptr mem) => - (MOVBstore [2] ptr (MOVBconst [0]) - (MOVBstore [1] ptr (MOVBconst [0]) - (MOVBstore ptr (MOVBconst [0]) mem))) + (MOVBstore [2] ptr (MOVDconst [0]) + (MOVBstore [1] ptr (MOVDconst [0]) + (MOVBstore ptr (MOVDconst [0]) mem))) (Zero [6] {t} ptr mem) && t.Alignment()%2 == 0 => - (MOVHstore [4] ptr (MOVHconst [0]) - (MOVHstore [2] ptr (MOVHconst [0]) - (MOVHstore ptr (MOVHconst [0]) mem))) + (MOVHstore [4] ptr (MOVDconst [0]) + (MOVHstore [2] ptr (MOVDconst [0]) + (MOVHstore ptr (MOVDconst [0]) mem))) (Zero [12] {t} ptr mem) && t.Alignment()%4 == 0 => - (MOVWstore [8] ptr (MOVWconst [0]) - (MOVWstore [4] ptr (MOVWconst [0]) - (MOVWstore ptr (MOVWconst [0]) mem))) + (MOVWstore [8] ptr (MOVDconst [0]) + (MOVWstore [4] ptr (MOVDconst [0]) + (MOVWstore ptr (MOVDconst [0]) mem))) (Zero [16] {t} ptr mem) && t.Alignment()%8 == 0 => (MOVDstore [8] ptr (MOVDconst [0]) (MOVDstore ptr (MOVDconst [0]) mem)) @@ -422,7 +423,7 @@ (Convert ...) => (MOVconvert ...) // Checks -(IsNonNil p) => (NeqPtr (MOVDconst [0]) p) +(IsNonNil ...) => (SNEZ ...) (IsInBounds ...) => (Less64U ...) (IsSliceInBounds ...) => (Leq64U ...) @@ -521,25 +522,14 @@ (OffPtr [off] ptr) && is32Bit(off) => (ADDI [off] ptr) (OffPtr [off] ptr) => (ADD (MOVDconst [off]) ptr) -// TODO(jsing): Check if we actually need MOV{B,H,W}const as most platforms -// use a single MOVDconst op. -(Const8 ...) => (MOVBconst ...) -(Const16 ...) => (MOVHconst ...) -(Const32 ...) => (MOVWconst ...) -(Const64 ...) => (MOVDconst ...) -(Const32F [val]) => (FMVSX (MOVWconst [int32(math.Float32bits(val))])) +(Const8 [val]) => (MOVDconst [int64(val)]) +(Const16 [val]) => (MOVDconst [int64(val)]) +(Const32 [val]) => (MOVDconst [int64(val)]) +(Const64 [val]) => (MOVDconst [int64(val)]) +(Const32F [val]) => (FMVSX (MOVDconst [int64(math.Float32bits(val))])) (Const64F [val]) => (FMVDX (MOVDconst [int64(math.Float64bits(val))])) (ConstNil) => (MOVDconst [0]) -(ConstBool [val]) => (MOVBconst [int8(b2i(val))]) - -// Convert 64 bit immediate to two 32 bit immediates, combine with add and shift. -// The lower 32 bit immediate will be treated as signed, -// so if it is negative, adjust for the borrow by incrementing the top half. -// We don't have to worry about overflow from the increment, -// because if the top half is all 1s, and int32(c) is negative, -// then the overall constant fits in an int32. -(MOVDconst [c]) && !is32Bit(c) && int32(c) < 0 => (ADD (SLLI [32] (MOVDconst [c>>32+1])) (MOVDconst [int64(int32(c))])) -(MOVDconst [c]) && !is32Bit(c) && int32(c) >= 0 => (ADD (SLLI [32] (MOVDconst [c>>32+0])) (MOVDconst [int64(int32(c))])) +(ConstBool [val]) => (MOVDconst [int64(b2i(val))]) (Addr {sym} base) => (MOVaddr {sym} [0] base) (LocalAddr {sym} base _) => (MOVaddr {sym} base) @@ -563,12 +553,28 @@ (AtomicAdd32 ...) => (LoweredAtomicAdd32 ...) (AtomicAdd64 ...) => (LoweredAtomicAdd64 ...) +// AtomicAnd8(ptr,val) => LoweredAtomicAnd32(ptr&^3, ^((uint8(val) ^ 0xff) << ((ptr & 3) * 8))) +(AtomicAnd8 ptr val mem) => + (LoweredAtomicAnd32 (ANDI [^3] ptr) + (NOT (SLL (XORI [0xff] (ZeroExt8to32 val)) + (SLLI [3] (ANDI [3] ptr)))) mem) + +(AtomicAnd32 ...) => (LoweredAtomicAnd32 ...) + (AtomicCompareAndSwap32 ...) => (LoweredAtomicCas32 ...) (AtomicCompareAndSwap64 ...) => (LoweredAtomicCas64 ...) (AtomicExchange32 ...) => (LoweredAtomicExchange32 ...) (AtomicExchange64 ...) => (LoweredAtomicExchange64 ...) +// AtomicOr8(ptr,val) => LoweredAtomicOr32(ptr&^3, uint32(val)<<((ptr&3)*8)) +(AtomicOr8 ptr val mem) => + (LoweredAtomicOr32 (ANDI [^3] ptr) + (SLL (ZeroExt8to32 val) + (SLLI [3] (ANDI [3] ptr))) mem) + +(AtomicOr32 ...) => (LoweredAtomicOr32 ...) + // Conditional branches (If cond yes no) => (BNEZ cond yes no) @@ -595,24 +601,18 @@ (BNE cond (MOVDconst [0]) yes no) => (BNEZ cond yes no) // Store zero -(MOVBstore [off] {sym} ptr (MOVBconst [0]) mem) => (MOVBstorezero [off] {sym} ptr mem) -(MOVHstore [off] {sym} ptr (MOVHconst [0]) mem) => (MOVHstorezero [off] {sym} ptr mem) -(MOVWstore [off] {sym} ptr (MOVWconst [0]) mem) => (MOVWstorezero [off] {sym} ptr mem) +(MOVBstore [off] {sym} ptr (MOVDconst [0]) mem) => (MOVBstorezero [off] {sym} ptr mem) +(MOVHstore [off] {sym} ptr (MOVDconst [0]) mem) => (MOVHstorezero [off] {sym} ptr mem) +(MOVWstore [off] {sym} ptr (MOVDconst [0]) mem) => (MOVWstorezero [off] {sym} ptr mem) (MOVDstore [off] {sym} ptr (MOVDconst [0]) mem) => (MOVDstorezero [off] {sym} ptr mem) // Avoid sign/zero extension for consts. -(MOVBreg (MOVBconst [c])) => (MOVDconst [int64(c)]) -(MOVHreg (MOVBconst [c])) => (MOVDconst [int64(c)]) -(MOVHreg (MOVHconst [c])) => (MOVDconst [int64(c)]) -(MOVWreg (MOVBconst [c])) => (MOVDconst [int64(c)]) -(MOVWreg (MOVHconst [c])) => (MOVDconst [int64(c)]) -(MOVWreg (MOVWconst [c])) => (MOVDconst [int64(c)]) -(MOVBUreg (MOVBconst [c])) => (MOVDconst [int64(uint8(c))]) -(MOVHUreg (MOVBconst [c])) => (MOVDconst [int64(uint16(c))]) -(MOVHUreg (MOVHconst [c])) => (MOVDconst [int64(uint16(c))]) -(MOVWUreg (MOVBconst [c])) => (MOVDconst [int64(uint32(c))]) -(MOVWUreg (MOVHconst [c])) => (MOVDconst [int64(uint32(c))]) -(MOVWUreg (MOVWconst [c])) => (MOVDconst [int64(uint32(c))]) +(MOVBreg (MOVDconst [c])) => (MOVDconst [int64(int8(c))]) +(MOVHreg (MOVDconst [c])) => (MOVDconst [int64(int16(c))]) +(MOVWreg (MOVDconst [c])) => (MOVDconst [int64(int32(c))]) +(MOVBUreg (MOVDconst [c])) => (MOVDconst [int64(uint8(c))]) +(MOVHUreg (MOVDconst [c])) => (MOVDconst [int64(uint16(c))]) +(MOVWUreg (MOVDconst [c])) => (MOVDconst [int64(uint32(c))]) // Avoid sign/zero extension after properly typed load. (MOVBreg x:(MOVBload _ _)) => (MOVDreg x) @@ -673,61 +673,29 @@ // MOVnop does not emit an instruction, only for ensuring the type. (MOVDreg x) && x.Uses == 1 => (MOVDnop x) +// TODO: we should be able to get rid of MOVDnop all together. +// But for now, this is enough to get rid of lots of them. +(MOVDnop (MOVDconst [c])) => (MOVDconst [c]) + // Fold constant into immediate instructions where possible. -(ADD (MOVBconst [val]) x) => (ADDI [int64(val)] x) -(ADD (MOVHconst [val]) x) => (ADDI [int64(val)] x) -(ADD (MOVWconst [val]) x) => (ADDI [int64(val)] x) (ADD (MOVDconst [val]) x) && is32Bit(val) => (ADDI [val] x) - -(AND (MOVBconst [val]) x) => (ANDI [int64(val)] x) -(AND (MOVHconst [val]) x) => (ANDI [int64(val)] x) -(AND (MOVWconst [val]) x) => (ANDI [int64(val)] x) (AND (MOVDconst [val]) x) && is32Bit(val) => (ANDI [val] x) - -(OR (MOVBconst [val]) x) => (ORI [int64(val)] x) -(OR (MOVHconst [val]) x) => (ORI [int64(val)] x) -(OR (MOVWconst [val]) x) => (ORI [int64(val)] x) -(OR (MOVDconst [val]) x) && is32Bit(val) => (ORI [val] x) - -(XOR (MOVBconst [val]) x) => (XORI [int64(val)] x) -(XOR (MOVHconst [val]) x) => (XORI [int64(val)] x) -(XOR (MOVWconst [val]) x) => (XORI [int64(val)] x) +(OR (MOVDconst [val]) x) && is32Bit(val) => (ORI [val] x) (XOR (MOVDconst [val]) x) && is32Bit(val) => (XORI [val] x) - -(SLL x (MOVBconst [val])) => (SLLI [int64(val&63)] x) -(SLL x (MOVHconst [val])) => (SLLI [int64(val&63)] x) -(SLL x (MOVWconst [val])) => (SLLI [int64(val&63)] x) (SLL x (MOVDconst [val])) => (SLLI [int64(val&63)] x) - -(SRL x (MOVBconst [val])) => (SRLI [int64(val&63)] x) -(SRL x (MOVHconst [val])) => (SRLI [int64(val&63)] x) -(SRL x (MOVWconst [val])) => (SRLI [int64(val&63)] x) (SRL x (MOVDconst [val])) => (SRLI [int64(val&63)] x) - -(SRA x (MOVBconst [val])) => (SRAI [int64(val&63)] x) -(SRA x (MOVHconst [val])) => (SRAI [int64(val&63)] x) -(SRA x (MOVWconst [val])) => (SRAI [int64(val&63)] x) (SRA x (MOVDconst [val])) => (SRAI [int64(val&63)] x) // Convert subtraction of a const into ADDI with negative immediate, where possible. -(SUB x (MOVBconst [val])) => (ADDI [-int64(val)] x) -(SUB x (MOVHconst [val])) => (ADDI [-int64(val)] x) -(SUB x (MOVWconst [val])) && is32Bit(-int64(val)) => (ADDI [-int64(val)] x) (SUB x (MOVDconst [val])) && is32Bit(-val) => (ADDI [-val] x) // Subtraction of zero. -(SUB x (MOVBconst [0])) => x -(SUB x (MOVHconst [0])) => x -(SUB x (MOVWconst [0])) => x (SUB x (MOVDconst [0])) => x // Subtraction of zero with sign extension. -(SUBW x (MOVWconst [0])) => (ADDIW [0] x) +(SUBW x (MOVDconst [0])) => (ADDIW [0] x) // Subtraction from zero. -(SUB (MOVBconst [0]) x) => (NEG x) -(SUB (MOVHconst [0]) x) => (NEG x) -(SUB (MOVWconst [0]) x) => (NEG x) (SUB (MOVDconst [0]) x) => (NEG x) // Subtraction from zero with sign extension. diff --git a/src/cmd/compile/internal/ssa/gen/RISCV64Ops.go b/src/cmd/compile/internal/ssa/gen/RISCV64Ops.go index f64319230b0f23e7e7c570b5086d9eece9f4f892..0ac9c5f62ad176700958b5b648992cd3a3410e3e 100644 --- a/src/cmd/compile/internal/ssa/gen/RISCV64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/RISCV64Ops.go @@ -126,6 +126,7 @@ func init() { gp11sb = regInfo{inputs: []regMask{gpspsbMask}, outputs: []regMask{gpMask}} gpxchg = regInfo{inputs: []regMask{gpspsbgMask, gpgMask}, outputs: []regMask{gpMask}} gpcas = regInfo{inputs: []regMask{gpspsbgMask, gpgMask, gpgMask}, outputs: []regMask{gpMask}} + gpatomic = regInfo{inputs: []regMask{gpspsbgMask, gpgMask}} fp11 = regInfo{inputs: []regMask{fpMask}, outputs: []regMask{fpMask}} fp21 = regInfo{inputs: []regMask{fpMask, fpMask}, outputs: []regMask{fpMask}} @@ -167,9 +168,6 @@ func init() { {name: "MOVaddr", argLength: 1, reg: gp11sb, asm: "MOV", aux: "SymOff", rematerializeable: true, symEffect: "RdWr"}, // arg0 + auxint + offset encoded in aux // auxint+aux == add auxint and the offset of the symbol in aux (if any) to the effective address - {name: "MOVBconst", reg: gp01, asm: "MOV", typ: "UInt8", aux: "Int8", rematerializeable: true}, // 8 low bits of auxint - {name: "MOVHconst", reg: gp01, asm: "MOV", typ: "UInt16", aux: "Int16", rematerializeable: true}, // 16 low bits of auxint - {name: "MOVWconst", reg: gp01, asm: "MOV", typ: "UInt32", aux: "Int32", rematerializeable: true}, // 32 low bits of auxint {name: "MOVDconst", reg: gp01, asm: "MOV", typ: "UInt64", aux: "Int64", rematerializeable: true}, // auxint // Loads: load bits from arg0+auxint+aux and extend to 64 bits; arg1=mem @@ -335,7 +333,7 @@ func init() { {name: "LoweredAtomicLoad64", argLength: 2, reg: gpload, faultOnNilArg0: true}, // Atomic stores. - // store arg1 to arg0. arg2=mem. returns memory. + // store arg1 to *arg0. arg2=mem. returns memory. {name: "LoweredAtomicStore8", argLength: 3, reg: gpstore, faultOnNilArg0: true, hasSideEffects: true}, {name: "LoweredAtomicStore32", argLength: 3, reg: gpstore, faultOnNilArg0: true, hasSideEffects: true}, {name: "LoweredAtomicStore64", argLength: 3, reg: gpstore, faultOnNilArg0: true, hasSideEffects: true}, @@ -367,6 +365,11 @@ func init() { {name: "LoweredAtomicCas32", argLength: 4, reg: gpcas, resultNotInArgs: true, faultOnNilArg0: true, hasSideEffects: true, unsafePoint: true}, {name: "LoweredAtomicCas64", argLength: 4, reg: gpcas, resultNotInArgs: true, faultOnNilArg0: true, hasSideEffects: true, unsafePoint: true}, + // Atomic 32 bit AND/OR. + // *arg0 &= (|=) arg1. arg2=mem. returns nil. + {name: "LoweredAtomicAnd32", argLength: 3, reg: gpatomic, asm: "AMOANDW", faultOnNilArg0: true, hasSideEffects: true}, + {name: "LoweredAtomicOr32", argLength: 3, reg: gpatomic, asm: "AMOORW", faultOnNilArg0: true, hasSideEffects: true}, + // Lowering pass-throughs {name: "LoweredNilCheck", argLength: 2, faultOnNilArg0: true, nilCheck: true, reg: regInfo{inputs: []regMask{gpspMask}}}, // arg0=ptr,arg1=mem, returns void. Faults if ptr is nil. {name: "LoweredGetClosurePtr", reg: regInfo{outputs: []regMask{regCtxt}}}, // scheduler ensures only at beginning of entry block diff --git a/src/cmd/compile/internal/ssa/gen/S390X.rules b/src/cmd/compile/internal/ssa/gen/S390X.rules index 384f2e807e05631614af1b9505b3e82e7332665f..88762f704589067c018bb110090397d7d51261de 100644 --- a/src/cmd/compile/internal/ssa/gen/S390X.rules +++ b/src/cmd/compile/internal/ssa/gen/S390X.rules @@ -142,6 +142,8 @@ (Round x) => (FIDBR [1] x) (FMA x y z) => (FMADD z x y) +(Sqrt32 ...) => (FSQRTS ...) + // Atomic loads and stores. // The SYNC instruction (fast-BCR-serialization) prevents store-load // reordering. Other sequences of memory operations (load-load, @@ -384,13 +386,13 @@ // MVC for other moves. Use up to 4 instructions (sizes up to 1024 bytes). (Move [s] dst src mem) && s > 0 && s <= 256 && logLargeCopy(v, s) => - (MVC [makeValAndOff32(int32(s), 0)] dst src mem) + (MVC [makeValAndOff(int32(s), 0)] dst src mem) (Move [s] dst src mem) && s > 256 && s <= 512 && logLargeCopy(v, s) => - (MVC [makeValAndOff32(int32(s)-256, 256)] dst src (MVC [makeValAndOff32(256, 0)] dst src mem)) + (MVC [makeValAndOff(int32(s)-256, 256)] dst src (MVC [makeValAndOff(256, 0)] dst src mem)) (Move [s] dst src mem) && s > 512 && s <= 768 && logLargeCopy(v, s) => - (MVC [makeValAndOff32(int32(s)-512, 512)] dst src (MVC [makeValAndOff32(256, 256)] dst src (MVC [makeValAndOff32(256, 0)] dst src mem))) + (MVC [makeValAndOff(int32(s)-512, 512)] dst src (MVC [makeValAndOff(256, 256)] dst src (MVC [makeValAndOff(256, 0)] dst src mem))) (Move [s] dst src mem) && s > 768 && s <= 1024 && logLargeCopy(v, s) => - (MVC [makeValAndOff32(int32(s)-768, 768)] dst src (MVC [makeValAndOff32(256, 512)] dst src (MVC [makeValAndOff32(256, 256)] dst src (MVC [makeValAndOff32(256, 0)] dst src mem)))) + (MVC [makeValAndOff(int32(s)-768, 768)] dst src (MVC [makeValAndOff(256, 512)] dst src (MVC [makeValAndOff(256, 256)] dst src (MVC [makeValAndOff(256, 0)] dst src mem)))) // Move more than 1024 bytes using a loop. (Move [s] dst src mem) && s > 1024 && logLargeCopy(v, s) => @@ -403,20 +405,20 @@ (Zero [4] destptr mem) => (MOVWstoreconst [0] destptr mem) (Zero [8] destptr mem) => (MOVDstoreconst [0] destptr mem) (Zero [3] destptr mem) => - (MOVBstoreconst [makeValAndOff32(0,2)] destptr + (MOVBstoreconst [makeValAndOff(0,2)] destptr (MOVHstoreconst [0] destptr mem)) (Zero [5] destptr mem) => - (MOVBstoreconst [makeValAndOff32(0,4)] destptr + (MOVBstoreconst [makeValAndOff(0,4)] destptr (MOVWstoreconst [0] destptr mem)) (Zero [6] destptr mem) => - (MOVHstoreconst [makeValAndOff32(0,4)] destptr + (MOVHstoreconst [makeValAndOff(0,4)] destptr (MOVWstoreconst [0] destptr mem)) (Zero [7] destptr mem) => - (MOVWstoreconst [makeValAndOff32(0,3)] destptr + (MOVWstoreconst [makeValAndOff(0,3)] destptr (MOVWstoreconst [0] destptr mem)) (Zero [s] destptr mem) && s > 0 && s <= 1024 => - (CLEAR [makeValAndOff32(int32(s), 0)] destptr mem) + (CLEAR [makeValAndOff(int32(s), 0)] destptr mem) // Zero more than 1024 bytes using a loop. (Zero [s] destptr mem) && s > 1024 => @@ -426,7 +428,7 @@ (Const(64|32|16|8) [val]) => (MOVDconst [int64(val)]) (Const(32|64)F ...) => (FMOV(S|D)const ...) (ConstNil) => (MOVDconst [0]) -(ConstBool [b]) => (MOVDconst [b2i(b)]) +(ConstBool [t]) => (MOVDconst [b2i(t)]) // Lowering calls (StaticCall ...) => (CALLstatic ...) @@ -785,7 +787,7 @@ => (RISBGZ x {s390x.NewRotateParams(r.Start, r.Start, -r.Start&63)}) // Canonicalize the order of arguments to comparisons - helps with CSE. -((CMP|CMPW|CMPU|CMPWU) x y) && x.ID > y.ID => (InvertFlags ((CMP|CMPW|CMPU|CMPWU) y x)) +((CMP|CMPW|CMPU|CMPWU) x y) && canonLessThan(x,y) => (InvertFlags ((CMP|CMPW|CMPU|CMPWU) y x)) // Use sign/zero extend instead of RISBGZ. (RISBGZ x {r}) && r == s390x.NewRotateParams(56, 63, 0) => (MOVBZreg x) @@ -946,22 +948,22 @@ // Fold constants into stores. (MOVDstore [off] {sym} ptr (MOVDconst [c]) mem) && is16Bit(c) && isU12Bit(int64(off)) && ptr.Op != OpSB => - (MOVDstoreconst [makeValAndOff32(int32(c),off)] {sym} ptr mem) + (MOVDstoreconst [makeValAndOff(int32(c),off)] {sym} ptr mem) (MOVWstore [off] {sym} ptr (MOVDconst [c]) mem) && is16Bit(c) && isU12Bit(int64(off)) && ptr.Op != OpSB => - (MOVWstoreconst [makeValAndOff32(int32(c),off)] {sym} ptr mem) + (MOVWstoreconst [makeValAndOff(int32(c),off)] {sym} ptr mem) (MOVHstore [off] {sym} ptr (MOVDconst [c]) mem) && isU12Bit(int64(off)) && ptr.Op != OpSB => - (MOVHstoreconst [makeValAndOff32(int32(int16(c)),off)] {sym} ptr mem) + (MOVHstoreconst [makeValAndOff(int32(int16(c)),off)] {sym} ptr mem) (MOVBstore [off] {sym} ptr (MOVDconst [c]) mem) && is20Bit(int64(off)) && ptr.Op != OpSB => - (MOVBstoreconst [makeValAndOff32(int32(int8(c)),off)] {sym} ptr mem) + (MOVBstoreconst [makeValAndOff(int32(int8(c)),off)] {sym} ptr mem) // Fold address offsets into constant stores. -(MOVDstoreconst [sc] {s} (ADDconst [off] ptr) mem) && isU12Bit(sc.Off()+int64(off)) => +(MOVDstoreconst [sc] {s} (ADDconst [off] ptr) mem) && isU12Bit(sc.Off64()+int64(off)) => (MOVDstoreconst [sc.addOffset32(off)] {s} ptr mem) -(MOVWstoreconst [sc] {s} (ADDconst [off] ptr) mem) && isU12Bit(sc.Off()+int64(off)) => +(MOVWstoreconst [sc] {s} (ADDconst [off] ptr) mem) && isU12Bit(sc.Off64()+int64(off)) => (MOVWstoreconst [sc.addOffset32(off)] {s} ptr mem) -(MOVHstoreconst [sc] {s} (ADDconst [off] ptr) mem) && isU12Bit(sc.Off()+int64(off)) => +(MOVHstoreconst [sc] {s} (ADDconst [off] ptr) mem) && isU12Bit(sc.Off64()+int64(off)) => (MOVHstoreconst [sc.addOffset32(off)] {s} ptr mem) -(MOVBstoreconst [sc] {s} (ADDconst [off] ptr) mem) && is20Bit(sc.Off()+int64(off)) => +(MOVBstoreconst [sc] {s} (ADDconst [off] ptr) mem) && is20Bit(sc.Off64()+int64(off)) => (MOVBstoreconst [sc.addOffset32(off)] {s} ptr mem) // Merge address calculations into loads and stores. @@ -1304,19 +1306,19 @@ && x.Uses == 1 && a.Off() + 1 == c.Off() && clobber(x) - => (MOVHstoreconst [makeValAndOff32(c.Val32()&0xff | a.Val32()<<8, a.Off32())] {s} p mem) + => (MOVHstoreconst [makeValAndOff(c.Val()&0xff | a.Val()<<8, a.Off())] {s} p mem) (MOVHstoreconst [c] {s} p x:(MOVHstoreconst [a] {s} p mem)) && p.Op != OpSB && x.Uses == 1 && a.Off() + 2 == c.Off() && clobber(x) - => (MOVWstore [a.Off32()] {s} p (MOVDconst [int64(c.Val32()&0xffff | a.Val32()<<16)]) mem) + => (MOVWstore [a.Off()] {s} p (MOVDconst [int64(c.Val()&0xffff | a.Val()<<16)]) mem) (MOVWstoreconst [c] {s} p x:(MOVWstoreconst [a] {s} p mem)) && p.Op != OpSB && x.Uses == 1 && a.Off() + 4 == c.Off() && clobber(x) - => (MOVDstore [a.Off32()] {s} p (MOVDconst [c.Val()&0xffffffff | a.Val()<<32]) mem) + => (MOVDstore [a.Off()] {s} p (MOVDconst [c.Val64()&0xffffffff | a.Val64()<<32]) mem) // Combine stores into larger (unaligned) stores. // It doesn't work on global data (based on SB) because stores with relative addressing @@ -1420,6 +1422,16 @@ && clobber(x) => (MOVDBRstore [i-4] {s} p w0 mem) +(MOVBstore [7] {s} p1 (SRDconst w) + x1:(MOVHBRstore [5] {s} p1 (SRDconst w) + x2:(MOVWBRstore [1] {s} p1 (SRDconst w) + x3:(MOVBstore [0] {s} p1 w mem)))) + && x1.Uses == 1 + && x2.Uses == 1 + && x3.Uses == 1 + && clobber(x1, x2, x3) + => (MOVDBRstore {s} p1 w mem) + // Combining byte loads into larger (unaligned) loads. // Big-endian loads diff --git a/src/cmd/compile/internal/ssa/gen/S390XOps.go b/src/cmd/compile/internal/ssa/gen/S390XOps.go index b24fd619422c292cd7ad67451d2106df28d67531..5b33ba710e9818799df21ca130049381ced03c5b 100644 --- a/src/cmd/compile/internal/ssa/gen/S390XOps.go +++ b/src/cmd/compile/internal/ssa/gen/S390XOps.go @@ -381,7 +381,8 @@ func init() { {name: "NOT", argLength: 1, reg: gp11, resultInArg0: true, clobberFlags: true}, // ^arg0 {name: "NOTW", argLength: 1, reg: gp11, resultInArg0: true, clobberFlags: true}, // ^arg0 - {name: "FSQRT", argLength: 1, reg: fp11, asm: "FSQRT"}, // sqrt(arg0) + {name: "FSQRT", argLength: 1, reg: fp11, asm: "FSQRT"}, // sqrt(arg0) + {name: "FSQRTS", argLength: 1, reg: fp11, asm: "FSQRTS"}, // sqrt(arg0), float32 // Conditional register-register moves. // The aux for these values is an s390x.CCMask value representing the condition code mask. diff --git a/src/cmd/compile/internal/ssa/gen/Wasm.rules b/src/cmd/compile/internal/ssa/gen/Wasm.rules index fc45cd3ed5f1d9159a38bfb9513ae53bb7e18d8c..7ad3d1c72e1fb04db56f2b937410dd030de4fea7 100644 --- a/src/cmd/compile/internal/ssa/gen/Wasm.rules +++ b/src/cmd/compile/internal/ssa/gen/Wasm.rules @@ -55,9 +55,9 @@ (ZeroExt32to64 x:(I64Load32U _ _)) => x (ZeroExt16to(64|32) x:(I64Load16U _ _)) => x (ZeroExt8to(64|32|16) x:(I64Load8U _ _)) => x -(SignExt32to64 x) && objabi.GOWASM.SignExt => (I64Extend32S x) -(SignExt8to(64|32|16) x) && objabi.GOWASM.SignExt => (I64Extend8S x) -(SignExt16to(64|32) x) && objabi.GOWASM.SignExt => (I64Extend16S x) +(SignExt32to64 x) && buildcfg.GOWASM.SignExt => (I64Extend32S x) +(SignExt8to(64|32|16) x) && buildcfg.GOWASM.SignExt => (I64Extend8S x) +(SignExt16to(64|32) x) && buildcfg.GOWASM.SignExt => (I64Extend16S x) (SignExt32to64 x) => (I64ShrS (I64Shl x (I64Const [32])) (I64Const [32])) (SignExt16to(64|32) x) => (I64ShrS (I64Shl x (I64Const [48])) (I64Const [48])) (SignExt8to(64|32|16) x) => (I64ShrS (I64Shl x (I64Const [56])) (I64Const [56])) @@ -332,6 +332,8 @@ (Abs ...) => (F64Abs ...) (Copysign ...) => (F64Copysign ...) +(Sqrt32 ...) => (F32Sqrt ...) + (Ctz64 ...) => (I64Ctz ...) (Ctz32 x) => (I64Ctz (I64Or x (I64Const [0x100000000]))) (Ctz16 x) => (I64Ctz (I64Or x (I64Const [0x10000]))) diff --git a/src/cmd/compile/internal/ssa/gen/WasmOps.go b/src/cmd/compile/internal/ssa/gen/WasmOps.go index 36c53bc78c2e645f4257735053e54c2384333744..c92878ca73b844a1a906829ba70b903e48391f90 100644 --- a/src/cmd/compile/internal/ssa/gen/WasmOps.go +++ b/src/cmd/compile/internal/ssa/gen/WasmOps.go @@ -238,13 +238,13 @@ func init() { {name: "I64Extend16S", asm: "I64Extend16S", argLength: 1, reg: gp11, typ: "Int64"}, // sign-extend arg0 from 16 to 64 bit {name: "I64Extend32S", asm: "I64Extend32S", argLength: 1, reg: gp11, typ: "Int64"}, // sign-extend arg0 from 32 to 64 bit - {name: "F32Sqrt", asm: "F32Sqrt", argLength: 1, reg: fp64_11, typ: "Float32"}, // sqrt(arg0) - {name: "F32Trunc", asm: "F32Trunc", argLength: 1, reg: fp64_11, typ: "Float32"}, // trunc(arg0) - {name: "F32Ceil", asm: "F32Ceil", argLength: 1, reg: fp64_11, typ: "Float32"}, // ceil(arg0) - {name: "F32Floor", asm: "F32Floor", argLength: 1, reg: fp64_11, typ: "Float32"}, // floor(arg0) - {name: "F32Nearest", asm: "F32Nearest", argLength: 1, reg: fp64_11, typ: "Float32"}, // round(arg0) - {name: "F32Abs", asm: "F32Abs", argLength: 1, reg: fp64_11, typ: "Float32"}, // abs(arg0) - {name: "F32Copysign", asm: "F32Copysign", argLength: 2, reg: fp64_21, typ: "Float32"}, // copysign(arg0, arg1) + {name: "F32Sqrt", asm: "F32Sqrt", argLength: 1, reg: fp32_11, typ: "Float32"}, // sqrt(arg0) + {name: "F32Trunc", asm: "F32Trunc", argLength: 1, reg: fp32_11, typ: "Float32"}, // trunc(arg0) + {name: "F32Ceil", asm: "F32Ceil", argLength: 1, reg: fp32_11, typ: "Float32"}, // ceil(arg0) + {name: "F32Floor", asm: "F32Floor", argLength: 1, reg: fp32_11, typ: "Float32"}, // floor(arg0) + {name: "F32Nearest", asm: "F32Nearest", argLength: 1, reg: fp32_11, typ: "Float32"}, // round(arg0) + {name: "F32Abs", asm: "F32Abs", argLength: 1, reg: fp32_11, typ: "Float32"}, // abs(arg0) + {name: "F32Copysign", asm: "F32Copysign", argLength: 2, reg: fp32_21, typ: "Float32"}, // copysign(arg0, arg1) {name: "F64Sqrt", asm: "F64Sqrt", argLength: 1, reg: fp64_11, typ: "Float64"}, // sqrt(arg0) {name: "F64Trunc", asm: "F64Trunc", argLength: 1, reg: fp64_11, typ: "Float64"}, // trunc(arg0) diff --git a/src/cmd/compile/internal/ssa/gen/dec.rules b/src/cmd/compile/internal/ssa/gen/dec.rules index 4c677f8418f61b9e5192c05e7290c0015ad27e13..b19489870dde044f2e93753b6cb2a502937ad340 100644 --- a/src/cmd/compile/internal/ssa/gen/dec.rules +++ b/src/cmd/compile/internal/ssa/gen/dec.rules @@ -56,6 +56,7 @@ (SlicePtr (SliceMake ptr _ _ )) => ptr (SliceLen (SliceMake _ len _)) => len (SliceCap (SliceMake _ _ cap)) => cap +(SlicePtrUnchecked (SliceMake ptr _ _ )) => ptr (Load ptr mem) && t.IsSlice() => (SliceMake diff --git a/src/cmd/compile/internal/ssa/gen/dec64.rules b/src/cmd/compile/internal/ssa/gen/dec64.rules index 9297ed8d2e0a04aeedb3d831210704c6f6205307..b0f10d0a0f4e9f21faaceba703ab1579f296495e 100644 --- a/src/cmd/compile/internal/ssa/gen/dec64.rules +++ b/src/cmd/compile/internal/ssa/gen/dec64.rules @@ -42,20 +42,20 @@ (Store {hi.Type} dst hi mem)) // These are not enabled during decomposeBuiltin if late call expansion, but they are always enabled for softFloat -(Arg {n} [off]) && is64BitInt(v.Type) && !config.BigEndian && v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin") => +(Arg {n} [off]) && is64BitInt(v.Type) && !config.BigEndian && v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin") => (Int64Make (Arg {n} [off+4]) (Arg {n} [off])) -(Arg {n} [off]) && is64BitInt(v.Type) && !config.BigEndian && !v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin") => +(Arg {n} [off]) && is64BitInt(v.Type) && !config.BigEndian && !v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin") => (Int64Make (Arg {n} [off+4]) (Arg {n} [off])) -(Arg {n} [off]) && is64BitInt(v.Type) && config.BigEndian && v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin") => +(Arg {n} [off]) && is64BitInt(v.Type) && config.BigEndian && v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin") => (Int64Make (Arg {n} [off]) (Arg {n} [off+4])) -(Arg {n} [off]) && is64BitInt(v.Type) && config.BigEndian && !v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin") => +(Arg {n} [off]) && is64BitInt(v.Type) && config.BigEndian && !v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin") => (Int64Make (Arg {n} [off]) (Arg {n} [off+4])) diff --git a/src/cmd/compile/internal/ssa/gen/decArgs.rules b/src/cmd/compile/internal/ssa/gen/decArgs.rules deleted file mode 100644 index 1c9a0bb23de24b7b9b4be6484050a6a4f5cb21d6..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/ssa/gen/decArgs.rules +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Decompose compound argument values -// Do this early to simplify tracking names for debugging. - -(Arg {n} [off]) && v.Type.IsString() => - (StringMake - (Arg {n} [off]) - (Arg {n} [off+int32(config.PtrSize)])) - -(Arg {n} [off]) && v.Type.IsSlice() => - (SliceMake - (Arg {n} [off]) - (Arg {n} [off+int32(config.PtrSize)]) - (Arg {n} [off+2*int32(config.PtrSize)])) - -(Arg {n} [off]) && v.Type.IsInterface() => - (IMake - (Arg {n} [off]) - (Arg {n} [off+int32(config.PtrSize)])) - -(Arg {n} [off]) && v.Type.IsComplex() && v.Type.Size() == 16 => - (ComplexMake - (Arg {n} [off]) - (Arg {n} [off+8])) - -(Arg {n} [off]) && v.Type.IsComplex() && v.Type.Size() == 8 => - (ComplexMake - (Arg {n} [off]) - (Arg {n} [off+4])) - -(Arg ) && t.IsStruct() && t.NumFields() == 0 && fe.CanSSA(t) => - (StructMake0) -(Arg {n} [off]) && t.IsStruct() && t.NumFields() == 1 && fe.CanSSA(t) => - (StructMake1 - (Arg {n} [off+int32(t.FieldOff(0))])) -(Arg {n} [off]) && t.IsStruct() && t.NumFields() == 2 && fe.CanSSA(t) => - (StructMake2 - (Arg {n} [off+int32(t.FieldOff(0))]) - (Arg {n} [off+int32(t.FieldOff(1))])) -(Arg {n} [off]) && t.IsStruct() && t.NumFields() == 3 && fe.CanSSA(t) => - (StructMake3 - (Arg {n} [off+int32(t.FieldOff(0))]) - (Arg {n} [off+int32(t.FieldOff(1))]) - (Arg {n} [off+int32(t.FieldOff(2))])) -(Arg {n} [off]) && t.IsStruct() && t.NumFields() == 4 && fe.CanSSA(t) => - (StructMake4 - (Arg {n} [off+int32(t.FieldOff(0))]) - (Arg {n} [off+int32(t.FieldOff(1))]) - (Arg {n} [off+int32(t.FieldOff(2))]) - (Arg {n} [off+int32(t.FieldOff(3))])) - -(Arg ) && t.IsArray() && t.NumElem() == 0 => - (ArrayMake0) -(Arg {n} [off]) && t.IsArray() && t.NumElem() == 1 && fe.CanSSA(t) => - (ArrayMake1 (Arg {n} [off])) diff --git a/src/cmd/compile/internal/ssa/gen/decArgsOps.go b/src/cmd/compile/internal/ssa/gen/decArgsOps.go deleted file mode 100644 index b73d9d39761021c4214009db0e9bd2d88636f1e9..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/ssa/gen/decArgsOps.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build ignore - -package main - -var decArgsOps = []opData{} - -var decArgsBlocks = []blockData{} - -func init() { - archs = append(archs, arch{ - name: "decArgs", - ops: decArgsOps, - blocks: decArgsBlocks, - generic: true, - }) -} diff --git a/src/cmd/compile/internal/ssa/gen/generic.rules b/src/cmd/compile/internal/ssa/gen/generic.rules index 1784923224d2b48d12ae0e1a4a961dcc147728fa..5cbc70cf41d1c68c928c203c9fc77c53baa8882d 100644 --- a/src/cmd/compile/internal/ssa/gen/generic.rules +++ b/src/cmd/compile/internal/ssa/gen/generic.rules @@ -768,7 +768,7 @@ => mem // Collapse OffPtr -(OffPtr (OffPtr p [b]) [a]) => (OffPtr p [a+b]) +(OffPtr (OffPtr p [y]) [x]) => (OffPtr p [x+y]) (OffPtr p [0]) && v.Type.Compare(p.Type) == types.CMPeq => p // indexing operations @@ -847,7 +847,7 @@ f0 mem)))) // Putting struct{*byte} and similar into direct interfaces. -(IMake typ (StructMake1 val)) => (IMake typ val) +(IMake _typ (StructMake1 val)) => (IMake _typ val) (StructSelect [0] (IData x)) => (IData x) // un-SSAable values use mem->mem copies @@ -869,7 +869,7 @@ (Store dst (ArrayMake1 e) mem) => (Store {e.Type} dst e mem) // Putting [1]*byte and similar into direct interfaces. -(IMake typ (ArrayMake1 val)) => (IMake typ val) +(IMake _typ (ArrayMake1 val)) => (IMake _typ val) (ArraySelect [0] (IData x)) => (IData x) // string ops @@ -1968,43 +1968,15 @@ (Div32F x (Const32F [c])) && reciprocalExact32(c) => (Mul32F x (Const32F [1/c])) (Div64F x (Const64F [c])) && reciprocalExact64(c) => (Mul64F x (Const64F [1/c])) -(Sqrt (Const64F [c])) && !math.IsNaN(math.Sqrt(c)) => (Const64F [math.Sqrt(c)]) +// rewrite single-precision sqrt expression "float32(math.Sqrt(float64(x)))" +(Cvt64Fto32F sqrt0:(Sqrt (Cvt32Fto64F x))) && sqrt0.Uses==1 => (Sqrt32 x) -// recognize runtime.newobject and don't Zero/Nilcheck it -(Zero (Load (OffPtr [c] (SP)) mem) mem) - && mem.Op == OpStaticCall - && isSameCall(mem.Aux, "runtime.newobject") - && c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value - => mem -(Store (Load (OffPtr [c] (SP)) mem) x mem) - && isConstZero(x) - && mem.Op == OpStaticCall - && isSameCall(mem.Aux, "runtime.newobject") - && c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value - => mem -(Store (OffPtr (Load (OffPtr [c] (SP)) mem)) x mem) - && isConstZero(x) - && mem.Op == OpStaticCall - && isSameCall(mem.Aux, "runtime.newobject") - && c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value - => mem -// nil checks just need to rewrite to something useless. -// they will be deadcode eliminated soon afterwards. -(NilCheck (Load (OffPtr [c] (SP)) (StaticCall {sym} _)) _) - && isSameCall(sym, "runtime.newobject") - && c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value - && warnRule(fe.Debug_checknil(), v, "removed nil check") - => (Invalid) -(NilCheck (OffPtr (Load (OffPtr [c] (SP)) (StaticCall {sym} _))) _) - && isSameCall(sym, "runtime.newobject") - && c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value - && warnRule(fe.Debug_checknil(), v, "removed nil check") - => (Invalid) +(Sqrt (Const64F [c])) && !math.IsNaN(math.Sqrt(c)) => (Const64F [math.Sqrt(c)]) // for rewriting results of some late-expanded rewrites (below) -(SelectN [0] (MakeResult a ___)) => a -(SelectN [1] (MakeResult a b ___)) => b -(SelectN [2] (MakeResult a b c ___)) => c +(SelectN [0] (MakeResult x ___)) => x +(SelectN [1] (MakeResult x y ___)) => y +(SelectN [2] (MakeResult x y z ___)) => z // for late-expanded calls, recognize newobject and remove zeroing and nilchecks (Zero (SelectN [0] call:(StaticLECall _ _)) mem:(SelectN [1] call)) @@ -2021,12 +1993,12 @@ && isSameCall(call.Aux, "runtime.newobject") => mem -(NilCheck (SelectN [0] call:(StaticLECall _ _)) (SelectN [1] call)) +(NilCheck (SelectN [0] call:(StaticLECall _ _)) _) && isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check") => (Invalid) -(NilCheck (OffPtr (SelectN [0] call:(StaticLECall _ _))) (SelectN [1] call)) +(NilCheck (OffPtr (SelectN [0] call:(StaticLECall _ _))) _) && isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check") => (Invalid) @@ -2041,18 +2013,18 @@ // Evaluate constant address comparisons. (EqPtr x x) => (ConstBool [true]) (NeqPtr x x) => (ConstBool [false]) -(EqPtr (Addr {a} _) (Addr {b} _)) => (ConstBool [a == b]) -(EqPtr (Addr {a} _) (OffPtr [o] (Addr {b} _))) => (ConstBool [a == b && o == 0]) -(EqPtr (OffPtr [o1] (Addr {a} _)) (OffPtr [o2] (Addr {b} _))) => (ConstBool [a == b && o1 == o2]) -(NeqPtr (Addr {a} _) (Addr {b} _)) => (ConstBool [a != b]) -(NeqPtr (Addr {a} _) (OffPtr [o] (Addr {b} _))) => (ConstBool [a != b || o != 0]) -(NeqPtr (OffPtr [o1] (Addr {a} _)) (OffPtr [o2] (Addr {b} _))) => (ConstBool [a != b || o1 != o2]) -(EqPtr (LocalAddr {a} _ _) (LocalAddr {b} _ _)) => (ConstBool [a == b]) -(EqPtr (LocalAddr {a} _ _) (OffPtr [o] (LocalAddr {b} _ _))) => (ConstBool [a == b && o == 0]) -(EqPtr (OffPtr [o1] (LocalAddr {a} _ _)) (OffPtr [o2] (LocalAddr {b} _ _))) => (ConstBool [a == b && o1 == o2]) -(NeqPtr (LocalAddr {a} _ _) (LocalAddr {b} _ _)) => (ConstBool [a != b]) -(NeqPtr (LocalAddr {a} _ _) (OffPtr [o] (LocalAddr {b} _ _))) => (ConstBool [a != b || o != 0]) -(NeqPtr (OffPtr [o1] (LocalAddr {a} _ _)) (OffPtr [o2] (LocalAddr {b} _ _))) => (ConstBool [a != b || o1 != o2]) +(EqPtr (Addr {x} _) (Addr {y} _)) => (ConstBool [x == y]) +(EqPtr (Addr {x} _) (OffPtr [o] (Addr {y} _))) => (ConstBool [x == y && o == 0]) +(EqPtr (OffPtr [o1] (Addr {x} _)) (OffPtr [o2] (Addr {y} _))) => (ConstBool [x == y && o1 == o2]) +(NeqPtr (Addr {x} _) (Addr {y} _)) => (ConstBool [x != y]) +(NeqPtr (Addr {x} _) (OffPtr [o] (Addr {y} _))) => (ConstBool [x != y || o != 0]) +(NeqPtr (OffPtr [o1] (Addr {x} _)) (OffPtr [o2] (Addr {y} _))) => (ConstBool [x != y || o1 != o2]) +(EqPtr (LocalAddr {x} _ _) (LocalAddr {y} _ _)) => (ConstBool [x == y]) +(EqPtr (LocalAddr {x} _ _) (OffPtr [o] (LocalAddr {y} _ _))) => (ConstBool [x == y && o == 0]) +(EqPtr (OffPtr [o1] (LocalAddr {x} _ _)) (OffPtr [o2] (LocalAddr {y} _ _))) => (ConstBool [x == y && o1 == o2]) +(NeqPtr (LocalAddr {x} _ _) (LocalAddr {y} _ _)) => (ConstBool [x != y]) +(NeqPtr (LocalAddr {x} _ _) (OffPtr [o] (LocalAddr {y} _ _))) => (ConstBool [x != y || o != 0]) +(NeqPtr (OffPtr [o1] (LocalAddr {x} _ _)) (OffPtr [o2] (LocalAddr {y} _ _))) => (ConstBool [x != y || o1 != o2]) (EqPtr (OffPtr [o1] p1) p2) && isSamePtr(p1, p2) => (ConstBool [o1 == 0]) (NeqPtr (OffPtr [o1] p1) p2) && isSamePtr(p1, p2) => (ConstBool [o1 != 0]) (EqPtr (OffPtr [o1] p1) (OffPtr [o2] p2)) && isSamePtr(p1, p2) => (ConstBool [o1 == o2]) @@ -2085,32 +2057,39 @@ // Inline small or disjoint runtime.memmove calls with constant length. // See the comment in op Move in genericOps.go for discussion of the type. -(StaticCall {sym} s1:(Store _ (Const(64|32) [sz]) s2:(Store _ src s3:(Store {t} _ dst mem)))) + +// Because expand calls runs after prove, constants useful to this pattern may not appear. +// Both versions need to exist; the memory and register variants. +// +// Match post-expansion calls, memory version. +(SelectN [0] call:(StaticCall {sym} s1:(Store _ (Const(64|32) [sz]) s2:(Store _ src s3:(Store {t} _ dst mem))))) && sz >= 0 && isSameCall(sym, "runtime.memmove") - && t.IsPtr() // avoids TUINTPTR, see issue 30061 + && t.IsPtr() // avoids TUNSAFEPTR, see issue 30061 && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) - && clobber(s1, s2, s3) + && clobber(s1, s2, s3, call) => (Move {t.Elem()} [int64(sz)] dst src mem) -// Inline small or disjoint runtime.memmove calls with constant length. -// See the comment in op Move in genericOps.go for discussion of the type. -(SelectN [0] call:(StaticLECall {sym} dst src (Const(64|32) [sz]) mem)) +// Match post-expansion calls, register version. +(SelectN [0] call:(StaticCall {sym} dst src (Const(64|32) [sz]) mem)) && sz >= 0 && call.Uses == 1 // this will exclude all calls with results && isSameCall(sym, "runtime.memmove") - && dst.Type.IsPtr() // avoids TUINTPTR, see issue 30061 + && dst.Type.IsPtr() // avoids TUNSAFEPTR, see issue 30061 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(call) => (Move {dst.Type.Elem()} [int64(sz)] dst src mem) -// De-virtualize interface calls into static calls. -// Note that (ITab (IMake)) doesn't get -// rewritten until after the first opt pass, -// so this rule should trigger reliably. -(InterCall [argsize] {auxCall} (Load (OffPtr [off] (ITab (IMake (Addr {itab} (SB)) _))) _) mem) && devirt(v, auxCall, itab, off) != nil => - (StaticCall [int32(argsize)] {devirt(v, auxCall, itab, off)} mem) +// Match pre-expansion calls. +(SelectN [0] call:(StaticLECall {sym} dst src (Const(64|32) [sz]) mem)) + && sz >= 0 + && call.Uses == 1 // this will exclude all calls with results + && isSameCall(sym, "runtime.memmove") + && dst.Type.IsPtr() // avoids TUNSAFEPTR, see issue 30061 + && isInlinableMemmove(dst, src, int64(sz), config) + && clobber(call) + => (Move {dst.Type.Elem()} [int64(sz)] dst src mem) // De-virtualize late-expanded interface calls into late-expanded static calls. // Note that (ITab (IMake)) doesn't get rewritten until after the first opt pass, @@ -2499,8 +2478,8 @@ (Store {t5} (OffPtr [o5] dst) d4 (Zero {t1} [n] dst mem))))) -// TODO this does not fire before call expansion; is that acceptable? -(StaticCall {sym} x) && needRaceCleanup(sym, v) => x +(SelectN [0] call:(StaticLECall {sym} a x)) && needRaceCleanup(sym, call) && clobber(call) => x +(SelectN [0] call:(StaticLECall {sym} x)) && needRaceCleanup(sym, call) && clobber(call) => x // Collapse moving A -> B -> C into just A -> C. // Later passes (deadstore, elim unread auto) will remove the A -> B move, if possible. diff --git a/src/cmd/compile/internal/ssa/gen/genericOps.go b/src/cmd/compile/internal/ssa/gen/genericOps.go index 8cfda35c22535175966dffb790fec702822a1db7..9f6664386c9d1749e51c4d6bd3a29c6fecd7d37d 100644 --- a/src/cmd/compile/internal/ssa/gen/genericOps.go +++ b/src/cmd/compile/internal/ssa/gen/genericOps.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore package main @@ -257,13 +258,14 @@ var genericOps = []opData{ {name: "RotateLeft32", argLength: 2}, // Rotate bits in arg[0] left by arg[1] {name: "RotateLeft64", argLength: 2}, // Rotate bits in arg[0] left by arg[1] - // Square root, float64 only. + // Square root. // Special cases: // +∞ → +∞ // ±0 → ±0 (sign preserved) // x<0 → NaN // NaN → NaN - {name: "Sqrt", argLength: 1}, // √arg0 + {name: "Sqrt", argLength: 1}, // √arg0 (floating point, double precision) + {name: "Sqrt32", argLength: 1}, // √arg0 (floating point, single precision) // Round to integer, float64 only. // Special cases: @@ -332,6 +334,11 @@ var genericOps = []opData{ {name: "InitMem", zeroWidth: true}, // memory input to the function. {name: "Arg", aux: "SymOff", symEffect: "Read", zeroWidth: true}, // argument to the function. aux=GCNode of arg, off = offset in that arg. + // Like Arg, these are generic ops that survive lowering. AuxInt is a register index, and the actual output register for each index is defined by the architecture. + // AuxInt = integer argument index (not a register number). ABI-specified spill loc obtained from function + {name: "ArgIntReg", aux: "NameOffsetInt8", zeroWidth: true}, // argument to the function in an int reg. + {name: "ArgFloatReg", aux: "NameOffsetInt8", zeroWidth: true}, // argument to the function in a float reg. + // The address of a variable. arg0 is the base pointer. // If the variable is a global, the base pointer will be SB and // the Aux field will be a *obj.LSym. @@ -389,9 +396,28 @@ var genericOps = []opData{ // TODO(josharian): ClosureCall and InterCall should have Int32 aux // to match StaticCall's 32 bit arg size limit. // TODO(drchase,josharian): could the arg size limit be bundled into the rules for CallOff? - {name: "ClosureCall", argLength: 3, aux: "CallOff", call: true}, // arg0=code pointer, arg1=context ptr, arg2=memory. auxint=arg size. Returns memory. - {name: "StaticCall", argLength: 1, aux: "CallOff", call: true}, // call function aux.(*obj.LSym), arg0=memory. auxint=arg size. Returns memory. - {name: "InterCall", argLength: 2, aux: "CallOff", call: true}, // interface call. arg0=code pointer, arg1=memory, auxint=arg size. Returns memory. + + // Before lowering, LECalls receive their fixed inputs (first), memory (last), + // and a variable number of input values in the middle. + // They produce a variable number of result values. + // These values are not necessarily "SSA-able"; they can be too large, + // but in that case inputs are loaded immediately before with OpDereference, + // and outputs are stored immediately with OpStore. + // + // After call expansion, Calls have the same fixed-middle-memory arrangement of inputs, + // with the difference that the "middle" is only the register-resident inputs, + // and the non-register inputs are instead stored at ABI-defined offsets from SP + // (and the stores thread through the memory that is ultimately an input to the call). + // Outputs follow a similar pattern; register-resident outputs are the leading elements + // of a Result-typed output, with memory last, and any memory-resident outputs have been + // stored to ABI-defined locations. Each non-memory input or output fits in a register. + // + // Subsequent architecture-specific lowering only changes the opcode. + + {name: "ClosureCall", argLength: -1, aux: "CallOff", call: true}, // arg0=code pointer, arg1=context ptr, arg2..argN-1 are register inputs, argN=memory. auxint=arg size. Returns Result of register results, plus memory. + {name: "StaticCall", argLength: -1, aux: "CallOff", call: true}, // call function aux.(*obj.LSym), arg0..argN-1 are register inputs, argN=memory. auxint=arg size. Returns Result of register results, plus memory. + {name: "InterCall", argLength: -1, aux: "CallOff", call: true}, // interface call. arg0=code pointer, arg1..argN-1 are register inputs, argN=memory, auxint=arg size. Returns Result of register results, plus memory. + {name: "ClosureLECall", argLength: -1, aux: "CallOff", call: true}, // late-expanded closure call. arg0=code pointer, arg1=context ptr, arg2..argN-1 are inputs, argN is mem. auxint = arg size. Result is tuple of result(s), plus mem. {name: "StaticLECall", argLength: -1, aux: "CallOff", call: true}, // late-expanded static call function aux.(*ssa.AuxCall.Fn). arg0..argN-1 are inputs, argN is mem. auxint = arg size. Result is tuple of result(s), plus mem. {name: "InterLECall", argLength: -1, aux: "CallOff", call: true}, // late-expanded interface call. arg0=code pointer, arg1..argN-1 are inputs, argN is mem. auxint = arg size. Result is tuple of result(s), plus mem. @@ -453,6 +479,10 @@ var genericOps = []opData{ {name: "SlicePtr", argLength: 1, typ: "BytePtr"}, // ptr(arg0) {name: "SliceLen", argLength: 1}, // len(arg0) {name: "SliceCap", argLength: 1}, // cap(arg0) + // SlicePtrUnchecked, like SlicePtr, extracts the pointer from a slice. + // SlicePtr values are assumed non-nil, because they are guarded by bounds checks. + // SlicePtrUnchecked values can be nil. + {name: "SlicePtrUnchecked", argLength: 1}, // Complex (part/whole) {name: "ComplexMake", argLength: 2}, // arg0=real, arg1=imag @@ -587,6 +617,7 @@ var genericOps = []opData{ // Clobber experiment op {name: "Clobber", argLength: 0, typ: "Void", aux: "SymOff", symEffect: "None"}, // write an invalid pointer value to the given pointer slot of a stack variable + {name: "ClobberReg", argLength: 0, typ: "Void"}, // clobber a register } // kind controls successors implicit exit diff --git a/src/cmd/compile/internal/ssa/gen/main.go b/src/cmd/compile/internal/ssa/gen/main.go index dfa146a28ae2579451477e0fc25b415196a8583e..8e5997b25a447339b1bd80b1604a772e2ef82b16 100644 --- a/src/cmd/compile/internal/ssa/gen/main.go +++ b/src/cmd/compile/internal/ssa/gen/main.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore // The gen command generates Go code (in the parent directory) for all @@ -30,21 +31,23 @@ import ( // apart from type names, and avoid awkward func parameters like "arch arch". type arch struct { - name string - pkg string // obj package to import for this arch. - genfile string // source file containing opcode code generation. - ops []opData - blocks []blockData - regnames []string - gpregmask regMask - fpregmask regMask - fp32regmask regMask - fp64regmask regMask - specialregmask regMask - framepointerreg int8 - linkreg int8 - generic bool - imports []string + name string + pkg string // obj package to import for this arch. + genfile string // source file containing opcode code generation. + ops []opData + blocks []blockData + regnames []string + ParamIntRegNames string + ParamFloatRegNames string + gpregmask regMask + fpregmask regMask + fp32regmask regMask + fp64regmask regMask + specialregmask regMask + framepointerreg int8 + linkreg int8 + generic bool + imports []string } type opData struct { @@ -63,7 +66,6 @@ type opData struct { nilCheck bool // this op is a nil check on arg0 faultOnNilArg0 bool // this op will fault if arg0 is nil (and aux encodes a small offset) faultOnNilArg1 bool // this op will fault if arg1 is nil (and aux encodes a small offset) - usesScratch bool // this op requires scratch memory space hasSideEffects bool // for "reasons", not to be eliminated. E.g., atomic store, #19182. zeroWidth bool // op never translates into any machine code. example: copy, which may sometimes translate to machine code, is not zero-width. unsafePoint bool // this op is an unsafe point, i.e. not safe for async preemption @@ -320,9 +322,6 @@ func genOp() { log.Fatalf("faultOnNilArg1 with aux %s not allowed", v.aux) } } - if v.usesScratch { - fmt.Fprintln(w, "usesScratch: true,") - } if v.hasSideEffects { fmt.Fprintln(w, "hasSideEffects: true,") } @@ -404,12 +403,11 @@ func genOp() { // generate op string method fmt.Fprintln(w, "func (o Op) String() string {return opcodeTable[o].name }") - fmt.Fprintln(w, "func (o Op) UsesScratch() bool { return opcodeTable[o].usesScratch }") - fmt.Fprintln(w, "func (o Op) SymEffect() SymEffect { return opcodeTable[o].symEffect }") fmt.Fprintln(w, "func (o Op) IsCall() bool { return opcodeTable[o].call }") fmt.Fprintln(w, "func (o Op) HasSideEffects() bool { return opcodeTable[o].hasSideEffects }") fmt.Fprintln(w, "func (o Op) UnsafePoint() bool { return opcodeTable[o].unsafePoint }") + fmt.Fprintln(w, "func (o Op) ResultInArg0() bool { return opcodeTable[o].resultInArg0 }") // generate registers for _, a := range archs { @@ -418,7 +416,9 @@ func genOp() { } fmt.Fprintf(w, "var registers%s = [...]Register {\n", a.name) var gcRegN int + num := map[string]int8{} for i, r := range a.regnames { + num[r] = int8(i) pkg := a.pkg[len("cmd/internal/obj/"):] var objname string // name in cmd/internal/obj/$ARCH switch r { @@ -441,11 +441,38 @@ func genOp() { } fmt.Fprintf(w, " {%d, %s, %d, \"%s\"},\n", i, objname, gcRegIdx, r) } + parameterRegisterList := func(paramNamesString string) []int8 { + paramNamesString = strings.TrimSpace(paramNamesString) + if paramNamesString == "" { + return nil + } + paramNames := strings.Split(paramNamesString, " ") + var paramRegs []int8 + for _, regName := range paramNames { + if regName == "" { + // forgive extra spaces + continue + } + if regNum, ok := num[regName]; ok { + paramRegs = append(paramRegs, regNum) + delete(num, regName) + } else { + log.Fatalf("parameter register %s for architecture %s not a register name (or repeated in parameter list)", regName, a.name) + } + } + return paramRegs + } + + paramIntRegs := parameterRegisterList(a.ParamIntRegNames) + paramFloatRegs := parameterRegisterList(a.ParamFloatRegNames) + if gcRegN > 32 { // Won't fit in a uint32 mask. log.Fatalf("too many GC registers (%d > 32) on %s", gcRegN, a.name) } fmt.Fprintln(w, "}") + fmt.Fprintf(w, "var paramIntReg%s = %#v\n", a.name, paramIntRegs) + fmt.Fprintf(w, "var paramFloatReg%s = %#v\n", a.name, paramFloatRegs) fmt.Fprintf(w, "var gpRegMask%s = regMask(%d)\n", a.name, a.gpregmask) fmt.Fprintf(w, "var fpRegMask%s = regMask(%d)\n", a.name, a.fpregmask) if a.fp32regmask != 0 { diff --git a/src/cmd/compile/internal/ssa/gen/rulegen.go b/src/cmd/compile/internal/ssa/gen/rulegen.go index aaf9101368fcf6e75315c678ca898facfa5c0122..fe8db4ed1f251898e7e4c5a449749a9ec263365c 100644 --- a/src/cmd/compile/internal/ssa/gen/rulegen.go +++ b/src/cmd/compile/internal/ssa/gen/rulegen.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build gen // +build gen // This program generates Go code that applies rewrite rules to a Value. @@ -194,7 +195,9 @@ func genRulesSuffix(arch arch, suff string) { swc.add(stmtf("return rewriteValue%s%s_%s(v)", arch.name, suff, op)) sw.add(swc) } - fn.add(sw) + if len(sw.List) > 0 { // skip if empty + fn.add(sw) + } fn.add(stmtf("return false")) genFile.add(fn) @@ -215,10 +218,10 @@ func genRulesSuffix(arch arch, suff string) { Suffix: fmt.Sprintf("_%s", op), ArgLen: opByName(arch, op).argLength, } - fn.add(declf("b", "v.Block")) - fn.add(declf("config", "b.Func.Config")) - fn.add(declf("fe", "b.Func.fe")) - fn.add(declf("typ", "&b.Func.Config.Types")) + fn.add(declReserved("b", "v.Block")) + fn.add(declReserved("config", "b.Func.Config")) + fn.add(declReserved("fe", "b.Func.fe")) + fn.add(declReserved("typ", "&b.Func.Config.Types")) for _, rule := range rules { if rr != nil && !rr.CanFail { log.Fatalf("unconditional rule %s is followed by other rules", rr.Match) @@ -247,8 +250,8 @@ func genRulesSuffix(arch arch, suff string) { // Generate block rewrite function. There are only a few block types // so we can make this one function with a switch. fn = &Func{Kind: "Block"} - fn.add(declf("config", "b.Func.Config")) - fn.add(declf("typ", "&b.Func.Config.Types")) + fn.add(declReserved("config", "b.Func.Config")) + fn.add(declReserved("typ", "&b.Func.Config.Types")) sw = &Switch{Expr: exprf("b.Kind")} ops = ops[:0] @@ -264,7 +267,9 @@ func genRulesSuffix(arch arch, suff string) { } sw.add(swc) } - fn.add(sw) + if len(sw.List) > 0 { // skip if empty + fn.add(sw) + } fn.add(stmtf("return false")) genFile.add(fn) @@ -579,9 +584,10 @@ func fprint(w io.Writer, n Node) { fmt.Fprintf(w, "\npackage ssa\n") for _, path := range append([]string{ "fmt", + "internal/buildcfg", "math", "cmd/internal/obj", - "cmd/internal/objabi", + "cmd/compile/internal/base", "cmd/compile/internal/types", }, n.Arch.imports...) { fmt.Fprintf(w, "import %q\n", path) @@ -822,12 +828,36 @@ func stmtf(format string, a ...interface{}) Statement { return file.Decls[0].(*ast.FuncDecl).Body.List[0] } -// declf constructs a simple "name := value" declaration, using exprf for its -// value. -func declf(name, format string, a ...interface{}) *Declare { +var reservedNames = map[string]bool{ + "v": true, // Values[i], etc + "b": true, // v.Block + "config": true, // b.Func.Config + "fe": true, // b.Func.fe + "typ": true, // &b.Func.Config.Types +} + +// declf constructs a simple "name := value" declaration, +// using exprf for its value. +// +// name must not be one of reservedNames. +// This helps prevent unintended shadowing and name clashes. +// To declare a reserved name, use declReserved. +func declf(loc, name, format string, a ...interface{}) *Declare { + if reservedNames[name] { + log.Fatalf("rule %s uses the reserved name %s", loc, name) + } return &Declare{name, exprf(format, a...)} } +// declReserved is like declf, but the name must be one of reservedNames. +// Calls to declReserved should generally be static and top-level. +func declReserved(name, value string) *Declare { + if !reservedNames[name] { + panic(fmt.Sprintf("declReserved call does not use a reserved name: %q", name)) + } + return &Declare{name, exprf(value)} +} + // breakf constructs a simple "if cond { break }" statement, using exprf for its // condition. func breakf(format string, a ...interface{}) *CondBreak { @@ -852,7 +882,7 @@ func genBlockRewrite(rule Rule, arch arch, data blockData) *RuleRewrite { if vname == "" { vname = fmt.Sprintf("v_%v", i) } - rr.add(declf(vname, cname)) + rr.add(declf(rr.Loc, vname, cname)) p, op := genMatch0(rr, arch, expr, vname, nil, false) // TODO: pass non-nil cnt? if op != "" { check := fmt.Sprintf("%s.Op == %s", cname, op) @@ -867,7 +897,7 @@ func genBlockRewrite(rule Rule, arch arch, data blockData) *RuleRewrite { } pos[i] = p } else { - rr.add(declf(arg, cname)) + rr.add(declf(rr.Loc, arg, cname)) pos[i] = arg + ".Pos" } } @@ -887,7 +917,7 @@ func genBlockRewrite(rule Rule, arch arch, data blockData) *RuleRewrite { if !token.IsIdentifier(e.name) || rr.declared(e.name) { rr.add(breakf("%sTo%s(b.%s) != %s", unTitle(e.field), title(e.dclType), e.field, e.name)) } else { - rr.add(declf(e.name, "%sTo%s(b.%s)", unTitle(e.field), title(e.dclType), e.field)) + rr.add(declf(rr.Loc, e.name, "%sTo%s(b.%s)", unTitle(e.field), title(e.dclType), e.field)) } } if rr.Cond != "" { @@ -1037,11 +1067,11 @@ func genMatch0(rr *RuleRewrite, arch arch, match, v string, cnt map[string]int, } else { switch e.field { case "Aux": - rr.add(declf(e.name, "auxTo%s(%s.%s)", title(e.dclType), v, e.field)) + rr.add(declf(rr.Loc, e.name, "auxTo%s(%s.%s)", title(e.dclType), v, e.field)) case "AuxInt": - rr.add(declf(e.name, "auxIntTo%s(%s.%s)", title(e.dclType), v, e.field)) + rr.add(declf(rr.Loc, e.name, "auxIntTo%s(%s.%s)", title(e.dclType), v, e.field)) case "Type": - rr.add(declf(e.name, "%s.%s", v, e.field)) + rr.add(declf(rr.Loc, e.name, "%s.%s", v, e.field)) } } } @@ -1071,7 +1101,7 @@ func genMatch0(rr *RuleRewrite, arch arch, match, v string, cnt map[string]int, continue } if !rr.declared(a) && token.IsIdentifier(a) && !(commutative && len(args) == 2) { - rr.add(declf(a, "%s.Args[%d]", v, n)) + rr.add(declf(rr.Loc, a, "%s.Args[%d]", v, n)) // delete the last argument so it is not reprocessed args = args[:n] } else { @@ -1083,7 +1113,7 @@ func genMatch0(rr *RuleRewrite, arch arch, match, v string, cnt map[string]int, if commutative && !pregenTop { for i := 0; i <= 1; i++ { vname := fmt.Sprintf("%s_%d", v, i) - rr.add(declf(vname, "%s.Args[%d]", v, i)) + rr.add(declf(rr.Loc, vname, "%s.Args[%d]", v, i)) } } if commutative { @@ -1110,7 +1140,7 @@ func genMatch0(rr *RuleRewrite, arch arch, match, v string, cnt map[string]int, rr.add(breakf("%s != %s", arg, rhs)) } else { if arg != rhs { - rr.add(declf(arg, "%s", rhs)) + rr.add(declf(rr.Loc, arg, "%s", rhs)) } } continue @@ -1125,7 +1155,7 @@ func genMatch0(rr *RuleRewrite, arch arch, match, v string, cnt map[string]int, } if argname != rhs { - rr.add(declf(argname, "%s", rhs)) + rr.add(declf(rr.Loc, argname, "%s", rhs)) } bexpr := exprf("%s.Op != addLater", argname) rr.add(&CondBreak{Cond: bexpr}) @@ -1202,7 +1232,7 @@ func genResult0(rr *RuleRewrite, arch arch, result string, top, move bool, pos s v = resname } rr.Alloc++ - rr.add(declf(v, "b.NewValue0(%s, Op%s%s, %s)", pos, oparch, op.name, typ)) + rr.add(declf(rr.Loc, v, "b.NewValue0(%s, Op%s%s, %s)", pos, oparch, op.name, typ)) if move && top { // Rewrite original into a copy rr.add(stmtf("v.copyOf(%s)", v)) diff --git a/src/cmd/compile/internal/ssa/html.go b/src/cmd/compile/internal/ssa/html.go index c06b5808e1c00296653f87cfd56a680c4a22c9b4..4d191199fbafe1b3f5f61882a40aced0e836cf36 100644 --- a/src/cmd/compile/internal/ssa/html.go +++ b/src/cmd/compile/internal/ssa/html.go @@ -1064,7 +1064,7 @@ func (f *Func) HTML(phase string, dot *dotWriter) string { p := htmlFuncPrinter{w: buf} fprintFunc(p, f) - // fprintFunc(&buf, f) // TODO: HTML, not text,
    for line breaks, etc. + // fprintFunc(&buf, f) // TODO: HTML, not text,
    for line breaks, etc. fmt.Fprint(buf, "
    ") return buf.String() } diff --git a/src/cmd/compile/internal/ssa/layout.go b/src/cmd/compile/internal/ssa/layout.go index 30b7b97d040cdb040ab12b99c520ec27cac3bca4..6abdb0d0c92a82f5fcdb4ce4e4991a803a0c18e6 100644 --- a/src/cmd/compile/internal/ssa/layout.go +++ b/src/cmd/compile/internal/ssa/layout.go @@ -12,26 +12,10 @@ func layout(f *Func) { } // Register allocation may use a different order which has constraints -// imposed by the linear-scan algorithm. Note that f.pass here is -// regalloc, so the switch is conditional on -d=ssa/regalloc/test=N +// imposed by the linear-scan algorithm. func layoutRegallocOrder(f *Func) []*Block { - - switch f.pass.test { - case 0: // layout order - return layoutOrder(f) - case 1: // existing block order - return f.Blocks - case 2: // reverse of postorder; legal, but usually not good. - po := f.postorder() - visitOrder := make([]*Block, len(po)) - for i, b := range po { - j := len(po) - i - 1 - visitOrder[j] = b - } - return visitOrder - } - - return nil + // remnant of an experiment; perhaps there will be another. + return layoutOrder(f) } func layoutOrder(f *Func) []*Block { @@ -41,8 +25,13 @@ func layoutOrder(f *Func) []*Block { indegree := make([]int, f.NumBlocks()) posdegree := f.newSparseSet(f.NumBlocks()) // blocks with positive remaining degree defer f.retSparseSet(posdegree) - zerodegree := f.newSparseSet(f.NumBlocks()) // blocks with zero remaining degree - defer f.retSparseSet(zerodegree) + // blocks with zero remaining degree. Use slice to simulate a LIFO queue to implement + // the depth-first topology sorting algorithm. + var zerodegree []ID + // LIFO queue. Track the successor blocks of the scheduled block so that when we + // encounter loops, we choose to schedule the successor block of the most recently + // scheduled block. + var succs []ID exit := f.newSparseSet(f.NumBlocks()) // exit blocks defer f.retSparseSet(exit) @@ -88,7 +77,8 @@ func layoutOrder(f *Func) []*Block { } indegree[b.ID] = len(b.Preds) if len(b.Preds) == 0 { - zerodegree.add(b.ID) + // Push an element to the tail of the queue. + zerodegree = append(zerodegree, b.ID) } else { posdegree.add(b.ID) } @@ -105,12 +95,24 @@ blockloop: break } - for _, e := range b.Succs { - c := e.b + // Here, the order of traversing the b.Succs affects the direction in which the topological + // sort advances in depth. Take the following cfg as an example, regardless of other factors. + // b1 + // 0/ \1 + // b2 b3 + // Traverse b.Succs in order, the right child node b3 will be scheduled immediately after + // b1, traverse b.Succs in reverse order, the left child node b2 will be scheduled + // immediately after b1. The test results show that reverse traversal performs a little + // better. + // Note: You need to consider both layout and register allocation when testing performance. + for i := len(b.Succs) - 1; i >= 0; i-- { + c := b.Succs[i].b indegree[c.ID]-- if indegree[c.ID] == 0 { posdegree.remove(c.ID) - zerodegree.add(c.ID) + zerodegree = append(zerodegree, c.ID) + } else { + succs = append(succs, c.ID) } } @@ -132,30 +134,30 @@ blockloop: // Use degree for now. bid = 0 - mindegree := f.NumBlocks() - for _, e := range order[len(order)-1].Succs { - c := e.b - if scheduled[c.ID] || c.Kind == BlockExit { - continue - } - if indegree[c.ID] < mindegree { - mindegree = indegree[c.ID] - bid = c.ID - } - } - if bid != 0 { - continue - } // TODO: improve this part // No successor of the previously scheduled block works. // Pick a zero-degree block if we can. - for zerodegree.size() > 0 { - cid := zerodegree.pop() + for len(zerodegree) > 0 { + // Pop an element from the tail of the queue. + cid := zerodegree[len(zerodegree)-1] + zerodegree = zerodegree[:len(zerodegree)-1] + if !scheduled[cid] { + bid = cid + continue blockloop + } + } + + // Still nothing, pick the unscheduled successor block encountered most recently. + for len(succs) > 0 { + // Pop an element from the tail of the queue. + cid := succs[len(succs)-1] + succs = succs[:len(succs)-1] if !scheduled[cid] { bid = cid continue blockloop } } + // Still nothing, pick any non-exit block. for posdegree.size() > 0 { cid := posdegree.pop() diff --git a/src/cmd/compile/internal/ssa/lca.go b/src/cmd/compile/internal/ssa/lca.go index 5cb73911dfa5e400b8e119ad453532e066d1a173..90daebe44f7141099c76d2d6ebb26e0795b0a0e8 100644 --- a/src/cmd/compile/internal/ssa/lca.go +++ b/src/cmd/compile/internal/ssa/lca.go @@ -4,6 +4,10 @@ package ssa +import ( + "math/bits" +) + // Code to compute lowest common ancestors in the dominator tree. // https://en.wikipedia.org/wiki/Lowest_common_ancestor // https://en.wikipedia.org/wiki/Range_minimum_query#Solution_using_constant_time_and_linearithmic_space @@ -79,7 +83,7 @@ func makeLCArange(f *Func) *lcaRange { } // Compute fast range-minimum query data structure - var rangeMin [][]ID + rangeMin := make([][]ID, 0, bits.Len64(uint64(len(tour)))) rangeMin = append(rangeMin, tour) // 1-size windows are just the tour itself. for logS, s := 1, 2; s < len(tour); logS, s = logS+1, s*2 { r := make([]ID, len(tour)-s+1) diff --git a/src/cmd/compile/internal/ssa/likelyadjust.go b/src/cmd/compile/internal/ssa/likelyadjust.go index 49898a1322b951b71dcc44f4c744f09e3cd97964..f462bf29a64d198fa6174f35b11822ccfe3ae6d3 100644 --- a/src/cmd/compile/internal/ssa/likelyadjust.go +++ b/src/cmd/compile/internal/ssa/likelyadjust.go @@ -222,6 +222,7 @@ func likelyadjust(f *Func) { if opcodeTable[v.Op].call { local[b.ID] = blCALL certain[b.ID] = max8(blCALL, certain[b.Succs[0].b.ID]) + break } } } diff --git a/src/cmd/compile/internal/ssa/location.go b/src/cmd/compile/internal/ssa/location.go index a333982389aa45c9d98e7b118b2bf7434b5e9738..252c47cdebc8b6ab4a4523e4042c3d34724520be 100644 --- a/src/cmd/compile/internal/ssa/location.go +++ b/src/cmd/compile/internal/ssa/location.go @@ -5,6 +5,7 @@ package ssa import ( + "cmd/compile/internal/ir" "cmd/compile/internal/types" "fmt" ) @@ -59,7 +60,7 @@ func (r *Register) GCNum() int16 { // { N: len, Type: int, Off: 0, SplitOf: parent, SplitOffset: 8} // parent = &{N: s, Type: string} type LocalSlot struct { - N GCNode // an ONAME *gc.Node representing a stack location. + N *ir.Name // an ONAME *ir.Name representing a stack location. Type *types.Type // type of slot Off int64 // offset of slot in N @@ -86,3 +87,23 @@ func (t LocPair) String() string { } return fmt.Sprintf("<%s,%s>", n0, n1) } + +type LocResults []Location + +func (t LocResults) String() string { + s := "<" + a := "" + for _, r := range t { + a += s + s = "," + a += r.String() + } + a += ">" + return a +} + +type Spill struct { + Type *types.Type + Offset int64 + Reg int16 +} diff --git a/src/cmd/compile/internal/ssa/loopreschedchecks.go b/src/cmd/compile/internal/ssa/loopreschedchecks.go index 9c73bcff2623ba13d20ea46793abe89d2feb58e1..738c62607adb497383bca5e32489277503fe4362 100644 --- a/src/cmd/compile/internal/ssa/loopreschedchecks.go +++ b/src/cmd/compile/internal/ssa/loopreschedchecks.go @@ -246,7 +246,8 @@ func insertLoopReschedChecks(f *Func) { // mem1 := call resched (mem0) // goto header resched := f.fe.Syslook("goschedguarded") - mem1 := sched.NewValue1A(bb.Pos, OpStaticCall, types.TypeMem, StaticAuxCall(resched, nil, nil), mem0) + // TODO(register args) -- will need more details + mem1 := sched.NewValue1A(bb.Pos, OpStaticCall, types.TypeMem, StaticAuxCall(resched, nil), mem0) sched.AddEdgeTo(h) headerMemPhi.AddArg(mem1) diff --git a/src/cmd/compile/internal/ssa/looprotate.go b/src/cmd/compile/internal/ssa/looprotate.go index 2e5e421df7ffa14f9d573129b0eaec7610182f10..35010a78d8e0492623bb3b017337aab9530c2719 100644 --- a/src/cmd/compile/internal/ssa/looprotate.go +++ b/src/cmd/compile/internal/ssa/looprotate.go @@ -68,12 +68,15 @@ func loopRotate(f *Func) { if nextb == p { // original loop predecessor is next break } - if loopnest.b2l[nextb.ID] != loop { // about to leave loop - break + if loopnest.b2l[nextb.ID] == loop { + after[p.ID] = append(after[p.ID], nextb) } - after[p.ID] = append(after[p.ID], nextb) b = nextb } + // Swap b and p so that we'll handle p before b when moving blocks. + f.Blocks[idToIdx[loop.header.ID]] = p + f.Blocks[idToIdx[p.ID]] = loop.header + idToIdx[loop.header.ID], idToIdx[p.ID] = idToIdx[p.ID], idToIdx[loop.header.ID] // Place b after p. for _, b := range after[p.ID] { @@ -86,21 +89,23 @@ func loopRotate(f *Func) { // before the rest of the loop. And that relies on the // fact that we only identify reducible loops. j := 0 - for i, b := range f.Blocks { + // Some blocks that are not part of a loop may be placed + // between loop blocks. In order to avoid these blocks from + // being overwritten, use a temporary slice. + newOrder := make([]*Block, 0, f.NumBlocks()) + for _, b := range f.Blocks { if _, ok := move[b.ID]; ok { continue } - f.Blocks[j] = b + newOrder = append(newOrder, b) j++ for _, a := range after[b.ID] { - if j > i { - f.Fatalf("head before tail in loop %s", b) - } - f.Blocks[j] = a + newOrder = append(newOrder, a) j++ } } if j != len(f.Blocks) { f.Fatalf("bad reordering in looprotate") } + f.Blocks = newOrder } diff --git a/src/cmd/compile/internal/ssa/lower.go b/src/cmd/compile/internal/ssa/lower.go index f332b2e028e72e96b78fd250cd8dc7c0bc920933..5760c356015f165b243f3d2c29a303232e1a4721 100644 --- a/src/cmd/compile/internal/ssa/lower.go +++ b/src/cmd/compile/internal/ssa/lower.go @@ -21,8 +21,12 @@ func checkLower(f *Func) { continue // lowered } switch v.Op { - case OpSP, OpSB, OpInitMem, OpArg, OpPhi, OpVarDef, OpVarKill, OpVarLive, OpKeepAlive, OpSelect0, OpSelect1, OpConvert, OpInlMark: + case OpSP, OpSB, OpInitMem, OpArg, OpArgIntReg, OpArgFloatReg, OpPhi, OpVarDef, OpVarKill, OpVarLive, OpKeepAlive, OpSelect0, OpSelect1, OpSelectN, OpConvert, OpInlMark: continue // ok not to lower + case OpMakeResult: + if b.Controls[0] == v { + continue + } case OpGetG: if f.Config.hasGReg { // has hardware g register, regalloc takes care of it @@ -30,6 +34,7 @@ func checkLower(f *Func) { } } s := "not lowered: " + v.String() + ", " + v.Op.String() + " " + v.Type.SimpleString() + for _, a := range v.Args { s += " " + a.Type.SimpleString() } diff --git a/src/cmd/compile/internal/ssa/nilcheck.go b/src/cmd/compile/internal/ssa/nilcheck.go index d1bad529e700cecf3c20b13fe4d5f99bc07a9cb2..14f511a5f1cfc6545f35a8f414b2a62b1cdae35d 100644 --- a/src/cmd/compile/internal/ssa/nilcheck.go +++ b/src/cmd/compile/internal/ssa/nilcheck.go @@ -5,8 +5,9 @@ package ssa import ( - "cmd/internal/objabi" + "cmd/compile/internal/ir" "cmd/internal/src" + "internal/buildcfg" ) // nilcheckelim eliminates unnecessary nil checks. @@ -191,7 +192,7 @@ func nilcheckelim(f *Func) { const minZeroPage = 4096 // faultOnLoad is true if a load to an address below minZeroPage will trigger a SIGSEGV. -var faultOnLoad = objabi.GOOS != "aix" +var faultOnLoad = buildcfg.GOOS != "aix" // nilcheckelim2 eliminates unnecessary nil checks. // Runs after lowering and scheduling. @@ -235,7 +236,7 @@ func nilcheckelim2(f *Func) { continue } if v.Type.IsMemory() || v.Type.IsTuple() && v.Type.FieldType(1).IsMemory() { - if v.Op == OpVarKill || v.Op == OpVarLive || (v.Op == OpVarDef && !v.Aux.(GCNode).Typ().HasPointers()) { + if v.Op == OpVarKill || v.Op == OpVarLive || (v.Op == OpVarDef && !v.Aux.(*ir.Name).Type().HasPointers()) { // These ops don't really change memory. continue // Note: OpVarDef requires that the defined variable not have pointers. diff --git a/src/cmd/compile/internal/ssa/nilcheck_test.go b/src/cmd/compile/internal/ssa/nilcheck_test.go index 16d94614d815c575435a220ee1a4e385d61e5dbc..2e32afe2a6ba6d6f6e5689d04b7c6e9e68a5ef97 100644 --- a/src/cmd/compile/internal/ssa/nilcheck_test.go +++ b/src/cmd/compile/internal/ssa/nilcheck_test.go @@ -212,7 +212,7 @@ func TestNilcheckPhi(t *testing.T) { Valu("mem", OpInitMem, types.TypeMem, 0, nil), Valu("sb", OpSB, c.config.Types.Uintptr, 0, nil), Valu("sp", OpSP, c.config.Types.Uintptr, 0, nil), - Valu("baddr", OpLocalAddr, c.config.Types.Bool, 0, "b", "sp", "mem"), + Valu("baddr", OpLocalAddr, c.config.Types.Bool, 0, StringToAux("b"), "sp", "mem"), Valu("bool1", OpLoad, c.config.Types.Bool, 0, nil, "baddr", "mem"), If("bool1", "b1", "b2")), Bloc("b1", diff --git a/src/cmd/compile/internal/ssa/numberlines.go b/src/cmd/compile/internal/ssa/numberlines.go index f4e62b88c4f6b64ab6665914c570973222e12182..9d6aeca9c0dc019b6cbafc7bd05f61b51383a036 100644 --- a/src/cmd/compile/internal/ssa/numberlines.go +++ b/src/cmd/compile/internal/ssa/numberlines.go @@ -5,7 +5,6 @@ package ssa import ( - "cmd/internal/obj" "cmd/internal/src" "fmt" "sort" @@ -17,21 +16,13 @@ func isPoorStatementOp(op Op) bool { // so that a debugger-user sees the stop before the panic, and can examine the value. case OpAddr, OpLocalAddr, OpOffPtr, OpStructSelect, OpPhi, OpITab, OpIData, OpIMake, OpStringMake, OpSliceMake, OpStructMake0, OpStructMake1, OpStructMake2, OpStructMake3, OpStructMake4, - OpConstBool, OpConst8, OpConst16, OpConst32, OpConst64, OpConst32F, OpConst64F: + OpConstBool, OpConst8, OpConst16, OpConst32, OpConst64, OpConst32F, OpConst64F, OpSB, OpSP, + OpArgIntReg, OpArgFloatReg: return true } return false } -// LosesStmtMark reports whether a prog with op as loses its statement mark on the way to DWARF. -// The attributes from some opcodes are lost in translation. -// TODO: this is an artifact of how funcpctab combines information for instructions at a single PC. -// Should try to fix it there. -func LosesStmtMark(as obj.As) bool { - // is_stmt does not work for these; it DOES for ANOP even though that generates no code. - return as == obj.APCDATA || as == obj.AFUNCDATA -} - // nextGoodStatementIndex returns an index at i or later that is believed // to be a good place to start the statement for b. This decision is // based on v's Op, the possibility of a better later operation, and @@ -71,7 +62,7 @@ func nextGoodStatementIndex(v *Value, i int, b *Block) int { // statement boundary. func notStmtBoundary(op Op) bool { switch op { - case OpCopy, OpPhi, OpVarKill, OpVarDef, OpVarLive, OpUnknown, OpFwdRef, OpArg: + case OpCopy, OpPhi, OpVarKill, OpVarDef, OpVarLive, OpUnknown, OpFwdRef, OpArg, OpArgIntReg, OpArgFloatReg: return true } return false diff --git a/src/cmd/compile/internal/ssa/op.go b/src/cmd/compile/internal/ssa/op.go index d1673352bd163e7332fa53cc02f2a3a4f9ac9b2d..f09a08abcf752102944a3aba872105ac9ebe392d 100644 --- a/src/cmd/compile/internal/ssa/op.go +++ b/src/cmd/compile/internal/ssa/op.go @@ -5,9 +5,12 @@ package ssa import ( + "cmd/compile/internal/abi" + "cmd/compile/internal/ir" "cmd/compile/internal/types" "cmd/internal/obj" "fmt" + "strings" ) // An Op encodes the specific operation that a Value performs. @@ -66,50 +69,187 @@ type regInfo struct { outputs []outputInfo } +func (r *regInfo) String() string { + s := "" + s += "INS:\n" + for _, i := range r.inputs { + mask := fmt.Sprintf("%64b", i.regs) + mask = strings.Replace(mask, "0", ".", -1) + s += fmt.Sprintf("%2d |%s|\n", i.idx, mask) + } + s += "OUTS:\n" + for _, i := range r.outputs { + mask := fmt.Sprintf("%64b", i.regs) + mask = strings.Replace(mask, "0", ".", -1) + s += fmt.Sprintf("%2d |%s|\n", i.idx, mask) + } + s += "CLOBBERS:\n" + mask := fmt.Sprintf("%64b", r.clobbers) + mask = strings.Replace(mask, "0", ".", -1) + s += fmt.Sprintf(" |%s|\n", mask) + return s +} + type auxType int8 -type Param struct { - Type *types.Type - Offset int32 // TODO someday this will be a register +type AuxNameOffset struct { + Name *ir.Name + Offset int64 +} + +func (a *AuxNameOffset) CanBeAnSSAAux() {} +func (a *AuxNameOffset) String() string { + return fmt.Sprintf("%s+%d", a.Name.Sym().Name, a.Offset) } type AuxCall struct { Fn *obj.LSym - args []Param // Includes receiver for method calls. Does NOT include hidden closure pointer. - results []Param -} - -// ResultForOffset returns the index of the result at a particular offset among the results -// This does not include the mem result for the call opcode. -func (a *AuxCall) ResultForOffset(offset int64) int64 { - which := int64(-1) - for i := int64(0); i < a.NResults(); i++ { // note aux NResults does not include mem result. - if a.OffsetOfResult(i) == offset { - which = i - break + reg *regInfo // regInfo for this call + abiInfo *abi.ABIParamResultInfo +} + +// Reg returns the regInfo for a given call, combining the derived in/out register masks +// with the machine-specific register information in the input i. (The machine-specific +// regInfo is much handier at the call site than it is when the AuxCall is being constructed, +// therefore do this lazily). +// +// TODO: there is a Clever Hack that allows pre-generation of a small-ish number of the slices +// of inputInfo and outputInfo used here, provided that we are willing to reorder the inputs +// and outputs from calls, so that all integer registers come first, then all floating registers. +// At this point (active development of register ABI) that is very premature, +// but if this turns out to be a cost, we could do it. +func (a *AuxCall) Reg(i *regInfo, c *Config) *regInfo { + if a.reg.clobbers != 0 { + // Already updated + return a.reg + } + if a.abiInfo.InRegistersUsed()+a.abiInfo.OutRegistersUsed() == 0 { + // Shortcut for zero case, also handles old ABI. + a.reg = i + return a.reg + } + + k := len(i.inputs) + for _, p := range a.abiInfo.InParams() { + for _, r := range p.Registers { + m := archRegForAbiReg(r, c) + a.reg.inputs = append(a.reg.inputs, inputInfo{idx: k, regs: (1 << m)}) + k++ + } + } + a.reg.inputs = append(a.reg.inputs, i.inputs...) // These are less constrained, thus should come last + k = len(i.outputs) + for _, p := range a.abiInfo.OutParams() { + for _, r := range p.Registers { + m := archRegForAbiReg(r, c) + a.reg.outputs = append(a.reg.outputs, outputInfo{idx: k, regs: (1 << m)}) + k++ + } + } + a.reg.outputs = append(a.reg.outputs, i.outputs...) + a.reg.clobbers = i.clobbers + return a.reg +} +func (a *AuxCall) ABI() *abi.ABIConfig { + return a.abiInfo.Config() +} +func (a *AuxCall) ABIInfo() *abi.ABIParamResultInfo { + return a.abiInfo +} +func (a *AuxCall) ResultReg(c *Config) *regInfo { + if a.abiInfo.OutRegistersUsed() == 0 { + return a.reg + } + if len(a.reg.inputs) > 0 { + return a.reg + } + k := 0 + for _, p := range a.abiInfo.OutParams() { + for _, r := range p.Registers { + m := archRegForAbiReg(r, c) + a.reg.inputs = append(a.reg.inputs, inputInfo{idx: k, regs: (1 << m)}) + k++ } } - return which + return a.reg +} + +// For ABI register index r, returns the (dense) register number used in +// SSA backend. +func archRegForAbiReg(r abi.RegIndex, c *Config) uint8 { + var m int8 + if int(r) < len(c.intParamRegs) { + m = c.intParamRegs[r] + } else { + m = c.floatParamRegs[int(r)-len(c.intParamRegs)] + } + return uint8(m) +} + +// For ABI register index r, returns the register number used in the obj +// package (assembler). +func ObjRegForAbiReg(r abi.RegIndex, c *Config) int16 { + m := archRegForAbiReg(r, c) + return c.registers[m].objNum +} + +// ArgWidth returns the amount of stack needed for all the inputs +// and outputs of a function or method, including ABI-defined parameter +// slots and ABI-defined spill slots for register-resident parameters. +// +// The name is taken from the types package's ArgWidth(), +// which predated changes to the ABI; this version handles those changes. +func (a *AuxCall) ArgWidth() int64 { + return a.abiInfo.ArgWidth() +} + +// ParamAssignmentForResult returns the ABI Parameter assignment for result which (indexed 0, 1, etc). +func (a *AuxCall) ParamAssignmentForResult(which int64) *abi.ABIParamAssignment { + return a.abiInfo.OutParam(int(which)) } // OffsetOfResult returns the SP offset of result which (indexed 0, 1, etc). func (a *AuxCall) OffsetOfResult(which int64) int64 { - return int64(a.results[which].Offset) + n := int64(a.abiInfo.OutParam(int(which)).Offset()) + return n } // OffsetOfArg returns the SP offset of argument which (indexed 0, 1, etc). +// If the call is to a method, the receiver is the first argument (i.e., index 0) func (a *AuxCall) OffsetOfArg(which int64) int64 { - return int64(a.args[which].Offset) + n := int64(a.abiInfo.InParam(int(which)).Offset()) + return n +} + +// RegsOfResult returns the register(s) used for result which (indexed 0, 1, etc). +func (a *AuxCall) RegsOfResult(which int64) []abi.RegIndex { + return a.abiInfo.OutParam(int(which)).Registers +} + +// RegsOfArg returns the register(s) used for argument which (indexed 0, 1, etc). +// If the call is to a method, the receiver is the first argument (i.e., index 0) +func (a *AuxCall) RegsOfArg(which int64) []abi.RegIndex { + return a.abiInfo.InParam(int(which)).Registers +} + +// NameOfResult returns the type of result which (indexed 0, 1, etc). +func (a *AuxCall) NameOfResult(which int64) *ir.Name { + name := a.abiInfo.OutParam(int(which)).Name + if name == nil { + return nil + } + return name.(*ir.Name) } // TypeOfResult returns the type of result which (indexed 0, 1, etc). func (a *AuxCall) TypeOfResult(which int64) *types.Type { - return a.results[which].Type + return a.abiInfo.OutParam(int(which)).Type } // TypeOfArg returns the type of argument which (indexed 0, 1, etc). +// If the call is to a method, the receiver is the first argument (i.e., index 0) func (a *AuxCall) TypeOfArg(which int64) *types.Type { - return a.args[which].Type + return a.abiInfo.InParam(int(which)).Type } // SizeOfResult returns the size of result which (indexed 0, 1, etc). @@ -118,13 +258,14 @@ func (a *AuxCall) SizeOfResult(which int64) int64 { } // SizeOfArg returns the size of argument which (indexed 0, 1, etc). +// If the call is to a method, the receiver is the first argument (i.e., index 0) func (a *AuxCall) SizeOfArg(which int64) int64 { return a.TypeOfArg(which).Width } // NResults returns the number of results func (a *AuxCall) NResults() int64 { - return int64(len(a.results)) + return int64(len(a.abiInfo.OutParams())) } // LateExpansionResultType returns the result type (including trailing mem) @@ -138,15 +279,12 @@ func (a *AuxCall) LateExpansionResultType() *types.Type { return types.NewResults(tys) } -// NArgs returns the number of arguments +// NArgs returns the number of arguments (including receiver, if there is one). func (a *AuxCall) NArgs() int64 { - return int64(len(a.args)) + return int64(len(a.abiInfo.InParams())) } -// String returns -// "AuxCall{()}" if len(results) == 0; -// "AuxCall{()}" if len(results) == 1; -// "AuxCall{()()}" otherwise. +// String returns "AuxCall{}" func (a *AuxCall) String() string { var fn string if a.Fn == nil { @@ -154,70 +292,75 @@ func (a *AuxCall) String() string { } else { fn = fmt.Sprintf("AuxCall{%v", a.Fn) } - - if len(a.args) == 0 { - fn += "()" - } else { - s := "(" - for _, arg := range a.args { - fn += fmt.Sprintf("%s[%v,%v]", s, arg.Type, arg.Offset) - s = "," - } - fn += ")" - } - - if len(a.results) > 0 { // usual is zero or one; only some RT calls have more than one. - if len(a.results) == 1 { - fn += fmt.Sprintf("[%v,%v]", a.results[0].Type, a.results[0].Offset) - } else { - s := "(" - for _, result := range a.results { - fn += fmt.Sprintf("%s[%v,%v]", s, result.Type, result.Offset) - s = "," - } - fn += ")" - } - } + // TODO how much of the ABI should be printed? return fn + "}" } // StaticAuxCall returns an AuxCall for a static call. -func StaticAuxCall(sym *obj.LSym, args []Param, results []Param) *AuxCall { - return &AuxCall{Fn: sym, args: args, results: results} +func StaticAuxCall(sym *obj.LSym, paramResultInfo *abi.ABIParamResultInfo) *AuxCall { + if paramResultInfo == nil { + panic(fmt.Errorf("Nil paramResultInfo, sym=%v", sym)) + } + var reg *regInfo + if paramResultInfo.InRegistersUsed()+paramResultInfo.OutRegistersUsed() > 0 { + reg = ®Info{} + } + return &AuxCall{Fn: sym, abiInfo: paramResultInfo, reg: reg} } // InterfaceAuxCall returns an AuxCall for an interface call. -func InterfaceAuxCall(args []Param, results []Param) *AuxCall { - return &AuxCall{Fn: nil, args: args, results: results} +func InterfaceAuxCall(paramResultInfo *abi.ABIParamResultInfo) *AuxCall { + var reg *regInfo + if paramResultInfo.InRegistersUsed()+paramResultInfo.OutRegistersUsed() > 0 { + reg = ®Info{} + } + return &AuxCall{Fn: nil, abiInfo: paramResultInfo, reg: reg} } // ClosureAuxCall returns an AuxCall for a closure call. -func ClosureAuxCall(args []Param, results []Param) *AuxCall { - return &AuxCall{Fn: nil, args: args, results: results} +func ClosureAuxCall(paramResultInfo *abi.ABIParamResultInfo) *AuxCall { + var reg *regInfo + if paramResultInfo.InRegistersUsed()+paramResultInfo.OutRegistersUsed() > 0 { + reg = ®Info{} + } + return &AuxCall{Fn: nil, abiInfo: paramResultInfo, reg: reg} +} + +func (*AuxCall) CanBeAnSSAAux() {} + +// OwnAuxCall returns a function's own AuxCall +func OwnAuxCall(fn *obj.LSym, paramResultInfo *abi.ABIParamResultInfo) *AuxCall { + // TODO if this remains identical to ClosureAuxCall above after new ABI is done, should deduplicate. + var reg *regInfo + if paramResultInfo.InRegistersUsed()+paramResultInfo.OutRegistersUsed() > 0 { + reg = ®Info{} + } + return &AuxCall{Fn: fn, abiInfo: paramResultInfo, reg: reg} } const ( - auxNone auxType = iota - auxBool // auxInt is 0/1 for false/true - auxInt8 // auxInt is an 8-bit integer - auxInt16 // auxInt is a 16-bit integer - auxInt32 // auxInt is a 32-bit integer - auxInt64 // auxInt is a 64-bit integer - auxInt128 // auxInt represents a 128-bit integer. Always 0. - auxUInt8 // auxInt is an 8-bit unsigned integer - auxFloat32 // auxInt is a float32 (encoded with math.Float64bits) - auxFloat64 // auxInt is a float64 (encoded with math.Float64bits) - auxFlagConstant // auxInt is a flagConstant - auxString // aux is a string - auxSym // aux is a symbol (a *gc.Node for locals, an *obj.LSym for globals, or nil for none) - auxSymOff // aux is a symbol, auxInt is an offset - auxSymValAndOff // aux is a symbol, auxInt is a ValAndOff - auxTyp // aux is a type - auxTypSize // aux is a type, auxInt is a size, must have Aux.(Type).Size() == AuxInt - auxCCop // aux is a ssa.Op that represents a flags-to-bool conversion (e.g. LessThan) - auxCall // aux is a *ssa.AuxCall - auxCallOff // aux is a *ssa.AuxCall, AuxInt is int64 param (in+out) size + auxNone auxType = iota + auxBool // auxInt is 0/1 for false/true + auxInt8 // auxInt is an 8-bit integer + auxInt16 // auxInt is a 16-bit integer + auxInt32 // auxInt is a 32-bit integer + auxInt64 // auxInt is a 64-bit integer + auxInt128 // auxInt represents a 128-bit integer. Always 0. + auxUInt8 // auxInt is an 8-bit unsigned integer + auxFloat32 // auxInt is a float32 (encoded with math.Float64bits) + auxFloat64 // auxInt is a float64 (encoded with math.Float64bits) + auxFlagConstant // auxInt is a flagConstant + auxNameOffsetInt8 // aux is a &struct{Name ir.Name, Offset int64}; auxInt is index in parameter registers array + auxString // aux is a string + auxSym // aux is a symbol (a *gc.Node for locals, an *obj.LSym for globals, or nil for none) + auxSymOff // aux is a symbol, auxInt is an offset + auxSymValAndOff // aux is a symbol, auxInt is a ValAndOff + auxTyp // aux is a type + auxTypSize // aux is a type, auxInt is a size, must have Aux.(Type).Size() == AuxInt + auxCCop // aux is a ssa.Op that represents a flags-to-bool conversion (e.g. LessThan) + auxCall // aux is a *ssa.AuxCall + auxCallOff // aux is a *ssa.AuxCall, AuxInt is int64 param (in+out) size // architecture specific aux types auxARM64BitField // aux is an arm64 bitfield lsb and width packed into auxInt @@ -247,8 +390,8 @@ const ( // - a *obj.LSym, for an offset from SB (the global pointer) // - nil, for no offset type Sym interface { - String() string CanBeAnSSASym() + CanBeAnSSAAux() } // A ValAndOff is used by the several opcodes. It holds @@ -259,13 +402,13 @@ type Sym interface { // The low 32 bits hold a pointer offset. type ValAndOff int64 -func (x ValAndOff) Val() int64 { return int64(x) >> 32 } -func (x ValAndOff) Val32() int32 { return int32(int64(x) >> 32) } +func (x ValAndOff) Val() int32 { return int32(int64(x) >> 32) } +func (x ValAndOff) Val64() int64 { return int64(x) >> 32 } func (x ValAndOff) Val16() int16 { return int16(int64(x) >> 32) } func (x ValAndOff) Val8() int8 { return int8(int64(x) >> 32) } -func (x ValAndOff) Off() int64 { return int64(int32(x)) } -func (x ValAndOff) Off32() int32 { return int32(x) } +func (x ValAndOff) Off64() int64 { return int64(int32(x)) } +func (x ValAndOff) Off() int32 { return int32(x) } func (x ValAndOff) String() string { return fmt.Sprintf("val=%d,off=%d", x.Val(), x.Off()) @@ -277,40 +420,16 @@ func validVal(val int64) bool { return val == int64(int32(val)) } -// validOff reports whether the offset can be used -// as an argument to makeValAndOff. -func validOff(off int64) bool { - return off == int64(int32(off)) -} - -// validValAndOff reports whether we can fit the value and offset into -// a ValAndOff value. -func validValAndOff(val, off int64) bool { - if !validVal(val) { - return false - } - if !validOff(off) { - return false - } - return true -} - -func makeValAndOff32(val, off int32) ValAndOff { +func makeValAndOff(val, off int32) ValAndOff { return ValAndOff(int64(val)<<32 + int64(uint32(off))) } -func makeValAndOff64(val, off int64) ValAndOff { - if !validValAndOff(val, off) { - panic("invalid makeValAndOff64") - } - return ValAndOff(val<<32 + int64(uint32(off))) -} func (x ValAndOff) canAdd32(off int32) bool { - newoff := x.Off() + int64(off) + newoff := x.Off64() + int64(off) return newoff == int64(int32(newoff)) } func (x ValAndOff) canAdd64(off int64) bool { - newoff := x.Off() + off + newoff := x.Off64() + off return newoff == int64(int32(newoff)) } @@ -318,13 +437,13 @@ func (x ValAndOff) addOffset32(off int32) ValAndOff { if !x.canAdd32(off) { panic("invalid ValAndOff.addOffset32") } - return makeValAndOff64(x.Val(), x.Off()+int64(off)) + return makeValAndOff(x.Val(), x.Off()+off) } func (x ValAndOff) addOffset64(off int64) ValAndOff { if !x.canAdd64(off) { panic("invalid ValAndOff.addOffset64") } - return makeValAndOff64(x.Val(), x.Off()+off) + return makeValAndOff(x.Val(), x.Off()+int32(off)) } // int128 is a type that stores a 128-bit constant. @@ -350,6 +469,7 @@ const ( BoundsSlice3BU // ... with unsigned high BoundsSlice3C // 3-arg slicing operation, 0 <= low <= high failed BoundsSlice3CU // ... with unsigned low + BoundsConvert // conversion to array pointer failed BoundsKindCount ) @@ -377,7 +497,8 @@ func boundsABI(b int64) int { case BoundsSlice3Alen, BoundsSlice3AlenU, BoundsSlice3Acap, - BoundsSlice3AcapU: + BoundsSlice3AcapU, + BoundsConvert: return 0 case BoundsSliceAlen, BoundsSliceAlenU, diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index e590f6ba5d990a05837f58bdc77277c97e519fac..1c37fbe0db4a49b7c7ff6d6a9668a6fdc745218a 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -432,6 +432,7 @@ const ( Op386BSRW Op386BSWAPL Op386SQRTSD + Op386SQRTSS Op386SBBLcarrymask Op386SETEQ Op386SETNE @@ -691,18 +692,6 @@ const ( OpAMD64BTRQconst OpAMD64BTSLconst OpAMD64BTSQconst - OpAMD64BTCQmodify - OpAMD64BTCLmodify - OpAMD64BTSQmodify - OpAMD64BTSLmodify - OpAMD64BTRQmodify - OpAMD64BTRLmodify - OpAMD64BTCQconstmodify - OpAMD64BTCLconstmodify - OpAMD64BTSQconstmodify - OpAMD64BTSLconstmodify - OpAMD64BTRQconstmodify - OpAMD64BTRLconstmodify OpAMD64TESTQ OpAMD64TESTL OpAMD64TESTW @@ -731,6 +720,8 @@ const ( OpAMD64SARLconst OpAMD64SARWconst OpAMD64SARBconst + OpAMD64SHRDQ + OpAMD64SHLDQ OpAMD64ROLQ OpAMD64ROLL OpAMD64ROLW @@ -888,6 +879,7 @@ const ( OpAMD64POPCNTQ OpAMD64POPCNTL OpAMD64SQRTSD + OpAMD64SQRTSS OpAMD64ROUNDSD OpAMD64VFMADD231SD OpAMD64SBBQcarrymask @@ -970,6 +962,7 @@ const ( OpAMD64MOVQstore OpAMD64MOVOload OpAMD64MOVOstore + OpAMD64MOVOstorezero OpAMD64MOVBloadidx1 OpAMD64MOVWloadidx1 OpAMD64MOVWloadidx2 @@ -998,7 +991,6 @@ const ( OpAMD64MOVQstoreconstidx1 OpAMD64MOVQstoreconstidx8 OpAMD64DUFFZERO - OpAMD64MOVOconst OpAMD64REPSTOSQ OpAMD64CALLstatic OpAMD64CALLclosure @@ -1090,6 +1082,7 @@ const ( OpARMNEGF OpARMNEGD OpARMSQRTD + OpARMSQRTF OpARMABSD OpARMCLZ OpARMREV @@ -1358,8 +1351,10 @@ const ( OpARM64FNEGS OpARM64FNEGD OpARM64FSQRTD + OpARM64FSQRTS OpARM64REV OpARM64REVW + OpARM64REV16 OpARM64REV16W OpARM64RBIT OpARM64RBITW @@ -1481,6 +1476,8 @@ const ( OpARM64MOVWloadidx4 OpARM64MOVWUloadidx4 OpARM64MOVDloadidx8 + OpARM64FMOVSloadidx4 + OpARM64FMOVDloadidx8 OpARM64MOVBstore OpARM64MOVHstore OpARM64MOVWstore @@ -1497,6 +1494,8 @@ const ( OpARM64MOVHstoreidx2 OpARM64MOVWstoreidx4 OpARM64MOVDstoreidx8 + OpARM64FMOVSstoreidx4 + OpARM64FMOVDstoreidx8 OpARM64MOVBstorezero OpARM64MOVHstorezero OpARM64MOVWstorezero @@ -1546,6 +1545,10 @@ const ( OpARM64FRINTZD OpARM64CSEL OpARM64CSEL0 + OpARM64CSINC + OpARM64CSINV + OpARM64CSNEG + OpARM64CSETM OpARM64CALLstatic OpARM64CALLclosure OpARM64CALLinter @@ -1637,6 +1640,7 @@ const ( OpMIPSNEGF OpMIPSNEGD OpMIPSSQRTD + OpMIPSSQRTF OpMIPSSLL OpMIPSSLLconst OpMIPSSRL @@ -1747,6 +1751,7 @@ const ( OpMIPS64NEGF OpMIPS64NEGD OpMIPS64SQRTD + OpMIPS64SQRTF OpMIPS64SLLV OpMIPS64SLLVconst OpMIPS64SRLV @@ -2073,9 +2078,6 @@ const ( OpRISCV64REMW OpRISCV64REMUW OpRISCV64MOVaddr - OpRISCV64MOVBconst - OpRISCV64MOVHconst - OpRISCV64MOVWconst OpRISCV64MOVDconst OpRISCV64MOVBload OpRISCV64MOVHload @@ -2139,6 +2141,8 @@ const ( OpRISCV64LoweredAtomicAdd64 OpRISCV64LoweredAtomicCas32 OpRISCV64LoweredAtomicCas64 + OpRISCV64LoweredAtomicAnd32 + OpRISCV64LoweredAtomicOr32 OpRISCV64LoweredNilCheck OpRISCV64LoweredGetClosurePtr OpRISCV64LoweredGetCallerSP @@ -2297,6 +2301,7 @@ const ( OpS390XNOT OpS390XNOTW OpS390XFSQRT + OpS390XFSQRTS OpS390XLOCGR OpS390XMOVBreg OpS390XMOVBZreg @@ -2723,6 +2728,7 @@ const ( OpRotateLeft32 OpRotateLeft64 OpSqrt + OpSqrt32 OpFloor OpCeil OpTrunc @@ -2747,6 +2753,8 @@ const ( OpConstSlice OpInitMem OpArg + OpArgIntReg + OpArgFloatReg OpAddr OpLocalAddr OpSP @@ -2814,6 +2822,7 @@ const ( OpSlicePtr OpSliceLen OpSliceCap + OpSlicePtrUnchecked OpComplexMake OpComplexReal OpComplexImag @@ -2902,6 +2911,7 @@ const ( OpAtomicOr8Variant OpAtomicOr32Variant OpClobber + OpClobberReg ) var opcodeTable = [...]opInfo{ @@ -2912,7 +2922,6 @@ var opcodeTable = [...]opInfo{ argLen: 2, commutative: true, resultInArg0: true, - usesScratch: true, asm: x86.AADDSS, reg: regInfo{ inputs: []inputInfo{ @@ -2944,7 +2953,6 @@ var opcodeTable = [...]opInfo{ name: "SUBSS", argLen: 2, resultInArg0: true, - usesScratch: true, asm: x86.ASUBSS, reg: regInfo{ inputs: []inputInfo{ @@ -2976,7 +2984,6 @@ var opcodeTable = [...]opInfo{ argLen: 2, commutative: true, resultInArg0: true, - usesScratch: true, asm: x86.AMULSS, reg: regInfo{ inputs: []inputInfo{ @@ -3008,7 +3015,6 @@ var opcodeTable = [...]opInfo{ name: "DIVSS", argLen: 2, resultInArg0: true, - usesScratch: true, asm: x86.ADIVSS, reg: regInfo{ inputs: []inputInfo{ @@ -4072,10 +4078,9 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "UCOMISS", - argLen: 2, - usesScratch: true, - asm: x86.AUCOMISS, + name: "UCOMISS", + argLen: 2, + asm: x86.AUCOMISS, reg: regInfo{ inputs: []inputInfo{ {0, 65280}, // X0 X1 X2 X3 X4 X5 X6 X7 @@ -4084,10 +4089,9 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "UCOMISD", - argLen: 2, - usesScratch: true, - asm: x86.AUCOMISD, + name: "UCOMISD", + argLen: 2, + asm: x86.AUCOMISD, reg: regInfo{ inputs: []inputInfo{ {0, 65280}, // X0 X1 X2 X3 X4 X5 X6 X7 @@ -4778,6 +4782,19 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "SQRTSS", + argLen: 1, + asm: x86.ASQRTSS, + reg: regInfo{ + inputs: []inputInfo{ + {0, 65280}, // X0 X1 X2 X3 X4 X5 X6 X7 + }, + outputs: []outputInfo{ + {0, 65280}, // X0 X1 X2 X3 X4 X5 X6 X7 + }, + }, + }, { name: "SBBLcarrymask", argLen: 1, @@ -5027,10 +5044,9 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "CVTTSD2SL", - argLen: 1, - usesScratch: true, - asm: x86.ACVTTSD2SL, + name: "CVTTSD2SL", + argLen: 1, + asm: x86.ACVTTSD2SL, reg: regInfo{ inputs: []inputInfo{ {0, 65280}, // X0 X1 X2 X3 X4 X5 X6 X7 @@ -5041,10 +5057,9 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "CVTTSS2SL", - argLen: 1, - usesScratch: true, - asm: x86.ACVTTSS2SL, + name: "CVTTSS2SL", + argLen: 1, + asm: x86.ACVTTSS2SL, reg: regInfo{ inputs: []inputInfo{ {0, 65280}, // X0 X1 X2 X3 X4 X5 X6 X7 @@ -5055,10 +5070,9 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "CVTSL2SS", - argLen: 1, - usesScratch: true, - asm: x86.ACVTSL2SS, + name: "CVTSL2SS", + argLen: 1, + asm: x86.ACVTSL2SS, reg: regInfo{ inputs: []inputInfo{ {0, 239}, // AX CX DX BX BP SI DI @@ -5069,10 +5083,9 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "CVTSL2SD", - argLen: 1, - usesScratch: true, - asm: x86.ACVTSL2SD, + name: "CVTSL2SD", + argLen: 1, + asm: x86.ACVTSL2SD, reg: regInfo{ inputs: []inputInfo{ {0, 239}, // AX CX DX BX BP SI DI @@ -5083,10 +5096,9 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "CVTSD2SS", - argLen: 1, - usesScratch: true, - asm: x86.ACVTSD2SS, + name: "CVTSD2SS", + argLen: 1, + asm: x86.ACVTSD2SS, reg: regInfo{ inputs: []inputInfo{ {0, 65280}, // X0 X1 X2 X3 X4 X5 X6 X7 @@ -6162,11 +6174,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDSS, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6178,11 +6190,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6193,11 +6205,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBSS, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6208,11 +6220,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6224,11 +6236,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AMULSS, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6240,11 +6252,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AMULSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6255,11 +6267,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ADIVSS, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6270,11 +6282,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ADIVSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6287,10 +6299,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVSS, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6303,10 +6315,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6318,7 +6330,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVSS, reg: regInfo{ outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6330,7 +6342,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVSD, reg: regInfo{ outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6343,11 +6355,11 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6360,11 +6372,11 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6377,11 +6389,11 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6394,11 +6406,11 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6411,8 +6423,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVSS, reg: regInfo{ inputs: []inputInfo{ - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, }, }, @@ -6425,8 +6437,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVSD, reg: regInfo{ inputs: []inputInfo{ - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, }, }, @@ -6439,9 +6451,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, }, }, @@ -6454,9 +6466,9 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, }, }, @@ -6469,9 +6481,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, }, }, @@ -6484,9 +6496,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, }, }, @@ -6500,11 +6512,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDSS, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6518,11 +6530,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6536,11 +6548,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBSS, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6554,11 +6566,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6572,11 +6584,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AMULSS, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6590,11 +6602,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AMULSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6608,11 +6620,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ADIVSS, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6626,11 +6638,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ADIVSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6644,12 +6656,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6663,12 +6675,12 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6682,12 +6694,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6701,12 +6713,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6720,12 +6732,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6739,12 +6751,12 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6758,12 +6770,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6777,12 +6789,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6796,12 +6808,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6815,12 +6827,12 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6834,12 +6846,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6853,12 +6865,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6872,12 +6884,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6891,12 +6903,12 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6910,12 +6922,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6929,12 +6941,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - {2, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -6946,11 +6958,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDQ, reg: regInfo{ inputs: []inputInfo{ - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -6962,11 +6974,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -6978,10 +6990,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -6993,10 +7005,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7010,7 +7022,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7024,7 +7036,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7036,11 +7048,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7052,11 +7064,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7069,10 +7081,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7085,10 +7097,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7101,11 +7113,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMULQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7118,11 +7130,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMULL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7134,10 +7146,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMUL3Q, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7149,10 +7161,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMUL3L, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7165,7 +7177,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 4, // DX outputs: []outputInfo{ @@ -7183,7 +7195,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 4, // DX outputs: []outputInfo{ @@ -7200,7 +7212,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 1, // AX outputs: []outputInfo{ @@ -7216,7 +7228,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 1, // AX outputs: []outputInfo{ @@ -7232,7 +7244,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 1, // AX outputs: []outputInfo{ @@ -7248,7 +7260,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 1, // AX outputs: []outputInfo{ @@ -7264,11 +7276,11 @@ var opcodeTable = [...]opInfo{ clobberFlags: true, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7281,7 +7293,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65531}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49147}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {0, 1}, // AX @@ -7298,7 +7310,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65531}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49147}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {0, 1}, // AX @@ -7315,7 +7327,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65531}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49147}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {0, 1}, // AX @@ -7331,7 +7343,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65531}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49147}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {0, 1}, // AX @@ -7347,7 +7359,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65531}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49147}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {0, 1}, // AX @@ -7363,7 +7375,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65531}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49147}, // AX CX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {0, 1}, // AX @@ -7378,11 +7390,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ANEGL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7394,12 +7406,12 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7411,12 +7423,12 @@ var opcodeTable = [...]opInfo{ asm: x86.AADCQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7428,11 +7440,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7444,11 +7456,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AADCQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7459,12 +7471,12 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7475,12 +7487,12 @@ var opcodeTable = [...]opInfo{ asm: x86.ASBBQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7492,11 +7504,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7508,11 +7520,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ASBBQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7525,7 +7537,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {0, 1}, // AX - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {0, 4}, // DX @@ -7542,7 +7554,7 @@ var opcodeTable = [...]opInfo{ inputs: []inputInfo{ {0, 4}, // DX {1, 1}, // AX - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {0, 1}, // AX @@ -7559,11 +7571,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7576,11 +7588,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7593,10 +7605,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7609,10 +7621,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7626,7 +7638,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7640,7 +7652,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7653,11 +7665,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7670,11 +7682,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7687,10 +7699,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7703,10 +7715,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7720,7 +7732,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7734,7 +7746,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7747,11 +7759,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7764,11 +7776,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7781,10 +7793,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7797,10 +7809,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7814,7 +7826,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7828,7 +7840,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7838,8 +7850,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7849,8 +7861,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7860,8 +7872,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7871,8 +7883,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPB, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7883,7 +7895,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7894,7 +7906,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7905,7 +7917,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7916,7 +7928,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPB, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -7929,8 +7941,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPQ, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7943,8 +7955,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPL, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7957,8 +7969,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPW, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7971,8 +7983,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPB, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7985,7 +7997,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -7998,7 +8010,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPL, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8011,7 +8023,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPW, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8024,7 +8036,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMPB, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8037,9 +8049,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8053,9 +8065,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8068,9 +8080,9 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8084,9 +8096,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8099,9 +8111,9 @@ var opcodeTable = [...]opInfo{ scale: 2, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8115,9 +8127,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8131,9 +8143,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8146,8 +8158,8 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8161,8 +8173,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8175,8 +8187,8 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8190,8 +8202,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8204,8 +8216,8 @@ var opcodeTable = [...]opInfo{ scale: 2, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8219,8 +8231,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8234,8 +8246,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -8245,8 +8257,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AUCOMISS, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -8256,8 +8268,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AUCOMISD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -8267,8 +8279,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8278,8 +8290,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8291,11 +8303,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTCL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8307,11 +8319,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTCQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8323,11 +8335,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTRL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8339,11 +8351,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTRQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8355,11 +8367,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTSL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8371,11 +8383,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTSQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8386,7 +8398,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8397,7 +8409,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8410,10 +8422,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTCL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8426,10 +8438,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTCQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8442,10 +8454,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTRL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8458,10 +8470,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTRQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8474,10 +8486,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTSL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8490,184 +8502,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ABTSQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - }, - }, - }, - { - name: "BTCQmodify", - auxType: auxSymOff, - argLen: 3, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTCQ, - reg: regInfo{ - inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTCLmodify", - auxType: auxSymOff, - argLen: 3, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTCL, - reg: regInfo{ - inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTSQmodify", - auxType: auxSymOff, - argLen: 3, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTSQ, - reg: regInfo{ - inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTSLmodify", - auxType: auxSymOff, - argLen: 3, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTSL, - reg: regInfo{ - inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTRQmodify", - auxType: auxSymOff, - argLen: 3, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTRQ, - reg: regInfo{ - inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTRLmodify", - auxType: auxSymOff, - argLen: 3, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTRL, - reg: regInfo{ - inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTCQconstmodify", - auxType: auxSymValAndOff, - argLen: 2, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTCQ, - reg: regInfo{ - inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTCLconstmodify", - auxType: auxSymValAndOff, - argLen: 2, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTCL, - reg: regInfo{ - inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTSQconstmodify", - auxType: auxSymValAndOff, - argLen: 2, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTSQ, - reg: regInfo{ - inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTSLconstmodify", - auxType: auxSymValAndOff, - argLen: 2, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTSL, - reg: regInfo{ - inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTRQconstmodify", - auxType: auxSymValAndOff, - argLen: 2, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTRQ, - reg: regInfo{ - inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB - }, - }, - }, - { - name: "BTRLconstmodify", - auxType: auxSymValAndOff, - argLen: 2, - clobberFlags: true, - faultOnNilArg0: true, - symEffect: SymRead | SymWrite, - asm: x86.ABTRL, - reg: regInfo{ - inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8678,8 +8516,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ATESTQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8690,8 +8528,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ATESTL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8702,8 +8540,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ATESTW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8714,8 +8552,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ATESTB, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8726,7 +8564,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ATESTQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8737,7 +8575,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ATESTL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8748,7 +8586,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ATESTW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8759,7 +8597,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ATESTB, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8772,10 +8610,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8788,10 +8626,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8804,10 +8642,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHLQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8820,10 +8658,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHLL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8836,10 +8674,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8852,10 +8690,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8868,10 +8706,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8884,10 +8722,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8900,10 +8738,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHRQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8916,10 +8754,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHRL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8932,10 +8770,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHRW, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8948,10 +8786,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHRB, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8964,10 +8802,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8980,10 +8818,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -8996,10 +8834,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9012,10 +8850,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9028,10 +8866,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASARQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9044,10 +8882,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASARL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9060,10 +8898,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ASARW, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9076,10 +8914,44 @@ var opcodeTable = [...]opInfo{ asm: x86.ASARB, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SHRDQ", + argLen: 3, + resultInArg0: true, + clobberFlags: true, + asm: x86.ASHRQ, + reg: regInfo{ + inputs: []inputInfo{ + {2, 2}, // CX + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SHLDQ", + argLen: 3, + resultInArg0: true, + clobberFlags: true, + asm: x86.ASHLQ, + reg: regInfo{ + inputs: []inputInfo{ + {2, 2}, // CX + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9092,10 +8964,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9108,10 +8980,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9124,10 +8996,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9140,10 +9012,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9156,10 +9028,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9172,10 +9044,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9188,10 +9060,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9204,10 +9076,10 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9220,10 +9092,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AROLQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9236,10 +9108,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AROLL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9252,10 +9124,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AROLW, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9268,10 +9140,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AROLB, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9286,11 +9158,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9305,11 +9177,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9324,11 +9196,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9343,11 +9215,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9362,11 +9234,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9381,11 +9253,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9400,11 +9272,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9419,11 +9291,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9438,11 +9310,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9457,11 +9329,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9476,12 +9348,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9496,12 +9368,12 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9516,12 +9388,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9536,12 +9408,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9556,12 +9428,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9576,12 +9448,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9596,12 +9468,12 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9616,12 +9488,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9636,12 +9508,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9656,12 +9528,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9676,12 +9548,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9696,12 +9568,12 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9716,12 +9588,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9736,12 +9608,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9756,12 +9628,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9776,12 +9648,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9796,12 +9668,12 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9816,12 +9688,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9836,12 +9708,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9856,12 +9728,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9876,12 +9748,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9896,12 +9768,12 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9916,12 +9788,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9936,12 +9808,12 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9956,12 +9828,12 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -9975,8 +9847,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDQ, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -9990,8 +9862,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBQ, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10005,8 +9877,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDQ, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10020,8 +9892,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AORQ, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10035,8 +9907,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORQ, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10050,8 +9922,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10065,8 +9937,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBL, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10080,8 +9952,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10095,8 +9967,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10110,8 +9982,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10125,9 +9997,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10141,9 +10013,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10157,9 +10029,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10173,9 +10045,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10189,9 +10061,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10205,9 +10077,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10221,9 +10093,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10237,9 +10109,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10253,9 +10125,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10269,9 +10141,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10285,9 +10157,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10301,9 +10173,9 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10317,9 +10189,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10333,9 +10205,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10349,9 +10221,9 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10365,9 +10237,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10381,9 +10253,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10397,9 +10269,9 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10413,9 +10285,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10429,9 +10301,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10445,9 +10317,9 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10461,9 +10333,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10477,9 +10349,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10493,9 +10365,9 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10509,9 +10381,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10525,8 +10397,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10540,8 +10412,8 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10555,8 +10427,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10570,8 +10442,8 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10585,8 +10457,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10600,8 +10472,8 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10615,8 +10487,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10630,8 +10502,8 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10645,8 +10517,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10660,8 +10532,8 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10675,8 +10547,8 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10690,8 +10562,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10705,8 +10577,8 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10720,8 +10592,8 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10735,8 +10607,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10750,8 +10622,8 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10765,8 +10637,8 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10780,8 +10652,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10795,8 +10667,8 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10810,8 +10682,8 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -10823,10 +10695,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ANEGQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10838,10 +10710,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ANEGL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10853,10 +10725,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ANOTQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10868,10 +10740,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ANOTL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10881,11 +10753,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSFQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10896,10 +10768,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSFL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10909,11 +10781,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSRQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10924,10 +10796,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSRL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10938,11 +10810,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQEQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10953,11 +10825,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQNE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10968,11 +10840,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQLT, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10983,11 +10855,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQGT, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -10998,11 +10870,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQLE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11013,11 +10885,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQGE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11028,11 +10900,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQLS, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11043,11 +10915,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQHI, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11058,11 +10930,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQCC, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11073,11 +10945,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQCS, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11088,11 +10960,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLEQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11103,11 +10975,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLNE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11118,11 +10990,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLLT, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11133,11 +11005,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLGT, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11148,11 +11020,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLLE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11163,11 +11035,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLGE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11178,11 +11050,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLLS, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11193,11 +11065,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLHI, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11208,11 +11080,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLCC, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11223,11 +11095,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLCS, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11238,11 +11110,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWEQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11253,11 +11125,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWNE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11268,11 +11140,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWLT, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11283,11 +11155,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWGT, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11298,11 +11170,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWLE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11313,11 +11185,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWGE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11328,11 +11200,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWLS, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11343,11 +11215,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWHI, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11358,11 +11230,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWCC, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11373,11 +11245,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWCS, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11388,12 +11260,12 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQNE, reg: regInfo{ inputs: []inputInfo{ - {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49134}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 1, // AX outputs: []outputInfo{ - {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49134}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11404,11 +11276,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQNE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11419,11 +11291,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQHI, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11434,11 +11306,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVQCC, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11449,12 +11321,12 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLNE, reg: regInfo{ inputs: []inputInfo{ - {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49134}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 1, // AX outputs: []outputInfo{ - {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49134}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11465,11 +11337,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLNE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11480,11 +11352,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLHI, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11495,11 +11367,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVLCC, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11510,12 +11382,12 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWNE, reg: regInfo{ inputs: []inputInfo{ - {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49134}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 1, // AX outputs: []outputInfo{ - {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49134}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11526,11 +11398,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWNE, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11541,11 +11413,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWHI, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11556,11 +11428,11 @@ var opcodeTable = [...]opInfo{ asm: x86.ACMOVWCC, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11572,10 +11444,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSWAPQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11587,10 +11459,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSWAPL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11601,10 +11473,10 @@ var opcodeTable = [...]opInfo{ asm: x86.APOPCNTQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11615,10 +11487,10 @@ var opcodeTable = [...]opInfo{ asm: x86.APOPCNTL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11628,10 +11500,23 @@ var opcodeTable = [...]opInfo{ asm: x86.ASQRTSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + }, + outputs: []outputInfo{ + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + }, + }, + }, + { + name: "SQRTSS", + argLen: 1, + asm: x86.ASQRTSS, + reg: regInfo{ + inputs: []inputInfo{ + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -11642,10 +11527,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AROUNDSD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -11656,12 +11541,12 @@ var opcodeTable = [...]opInfo{ asm: x86.AVFMADD231SD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {2, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {2, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -11671,7 +11556,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASBBQ, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11681,7 +11566,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASBBL, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11691,7 +11576,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETEQ, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11701,7 +11586,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETNE, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11711,7 +11596,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETLT, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11721,7 +11606,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETLE, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11731,7 +11616,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETGT, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11741,7 +11626,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETGE, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11751,7 +11636,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETCS, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11761,7 +11646,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETLS, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11771,7 +11656,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETHI, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11781,7 +11666,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETCC, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11791,7 +11676,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETOS, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11804,7 +11689,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETEQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -11817,7 +11702,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETNE, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -11830,7 +11715,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETLT, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -11843,7 +11728,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETLE, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -11856,7 +11741,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETGT, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -11869,7 +11754,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETGE, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -11882,7 +11767,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETCS, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -11895,7 +11780,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETLS, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -11908,7 +11793,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETHI, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -11921,7 +11806,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETCC, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -11933,7 +11818,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ clobbers: 1, // AX outputs: []outputInfo{ - {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49134}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11945,7 +11830,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ clobbers: 1, // AX outputs: []outputInfo{ - {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49134}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11955,7 +11840,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETPC, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11965,7 +11850,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETPS, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11975,7 +11860,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETHI, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11985,7 +11870,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASETCC, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -11995,10 +11880,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVBQSX, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12008,10 +11893,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVBLZX, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12021,10 +11906,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVWQSX, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12034,10 +11919,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVWLZX, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12047,10 +11932,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVLQSX, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12060,10 +11945,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12075,7 +11960,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVL, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12087,7 +11972,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVQ, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12097,10 +11982,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ACVTTSD2SL, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12110,10 +11995,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ACVTTSD2SQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12123,10 +12008,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ACVTTSS2SL, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12136,10 +12021,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ACVTTSS2SQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12149,10 +12034,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ACVTSL2SS, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -12162,10 +12047,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ACVTSL2SD, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -12175,10 +12060,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ACVTSQ2SS, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -12188,10 +12073,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ACVTSQ2SD, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -12201,10 +12086,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ACVTSD2SS, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -12214,10 +12099,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ACVTSS2SD, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -12226,10 +12111,10 @@ var opcodeTable = [...]opInfo{ argLen: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -12238,10 +12123,10 @@ var opcodeTable = [...]opInfo{ argLen: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12250,10 +12135,10 @@ var opcodeTable = [...]opInfo{ argLen: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -12262,10 +12147,10 @@ var opcodeTable = [...]opInfo{ argLen: 1, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12277,11 +12162,11 @@ var opcodeTable = [...]opInfo{ asm: x86.APXOR, reg: regInfo{ inputs: []inputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -12294,10 +12179,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ALEAQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12310,10 +12195,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ALEAL, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12326,10 +12211,10 @@ var opcodeTable = [...]opInfo{ asm: x86.ALEAW, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12343,11 +12228,11 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12361,11 +12246,11 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12379,11 +12264,11 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12396,11 +12281,11 @@ var opcodeTable = [...]opInfo{ scale: 2, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12413,11 +12298,11 @@ var opcodeTable = [...]opInfo{ scale: 2, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12430,11 +12315,11 @@ var opcodeTable = [...]opInfo{ scale: 2, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12447,11 +12332,11 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12464,11 +12349,11 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12481,11 +12366,11 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12498,11 +12383,11 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12515,11 +12400,11 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12532,11 +12417,11 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12549,10 +12434,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVBLZX, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12565,10 +12450,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVBQSX, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12581,10 +12466,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVWLZX, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12597,10 +12482,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVWQSX, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12613,10 +12498,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVL, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12629,10 +12514,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVLQSX, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12645,10 +12530,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12661,8 +12546,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVB, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12675,8 +12560,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVW, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12689,8 +12574,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVL, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12703,8 +12588,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVQ, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12717,10 +12602,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVUPS, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + {0, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, }, @@ -12733,8 +12618,21 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVUPS, reg: regInfo{ inputs: []inputInfo{ - {1, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 2147418112}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB + }, + }, + }, + { + name: "MOVOstorezero", + auxType: auxSymOff, + argLen: 2, + faultOnNilArg0: true, + symEffect: SymWrite, + asm: x86.AMOVUPS, + reg: regInfo{ + inputs: []inputInfo{ + {0, 4295016447}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 SB }, }, }, @@ -12748,11 +12646,11 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12766,11 +12664,11 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12783,11 +12681,11 @@ var opcodeTable = [...]opInfo{ scale: 2, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12801,11 +12699,11 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12818,11 +12716,11 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12835,11 +12733,11 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12853,11 +12751,11 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12870,11 +12768,11 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -12888,9 +12786,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12904,9 +12802,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12919,9 +12817,9 @@ var opcodeTable = [...]opInfo{ scale: 2, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12935,9 +12833,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12950,9 +12848,9 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12965,9 +12863,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12981,9 +12879,9 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -12996,9 +12894,9 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13011,7 +12909,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVB, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13024,7 +12922,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVW, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13037,7 +12935,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVL, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13050,7 +12948,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13064,8 +12962,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13079,8 +12977,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13093,8 +12991,8 @@ var opcodeTable = [...]opInfo{ scale: 2, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13108,8 +13006,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13122,8 +13020,8 @@ var opcodeTable = [...]opInfo{ scale: 4, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13137,8 +13035,8 @@ var opcodeTable = [...]opInfo{ scale: 1, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13151,36 +13049,24 @@ var opcodeTable = [...]opInfo{ scale: 8, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, { name: "DUFFZERO", auxType: auxInt64, - argLen: 3, + argLen: 2, faultOnNilArg0: true, unsafePoint: true, reg: regInfo{ inputs: []inputInfo{ - {0, 128}, // DI - {1, 65536}, // X0 + {0, 128}, // DI }, clobbers: 128, // DI }, }, - { - name: "MOVOconst", - auxType: auxInt128, - argLen: 0, - rematerializeable: true, - reg: regInfo{ - outputs: []outputInfo{ - {0, 4294901760}, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 - }, - }, - }, { name: "REPSTOSQ", argLen: 4, @@ -13197,38 +13083,38 @@ var opcodeTable = [...]opInfo{ { name: "CALLstatic", auxType: auxCallOff, - argLen: 1, + argLen: -1, clobberFlags: true, call: true, reg: regInfo{ - clobbers: 4294967279, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + clobbers: 2147483631, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 g R15 X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, { name: "CALLclosure", auxType: auxCallOff, - argLen: 3, + argLen: -1, clobberFlags: true, call: true, reg: regInfo{ inputs: []inputInfo{ {1, 4}, // DX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, - clobbers: 4294967279, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + clobbers: 2147483631, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 g R15 X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, { name: "CALLinter", auxType: auxCallOff, - argLen: 2, + argLen: -1, clobberFlags: true, call: true, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, - clobbers: 4294967279, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + clobbers: 2147483631, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 g R15 X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, { @@ -13271,7 +13157,7 @@ var opcodeTable = [...]opInfo{ argLen: 1, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13291,7 +13177,7 @@ var opcodeTable = [...]opInfo{ rematerializeable: true, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13301,7 +13187,7 @@ var opcodeTable = [...]opInfo{ rematerializeable: true, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13313,7 +13199,7 @@ var opcodeTable = [...]opInfo{ faultOnNilArg0: true, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13328,7 +13214,7 @@ var opcodeTable = [...]opInfo{ {0, 128}, // DI {1, 879}, // AX CX DX BX BP SI R8 R9 }, - clobbers: 4294901760, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 + clobbers: 2147418112, // X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 }, }, { @@ -13339,7 +13225,7 @@ var opcodeTable = [...]opInfo{ symEffect: SymNone, reg: regInfo{ outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13413,10 +13299,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVB, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13429,10 +13315,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVL, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13445,10 +13331,10 @@ var opcodeTable = [...]opInfo{ asm: x86.AMOVQ, reg: regInfo{ inputs: []inputInfo{ - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13463,11 +13349,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AXCHGB, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13482,11 +13368,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AXCHGL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13501,11 +13387,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AXCHGQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13521,11 +13407,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AXADDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13541,11 +13427,11 @@ var opcodeTable = [...]opInfo{ asm: x86.AXADDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, outputs: []outputInfo{ - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13571,13 +13457,13 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 1}, // AX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 1, // AX outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13593,13 +13479,13 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 1}, // AX - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {2, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, clobbers: 1, // AX outputs: []outputInfo{ {1, 0}, - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 }, }, }, @@ -13614,8 +13500,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDB, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13630,8 +13516,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13646,8 +13532,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AORB, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13662,8 +13548,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 SB + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB }, }, }, @@ -13807,7 +13693,7 @@ var opcodeTable = [...]opInfo{ {0, 2}, // R1 {1, 1}, // R0 }, - clobbers: 16396, // R2 R3 R14 + clobbers: 20492, // R2 R3 R12 R14 outputs: []outputInfo{ {0, 1}, // R0 {1, 2}, // R1 @@ -14428,6 +14314,19 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "SQRTF", + argLen: 1, + asm: arm.ASQRTF, + reg: regInfo{ + inputs: []inputInfo{ + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 + }, + outputs: []outputInfo{ + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 + }, + }, + }, { name: "ABSD", argLen: 1, @@ -17143,7 +17042,7 @@ var opcodeTable = [...]opInfo{ {0, 2}, // R1 {1, 1}, // R0 }, - clobbers: 16386, // R1 R14 + clobbers: 20482, // R1 R12 R14 }, }, { @@ -17157,7 +17056,7 @@ var opcodeTable = [...]opInfo{ {0, 4}, // R2 {1, 2}, // R1 }, - clobbers: 16391, // R0 R1 R2 R14 + clobbers: 20487, // R0 R1 R2 R12 R14 }, }, { @@ -17318,7 +17217,7 @@ var opcodeTable = [...]opInfo{ {0, 4}, // R2 {1, 8}, // R3 }, - clobbers: 4294918144, // R14 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 + clobbers: 4294922240, // R12 R14 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, }, @@ -18090,6 +17989,19 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "FSQRTS", + argLen: 1, + asm: arm64.AFSQRTS, + reg: regInfo{ + inputs: []inputInfo{ + {0, 9223372034707292160}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + }, + outputs: []outputInfo{ + {0, 9223372034707292160}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + }, + }, + }, { name: "REV", argLen: 1, @@ -18116,6 +18028,19 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "REV16", + argLen: 1, + asm: arm64.AREV16, + reg: regInfo{ + inputs: []inputInfo{ + {0, 805044223}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 + }, + outputs: []outputInfo{ + {0, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + }, + }, + }, { name: "REV16W", argLen: 1, @@ -19795,6 +19720,34 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "FMOVSloadidx4", + argLen: 3, + asm: arm64.AFMOVS, + reg: regInfo{ + inputs: []inputInfo{ + {1, 805044223}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 + {0, 9223372038733561855}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 SP SB + }, + outputs: []outputInfo{ + {0, 9223372034707292160}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + }, + }, + }, + { + name: "FMOVDloadidx8", + argLen: 3, + asm: arm64.AFMOVD, + reg: regInfo{ + inputs: []inputInfo{ + {1, 805044223}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 + {0, 9223372038733561855}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 SP SB + }, + outputs: []outputInfo{ + {0, 9223372034707292160}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + }, + }, + }, { name: "MOVBstore", auxType: auxSymOff, @@ -20002,6 +19955,30 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "FMOVSstoreidx4", + argLen: 4, + asm: arm64.AFMOVS, + reg: regInfo{ + inputs: []inputInfo{ + {1, 805044223}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 + {0, 9223372038733561855}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 SP SB + {2, 9223372034707292160}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + }, + }, + }, + { + name: "FMOVDstoreidx8", + argLen: 4, + asm: arm64.AFMOVD, + reg: regInfo{ + inputs: []inputInfo{ + {1, 805044223}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 + {0, 9223372038733561855}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 SP SB + {2, 9223372034707292160}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + }, + }, + }, { name: "MOVBstorezero", auxType: auxSymOff, @@ -20628,6 +20605,62 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "CSINC", + auxType: auxCCop, + argLen: 3, + asm: arm64.ACSINC, + reg: regInfo{ + inputs: []inputInfo{ + {0, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + {1, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + }, + outputs: []outputInfo{ + {0, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + }, + }, + }, + { + name: "CSINV", + auxType: auxCCop, + argLen: 3, + asm: arm64.ACSINV, + reg: regInfo{ + inputs: []inputInfo{ + {0, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + {1, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + }, + outputs: []outputInfo{ + {0, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + }, + }, + }, + { + name: "CSNEG", + auxType: auxCCop, + argLen: 3, + asm: arm64.ACSNEG, + reg: regInfo{ + inputs: []inputInfo{ + {0, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + {1, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + }, + outputs: []outputInfo{ + {0, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + }, + }, + }, + { + name: "CSETM", + auxType: auxCCop, + argLen: 1, + asm: arm64.ACSETM, + reg: regInfo{ + outputs: []outputInfo{ + {0, 670826495}, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 R30 + }, + }, + }, { name: "CALLstatic", auxType: auxCallOff, @@ -20848,7 +20881,7 @@ var opcodeTable = [...]opInfo{ inputs: []inputInfo{ {0, 1048576}, // R20 }, - clobbers: 537919488, // R20 R30 + clobbers: 538116096, // R16 R17 R20 R30 }, }, { @@ -20876,7 +20909,7 @@ var opcodeTable = [...]opInfo{ {0, 2097152}, // R21 {1, 1048576}, // R20 }, - clobbers: 607125504, // R20 R21 R26 R30 + clobbers: 607322112, // R16 R17 R20 R21 R26 R30 }, }, { @@ -21373,7 +21406,7 @@ var opcodeTable = [...]opInfo{ {0, 4}, // R2 {1, 8}, // R3 }, - clobbers: 9223372035244163072, // R30 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + clobbers: 9223372035244359680, // R16 R17 R30 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 }, }, { @@ -21831,6 +21864,19 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "SQRTF", + argLen: 1, + asm: mips.ASQRTF, + reg: regInfo{ + inputs: []inputInfo{ + {0, 35183835217920}, // F0 F2 F4 F6 F8 F10 F12 F14 F16 F18 F20 F22 F24 F26 F28 F30 + }, + outputs: []outputInfo{ + {0, 35183835217920}, // F0 F2 F4 F6 F8 F10 F12 F14 F16 F18 F20 F22 F24 F26 F28 F30 + }, + }, + }, { name: "SLL", argLen: 2, @@ -23310,6 +23356,19 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "SQRTF", + argLen: 1, + asm: mips.ASQRTF, + reg: regInfo{ + inputs: []inputInfo{ + {0, 1152921504338411520}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + }, + outputs: []outputInfo{ + {0, 1152921504338411520}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + }, + }, + }, { name: "SLLV", argLen: 2, @@ -27672,42 +27731,6 @@ var opcodeTable = [...]opInfo{ }, }, }, - { - name: "MOVBconst", - auxType: auxInt8, - argLen: 0, - rematerializeable: true, - asm: riscv.AMOV, - reg: regInfo{ - outputs: []outputInfo{ - {0, 1006632948}, // X3 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X28 X29 X30 - }, - }, - }, - { - name: "MOVHconst", - auxType: auxInt16, - argLen: 0, - rematerializeable: true, - asm: riscv.AMOV, - reg: regInfo{ - outputs: []outputInfo{ - {0, 1006632948}, // X3 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X28 X29 X30 - }, - }, - }, - { - name: "MOVWconst", - auxType: auxInt32, - argLen: 0, - rematerializeable: true, - asm: riscv.AMOV, - reg: regInfo{ - outputs: []outputInfo{ - {0, 1006632948}, // X3 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X28 X29 X30 - }, - }, - }, { name: "MOVDconst", auxType: auxInt64, @@ -28588,6 +28611,32 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "LoweredAtomicAnd32", + argLen: 3, + faultOnNilArg0: true, + hasSideEffects: true, + asm: riscv.AAMOANDW, + reg: regInfo{ + inputs: []inputInfo{ + {1, 1073741812}, // X3 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 g X28 X29 X30 + {0, 9223372037928517622}, // SP X3 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 g X28 X29 X30 SB + }, + }, + }, + { + name: "LoweredAtomicOr32", + argLen: 3, + faultOnNilArg0: true, + hasSideEffects: true, + asm: riscv.AAMOORW, + reg: regInfo{ + inputs: []inputInfo{ + {1, 1073741812}, // X3 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 g X28 X29 X30 + {0, 9223372037928517622}, // SP X3 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 g X28 X29 X30 SB + }, + }, + }, { name: "LoweredNilCheck", argLen: 2, @@ -30894,6 +30943,19 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "FSQRTS", + argLen: 1, + asm: s390x.AFSQRTS, + reg: regInfo{ + inputs: []inputInfo{ + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 + }, + outputs: []outputInfo{ + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 + }, + }, + }, { name: "LOCGR", auxType: auxS390XCCMask, @@ -33828,10 +33890,10 @@ var opcodeTable = [...]opInfo{ asm: wasm.AF32Sqrt, reg: regInfo{ inputs: []inputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, outputs: []outputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, }, }, @@ -33841,10 +33903,10 @@ var opcodeTable = [...]opInfo{ asm: wasm.AF32Trunc, reg: regInfo{ inputs: []inputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, outputs: []outputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, }, }, @@ -33854,10 +33916,10 @@ var opcodeTable = [...]opInfo{ asm: wasm.AF32Ceil, reg: regInfo{ inputs: []inputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, outputs: []outputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, }, }, @@ -33867,10 +33929,10 @@ var opcodeTable = [...]opInfo{ asm: wasm.AF32Floor, reg: regInfo{ inputs: []inputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, outputs: []outputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, }, }, @@ -33880,10 +33942,10 @@ var opcodeTable = [...]opInfo{ asm: wasm.AF32Nearest, reg: regInfo{ inputs: []inputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, outputs: []outputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, }, }, @@ -33893,10 +33955,10 @@ var opcodeTable = [...]opInfo{ asm: wasm.AF32Abs, reg: regInfo{ inputs: []inputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, outputs: []outputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, }, }, @@ -33906,11 +33968,11 @@ var opcodeTable = [...]opInfo{ asm: wasm.AF32Copysign, reg: regInfo{ inputs: []inputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 - {1, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 + {1, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, outputs: []outputInfo{ - {0, 281470681743360}, // F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + {0, 4294901760}, // F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 }, }, }, @@ -35128,6 +35190,11 @@ var opcodeTable = [...]opInfo{ argLen: 1, generic: true, }, + { + name: "Sqrt32", + argLen: 1, + generic: true, + }, { name: "Floor", argLen: 1, @@ -35263,6 +35330,20 @@ var opcodeTable = [...]opInfo{ symEffect: SymRead, generic: true, }, + { + name: "ArgIntReg", + auxType: auxNameOffsetInt8, + argLen: 0, + zeroWidth: true, + generic: true, + }, + { + name: "ArgFloatReg", + auxType: auxNameOffsetInt8, + argLen: 0, + zeroWidth: true, + generic: true, + }, { name: "Addr", auxType: auxSym, @@ -35366,21 +35447,21 @@ var opcodeTable = [...]opInfo{ { name: "ClosureCall", auxType: auxCallOff, - argLen: 3, + argLen: -1, call: true, generic: true, }, { name: "StaticCall", auxType: auxCallOff, - argLen: 1, + argLen: -1, call: true, generic: true, }, { name: "InterCall", auxType: auxCallOff, - argLen: 2, + argLen: -1, call: true, generic: true, }, @@ -35632,6 +35713,11 @@ var opcodeTable = [...]opInfo{ argLen: 1, generic: true, }, + { + name: "SlicePtrUnchecked", + argLen: 1, + generic: true, + }, { name: "ComplexMake", argLen: 2, @@ -36122,16 +36208,21 @@ var opcodeTable = [...]opInfo{ symEffect: SymNone, generic: true, }, + { + name: "ClobberReg", + argLen: 0, + generic: true, + }, } func (o Op) Asm() obj.As { return opcodeTable[o].asm } func (o Op) Scale() int16 { return int16(opcodeTable[o].scale) } func (o Op) String() string { return opcodeTable[o].name } -func (o Op) UsesScratch() bool { return opcodeTable[o].usesScratch } func (o Op) SymEffect() SymEffect { return opcodeTable[o].symEffect } func (o Op) IsCall() bool { return opcodeTable[o].call } func (o Op) HasSideEffects() bool { return opcodeTable[o].hasSideEffects } func (o Op) UnsafePoint() bool { return opcodeTable[o].unsafePoint } +func (o Op) ResultInArg0() bool { return opcodeTable[o].resultInArg0 } var registers386 = [...]Register{ {0, x86.REG_AX, 0, "AX"}, @@ -36152,6 +36243,8 @@ var registers386 = [...]Register{ {15, x86.REG_X7, -1, "X7"}, {16, 0, -1, "SB"}, } +var paramIntReg386 = []int8(nil) +var paramFloatReg386 = []int8(nil) var gpRegMask386 = regMask(239) var fpRegMask386 = regMask(65280) var specialRegMask386 = regMask(0) @@ -36172,8 +36265,8 @@ var registersAMD64 = [...]Register{ {11, x86.REG_R11, 10, "R11"}, {12, x86.REG_R12, 11, "R12"}, {13, x86.REG_R13, 12, "R13"}, - {14, x86.REG_R14, 13, "R14"}, - {15, x86.REG_R15, 14, "R15"}, + {14, x86.REGG, -1, "g"}, + {15, x86.REG_R15, 13, "R15"}, {16, x86.REG_X0, -1, "X0"}, {17, x86.REG_X1, -1, "X1"}, {18, x86.REG_X2, -1, "X2"}, @@ -36192,9 +36285,11 @@ var registersAMD64 = [...]Register{ {31, x86.REG_X15, -1, "X15"}, {32, 0, -1, "SB"}, } -var gpRegMaskAMD64 = regMask(65519) -var fpRegMaskAMD64 = regMask(4294901760) -var specialRegMaskAMD64 = regMask(0) +var paramIntRegAMD64 = []int8{0, 3, 1, 7, 6, 8, 9, 10, 11} +var paramFloatRegAMD64 = []int8{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30} +var gpRegMaskAMD64 = regMask(49135) +var fpRegMaskAMD64 = regMask(2147418112) +var specialRegMaskAMD64 = regMask(2147483648) var framepointerRegAMD64 = int8(5) var linkRegAMD64 = int8(-1) var registersARM = [...]Register{ @@ -36232,6 +36327,8 @@ var registersARM = [...]Register{ {31, arm.REG_F15, -1, "F15"}, {32, 0, -1, "SB"}, } +var paramIntRegARM = []int8(nil) +var paramFloatRegARM = []int8(nil) var gpRegMaskARM = regMask(21503) var fpRegMaskARM = regMask(4294901760) var specialRegMaskARM = regMask(0) @@ -36303,6 +36400,8 @@ var registersARM64 = [...]Register{ {62, arm64.REG_F31, -1, "F31"}, {63, 0, -1, "SB"}, } +var paramIntRegARM64 = []int8(nil) +var paramFloatRegARM64 = []int8(nil) var gpRegMaskARM64 = regMask(670826495) var fpRegMaskARM64 = regMask(9223372034707292160) var specialRegMaskARM64 = regMask(0) @@ -36358,6 +36457,8 @@ var registersMIPS = [...]Register{ {46, mips.REG_LO, -1, "LO"}, {47, 0, -1, "SB"}, } +var paramIntRegMIPS = []int8(nil) +var paramFloatRegMIPS = []int8(nil) var gpRegMaskMIPS = regMask(335544318) var fpRegMaskMIPS = regMask(35183835217920) var specialRegMaskMIPS = regMask(105553116266496) @@ -36428,6 +36529,8 @@ var registersMIPS64 = [...]Register{ {61, mips.REG_LO, -1, "LO"}, {62, 0, -1, "SB"}, } +var paramIntRegMIPS64 = []int8(nil) +var paramFloatRegMIPS64 = []int8(nil) var gpRegMaskMIPS64 = regMask(167772158) var fpRegMaskMIPS64 = regMask(1152921504338411520) var specialRegMaskMIPS64 = regMask(3458764513820540928) @@ -36499,6 +36602,8 @@ var registersPPC64 = [...]Register{ {62, ppc64.REG_F30, -1, "F30"}, {63, ppc64.REG_F31, -1, "F31"}, } +var paramIntRegPPC64 = []int8(nil) +var paramFloatRegPPC64 = []int8(nil) var gpRegMaskPPC64 = regMask(1073733624) var fpRegMaskPPC64 = regMask(576460743713488896) var specialRegMaskPPC64 = regMask(0) @@ -36570,6 +36675,8 @@ var registersRISCV64 = [...]Register{ {62, riscv.REG_F31, -1, "F31"}, {63, 0, -1, "SB"}, } +var paramIntRegRISCV64 = []int8(nil) +var paramFloatRegRISCV64 = []int8(nil) var gpRegMaskRISCV64 = regMask(1006632948) var fpRegMaskRISCV64 = regMask(9223372034707292160) var specialRegMaskRISCV64 = regMask(0) @@ -36610,6 +36717,8 @@ var registersS390X = [...]Register{ {31, s390x.REG_F15, -1, "F15"}, {32, 0, -1, "SB"}, } +var paramIntRegS390X = []int8(nil) +var paramFloatRegS390X = []int8(nil) var gpRegMaskS390X = regMask(23551) var fpRegMaskS390X = regMask(4294901760) var specialRegMaskS390X = regMask(0) @@ -36668,6 +36777,8 @@ var registersWasm = [...]Register{ {49, wasm.REGG, -1, "g"}, {50, 0, -1, "SB"}, } +var paramIntRegWasm = []int8(nil) +var paramFloatRegWasm = []int8(nil) var gpRegMaskWasm = regMask(65535) var fpRegMaskWasm = regMask(281474976645120) var fp32RegMaskWasm = regMask(4294901760) diff --git a/src/cmd/compile/internal/ssa/phiopt.go b/src/cmd/compile/internal/ssa/phiopt.go index db7b02275cf934c62d66a9e9434bc61d041fff38..745c61cb860a950fb7efb2f6d2576e6feae5d028 100644 --- a/src/cmd/compile/internal/ssa/phiopt.go +++ b/src/cmd/compile/internal/ssa/phiopt.go @@ -46,7 +46,6 @@ func phiopt(f *Func) { continue } // b0 is the if block giving the boolean value. - // reverse is the predecessor from which the truth value comes. var reverse int if b0.Succs[0].b == pb0 && b0.Succs[1].b == pb1 { @@ -120,6 +119,141 @@ func phiopt(f *Func) { } } } + // strengthen phi optimization. + // Main use case is to transform: + // x := false + // if c { + // x = true + // ... + // } + // into + // x := c + // if x { ... } + // + // For example, in SSA code a case appears as + // b0 + // If c -> b, sb0 + // sb0 + // If d -> sd0, sd1 + // sd1 + // ... + // sd0 + // Plain -> b + // b + // x = (OpPhi (ConstBool [true]) (ConstBool [false])) + // + // In this case we can also replace x with a copy of c. + // + // The optimization idea: + // 1. block b has a phi value x, x = OpPhi (ConstBool [true]) (ConstBool [false]), + // and len(b.Preds) is equal to 2. + // 2. find the common dominator(b0) of the predecessors(pb0, pb1) of block b, and the + // dominator(b0) is a If block. + // Special case: one of the predecessors(pb0 or pb1) is the dominator(b0). + // 3. the successors(sb0, sb1) of the dominator need to dominate the predecessors(pb0, pb1) + // of block b respectively. + // 4. replace this boolean Phi based on dominator block. + // + // b0(pb0) b0(pb1) b0 + // | \ / | / \ + // | sb1 sb0 | sb0 sb1 + // | ... ... | ... ... + // | pb1 pb0 | pb0 pb1 + // | / \ | \ / + // b b b + // + var lca *lcaRange + for _, b := range f.Blocks { + if len(b.Preds) != 2 || len(b.Values) == 0 { + // TODO: handle more than 2 predecessors, e.g. a || b || c. + continue + } + + for _, v := range b.Values { + // find a phi value v = OpPhi (ConstBool [true]) (ConstBool [false]). + // TODO: v = OpPhi (ConstBool [true]) (Arg {value}) + if v.Op != OpPhi { + continue + } + if v.Args[0].Op != OpConstBool || v.Args[1].Op != OpConstBool { + continue + } + if v.Args[0].AuxInt == v.Args[1].AuxInt { + continue + } + + pb0 := b.Preds[0].b + pb1 := b.Preds[1].b + if pb0.Kind == BlockIf && pb0 == sdom.Parent(b) { + // special case: pb0 is the dominator block b0. + // b0(pb0) + // | \ + // | sb1 + // | ... + // | pb1 + // | / + // b + // if another successor sb1 of b0(pb0) dominates pb1, do replace. + ei := b.Preds[0].i + sb1 := pb0.Succs[1-ei].b + if sdom.IsAncestorEq(sb1, pb1) { + convertPhi(pb0, v, ei) + break + } + } else if pb1.Kind == BlockIf && pb1 == sdom.Parent(b) { + // special case: pb1 is the dominator block b0. + // b0(pb1) + // / | + // sb0 | + // ... | + // pb0 | + // \ | + // b + // if another successor sb0 of b0(pb0) dominates pb0, do replace. + ei := b.Preds[1].i + sb0 := pb1.Succs[1-ei].b + if sdom.IsAncestorEq(sb0, pb0) { + convertPhi(pb1, v, 1-ei) + break + } + } else { + // b0 + // / \ + // sb0 sb1 + // ... ... + // pb0 pb1 + // \ / + // b + // + // Build data structure for fast least-common-ancestor queries. + if lca == nil { + lca = makeLCArange(f) + } + b0 := lca.find(pb0, pb1) + if b0.Kind != BlockIf { + break + } + sb0 := b0.Succs[0].b + sb1 := b0.Succs[1].b + var reverse int + if sdom.IsAncestorEq(sb0, pb0) && sdom.IsAncestorEq(sb1, pb1) { + reverse = 0 + } else if sdom.IsAncestorEq(sb1, pb0) && sdom.IsAncestorEq(sb0, pb1) { + reverse = 1 + } else { + break + } + if len(sb0.Preds) != 1 || len(sb1.Preds) != 1 { + // we can not replace phi value x in the following case. + // if gp == nil || sp < lo { x = true} + // if a || b { x = true } + // so the if statement can only have one condition. + break + } + convertPhi(b0, v, reverse) + } + } + } } func phioptint(v *Value, b0 *Block, reverse int) { @@ -174,3 +308,16 @@ func phioptint(v *Value, b0 *Block, reverse int) { f.Warnl(v.Block.Pos, "converted OpPhi bool -> int%d", v.Type.Size()*8) } } + +// b is the If block giving the boolean value. +// v is the phi value v = (OpPhi (ConstBool [true]) (ConstBool [false])). +// reverse is the predecessor from which the truth value comes. +func convertPhi(b *Block, v *Value, reverse int) { + f := b.Func + ops := [2]Op{OpNot, OpCopy} + v.reset(ops[v.Args[reverse].AuxInt]) + v.AddArg(b.Controls[0]) + if f.pass.debug > 0 { + f.Warnl(b.Pos, "converted OpPhi to %v", v.Op) + } +} diff --git a/src/cmd/compile/internal/ssa/poset.go b/src/cmd/compile/internal/ssa/poset.go index f5a2b3a8c2104ff785f4daf0bb3decb2a64a8d01..d2719eb8a1d9d4f9b4cbda90e1a1a6b4ef1c0ab2 100644 --- a/src/cmd/compile/internal/ssa/poset.go +++ b/src/cmd/compile/internal/ssa/poset.go @@ -12,7 +12,7 @@ import ( // If true, check poset integrity after every mutation var debugPoset = false -const uintSize = 32 << (^uint(0) >> 32 & 1) // 32 or 64 +const uintSize = 32 << (^uint(0) >> 63) // 32 or 64 // bitset is a bit array for dense indexes. type bitset []uint @@ -136,13 +136,13 @@ type posetNode struct { // Most internal data structures are pre-allocated and flat, so for instance adding a // new relation does not cause any allocation. For performance reasons, // each node has only up to two outgoing edges (like a binary tree), so intermediate -// "dummy" nodes are required to represent more than two relations. For instance, +// "extra" nodes are required to represent more than two relations. For instance, // to record that A r i1 // i2 \ / // i2 // - dummy := po.newnode(nil) - po.changeroot(r, dummy) - po.upush(undoChangeRoot, dummy, newedge(r, false)) - po.addchild(dummy, r, false) - po.addchild(dummy, i1, false) + extra := po.newnode(nil) + po.changeroot(r, extra) + po.upush(undoChangeRoot, extra, newedge(r, false)) + po.addchild(extra, r, false) + po.addchild(extra, i1, false) po.addchild(i1, i2, strict) case f1 && f2: diff --git a/src/cmd/compile/internal/ssa/print.go b/src/cmd/compile/internal/ssa/print.go index 36f09c3ad978a90dcef12a0bbe6e23e3d76e03f5..d917183c70f5e8f23ff904a6421983e8a1770b25 100644 --- a/src/cmd/compile/internal/ssa/print.go +++ b/src/cmd/compile/internal/ssa/print.go @@ -154,6 +154,6 @@ func fprintFunc(p funcPrinter, f *Func) { p.endBlock(b) } for _, name := range f.Names { - p.named(name, f.NamedValues[name]) + p.named(*name, f.NamedValues[*name]) } } diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go index 8a2e7c09bc5a36c08122d21b8438a004e3c3f73b..b203584c6b42cfae2b90d7ef06945900b1982722 100644 --- a/src/cmd/compile/internal/ssa/prove.go +++ b/src/cmd/compile/internal/ssa/prove.go @@ -726,6 +726,20 @@ var ( } ) +// cleanup returns the posets to the free list +func (ft *factsTable) cleanup(f *Func) { + for _, po := range []*poset{ft.orderS, ft.orderU} { + // Make sure it's empty as it should be. A non-empty poset + // might cause errors and miscompilations if reused. + if checkEnabled { + if err := po.CheckEmpty(); err != nil { + f.Fatalf("poset not empty after function %s: %v", f.Name, err) + } + } + f.retPoset(po) + } +} + // prove removes redundant BlockIf branches that can be inferred // from previous dominating comparisons. // @@ -778,7 +792,14 @@ func prove(f *Func) { if ft.lens == nil { ft.lens = map[ID]*Value{} } - ft.lens[v.Args[0].ID] = v + // Set all len Values for the same slice as equal in the poset. + // The poset handles transitive relations, so Values related to + // any OpSliceLen for this slice will be correctly related to others. + if l, ok := ft.lens[v.Args[0].ID]; ok { + ft.update(b, v, l, signed, eq) + } else { + ft.lens[v.Args[0].ID] = v + } ft.update(b, v, ft.zero, signed, gt|eq) if v.Args[0].Op == OpSliceMake { if lensVars == nil { @@ -790,7 +811,12 @@ func prove(f *Func) { if ft.caps == nil { ft.caps = map[ID]*Value{} } - ft.caps[v.Args[0].ID] = v + // Same as case OpSliceLen above, but for slice cap. + if c, ok := ft.caps[v.Args[0].ID]; ok { + ft.update(b, v, c, signed, eq) + } else { + ft.caps[v.Args[0].ID] = v + } ft.update(b, v, ft.zero, signed, gt|eq) if v.Args[0].Op == OpSliceMake { if lensVars == nil { @@ -905,17 +931,7 @@ func prove(f *Func) { ft.restore() - // Return the posets to the free list - for _, po := range []*poset{ft.orderS, ft.orderU} { - // Make sure it's empty as it should be. A non-empty poset - // might cause errors and miscompilations if reused. - if checkEnabled { - if err := po.CheckEmpty(); err != nil { - f.Fatalf("prove poset not empty after function %s: %v", f.Name, err) - } - } - f.retPoset(po) - } + ft.cleanup(f) } // getBranch returns the range restrictions added by p diff --git a/src/cmd/compile/internal/ssa/redblack32.go b/src/cmd/compile/internal/ssa/redblack32.go deleted file mode 100644 index fc9cc71ba036bf54596e29a6b24836f9ed293603..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/ssa/redblack32.go +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssa - -import "fmt" - -const ( - rankLeaf rbrank = 1 - rankZero rbrank = 0 -) - -type rbrank int8 - -// RBTint32 is a red-black tree with data stored at internal nodes, -// following Tarjan, Data Structures and Network Algorithms, -// pp 48-52, using explicit rank instead of red and black. -// Deletion is not yet implemented because it is not yet needed. -// Extra operations glb, lub, glbEq, lubEq are provided for -// use in sparse lookup algorithms. -type RBTint32 struct { - root *node32 - // An extra-clever implementation will have special cases - // for small sets, but we are not extra-clever today. -} - -func (t *RBTint32) String() string { - if t.root == nil { - return "[]" - } - return "[" + t.root.String() + "]" -} - -func (t *node32) String() string { - s := "" - if t.left != nil { - s = t.left.String() + " " - } - s = s + fmt.Sprintf("k=%d,d=%v", t.key, t.data) - if t.right != nil { - s = s + " " + t.right.String() - } - return s -} - -type node32 struct { - // Standard conventions hold for left = smaller, right = larger - left, right, parent *node32 - data interface{} - key int32 - rank rbrank // From Tarjan pp 48-49: - // If x is a node with a parent, then x.rank <= x.parent.rank <= x.rank+1. - // If x is a node with a grandparent, then x.rank < x.parent.parent.rank. - // If x is an "external [null] node", then x.rank = 0 && x.parent.rank = 1. - // Any node with one or more null children should have rank = 1. -} - -// makeNode returns a new leaf node with the given key and nil data. -func (t *RBTint32) makeNode(key int32) *node32 { - return &node32{key: key, rank: rankLeaf} -} - -// IsEmpty reports whether t is empty. -func (t *RBTint32) IsEmpty() bool { - return t.root == nil -} - -// IsSingle reports whether t is a singleton (leaf). -func (t *RBTint32) IsSingle() bool { - return t.root != nil && t.root.isLeaf() -} - -// VisitInOrder applies f to the key and data pairs in t, -// with keys ordered from smallest to largest. -func (t *RBTint32) VisitInOrder(f func(int32, interface{})) { - if t.root == nil { - return - } - t.root.visitInOrder(f) -} - -func (n *node32) Data() interface{} { - if n == nil { - return nil - } - return n.data -} - -func (n *node32) keyAndData() (k int32, d interface{}) { - if n == nil { - k = 0 - d = nil - } else { - k = n.key - d = n.data - } - return -} - -func (n *node32) Rank() rbrank { - if n == nil { - return 0 - } - return n.rank -} - -// Find returns the data associated with key in the tree, or -// nil if key is not in the tree. -func (t *RBTint32) Find(key int32) interface{} { - return t.root.find(key).Data() -} - -// Insert adds key to the tree and associates key with data. -// If key was already in the tree, it updates the associated data. -// Insert returns the previous data associated with key, -// or nil if key was not present. -// Insert panics if data is nil. -func (t *RBTint32) Insert(key int32, data interface{}) interface{} { - if data == nil { - panic("Cannot insert nil data into tree") - } - n := t.root - var newroot *node32 - if n == nil { - n = t.makeNode(key) - newroot = n - } else { - newroot, n = n.insert(key, t) - } - r := n.data - n.data = data - t.root = newroot - return r -} - -// Min returns the minimum element of t and its associated data. -// If t is empty, then (0, nil) is returned. -func (t *RBTint32) Min() (k int32, d interface{}) { - return t.root.min().keyAndData() -} - -// Max returns the maximum element of t and its associated data. -// If t is empty, then (0, nil) is returned. -func (t *RBTint32) Max() (k int32, d interface{}) { - return t.root.max().keyAndData() -} - -// Glb returns the greatest-lower-bound-exclusive of x and its associated -// data. If x has no glb in the tree, then (0, nil) is returned. -func (t *RBTint32) Glb(x int32) (k int32, d interface{}) { - return t.root.glb(x, false).keyAndData() -} - -// GlbEq returns the greatest-lower-bound-inclusive of x and its associated -// data. If x has no glbEQ in the tree, then (0, nil) is returned. -func (t *RBTint32) GlbEq(x int32) (k int32, d interface{}) { - return t.root.glb(x, true).keyAndData() -} - -// Lub returns the least-upper-bound-exclusive of x and its associated -// data. If x has no lub in the tree, then (0, nil) is returned. -func (t *RBTint32) Lub(x int32) (k int32, d interface{}) { - return t.root.lub(x, false).keyAndData() -} - -// LubEq returns the least-upper-bound-inclusive of x and its associated -// data. If x has no lubEq in the tree, then (0, nil) is returned. -func (t *RBTint32) LubEq(x int32) (k int32, d interface{}) { - return t.root.lub(x, true).keyAndData() -} - -func (t *node32) isLeaf() bool { - return t.left == nil && t.right == nil -} - -func (t *node32) visitInOrder(f func(int32, interface{})) { - if t.left != nil { - t.left.visitInOrder(f) - } - f(t.key, t.data) - if t.right != nil { - t.right.visitInOrder(f) - } -} - -func (t *node32) maxChildRank() rbrank { - if t.left == nil { - if t.right == nil { - return rankZero - } - return t.right.rank - } - if t.right == nil { - return t.left.rank - } - if t.right.rank > t.left.rank { - return t.right.rank - } - return t.left.rank -} - -func (t *node32) minChildRank() rbrank { - if t.left == nil || t.right == nil { - return rankZero - } - if t.right.rank < t.left.rank { - return t.right.rank - } - return t.left.rank -} - -func (t *node32) find(key int32) *node32 { - for t != nil { - if key < t.key { - t = t.left - } else if key > t.key { - t = t.right - } else { - return t - } - } - return nil -} - -func (t *node32) min() *node32 { - if t == nil { - return t - } - for t.left != nil { - t = t.left - } - return t -} - -func (t *node32) max() *node32 { - if t == nil { - return t - } - for t.right != nil { - t = t.right - } - return t -} - -func (t *node32) glb(key int32, allow_eq bool) *node32 { - var best *node32 - for t != nil { - if key <= t.key { - if key == t.key && allow_eq { - return t - } - // t is too big, glb is to left. - t = t.left - } else { - // t is a lower bound, record it and seek a better one. - best = t - t = t.right - } - } - return best -} - -func (t *node32) lub(key int32, allow_eq bool) *node32 { - var best *node32 - for t != nil { - if key >= t.key { - if key == t.key && allow_eq { - return t - } - // t is too small, lub is to right. - t = t.right - } else { - // t is a upper bound, record it and seek a better one. - best = t - t = t.left - } - } - return best -} - -func (t *node32) insert(x int32, w *RBTint32) (newroot, newnode *node32) { - // defaults - newroot = t - newnode = t - if x == t.key { - return - } - if x < t.key { - if t.left == nil { - n := w.makeNode(x) - n.parent = t - t.left = n - newnode = n - return - } - var new_l *node32 - new_l, newnode = t.left.insert(x, w) - t.left = new_l - new_l.parent = t - newrank := 1 + new_l.maxChildRank() - if newrank > t.rank { - if newrank > 1+t.right.Rank() { // rotations required - if new_l.left.Rank() < new_l.right.Rank() { - // double rotation - t.left = new_l.rightToRoot() - } - newroot = t.leftToRoot() - return - } else { - t.rank = newrank - } - } - } else { // x > t.key - if t.right == nil { - n := w.makeNode(x) - n.parent = t - t.right = n - newnode = n - return - } - var new_r *node32 - new_r, newnode = t.right.insert(x, w) - t.right = new_r - new_r.parent = t - newrank := 1 + new_r.maxChildRank() - if newrank > t.rank { - if newrank > 1+t.left.Rank() { // rotations required - if new_r.right.Rank() < new_r.left.Rank() { - // double rotation - t.right = new_r.leftToRoot() - } - newroot = t.rightToRoot() - return - } else { - t.rank = newrank - } - } - } - return -} - -func (t *node32) rightToRoot() *node32 { - // this - // left right - // rl rr - // - // becomes - // - // right - // this rr - // left rl - // - right := t.right - rl := right.left - right.parent = t.parent - right.left = t - t.parent = right - // parent's child ptr fixed in caller - t.right = rl - if rl != nil { - rl.parent = t - } - return right -} - -func (t *node32) leftToRoot() *node32 { - // this - // left right - // ll lr - // - // becomes - // - // left - // ll this - // lr right - // - left := t.left - lr := left.right - left.parent = t.parent - left.right = t - t.parent = left - // parent's child ptr fixed in caller - t.left = lr - if lr != nil { - lr.parent = t - } - return left -} - -// next returns the successor of t in a left-to-right -// walk of the tree in which t is embedded. -func (t *node32) next() *node32 { - // If there is a right child, it is to the right - r := t.right - if r != nil { - return r.min() - } - // if t is p.left, then p, else repeat. - p := t.parent - for p != nil { - if p.left == t { - return p - } - t = p - p = t.parent - } - return nil -} - -// prev returns the predecessor of t in a left-to-right -// walk of the tree in which t is embedded. -func (t *node32) prev() *node32 { - // If there is a left child, it is to the left - l := t.left - if l != nil { - return l.max() - } - // if t is p.right, then p, else repeat. - p := t.parent - for p != nil { - if p.right == t { - return p - } - t = p - p = t.parent - } - return nil -} diff --git a/src/cmd/compile/internal/ssa/redblack32_test.go b/src/cmd/compile/internal/ssa/redblack32_test.go deleted file mode 100644 index 376e8cff8ddc0b947b67af045d7b6bada2e73a28..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/ssa/redblack32_test.go +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssa - -import ( - "fmt" - "testing" -) - -type sstring string - -func (s sstring) String() string { - return string(s) -} - -// wellFormed ensures that a red-black tree meets -// all of its invariants and returns a string identifying -// the first problem encountered. If there is no problem -// then the returned string is empty. The size is also -// returned to allow comparison of calculated tree size -// with expected. -func (t *RBTint32) wellFormed() (s string, i int) { - if t.root == nil { - s = "" - i = 0 - return - } - return t.root.wellFormedSubtree(nil, -0x80000000, 0x7fffffff) -} - -// wellFormedSubtree ensures that a red-black subtree meets -// all of its invariants and returns a string identifying -// the first problem encountered. If there is no problem -// then the returned string is empty. The size is also -// returned to allow comparison of calculated tree size -// with expected. -func (t *node32) wellFormedSubtree(parent *node32, min, max int32) (s string, i int) { - i = -1 // initialize to a failing value - s = "" // s is the reason for failure; empty means okay. - - if t.parent != parent { - s = "t.parent != parent" - return - } - - if min >= t.key { - s = "min >= t.key" - return - } - - if max <= t.key { - s = "max <= t.key" - return - } - - l := t.left - r := t.right - if l == nil && r == nil { - if t.rank != rankLeaf { - s = "leaf rank wrong" - return - } - } - if l != nil { - if t.rank < l.rank { - s = "t.rank < l.rank" - } else if t.rank > 1+l.rank { - s = "t.rank > 1+l.rank" - } else if t.rank <= l.maxChildRank() { - s = "t.rank <= l.maxChildRank()" - } else if t.key <= l.key { - s = "t.key <= l.key" - } - if s != "" { - return - } - } else { - if t.rank != 1 { - s = "t w/ left nil has rank != 1" - return - } - } - if r != nil { - if t.rank < r.rank { - s = "t.rank < r.rank" - } else if t.rank > 1+r.rank { - s = "t.rank > 1+r.rank" - } else if t.rank <= r.maxChildRank() { - s = "t.rank <= r.maxChildRank()" - } else if t.key >= r.key { - s = "t.key >= r.key" - } - if s != "" { - return - } - } else { - if t.rank != 1 { - s = "t w/ right nil has rank != 1" - return - } - } - ii := 1 - if l != nil { - res, il := l.wellFormedSubtree(t, min, t.key) - if res != "" { - s = "L." + res - return - } - ii += il - } - if r != nil { - res, ir := r.wellFormedSubtree(t, t.key, max) - if res != "" { - s = "R." + res - return - } - ii += ir - } - i = ii - return -} - -func (t *RBTint32) DebugString() string { - if t.root == nil { - return "" - } - return t.root.DebugString() -} - -// DebugString prints the tree with nested information -// to allow an eyeball check on the tree balance. -func (t *node32) DebugString() string { - s := "" - if t.left != nil { - s += "[" - s += t.left.DebugString() - s += "]" - } - s += fmt.Sprintf("%v=%v:%d", t.key, t.data, t.rank) - if t.right != nil { - s += "[" - s += t.right.DebugString() - s += "]" - } - return s -} - -func allRBT32Ops(te *testing.T, x []int32) { - t := &RBTint32{} - for i, d := range x { - x[i] = d + d // Double everything for glb/lub testing - } - - // fmt.Printf("Inserting double of %v", x) - k := 0 - min := int32(0x7fffffff) - max := int32(-0x80000000) - for _, d := range x { - if d < min { - min = d - } - - if d > max { - max = d - } - - t.Insert(d, sstring(fmt.Sprintf("%v", d))) - k++ - s, i := t.wellFormed() - if i != k { - te.Errorf("Wrong tree size %v, expected %v for %v", i, k, t.DebugString()) - } - if s != "" { - te.Errorf("Tree consistency problem at %v", s) - return - } - } - - oops := false - - for _, d := range x { - s := fmt.Sprintf("%v", d) - f := t.Find(d) - - // data - if s != fmt.Sprintf("%v", f) { - te.Errorf("s(%v) != f(%v)", s, f) - oops = true - } - } - - if !oops { - for _, d := range x { - s := fmt.Sprintf("%v", d) - - kg, g := t.Glb(d + 1) - kge, ge := t.GlbEq(d) - kl, l := t.Lub(d - 1) - kle, le := t.LubEq(d) - - // keys - if d != kg { - te.Errorf("d(%v) != kg(%v)", d, kg) - } - if d != kl { - te.Errorf("d(%v) != kl(%v)", d, kl) - } - if d != kge { - te.Errorf("d(%v) != kge(%v)", d, kge) - } - if d != kle { - te.Errorf("d(%v) != kle(%v)", d, kle) - } - // data - if s != fmt.Sprintf("%v", g) { - te.Errorf("s(%v) != g(%v)", s, g) - } - if s != fmt.Sprintf("%v", l) { - te.Errorf("s(%v) != l(%v)", s, l) - } - if s != fmt.Sprintf("%v", ge) { - te.Errorf("s(%v) != ge(%v)", s, ge) - } - if s != fmt.Sprintf("%v", le) { - te.Errorf("s(%v) != le(%v)", s, le) - } - } - - for _, d := range x { - s := fmt.Sprintf("%v", d) - kge, ge := t.GlbEq(d + 1) - kle, le := t.LubEq(d - 1) - if d != kge { - te.Errorf("d(%v) != kge(%v)", d, kge) - } - if d != kle { - te.Errorf("d(%v) != kle(%v)", d, kle) - } - if s != fmt.Sprintf("%v", ge) { - te.Errorf("s(%v) != ge(%v)", s, ge) - } - if s != fmt.Sprintf("%v", le) { - te.Errorf("s(%v) != le(%v)", s, le) - } - } - - kg, g := t.Glb(min) - kge, ge := t.GlbEq(min - 1) - kl, l := t.Lub(max) - kle, le := t.LubEq(max + 1) - fmin := t.Find(min - 1) - fmax := t.Find(min + 11) - - if kg != 0 || kge != 0 || kl != 0 || kle != 0 { - te.Errorf("Got non-zero-key for missing query") - } - - if g != nil || ge != nil || l != nil || le != nil || fmin != nil || fmax != nil { - te.Errorf("Got non-error-data for missing query") - } - - } -} - -func TestAllRBTreeOps(t *testing.T) { - allRBT32Ops(t, []int32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}) - allRBT32Ops(t, []int32{22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 3, 2, 1, 25, 24, 23, 12, 11, 10, 9, 8, 7, 6, 5, 4}) - allRBT32Ops(t, []int32{25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}) - allRBT32Ops(t, []int32{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24}) - allRBT32Ops(t, []int32{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2}) - allRBT32Ops(t, []int32{24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25}) -} diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go index 0339b073ae15f6a5077eddeb571f66162522afa2..3b90b8769c2f1d8ffe484b0ab40d832aea650175 100644 --- a/src/cmd/compile/internal/ssa/regalloc.go +++ b/src/cmd/compile/internal/ssa/regalloc.go @@ -104,7 +104,7 @@ // If b3 is the primary predecessor of b2, then we use x3 in b2 and // add a x4:CX->BX copy at the end of b4. // But the definition of x3 doesn't dominate b2. We should really -// insert a dummy phi at the start of b2 (x5=phi(x3,x4):BX) to keep +// insert an extra phi at the start of b2 (x5=phi(x3,x4):BX) to keep // SSA form. For now, we ignore this problem as remaining in strict // SSA form isn't needed after regalloc. We'll just leave the use // of x3 not dominated by the definition of x3, and the CX->BX copy @@ -114,11 +114,13 @@ package ssa import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/types" - "cmd/internal/objabi" "cmd/internal/src" "cmd/internal/sys" "fmt" + "internal/buildcfg" "math/bits" "unsafe" ) @@ -150,6 +152,14 @@ type register uint8 const noRegister register = 255 +// For bulk initializing +var noRegisters [32]register = [32]register{ + noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, + noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, + noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, + noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, noRegister, +} + // A regMask encodes a set of machine registers. // TODO: regMask -> regSet? type regMask uint64 @@ -232,12 +242,6 @@ type regAllocState struct { GReg register allocatable regMask - // for each block, its primary predecessor. - // A predecessor of b is primary if it is the closest - // predecessor that appears before b in the layout order. - // We record the index in the Preds list where the primary predecessor sits. - primary []int32 - // live values at the end of each block. live[b.ID] is a list of value IDs // which are live at the end of b, together with a count of how many instructions // forward to the next use. @@ -295,6 +299,12 @@ type regAllocState struct { // choose a good order in which to visit blocks for allocation purposes. visitOrder []*Block + + // blockOrder[b.ID] corresponds to the index of block b in visitOrder. + blockOrder []int32 + + // whether to insert instructions that clobber dead registers at call sites + doClobber bool } type endReg struct { @@ -333,6 +343,17 @@ func (s *regAllocState) freeRegs(m regMask) { } } +// clobberRegs inserts instructions that clobber registers listed in m. +func (s *regAllocState) clobberRegs(m regMask) { + m &= s.allocatable & s.f.Config.gpRegMask // only integer register can contain pointers, only clobber them + for m != 0 { + r := pickReg(m) + m &^= 1 << r + x := s.curBlock.NewValue0(src.NoXPos, OpClobberReg, types.TypeVoid) + s.f.setHome(x, &s.registers[r]) + } +} + // setOrig records that c's original value is the same as // v's original value. func (s *regAllocState) setOrig(c *Value, v *Value) { @@ -588,7 +609,7 @@ func (s *regAllocState) init(f *Func) { if s.f.Config.hasGReg { s.allocatable &^= 1 << s.GReg } - if objabi.Framepointer_enabled && s.f.Config.FPReg >= 0 { + if buildcfg.FramePointerEnabled && s.f.Config.FPReg >= 0 { s.allocatable &^= 1 << uint(s.f.Config.FPReg) } if s.f.Config.LinkReg != -1 { @@ -596,12 +617,6 @@ func (s *regAllocState) init(f *Func) { // Leaf functions don't save/restore the link register. s.allocatable &^= 1 << uint(s.f.Config.LinkReg) } - if s.f.Config.arch == "arm" && objabi.GOARM == 5 { - // On ARMv5 we insert softfloat calls at each FP instruction. - // This clobbers LR almost everywhere. Disable allocating LR - // on ARMv5. - s.allocatable &^= 1 << uint(s.f.Config.LinkReg) - } } if s.f.Config.ctxt.Flag_dynlink { switch s.f.Config.arch { @@ -633,9 +648,9 @@ func (s *regAllocState) init(f *Func) { // Compute block order. This array allows us to distinguish forward edges // from backward edges and compute how far they go. - blockOrder := make([]int32, f.NumBlocks()) + s.blockOrder = make([]int32, f.NumBlocks()) for i, b := range s.visitOrder { - blockOrder[b.ID] = int32(i) + s.blockOrder[b.ID] = int32(i) } s.regs = make([]regState, s.numRegs) @@ -661,22 +676,6 @@ func (s *regAllocState) init(f *Func) { } s.computeLive() - // Compute primary predecessors. - s.primary = make([]int32, f.NumBlocks()) - for _, b := range s.visitOrder { - best := -1 - for i, e := range b.Preds { - p := e.b - if blockOrder[p.ID] >= blockOrder[b.ID] { - continue // backward edge - } - if best == -1 || blockOrder[p.ID] > blockOrder[b.Preds[best].b.ID] { - best = i - } - } - s.primary[b.ID] = int32(best) - } - s.endRegs = make([][]endReg, f.NumBlocks()) s.startRegs = make([][]startReg, f.NumBlocks()) s.spillLive = make([][]ID, f.NumBlocks()) @@ -716,6 +715,14 @@ func (s *regAllocState) init(f *Func) { } } } + + // The clobberdeadreg experiment inserts code to clobber dead registers + // at call sites. + // Ignore huge functions to avoid doing too much work. + if base.Flag.ClobberDeadReg && len(s.f.Blocks) <= 10000 { + // TODO: honor GOCLOBBERDEADHASH, or maybe GOSSAHASH. + s.doClobber = true + } } // Adds a use record for id at distance dist from the start of the block. @@ -760,6 +767,9 @@ func (s *regAllocState) advanceUses(v *Value) { // current instruction. func (s *regAllocState) liveAfterCurrentInstruction(v *Value) bool { u := s.values[v.ID].uses + if u == nil { + panic(fmt.Errorf("u is nil, v = %s, s.values[v.ID] = %v", v.LongString(), s.values[v.ID])) + } d := u.dist for u != nil && u.dist == d { u = u.next @@ -782,9 +792,9 @@ func (s *regAllocState) compatRegs(t *types.Type) regMask { return 0 } if t.IsFloat() || t == types.TypeInt128 { - if t.Etype == types.TFLOAT32 && s.f.Config.fp32RegMask != 0 { + if t.Kind() == types.TFLOAT32 && s.f.Config.fp32RegMask != 0 { m = s.f.Config.fp32RegMask - } else if t.Etype == types.TFLOAT64 && s.f.Config.fp64RegMask != 0 { + } else if t.Kind() == types.TFLOAT64 && s.f.Config.fp64RegMask != 0 { m = s.f.Config.fp64RegMask } else { m = s.f.Config.fpRegMask @@ -796,7 +806,8 @@ func (s *regAllocState) compatRegs(t *types.Type) regMask { } // regspec returns the regInfo for operation op. -func (s *regAllocState) regspec(op Op) regInfo { +func (s *regAllocState) regspec(v *Value) regInfo { + op := v.Op if op == OpConvert { // OpConvert is a generic op, so it doesn't have a // register set in the static table. It can use any @@ -804,6 +815,22 @@ func (s *regAllocState) regspec(op Op) regInfo { m := s.allocatable & s.f.Config.gpRegMask return regInfo{inputs: []inputInfo{{regs: m}}, outputs: []outputInfo{{regs: m}}} } + if op == OpArgIntReg { + reg := v.Block.Func.Config.intParamRegs[v.AuxInt8()] + return regInfo{outputs: []outputInfo{{regs: 1 << uint(reg)}}} + } + if op == OpArgFloatReg { + reg := v.Block.Func.Config.floatParamRegs[v.AuxInt8()] + return regInfo{outputs: []outputInfo{{regs: 1 << uint(reg)}}} + } + if op.IsCall() { + if ac, ok := v.Aux.(*AuxCall); ok && ac.reg != nil { + return *ac.Reg(&opcodeTable[op].reg, s.f.Config) + } + } + if op == OpMakeResult && s.f.OwnAux.reg != nil { + return *s.f.OwnAux.ResultReg(s.f.Config) + } return opcodeTable[op].reg } @@ -934,10 +961,49 @@ func (s *regAllocState) regalloc(f *Func) { // This is the complicated case. We have more than one predecessor, // which means we may have Phi ops. - // Start with the final register state of the primary predecessor - idx := s.primary[b.ID] + // Start with the final register state of the predecessor with least spill values. + // This is based on the following points: + // 1, The less spill value indicates that the register pressure of this path is smaller, + // so the values of this block are more likely to be allocated to registers. + // 2, Avoid the predecessor that contains the function call, because the predecessor that + // contains the function call usually generates a lot of spills and lose the previous + // allocation state. + // TODO: Improve this part. At least the size of endRegs of the predecessor also has + // an impact on the code size and compiler speed. But it is not easy to find a simple + // and efficient method that combines multiple factors. + idx := -1 + for i, p := range b.Preds { + // If the predecessor has not been visited yet, skip it because its end state + // (redRegs and spillLive) has not been computed yet. + pb := p.b + if s.blockOrder[pb.ID] >= s.blockOrder[b.ID] { + continue + } + if idx == -1 { + idx = i + continue + } + pSel := b.Preds[idx].b + if len(s.spillLive[pb.ID]) < len(s.spillLive[pSel.ID]) { + idx = i + } else if len(s.spillLive[pb.ID]) == len(s.spillLive[pSel.ID]) { + // Use a bit of likely information. After critical pass, pb and pSel must + // be plain blocks, so check edge pb->pb.Preds instead of edge pb->b. + // TODO: improve the prediction of the likely predecessor. The following + // method is only suitable for the simplest cases. For complex cases, + // the prediction may be inaccurate, but this does not affect the + // correctness of the program. + // According to the layout algorithm, the predecessor with the + // smaller blockOrder is the true branch, and the test results show + // that it is better to choose the predecessor with a smaller + // blockOrder than no choice. + if pb.likelyBranch() && !pSel.likelyBranch() || s.blockOrder[pb.ID] < s.blockOrder[pSel.ID] { + idx = i + } + } + } if idx < 0 { - f.Fatalf("block with no primary predecessor %s", b) + f.Fatalf("bad visitOrder, no predecessor of %s has been visited before it", b) } p := b.Preds[idx].b s.setState(s.endRegs[p.ID]) @@ -1025,7 +1091,7 @@ func (s *regAllocState) regalloc(f *Func) { // If one of the other inputs of v is in a register, and the register is available, // select this register, which can save some unnecessary copies. for i, pe := range b.Preds { - if int32(i) == idx { + if i == idx { continue } ri := noRegister @@ -1159,7 +1225,7 @@ func (s *regAllocState) regalloc(f *Func) { for i := len(oldSched) - 1; i >= 0; i-- { v := oldSched[i] prefs := desired.remove(v.ID) - regspec := s.regspec(v.Op) + regspec := s.regspec(v) desired.clobber(regspec.clobbers) for _, j := range regspec.inputs { if countRegs(j.regs) != 1 { @@ -1189,7 +1255,7 @@ func (s *regAllocState) regalloc(f *Func) { if s.f.pass.debug > regDebug { fmt.Printf(" processing %s\n", v.LongString()) } - regspec := s.regspec(v.Op) + regspec := s.regspec(v) if v.Op == OpPhi { f.Fatalf("phi %s not at start of block", v) } @@ -1207,13 +1273,17 @@ func (s *regAllocState) regalloc(f *Func) { s.sb = v.ID continue } - if v.Op == OpSelect0 || v.Op == OpSelect1 { + if v.Op == OpSelect0 || v.Op == OpSelect1 || v.Op == OpSelectN { if s.values[v.ID].needReg { - var i = 0 - if v.Op == OpSelect1 { - i = 1 + if v.Op == OpSelectN { + s.assignReg(register(s.f.getHome(v.Args[0].ID).(LocResults)[int(v.AuxInt)].(*Register).num), v, v) + } else { + var i = 0 + if v.Op == OpSelect1 { + i = 1 + } + s.assignReg(register(s.f.getHome(v.Args[0].ID).(LocPair)[i].(*Register).num), v, v) } - s.assignReg(register(s.f.getHome(v.Args[0].ID).(LocPair)[i].(*Register).num), v, v) } b.Values = append(b.Values, v) s.advanceUses(v) @@ -1248,7 +1318,7 @@ func (s *regAllocState) regalloc(f *Func) { // This forces later liveness analysis to make the // value live at this point. v.SetArg(0, s.makeSpill(a, b)) - } else if _, ok := a.Aux.(GCNode); ok && vi.rematerializeable { + } else if _, ok := a.Aux.(*ir.Name); ok && vi.rematerializeable { // Rematerializeable value with a gc.Node. This is the address of // a stack object (e.g. an LEAQ). Keep the object live. // Change it to VarLive, which is what plive expects for locals. @@ -1267,6 +1337,9 @@ func (s *regAllocState) regalloc(f *Func) { } if len(regspec.inputs) == 0 && len(regspec.outputs) == 0 { // No register allocation required (or none specified yet) + if s.doClobber && v.Op.IsCall() { + s.clobberRegs(regspec.clobbers) + } s.freeRegs(regspec.clobbers) b.Values = append(b.Values, v) s.advanceUses(v) @@ -1304,12 +1377,58 @@ func (s *regAllocState) regalloc(f *Func) { } } - // Move arguments to registers. Process in an ordering defined + // Move arguments to registers. + // First, if an arg must be in a specific register and it is already + // in place, keep it. + args = append(args[:0], make([]*Value, len(v.Args))...) + for i, a := range v.Args { + if !s.values[a.ID].needReg { + args[i] = a + } + } + for _, i := range regspec.inputs { + mask := i.regs + if countRegs(mask) == 1 && mask&s.values[v.Args[i.idx].ID].regs != 0 { + args[i.idx] = s.allocValToReg(v.Args[i.idx], mask, true, v.Pos) + } + } + // Then, if an arg must be in a specific register and that + // register is free, allocate that one. Otherwise when processing + // another input we may kick a value into the free register, which + // then will be kicked out again. + // This is a common case for passing-in-register arguments for + // function calls. + for { + freed := false + for _, i := range regspec.inputs { + if args[i.idx] != nil { + continue // already allocated + } + mask := i.regs + if countRegs(mask) == 1 && mask&^s.used != 0 { + args[i.idx] = s.allocValToReg(v.Args[i.idx], mask, true, v.Pos) + // If the input is in other registers that will be clobbered by v, + // or the input is dead, free the registers. This may make room + // for other inputs. + oldregs := s.values[v.Args[i.idx].ID].regs + if oldregs&^regspec.clobbers == 0 || !s.liveAfterCurrentInstruction(v.Args[i.idx]) { + s.freeRegs(oldregs &^ mask &^ s.nospill) + freed = true + } + } + } + if !freed { + break + } + } + // Last, allocate remaining ones, in an ordering defined // by the register specification (most constrained first). - args = append(args[:0], v.Args...) for _, i := range regspec.inputs { + if args[i.idx] != nil { + continue // already allocated + } mask := i.regs - if mask&s.values[args[i.idx].ID].regs == 0 { + if mask&s.values[v.Args[i.idx].ID].regs == 0 { // Need a new register for the input. mask &= s.allocatable mask &^= s.nospill @@ -1328,7 +1447,7 @@ func (s *regAllocState) regalloc(f *Func) { mask &^= desired.avoid } } - args[i.idx] = s.allocValToReg(args[i.idx], mask, true, v.Pos) + args[i.idx] = s.allocValToReg(v.Args[i.idx], mask, true, v.Pos) } // If the output clobbers the input register, make sure we have @@ -1374,9 +1493,9 @@ func (s *regAllocState) regalloc(f *Func) { goto ok } - // Try to move an input to the desired output. + // Try to move an input to the desired output, if allowed. for _, r := range dinfo[idx].out { - if r != noRegister && m>>r&1 != 0 { + if r != noRegister && (m®spec.outputs[0].regs)>>r&1 != 0 { m = regMask(1) << r args[0] = s.allocValToReg(v.Args[0], m, true, v.Pos) // Note: we update args[0] so the instruction will @@ -1428,12 +1547,18 @@ func (s *regAllocState) regalloc(f *Func) { } // Dump any registers which will be clobbered + if s.doClobber && v.Op.IsCall() { + // clobber registers that are marked as clobber in regmask, but + // don't clobber inputs. + s.clobberRegs(regspec.clobbers &^ s.tmpused &^ s.nospill) + } s.freeRegs(regspec.clobbers) s.tmpused |= regspec.clobbers // Pick registers for outputs. { - outRegs := [2]register{noRegister, noRegister} + outRegs := noRegisters // TODO if this is costly, hoist and clear incrementally below. + maxOutIdx := -1 var used regMask for _, out := range regspec.outputs { mask := out.regs & s.allocatable &^ used @@ -1444,6 +1569,9 @@ func (s *regAllocState) regalloc(f *Func) { if !opcodeTable[v.Op].commutative { // Output must use the same register as input 0. r := register(s.f.getHome(args[0].ID).(*Register).num) + if mask>>r&1 == 0 { + s.f.Fatalf("resultInArg0 value's input %v cannot be an output of %s", s.f.getHome(args[0].ID).(*Register), v.LongString()) + } mask = regMask(1) << r } else { // Output must use the same register as input 0 or 1. @@ -1479,6 +1607,9 @@ func (s *regAllocState) regalloc(f *Func) { mask &^= desired.avoid } r := s.allocReg(mask, v) + if out.idx > maxOutIdx { + maxOutIdx = out.idx + } outRegs[out.idx] = r used |= regMask(1) << r s.tmpused |= regMask(1) << r @@ -1494,6 +1625,15 @@ func (s *regAllocState) regalloc(f *Func) { } s.f.setHome(v, outLocs) // Note that subsequent SelectX instructions will do the assignReg calls. + } else if v.Type.IsResults() { + // preallocate outLocs to the right size, which is maxOutIdx+1 + outLocs := make(LocResults, maxOutIdx+1, maxOutIdx+1) + for i := 0; i <= maxOutIdx; i++ { + if r := outRegs[i]; r != noRegister { + outLocs[i] = &s.registers[r] + } + } + s.f.setHome(v, outLocs) } else { if r := outRegs[0]; r != noRegister { s.assignReg(r, v, v) @@ -1742,6 +1882,10 @@ func (s *regAllocState) placeSpills() { phiRegs[b.ID] = m } + mustBeFirst := func(op Op) bool { + return op.isLoweredGetClosurePtr() || op == OpPhi || op == OpArgIntReg || op == OpArgFloatReg + } + // Start maps block IDs to the list of spills // that go at the start of the block (but after any phis). start := map[ID][]*Value{} @@ -1766,6 +1910,9 @@ func (s *regAllocState) placeSpills() { // put the spill of v. At the start "best" is the best place // we have found so far. // TODO: find a way to make this O(1) without arbitrary cutoffs. + if v == nil { + panic(fmt.Errorf("nil v, s.orig[%d], vi = %v, spill = %s", i, vi, spill.LongString())) + } best := v.Block bestArg := v var bestDepth int16 @@ -1828,7 +1975,7 @@ func (s *regAllocState) placeSpills() { // Put the spill in the best block we found. spill.Block = best spill.AddArg(bestArg) - if best == v.Block && v.Op != OpPhi { + if best == v.Block && !mustBeFirst(v.Op) { // Place immediately after v. after[v.ID] = append(after[v.ID], spill) } else { @@ -1840,15 +1987,15 @@ func (s *regAllocState) placeSpills() { // Insert spill instructions into the block schedules. var oldSched []*Value for _, b := range s.visitOrder { - nphi := 0 + nfirst := 0 for _, v := range b.Values { - if v.Op != OpPhi { + if !mustBeFirst(v.Op) { break } - nphi++ + nfirst++ } - oldSched = append(oldSched[:0], b.Values[nphi:]...) - b.Values = b.Values[:nphi] + oldSched = append(oldSched[:0], b.Values[nfirst:]...) + b.Values = b.Values[:nfirst] b.Values = append(b.Values, start[b.ID]...) for _, v := range oldSched { b.Values = append(b.Values, v) @@ -2436,7 +2583,7 @@ func (s *regAllocState) computeLive() { // desired registers back though phi nodes. continue } - regspec := s.regspec(v.Op) + regspec := s.regspec(v) // Cancel desired registers if they get clobbered. desired.clobber(regspec.clobbers) // Update desired registers if there are any fixed register inputs. diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index f5d1a7889fb19f6fb4bdbe64f1df4d337bf2f079..375c4d5a5605f531b6b5ab761be276f41d72031b 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -27,7 +27,7 @@ const ( removeDeadValues = true ) -// deadcode indicates that rewrite should try to remove any values that become dead. +// deadcode indicates whether rewrite should try to remove any values that become dead. func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter, deadcode deadValueChoice) { // repeat rewrites until we find no more rewrites pendingLines := f.cachedLineStarts // Holds statement boundaries that need to be moved to a new value/block @@ -159,7 +159,7 @@ func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter, deadcode deadValu f.freeValue(v) continue } - if v.Pos.IsStmt() != src.PosNotStmt && pendingLines.get(vl) == int32(b.ID) { + if v.Pos.IsStmt() != src.PosNotStmt && !notStmtBoundary(v.Op) && pendingLines.get(vl) == int32(b.ID) { pendingLines.remove(vl) v.Pos = v.Pos.WithIsStmt() } @@ -521,6 +521,18 @@ func shiftIsBounded(v *Value) bool { return v.AuxInt != 0 } +// canonLessThan returns whether x is "ordered" less than y, for purposes of normalizing +// generated code as much as possible. +func canonLessThan(x, y *Value) bool { + if x.Op != y.Op { + return x.Op < y.Op + } + if !x.Pos.SameFileAndLine(y.Pos) { + return x.Pos.Before(y.Pos) + } + return x.ID < y.ID +} + // truncate64Fto32F converts a float64 value to a float32 preserving the bit pattern // of the mantissa. It will panic if the truncation results in lost information. func truncate64Fto32F(f float64) float32 { @@ -678,43 +690,53 @@ func opToAuxInt(o Op) int64 { return int64(o) } -func auxToString(i interface{}) string { - return i.(string) +// Aux is an interface to hold miscellaneous data in Blocks and Values. +type Aux interface { + CanBeAnSSAAux() } -func auxToSym(i interface{}) Sym { + +// stringAux wraps string values for use in Aux. +type stringAux string + +func (stringAux) CanBeAnSSAAux() {} + +func auxToString(i Aux) string { + return string(i.(stringAux)) +} +func auxToSym(i Aux) Sym { // TODO: kind of a hack - allows nil interface through s, _ := i.(Sym) return s } -func auxToType(i interface{}) *types.Type { +func auxToType(i Aux) *types.Type { return i.(*types.Type) } -func auxToCall(i interface{}) *AuxCall { +func auxToCall(i Aux) *AuxCall { return i.(*AuxCall) } -func auxToS390xCCMask(i interface{}) s390x.CCMask { +func auxToS390xCCMask(i Aux) s390x.CCMask { return i.(s390x.CCMask) } -func auxToS390xRotateParams(i interface{}) s390x.RotateParams { +func auxToS390xRotateParams(i Aux) s390x.RotateParams { return i.(s390x.RotateParams) } -func stringToAux(s string) interface{} { - return s +func StringToAux(s string) Aux { + return stringAux(s) } -func symToAux(s Sym) interface{} { +func symToAux(s Sym) Aux { return s } -func callToAux(s *AuxCall) interface{} { +func callToAux(s *AuxCall) Aux { return s } -func typeToAux(t *types.Type) interface{} { +func typeToAux(t *types.Type) Aux { return t } -func s390xCCMaskToAux(c s390x.CCMask) interface{} { +func s390xCCMaskToAux(c s390x.CCMask) Aux { return c } -func s390xRotateParamsToAux(r s390x.RotateParams) interface{} { +func s390xRotateParamsToAux(r s390x.RotateParams) Aux { return r } @@ -725,7 +747,7 @@ func uaddOvf(a, b int64) bool { // de-virtualize an InterCall // 'sym' is the symbol for the itab -func devirt(v *Value, aux interface{}, sym Sym, offset int64) *AuxCall { +func devirt(v *Value, aux Aux, sym Sym, offset int64) *AuxCall { f := v.Block.Func n, ok := sym.(*obj.LSym) if !ok { @@ -743,12 +765,12 @@ func devirt(v *Value, aux interface{}, sym Sym, offset int64) *AuxCall { return nil } va := aux.(*AuxCall) - return StaticAuxCall(lsym, va.args, va.results) + return StaticAuxCall(lsym, va.abiInfo) } // de-virtualize an InterLECall // 'sym' is the symbol for the itab -func devirtLESym(v *Value, aux interface{}, sym Sym, offset int64) *obj.LSym { +func devirtLESym(v *Value, aux Aux, sym Sym, offset int64) *obj.LSym { n, ok := sym.(*obj.LSym) if !ok { return nil @@ -771,7 +793,8 @@ func devirtLESym(v *Value, aux interface{}, sym Sym, offset int64) *obj.LSym { func devirtLECall(v *Value, sym *obj.LSym) *Value { v.Op = OpStaticLECall - v.Aux.(*AuxCall).Fn = sym + auxcall := v.Aux.(*AuxCall) + auxcall.Fn = sym v.RemoveArg(0) return v } @@ -836,13 +859,13 @@ func disjoint(p1 *Value, n1 int64, p2 *Value, n2 int64) bool { if p2.Op == OpAddr || p2.Op == OpLocalAddr || p2.Op == OpSP { return true } - return p2.Op == OpArg && p1.Args[0].Op == OpSP - case OpArg: + return (p2.Op == OpArg || p2.Op == OpArgIntReg) && p1.Args[0].Op == OpSP + case OpArg, OpArgIntReg: if p2.Op == OpSP || p2.Op == OpLocalAddr { return true } case OpSP: - return p2.Op == OpAddr || p2.Op == OpLocalAddr || p2.Op == OpArg || p2.Op == OpSP + return p2.Op == OpAddr || p2.Op == OpLocalAddr || p2.Op == OpArg || p2.Op == OpArgIntReg || p2.Op == OpSP } return false } @@ -1391,7 +1414,7 @@ func isPPC64WordRotateMask(v64 int64) bool { return (v&vp == 0 || vn&vpn == 0) && v != 0 } -// Compress mask and and shift into single value of the form +// Compress mask and shift into single value of the form // me | mb<<8 | rotate<<16 | nbits<<24 where me and mb can // be used to regenerate the input mask. func encodePPC64RotateMask(rotate, mask, nbits int64) int64 { @@ -1469,7 +1492,7 @@ func mergePPC64AndSrwi(m, s int64) int64 { if !isPPC64WordRotateMask(mask) { return 0 } - return encodePPC64RotateMask(32-s, mask, 32) + return encodePPC64RotateMask((32-s)&31, mask, 32) } // Test if a shift right feeding into a CLRLSLDI can be merged into RLWINM. @@ -1589,18 +1612,18 @@ func needRaceCleanup(sym *AuxCall, v *Value) bool { if !f.Config.Race { return false } - if !isSameCall(sym, "runtime.racefuncenter") && !isSameCall(sym, "runtime.racefuncenterfp") && !isSameCall(sym, "runtime.racefuncexit") { + if !isSameCall(sym, "runtime.racefuncenter") && !isSameCall(sym, "runtime.racefuncexit") { return false } for _, b := range f.Blocks { for _, v := range b.Values { switch v.Op { - case OpStaticCall: - // Check for racefuncenter/racefuncenterfp will encounter racefuncexit and vice versa. + case OpStaticCall, OpStaticLECall: + // Check for racefuncenter will encounter racefuncexit and vice versa. // Allow calls to panic* s := v.Aux.(*AuxCall).Fn.String() switch s { - case "runtime.racefuncenter", "runtime.racefuncenterfp", "runtime.racefuncexit", + case "runtime.racefuncenter", "runtime.racefuncexit", "runtime.panicdivide", "runtime.panicwrap", "runtime.panicshift": continue @@ -1610,15 +1633,20 @@ func needRaceCleanup(sym *AuxCall, v *Value) bool { return false case OpPanicBounds, OpPanicExtend: // Note: these are panic generators that are ok (like the static calls above). - case OpClosureCall, OpInterCall: + case OpClosureCall, OpInterCall, OpClosureLECall, OpInterLECall: // We must keep the race functions if there are any other call types. return false } } } if isSameCall(sym, "runtime.racefuncenter") { + // TODO REGISTER ABI this needs to be cleaned up. // If we're removing racefuncenter, remove its argument as well. if v.Args[0].Op != OpStore { + if v.Op == OpStaticLECall { + // there is no store, yet. + return true + } return false } mem := v.Args[0].Args[2] diff --git a/src/cmd/compile/internal/ssa/rewrite386.go b/src/cmd/compile/internal/ssa/rewrite386.go index 2acdccd5684aaf5364eb4a58ae44d9688769dc8d..1ec2d26f750f6b7998933fca8cbeb668d9188f1a 100644 --- a/src/cmd/compile/internal/ssa/rewrite386.go +++ b/src/cmd/compile/internal/ssa/rewrite386.go @@ -620,6 +620,9 @@ func rewriteValue386(v *Value) bool { case OpSqrt: v.Op = Op386SQRTSD return true + case OpSqrt32: + v.Op = Op386SQRTSS + return true case OpStaticCall: v.Op = Op386CALLstatic return true @@ -1785,12 +1788,12 @@ func rewriteValue386_Op386CMPB(v *Value) bool { return true } // match: (CMPB x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPB y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(Op386InvertFlags) @@ -1993,8 +1996,8 @@ func rewriteValue386_Op386CMPBconst(v *Value) bool { return true } // match: (CMPBconst l:(MOVBload {sym} [off] ptr mem) [c]) - // cond: l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l) - // result: @l.Block (CMPBconstload {sym} [makeValAndOff32(int32(c),int32(off))] ptr mem) + // cond: l.Uses == 1 && clobber(l) + // result: @l.Block (CMPBconstload {sym} [makeValAndOff(int32(c),off)] ptr mem) for { c := auxIntToInt8(v.AuxInt) l := v_0 @@ -2005,13 +2008,13 @@ func rewriteValue386_Op386CMPBconst(v *Value) bool { sym := auxToSym(l.Aux) mem := l.Args[1] ptr := l.Args[0] - if !(l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l)) { + if !(l.Uses == 1 && clobber(l)) { break } b = l.Block v0 := b.NewValue0(l.Pos, Op386CMPBconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), int32(off))) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) return true @@ -2023,8 +2026,7 @@ func rewriteValue386_Op386CMPBload(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (CMPBload {sym} [off] ptr (MOVLconst [c]) mem) - // cond: validValAndOff(int64(int8(c)),int64(off)) - // result: (CMPBconstload {sym} [makeValAndOff32(int32(int8(c)),off)] ptr mem) + // result: (CMPBconstload {sym} [makeValAndOff(int32(int8(c)),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -2034,11 +2036,8 @@ func rewriteValue386_Op386CMPBload(v *Value) bool { } c := auxIntToInt32(v_1.AuxInt) mem := v_2 - if !(validValAndOff(int64(int8(c)), int64(off))) { - break - } v.reset(Op386CMPBconstload) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(int8(c)), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(int8(c)), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -2078,12 +2077,12 @@ func rewriteValue386_Op386CMPL(v *Value) bool { return true } // match: (CMPL x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPL y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(Op386InvertFlags) @@ -2301,8 +2300,8 @@ func rewriteValue386_Op386CMPLconst(v *Value) bool { return true } // match: (CMPLconst l:(MOVLload {sym} [off] ptr mem) [c]) - // cond: l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l) - // result: @l.Block (CMPLconstload {sym} [makeValAndOff32(int32(c),int32(off))] ptr mem) + // cond: l.Uses == 1 && clobber(l) + // result: @l.Block (CMPLconstload {sym} [makeValAndOff(int32(c),off)] ptr mem) for { c := auxIntToInt32(v.AuxInt) l := v_0 @@ -2313,13 +2312,13 @@ func rewriteValue386_Op386CMPLconst(v *Value) bool { sym := auxToSym(l.Aux) mem := l.Args[1] ptr := l.Args[0] - if !(l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l)) { + if !(l.Uses == 1 && clobber(l)) { break } b = l.Block v0 := b.NewValue0(l.Pos, Op386CMPLconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), int32(off))) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) return true @@ -2331,8 +2330,7 @@ func rewriteValue386_Op386CMPLload(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (CMPLload {sym} [off] ptr (MOVLconst [c]) mem) - // cond: validValAndOff(int64(c),int64(off)) - // result: (CMPLconstload {sym} [makeValAndOff32(c,off)] ptr mem) + // result: (CMPLconstload {sym} [makeValAndOff(c,off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -2342,11 +2340,8 @@ func rewriteValue386_Op386CMPLload(v *Value) bool { } c := auxIntToInt32(v_1.AuxInt) mem := v_2 - if !(validValAndOff(int64(c), int64(off))) { - break - } v.reset(Op386CMPLconstload) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -2386,12 +2381,12 @@ func rewriteValue386_Op386CMPW(v *Value) bool { return true } // match: (CMPW x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPW y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(Op386InvertFlags) @@ -2594,8 +2589,8 @@ func rewriteValue386_Op386CMPWconst(v *Value) bool { return true } // match: (CMPWconst l:(MOVWload {sym} [off] ptr mem) [c]) - // cond: l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l) - // result: @l.Block (CMPWconstload {sym} [makeValAndOff32(int32(c),int32(off))] ptr mem) + // cond: l.Uses == 1 && clobber(l) + // result: @l.Block (CMPWconstload {sym} [makeValAndOff(int32(c),off)] ptr mem) for { c := auxIntToInt16(v.AuxInt) l := v_0 @@ -2606,13 +2601,13 @@ func rewriteValue386_Op386CMPWconst(v *Value) bool { sym := auxToSym(l.Aux) mem := l.Args[1] ptr := l.Args[0] - if !(l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l)) { + if !(l.Uses == 1 && clobber(l)) { break } b = l.Block v0 := b.NewValue0(l.Pos, Op386CMPWconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), int32(off))) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) return true @@ -2624,8 +2619,7 @@ func rewriteValue386_Op386CMPWload(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (CMPWload {sym} [off] ptr (MOVLconst [c]) mem) - // cond: validValAndOff(int64(int16(c)),int64(off)) - // result: (CMPWconstload {sym} [makeValAndOff32(int32(int16(c)),off)] ptr mem) + // result: (CMPWconstload {sym} [makeValAndOff(int32(int16(c)),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -2635,11 +2629,8 @@ func rewriteValue386_Op386CMPWload(v *Value) bool { } c := auxIntToInt32(v_1.AuxInt) mem := v_2 - if !(validValAndOff(int64(int16(c)), int64(off))) { - break - } v.reset(Op386CMPWconstload) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(int16(c)), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(int16(c)), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -3732,8 +3723,7 @@ func rewriteValue386_Op386MOVBstore(v *Value) bool { return true } // match: (MOVBstore [off] {sym} ptr (MOVLconst [c]) mem) - // cond: validOff(int64(off)) - // result: (MOVBstoreconst [makeValAndOff32(c,off)] {sym} ptr mem) + // result: (MOVBstoreconst [makeValAndOff(c,off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -3743,11 +3733,8 @@ func rewriteValue386_Op386MOVBstore(v *Value) bool { } c := auxIntToInt32(v_1.AuxInt) mem := v_2 - if !(validOff(int64(off))) { - break - } v.reset(Op386MOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -4087,7 +4074,7 @@ func rewriteValue386_Op386MOVBstoreconst(v *Value) bool { } // match: (MOVBstoreconst [c] {s} p x:(MOVBstoreconst [a] {s} p mem)) // cond: x.Uses == 1 && a.Off() + 1 == c.Off() && clobber(x) - // result: (MOVWstoreconst [makeValAndOff32(int32(a.Val()&0xff | c.Val()<<8), a.Off32())] {s} p mem) + // result: (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -4105,14 +4092,14 @@ func rewriteValue386_Op386MOVBstoreconst(v *Value) bool { break } v.reset(Op386MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(a.Val()&0xff|c.Val()<<8), a.Off32())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xff|c.Val()<<8, a.Off())) v.Aux = symToAux(s) v.AddArg2(p, mem) return true } // match: (MOVBstoreconst [a] {s} p x:(MOVBstoreconst [c] {s} p mem)) // cond: x.Uses == 1 && a.Off() + 1 == c.Off() && clobber(x) - // result: (MOVWstoreconst [makeValAndOff32(int32(a.Val()&0xff | c.Val()<<8), a.Off32())] {s} p mem) + // result: (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) for { a := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -4130,14 +4117,14 @@ func rewriteValue386_Op386MOVBstoreconst(v *Value) bool { break } v.reset(Op386MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(a.Val()&0xff|c.Val()<<8), a.Off32())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xff|c.Val()<<8, a.Off())) v.Aux = symToAux(s) v.AddArg2(p, mem) return true } // match: (MOVBstoreconst [c] {s} p1 x:(MOVBstoreconst [a] {s} p0 mem)) // cond: x.Uses == 1 && a.Off() == c.Off() && sequentialAddresses(p0, p1, 1) && clobber(x) - // result: (MOVWstoreconst [makeValAndOff32(int32(a.Val()&0xff | c.Val()<<8), a.Off32())] {s} p0 mem) + // result: (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p0 mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -4156,14 +4143,14 @@ func rewriteValue386_Op386MOVBstoreconst(v *Value) bool { break } v.reset(Op386MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(a.Val()&0xff|c.Val()<<8), a.Off32())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xff|c.Val()<<8, a.Off())) v.Aux = symToAux(s) v.AddArg2(p0, mem) return true } // match: (MOVBstoreconst [a] {s} p0 x:(MOVBstoreconst [c] {s} p1 mem)) // cond: x.Uses == 1 && a.Off() == c.Off() && sequentialAddresses(p0, p1, 1) && clobber(x) - // result: (MOVWstoreconst [makeValAndOff32(int32(a.Val()&0xff | c.Val()<<8), a.Off32())] {s} p0 mem) + // result: (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p0 mem) for { a := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -4182,7 +4169,7 @@ func rewriteValue386_Op386MOVBstoreconst(v *Value) bool { break } v.reset(Op386MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(a.Val()&0xff|c.Val()<<8), a.Off32())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xff|c.Val()<<8, a.Off())) v.Aux = symToAux(s) v.AddArg2(p0, mem) return true @@ -4301,8 +4288,7 @@ func rewriteValue386_Op386MOVLstore(v *Value) bool { return true } // match: (MOVLstore [off] {sym} ptr (MOVLconst [c]) mem) - // cond: validOff(int64(off)) - // result: (MOVLstoreconst [makeValAndOff32(c,off)] {sym} ptr mem) + // result: (MOVLstoreconst [makeValAndOff(c,off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -4312,11 +4298,8 @@ func rewriteValue386_Op386MOVLstore(v *Value) bool { } c := auxIntToInt32(v_1.AuxInt) mem := v_2 - if !(validOff(int64(off))) { - break - } v.reset(Op386MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -4599,8 +4582,8 @@ func rewriteValue386_Op386MOVLstore(v *Value) bool { break } // match: (MOVLstore {sym} [off] ptr y:(ADDLconst [c] l:(MOVLload [off] {sym} ptr mem)) mem) - // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) && validValAndOff(int64(c),int64(off)) - // result: (ADDLconstmodify [makeValAndOff32(c,off)] {sym} ptr mem) + // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) + // result: (ADDLconstmodify [makeValAndOff(c,off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -4615,18 +4598,18 @@ func rewriteValue386_Op386MOVLstore(v *Value) bool { break } mem := l.Args[1] - if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l) && validValAndOff(int64(c), int64(off))) { + if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l)) { break } v.reset(Op386ADDLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVLstore {sym} [off] ptr y:(ANDLconst [c] l:(MOVLload [off] {sym} ptr mem)) mem) - // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) && validValAndOff(int64(c),int64(off)) - // result: (ANDLconstmodify [makeValAndOff32(c,off)] {sym} ptr mem) + // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) + // result: (ANDLconstmodify [makeValAndOff(c,off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -4641,18 +4624,18 @@ func rewriteValue386_Op386MOVLstore(v *Value) bool { break } mem := l.Args[1] - if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l) && validValAndOff(int64(c), int64(off))) { + if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l)) { break } v.reset(Op386ANDLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVLstore {sym} [off] ptr y:(ORLconst [c] l:(MOVLload [off] {sym} ptr mem)) mem) - // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) && validValAndOff(int64(c),int64(off)) - // result: (ORLconstmodify [makeValAndOff32(c,off)] {sym} ptr mem) + // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) + // result: (ORLconstmodify [makeValAndOff(c,off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -4667,18 +4650,18 @@ func rewriteValue386_Op386MOVLstore(v *Value) bool { break } mem := l.Args[1] - if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l) && validValAndOff(int64(c), int64(off))) { + if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l)) { break } v.reset(Op386ORLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVLstore {sym} [off] ptr y:(XORLconst [c] l:(MOVLload [off] {sym} ptr mem)) mem) - // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) && validValAndOff(int64(c),int64(off)) - // result: (XORLconstmodify [makeValAndOff32(c,off)] {sym} ptr mem) + // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) + // result: (XORLconstmodify [makeValAndOff(c,off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -4693,11 +4676,11 @@ func rewriteValue386_Op386MOVLstore(v *Value) bool { break } mem := l.Args[1] - if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l) && validValAndOff(int64(c), int64(off))) { + if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l)) { break } v.reset(Op386XORLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -5283,8 +5266,7 @@ func rewriteValue386_Op386MOVWstore(v *Value) bool { return true } // match: (MOVWstore [off] {sym} ptr (MOVLconst [c]) mem) - // cond: validOff(int64(off)) - // result: (MOVWstoreconst [makeValAndOff32(c,off)] {sym} ptr mem) + // result: (MOVWstoreconst [makeValAndOff(c,off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -5294,11 +5276,8 @@ func rewriteValue386_Op386MOVWstore(v *Value) bool { } c := auxIntToInt32(v_1.AuxInt) mem := v_2 - if !(validOff(int64(off))) { - break - } v.reset(Op386MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -5487,7 +5466,7 @@ func rewriteValue386_Op386MOVWstoreconst(v *Value) bool { } // match: (MOVWstoreconst [c] {s} p x:(MOVWstoreconst [a] {s} p mem)) // cond: x.Uses == 1 && a.Off() + 2 == c.Off() && clobber(x) - // result: (MOVLstoreconst [makeValAndOff32(int32(a.Val()&0xffff | c.Val()<<16), a.Off32())] {s} p mem) + // result: (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -5505,14 +5484,14 @@ func rewriteValue386_Op386MOVWstoreconst(v *Value) bool { break } v.reset(Op386MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(a.Val()&0xffff|c.Val()<<16), a.Off32())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xffff|c.Val()<<16, a.Off())) v.Aux = symToAux(s) v.AddArg2(p, mem) return true } // match: (MOVWstoreconst [a] {s} p x:(MOVWstoreconst [c] {s} p mem)) // cond: x.Uses == 1 && ValAndOff(a).Off() + 2 == ValAndOff(c).Off() && clobber(x) - // result: (MOVLstoreconst [makeValAndOff32(int32(a.Val()&0xffff | c.Val()<<16), a.Off32())] {s} p mem) + // result: (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) for { a := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -5530,14 +5509,14 @@ func rewriteValue386_Op386MOVWstoreconst(v *Value) bool { break } v.reset(Op386MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(a.Val()&0xffff|c.Val()<<16), a.Off32())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xffff|c.Val()<<16, a.Off())) v.Aux = symToAux(s) v.AddArg2(p, mem) return true } // match: (MOVWstoreconst [c] {s} p1 x:(MOVWstoreconst [a] {s} p0 mem)) // cond: x.Uses == 1 && a.Off() == c.Off() && sequentialAddresses(p0, p1, 2) && clobber(x) - // result: (MOVLstoreconst [makeValAndOff32(int32(a.Val()&0xffff | c.Val()<<16), a.Off32())] {s} p0 mem) + // result: (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p0 mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -5556,14 +5535,14 @@ func rewriteValue386_Op386MOVWstoreconst(v *Value) bool { break } v.reset(Op386MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(a.Val()&0xffff|c.Val()<<16), a.Off32())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xffff|c.Val()<<16, a.Off())) v.Aux = symToAux(s) v.AddArg2(p0, mem) return true } // match: (MOVWstoreconst [a] {s} p0 x:(MOVWstoreconst [c] {s} p1 mem)) // cond: x.Uses == 1 && a.Off() == c.Off() && sequentialAddresses(p0, p1, 2) && clobber(x) - // result: (MOVLstoreconst [makeValAndOff32(int32(a.Val()&0xffff | c.Val()<<16), a.Off32())] {s} p0 mem) + // result: (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p0 mem) for { a := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -5582,7 +5561,7 @@ func rewriteValue386_Op386MOVWstoreconst(v *Value) bool { break } v.reset(Op386MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(a.Val()&0xffff|c.Val()<<16), a.Off32())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xffff|c.Val()<<16, a.Off())) v.Aux = symToAux(s) v.AddArg2(p0, mem) return true @@ -11571,7 +11550,7 @@ func rewriteValue386_OpZero(v *Value) bool { return true } // match: (Zero [3] destptr mem) - // result: (MOVBstoreconst [makeValAndOff32(0,2)] destptr (MOVWstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVBstoreconst [makeValAndOff(0,2)] destptr (MOVWstoreconst [makeValAndOff(0,0)] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 3 { break @@ -11579,15 +11558,15 @@ func rewriteValue386_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(Op386MOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 2)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 2)) v0 := b.NewValue0(v.Pos, Op386MOVWstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true } // match: (Zero [5] destptr mem) - // result: (MOVBstoreconst [makeValAndOff32(0,4)] destptr (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVBstoreconst [makeValAndOff(0,4)] destptr (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 5 { break @@ -11595,15 +11574,15 @@ func rewriteValue386_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(Op386MOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 4)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 4)) v0 := b.NewValue0(v.Pos, Op386MOVLstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true } // match: (Zero [6] destptr mem) - // result: (MOVWstoreconst [makeValAndOff32(0,4)] destptr (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVWstoreconst [makeValAndOff(0,4)] destptr (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 6 { break @@ -11611,15 +11590,15 @@ func rewriteValue386_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(Op386MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 4)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 4)) v0 := b.NewValue0(v.Pos, Op386MOVLstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true } // match: (Zero [7] destptr mem) - // result: (MOVLstoreconst [makeValAndOff32(0,3)] destptr (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVLstoreconst [makeValAndOff(0,3)] destptr (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 7 { break @@ -11627,9 +11606,9 @@ func rewriteValue386_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(Op386MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 3)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 3)) v0 := b.NewValue0(v.Pos, Op386MOVLstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true @@ -11656,7 +11635,7 @@ func rewriteValue386_OpZero(v *Value) bool { return true } // match: (Zero [8] destptr mem) - // result: (MOVLstoreconst [makeValAndOff32(0,4)] destptr (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVLstoreconst [makeValAndOff(0,4)] destptr (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 8 { break @@ -11664,15 +11643,15 @@ func rewriteValue386_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(Op386MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 4)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 4)) v0 := b.NewValue0(v.Pos, Op386MOVLstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true } // match: (Zero [12] destptr mem) - // result: (MOVLstoreconst [makeValAndOff32(0,8)] destptr (MOVLstoreconst [makeValAndOff32(0,4)] destptr (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem))) + // result: (MOVLstoreconst [makeValAndOff(0,8)] destptr (MOVLstoreconst [makeValAndOff(0,4)] destptr (MOVLstoreconst [makeValAndOff(0,0)] destptr mem))) for { if auxIntToInt64(v.AuxInt) != 12 { break @@ -11680,18 +11659,18 @@ func rewriteValue386_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(Op386MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 8)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 8)) v0 := b.NewValue0(v.Pos, Op386MOVLstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 4)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 4)) v1 := b.NewValue0(v.Pos, Op386MOVLstoreconst, types.TypeMem) - v1.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v1.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v1.AddArg2(destptr, mem) v0.AddArg2(destptr, v1) v.AddArg2(destptr, v0) return true } // match: (Zero [16] destptr mem) - // result: (MOVLstoreconst [makeValAndOff32(0,12)] destptr (MOVLstoreconst [makeValAndOff32(0,8)] destptr (MOVLstoreconst [makeValAndOff32(0,4)] destptr (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)))) + // result: (MOVLstoreconst [makeValAndOff(0,12)] destptr (MOVLstoreconst [makeValAndOff(0,8)] destptr (MOVLstoreconst [makeValAndOff(0,4)] destptr (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)))) for { if auxIntToInt64(v.AuxInt) != 16 { break @@ -11699,13 +11678,13 @@ func rewriteValue386_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(Op386MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 12)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 12)) v0 := b.NewValue0(v.Pos, Op386MOVLstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 8)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 8)) v1 := b.NewValue0(v.Pos, Op386MOVLstoreconst, types.TypeMem) - v1.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 4)) + v1.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 4)) v2 := b.NewValue0(v.Pos, Op386MOVLstoreconst, types.TypeMem) - v2.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v2.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v2.AddArg2(destptr, mem) v1.AddArg2(destptr, v2) v0.AddArg2(destptr, v1) diff --git a/src/cmd/compile/internal/ssa/rewrite386splitload.go b/src/cmd/compile/internal/ssa/rewrite386splitload.go index fff26fa77e08305793ecf07e4c9d610c37a7a50e..670e7f4f8f6b5f3eb6cd0007f4307134aa7becdd 100644 --- a/src/cmd/compile/internal/ssa/rewrite386splitload.go +++ b/src/cmd/compile/internal/ssa/rewrite386splitload.go @@ -26,7 +26,7 @@ func rewriteValue386splitload_Op386CMPBconstload(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (CMPBconstload {sym} [vo] ptr mem) - // result: (CMPBconst (MOVBload {sym} [vo.Off32()] ptr mem) [vo.Val8()]) + // result: (CMPBconst (MOVBload {sym} [vo.Off()] ptr mem) [vo.Val8()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -35,7 +35,7 @@ func rewriteValue386splitload_Op386CMPBconstload(v *Value) bool { v.reset(Op386CMPBconst) v.AuxInt = int8ToAuxInt(vo.Val8()) v0 := b.NewValue0(v.Pos, Op386MOVBload, typ.UInt8) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) v.AddArg(v0) @@ -71,16 +71,16 @@ func rewriteValue386splitload_Op386CMPLconstload(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (CMPLconstload {sym} [vo] ptr mem) - // result: (CMPLconst (MOVLload {sym} [vo.Off32()] ptr mem) [vo.Val32()]) + // result: (CMPLconst (MOVLload {sym} [vo.Off()] ptr mem) [vo.Val()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) ptr := v_0 mem := v_1 v.reset(Op386CMPLconst) - v.AuxInt = int32ToAuxInt(vo.Val32()) + v.AuxInt = int32ToAuxInt(vo.Val()) v0 := b.NewValue0(v.Pos, Op386MOVLload, typ.UInt32) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) v.AddArg(v0) @@ -116,7 +116,7 @@ func rewriteValue386splitload_Op386CMPWconstload(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (CMPWconstload {sym} [vo] ptr mem) - // result: (CMPWconst (MOVWload {sym} [vo.Off32()] ptr mem) [vo.Val16()]) + // result: (CMPWconst (MOVWload {sym} [vo.Off()] ptr mem) [vo.Val16()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -125,7 +125,7 @@ func rewriteValue386splitload_Op386CMPWconstload(v *Value) bool { v.reset(Op386CMPWconst) v.AuxInt = int16ToAuxInt(vo.Val16()) v0 := b.NewValue0(v.Pos, Op386MOVWload, typ.UInt16) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) v.AddArg(v0) @@ -156,7 +156,5 @@ func rewriteValue386splitload_Op386CMPWload(v *Value) bool { } } func rewriteBlock386splitload(b *Block) bool { - switch b.Kind { - } return false } diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index 8272a406d9c629b97dd2aca3b4dfe55784a36424..5045ba7351f447ce13814b8ee6b3580cb9ed7ea6 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -3,7 +3,9 @@ package ssa +import "internal/buildcfg" import "math" +import "cmd/internal/obj" import "cmd/compile/internal/types" func rewriteValueAMD64(v *Value) bool { @@ -66,44 +68,20 @@ func rewriteValueAMD64(v *Value) bool { return rewriteValueAMD64_OpAMD64BSFQ(v) case OpAMD64BTCLconst: return rewriteValueAMD64_OpAMD64BTCLconst(v) - case OpAMD64BTCLconstmodify: - return rewriteValueAMD64_OpAMD64BTCLconstmodify(v) - case OpAMD64BTCLmodify: - return rewriteValueAMD64_OpAMD64BTCLmodify(v) case OpAMD64BTCQconst: return rewriteValueAMD64_OpAMD64BTCQconst(v) - case OpAMD64BTCQconstmodify: - return rewriteValueAMD64_OpAMD64BTCQconstmodify(v) - case OpAMD64BTCQmodify: - return rewriteValueAMD64_OpAMD64BTCQmodify(v) case OpAMD64BTLconst: return rewriteValueAMD64_OpAMD64BTLconst(v) case OpAMD64BTQconst: return rewriteValueAMD64_OpAMD64BTQconst(v) case OpAMD64BTRLconst: return rewriteValueAMD64_OpAMD64BTRLconst(v) - case OpAMD64BTRLconstmodify: - return rewriteValueAMD64_OpAMD64BTRLconstmodify(v) - case OpAMD64BTRLmodify: - return rewriteValueAMD64_OpAMD64BTRLmodify(v) case OpAMD64BTRQconst: return rewriteValueAMD64_OpAMD64BTRQconst(v) - case OpAMD64BTRQconstmodify: - return rewriteValueAMD64_OpAMD64BTRQconstmodify(v) - case OpAMD64BTRQmodify: - return rewriteValueAMD64_OpAMD64BTRQmodify(v) case OpAMD64BTSLconst: return rewriteValueAMD64_OpAMD64BTSLconst(v) - case OpAMD64BTSLconstmodify: - return rewriteValueAMD64_OpAMD64BTSLconstmodify(v) - case OpAMD64BTSLmodify: - return rewriteValueAMD64_OpAMD64BTSLmodify(v) case OpAMD64BTSQconst: return rewriteValueAMD64_OpAMD64BTSQconst(v) - case OpAMD64BTSQconstmodify: - return rewriteValueAMD64_OpAMD64BTSQconstmodify(v) - case OpAMD64BTSQmodify: - return rewriteValueAMD64_OpAMD64BTSQmodify(v) case OpAMD64CMOVLCC: return rewriteValueAMD64_OpAMD64CMOVLCC(v) case OpAMD64CMOVLCS: @@ -767,8 +745,7 @@ func rewriteValueAMD64(v *Value) bool { v.Op = OpAMD64LoweredGetClosurePtr return true case OpGetG: - v.Op = OpAMD64LoweredGetG - return true + return rewriteValueAMD64_OpGetG(v) case OpHasCPUFeature: return rewriteValueAMD64_OpHasCPUFeature(v) case OpHmul32: @@ -1061,6 +1038,8 @@ func rewriteValueAMD64(v *Value) bool { return rewriteValueAMD64_OpSelect0(v) case OpSelect1: return rewriteValueAMD64_OpSelect1(v) + case OpSelectN: + return rewriteValueAMD64_OpSelectN(v) case OpSignExt16to32: v.Op = OpAMD64MOVWQSX return true @@ -1088,6 +1067,9 @@ func rewriteValueAMD64(v *Value) bool { case OpSqrt: v.Op = OpAMD64SQRTSD return true + case OpSqrt32: + v.Op = OpAMD64SQRTSS + return true case OpStaticCall: v.Op = OpAMD64CALLstatic return true @@ -2998,36 +2980,6 @@ func rewriteValueAMD64_OpAMD64ANDLmodify(v *Value) bool { v_2 := v.Args[2] v_1 := v.Args[1] v_0 := v.Args[0] - b := v.Block - // match: (ANDLmodify [off] {sym} ptr (NOTL s:(SHLL (MOVLconst [1]) x)) mem) - // result: (BTRLmodify [off] {sym} ptr (ANDLconst [31] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - if v_1.Op != OpAMD64NOTL { - break - } - s := v_1.Args[0] - if s.Op != OpAMD64SHLL { - break - } - t := s.Type - x := s.Args[1] - s_0 := s.Args[0] - if s_0.Op != OpAMD64MOVLconst || auxIntToInt32(s_0.AuxInt) != 1 { - break - } - mem := v_2 - v.reset(OpAMD64BTRLmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(v.Pos, OpAMD64ANDLconst, t) - v0.AuxInt = int32ToAuxInt(31) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } // match: (ANDLmodify [off1] {sym} (ADDQconst [off2] base) val mem) // cond: is32Bit(int64(off1)+int64(off2)) // result: (ANDLmodify [off1+off2] {sym} base val mem) @@ -3407,36 +3359,6 @@ func rewriteValueAMD64_OpAMD64ANDQmodify(v *Value) bool { v_2 := v.Args[2] v_1 := v.Args[1] v_0 := v.Args[0] - b := v.Block - // match: (ANDQmodify [off] {sym} ptr (NOTQ s:(SHLQ (MOVQconst [1]) x)) mem) - // result: (BTRQmodify [off] {sym} ptr (ANDQconst [63] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - if v_1.Op != OpAMD64NOTQ { - break - } - s := v_1.Args[0] - if s.Op != OpAMD64SHLQ { - break - } - t := s.Type - x := s.Args[1] - s_0 := s.Args[0] - if s_0.Op != OpAMD64MOVQconst || auxIntToInt64(s_0.AuxInt) != 1 { - break - } - mem := v_2 - v.reset(OpAMD64BTRQmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(v.Pos, OpAMD64ANDQconst, t) - v0.AuxInt = int32ToAuxInt(63) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } // match: (ANDQmodify [off1] {sym} (ADDQconst [off2] base) val mem) // cond: is32Bit(int64(off1)+int64(off2)) // result: (ANDQmodify [off1+off2] {sym} base val mem) @@ -3577,105 +3499,6 @@ func rewriteValueAMD64_OpAMD64BTCLconst(v *Value) bool { } return false } -func rewriteValueAMD64_OpAMD64BTCLconstmodify(v *Value) bool { - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTCLconstmodify [valoff1] {sym} (ADDQconst [off2] base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) - // result: (BTCLconstmodify [ValAndOff(valoff1).addOffset32(off2)] {sym} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2)) { - break - } - v.reset(OpAMD64BTCLconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(sym) - v.AddArg2(base, mem) - return true - } - // match: (BTCLconstmodify [valoff1] {sym1} (LEAQ [off2] {sym2} base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2) - // result: (BTCLconstmodify [ValAndOff(valoff1).addOffset32(off2)] {mergeSym(sym1,sym2)} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTCLconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg2(base, mem) - return true - } - return false -} -func rewriteValueAMD64_OpAMD64BTCLmodify(v *Value) bool { - v_2 := v.Args[2] - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTCLmodify [off1] {sym} (ADDQconst [off2] base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) - // result: (BTCLmodify [off1+off2] {sym} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1) + int64(off2))) { - break - } - v.reset(OpAMD64BTCLmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(sym) - v.AddArg3(base, val, mem) - return true - } - // match: (BTCLmodify [off1] {sym1} (LEAQ [off2] {sym2} base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) - // result: (BTCLmodify [off1+off2] {mergeSym(sym1,sym2)} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTCLmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg3(base, val, mem) - return true - } - return false -} func rewriteValueAMD64_OpAMD64BTCQconst(v *Value) bool { v_0 := v.Args[0] // match: (BTCQconst [c] (XORQconst [d] x)) @@ -3728,105 +3551,6 @@ func rewriteValueAMD64_OpAMD64BTCQconst(v *Value) bool { } return false } -func rewriteValueAMD64_OpAMD64BTCQconstmodify(v *Value) bool { - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTCQconstmodify [valoff1] {sym} (ADDQconst [off2] base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) - // result: (BTCQconstmodify [ValAndOff(valoff1).addOffset32(off2)] {sym} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2)) { - break - } - v.reset(OpAMD64BTCQconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(sym) - v.AddArg2(base, mem) - return true - } - // match: (BTCQconstmodify [valoff1] {sym1} (LEAQ [off2] {sym2} base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2) - // result: (BTCQconstmodify [ValAndOff(valoff1).addOffset32(off2)] {mergeSym(sym1,sym2)} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTCQconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg2(base, mem) - return true - } - return false -} -func rewriteValueAMD64_OpAMD64BTCQmodify(v *Value) bool { - v_2 := v.Args[2] - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTCQmodify [off1] {sym} (ADDQconst [off2] base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) - // result: (BTCQmodify [off1+off2] {sym} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1) + int64(off2))) { - break - } - v.reset(OpAMD64BTCQmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(sym) - v.AddArg3(base, val, mem) - return true - } - // match: (BTCQmodify [off1] {sym1} (LEAQ [off2] {sym2} base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) - // result: (BTCQmodify [off1+off2] {mergeSym(sym1,sym2)} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTCQmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg3(base, val, mem) - return true - } - return false -} func rewriteValueAMD64_OpAMD64BTLconst(v *Value) bool { v_0 := v.Args[0] // match: (BTLconst [c] (SHRQconst [d] x)) @@ -4061,105 +3785,6 @@ func rewriteValueAMD64_OpAMD64BTRLconst(v *Value) bool { } return false } -func rewriteValueAMD64_OpAMD64BTRLconstmodify(v *Value) bool { - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTRLconstmodify [valoff1] {sym} (ADDQconst [off2] base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) - // result: (BTRLconstmodify [ValAndOff(valoff1).addOffset32(off2)] {sym} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2)) { - break - } - v.reset(OpAMD64BTRLconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(sym) - v.AddArg2(base, mem) - return true - } - // match: (BTRLconstmodify [valoff1] {sym1} (LEAQ [off2] {sym2} base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2) - // result: (BTRLconstmodify [ValAndOff(valoff1).addOffset32(off2)] {mergeSym(sym1,sym2)} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTRLconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg2(base, mem) - return true - } - return false -} -func rewriteValueAMD64_OpAMD64BTRLmodify(v *Value) bool { - v_2 := v.Args[2] - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTRLmodify [off1] {sym} (ADDQconst [off2] base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) - // result: (BTRLmodify [off1+off2] {sym} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1) + int64(off2))) { - break - } - v.reset(OpAMD64BTRLmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(sym) - v.AddArg3(base, val, mem) - return true - } - // match: (BTRLmodify [off1] {sym1} (LEAQ [off2] {sym2} base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) - // result: (BTRLmodify [off1+off2] {mergeSym(sym1,sym2)} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTRLmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg3(base, val, mem) - return true - } - return false -} func rewriteValueAMD64_OpAMD64BTRQconst(v *Value) bool { v_0 := v.Args[0] // match: (BTRQconst [c] (BTSQconst [c] x)) @@ -4238,105 +3863,6 @@ func rewriteValueAMD64_OpAMD64BTRQconst(v *Value) bool { } return false } -func rewriteValueAMD64_OpAMD64BTRQconstmodify(v *Value) bool { - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTRQconstmodify [valoff1] {sym} (ADDQconst [off2] base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) - // result: (BTRQconstmodify [ValAndOff(valoff1).addOffset32(off2)] {sym} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2)) { - break - } - v.reset(OpAMD64BTRQconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(sym) - v.AddArg2(base, mem) - return true - } - // match: (BTRQconstmodify [valoff1] {sym1} (LEAQ [off2] {sym2} base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2) - // result: (BTRQconstmodify [ValAndOff(valoff1).addOffset32(off2)] {mergeSym(sym1,sym2)} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTRQconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg2(base, mem) - return true - } - return false -} -func rewriteValueAMD64_OpAMD64BTRQmodify(v *Value) bool { - v_2 := v.Args[2] - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTRQmodify [off1] {sym} (ADDQconst [off2] base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) - // result: (BTRQmodify [off1+off2] {sym} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1) + int64(off2))) { - break - } - v.reset(OpAMD64BTRQmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(sym) - v.AddArg3(base, val, mem) - return true - } - // match: (BTRQmodify [off1] {sym1} (LEAQ [off2] {sym2} base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) - // result: (BTRQmodify [off1+off2] {mergeSym(sym1,sym2)} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTRQmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg3(base, val, mem) - return true - } - return false -} func rewriteValueAMD64_OpAMD64BTSLconst(v *Value) bool { v_0 := v.Args[0] // match: (BTSLconst [c] (BTRLconst [c] x)) @@ -4407,105 +3933,6 @@ func rewriteValueAMD64_OpAMD64BTSLconst(v *Value) bool { } return false } -func rewriteValueAMD64_OpAMD64BTSLconstmodify(v *Value) bool { - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTSLconstmodify [valoff1] {sym} (ADDQconst [off2] base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) - // result: (BTSLconstmodify [ValAndOff(valoff1).addOffset32(off2)] {sym} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2)) { - break - } - v.reset(OpAMD64BTSLconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(sym) - v.AddArg2(base, mem) - return true - } - // match: (BTSLconstmodify [valoff1] {sym1} (LEAQ [off2] {sym2} base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2) - // result: (BTSLconstmodify [ValAndOff(valoff1).addOffset32(off2)] {mergeSym(sym1,sym2)} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTSLconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg2(base, mem) - return true - } - return false -} -func rewriteValueAMD64_OpAMD64BTSLmodify(v *Value) bool { - v_2 := v.Args[2] - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTSLmodify [off1] {sym} (ADDQconst [off2] base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) - // result: (BTSLmodify [off1+off2] {sym} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1) + int64(off2))) { - break - } - v.reset(OpAMD64BTSLmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(sym) - v.AddArg3(base, val, mem) - return true - } - // match: (BTSLmodify [off1] {sym1} (LEAQ [off2] {sym2} base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) - // result: (BTSLmodify [off1+off2] {mergeSym(sym1,sym2)} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTSLmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg3(base, val, mem) - return true - } - return false -} func rewriteValueAMD64_OpAMD64BTSQconst(v *Value) bool { v_0 := v.Args[0] // match: (BTSQconst [c] (BTRQconst [c] x)) @@ -4584,105 +4011,6 @@ func rewriteValueAMD64_OpAMD64BTSQconst(v *Value) bool { } return false } -func rewriteValueAMD64_OpAMD64BTSQconstmodify(v *Value) bool { - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTSQconstmodify [valoff1] {sym} (ADDQconst [off2] base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) - // result: (BTSQconstmodify [ValAndOff(valoff1).addOffset32(off2)] {sym} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2)) { - break - } - v.reset(OpAMD64BTSQconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(sym) - v.AddArg2(base, mem) - return true - } - // match: (BTSQconstmodify [valoff1] {sym1} (LEAQ [off2] {sym2} base) mem) - // cond: ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2) - // result: (BTSQconstmodify [ValAndOff(valoff1).addOffset32(off2)] {mergeSym(sym1,sym2)} base mem) - for { - valoff1 := auxIntToValAndOff(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - mem := v_1 - if !(ValAndOff(valoff1).canAdd32(off2) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTSQconstmodify) - v.AuxInt = valAndOffToAuxInt(ValAndOff(valoff1).addOffset32(off2)) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg2(base, mem) - return true - } - return false -} -func rewriteValueAMD64_OpAMD64BTSQmodify(v *Value) bool { - v_2 := v.Args[2] - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (BTSQmodify [off1] {sym} (ADDQconst [off2] base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) - // result: (BTSQmodify [off1+off2] {sym} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - if v_0.Op != OpAMD64ADDQconst { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1) + int64(off2))) { - break - } - v.reset(OpAMD64BTSQmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(sym) - v.AddArg3(base, val, mem) - return true - } - // match: (BTSQmodify [off1] {sym1} (LEAQ [off2] {sym2} base) val mem) - // cond: is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2) - // result: (BTSQmodify [off1+off2] {mergeSym(sym1,sym2)} base val mem) - for { - off1 := auxIntToInt32(v.AuxInt) - sym1 := auxToSym(v.Aux) - if v_0.Op != OpAMD64LEAQ { - break - } - off2 := auxIntToInt32(v_0.AuxInt) - sym2 := auxToSym(v_0.Aux) - base := v_0.Args[0] - val := v_1 - mem := v_2 - if !(is32Bit(int64(off1)+int64(off2)) && canMergeSym(sym1, sym2)) { - break - } - v.reset(OpAMD64BTSQmodify) - v.AuxInt = int32ToAuxInt(off1 + off2) - v.Aux = symToAux(mergeSym(sym1, sym2)) - v.AddArg3(base, val, mem) - return true - } - return false -} func rewriteValueAMD64_OpAMD64CMOVLCC(v *Value) bool { v_2 := v.Args[2] v_1 := v.Args[1] @@ -6809,12 +6137,12 @@ func rewriteValueAMD64_OpAMD64CMPB(v *Value) bool { return true } // match: (CMPB x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPB y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpAMD64InvertFlags) @@ -7018,7 +6346,7 @@ func rewriteValueAMD64_OpAMD64CMPBconst(v *Value) bool { } // match: (CMPBconst l:(MOVBload {sym} [off] ptr mem) [c]) // cond: l.Uses == 1 && clobber(l) - // result: @l.Block (CMPBconstload {sym} [makeValAndOff32(int32(c),off)] ptr mem) + // result: @l.Block (CMPBconstload {sym} [makeValAndOff(int32(c),off)] ptr mem) for { c := auxIntToInt8(v.AuxInt) l := v_0 @@ -7035,7 +6363,7 @@ func rewriteValueAMD64_OpAMD64CMPBconst(v *Value) bool { b = l.Block v0 := b.NewValue0(l.Pos, OpAMD64CMPBconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) return true @@ -7140,8 +6468,7 @@ func rewriteValueAMD64_OpAMD64CMPBload(v *Value) bool { return true } // match: (CMPBload {sym} [off] ptr (MOVLconst [c]) mem) - // cond: validValAndOff(int64(int8(c)),int64(off)) - // result: (CMPBconstload {sym} [makeValAndOff32(int32(int8(c)),off)] ptr mem) + // result: (CMPBconstload {sym} [makeValAndOff(int32(int8(c)),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -7151,11 +6478,8 @@ func rewriteValueAMD64_OpAMD64CMPBload(v *Value) bool { } c := auxIntToInt32(v_1.AuxInt) mem := v_2 - if !(validValAndOff(int64(int8(c)), int64(off))) { - break - } v.reset(OpAMD64CMPBconstload) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(int8(c)), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(int8(c)), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -7195,12 +6519,12 @@ func rewriteValueAMD64_OpAMD64CMPL(v *Value) bool { return true } // match: (CMPL x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPL y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpAMD64InvertFlags) @@ -7419,7 +6743,7 @@ func rewriteValueAMD64_OpAMD64CMPLconst(v *Value) bool { } // match: (CMPLconst l:(MOVLload {sym} [off] ptr mem) [c]) // cond: l.Uses == 1 && clobber(l) - // result: @l.Block (CMPLconstload {sym} [makeValAndOff32(c,off)] ptr mem) + // result: @l.Block (CMPLconstload {sym} [makeValAndOff(c,off)] ptr mem) for { c := auxIntToInt32(v.AuxInt) l := v_0 @@ -7436,7 +6760,7 @@ func rewriteValueAMD64_OpAMD64CMPLconst(v *Value) bool { b = l.Block v0 := b.NewValue0(l.Pos, OpAMD64CMPLconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) return true @@ -7541,8 +6865,7 @@ func rewriteValueAMD64_OpAMD64CMPLload(v *Value) bool { return true } // match: (CMPLload {sym} [off] ptr (MOVLconst [c]) mem) - // cond: validValAndOff(int64(c),int64(off)) - // result: (CMPLconstload {sym} [makeValAndOff32(c,off)] ptr mem) + // result: (CMPLconstload {sym} [makeValAndOff(c,off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -7552,11 +6875,8 @@ func rewriteValueAMD64_OpAMD64CMPLload(v *Value) bool { } c := auxIntToInt32(v_1.AuxInt) mem := v_2 - if !(validValAndOff(int64(c), int64(off))) { - break - } v.reset(OpAMD64CMPLconstload) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -7604,12 +6924,12 @@ func rewriteValueAMD64_OpAMD64CMPQ(v *Value) bool { return true } // match: (CMPQ x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPQ y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpAMD64InvertFlags) @@ -7989,7 +7309,7 @@ func rewriteValueAMD64_OpAMD64CMPQconst(v *Value) bool { } // match: (CMPQconst l:(MOVQload {sym} [off] ptr mem) [c]) // cond: l.Uses == 1 && clobber(l) - // result: @l.Block (CMPQconstload {sym} [makeValAndOff32(c,off)] ptr mem) + // result: @l.Block (CMPQconstload {sym} [makeValAndOff(c,off)] ptr mem) for { c := auxIntToInt32(v.AuxInt) l := v_0 @@ -8006,7 +7326,7 @@ func rewriteValueAMD64_OpAMD64CMPQconst(v *Value) bool { b = l.Block v0 := b.NewValue0(l.Pos, OpAMD64CMPQconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(c, off)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(c, off)) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) return true @@ -8111,8 +7431,8 @@ func rewriteValueAMD64_OpAMD64CMPQload(v *Value) bool { return true } // match: (CMPQload {sym} [off] ptr (MOVQconst [c]) mem) - // cond: validValAndOff(c,int64(off)) - // result: (CMPQconstload {sym} [makeValAndOff64(c,int64(off))] ptr mem) + // cond: validVal(c) + // result: (CMPQconstload {sym} [makeValAndOff(int32(c),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -8122,11 +7442,11 @@ func rewriteValueAMD64_OpAMD64CMPQload(v *Value) bool { } c := auxIntToInt64(v_1.AuxInt) mem := v_2 - if !(validValAndOff(c, int64(off))) { + if !(validVal(c)) { break } v.reset(OpAMD64CMPQconstload) - v.AuxInt = valAndOffToAuxInt(makeValAndOff64(c, int64(off))) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -8166,12 +7486,12 @@ func rewriteValueAMD64_OpAMD64CMPW(v *Value) bool { return true } // match: (CMPW x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPW y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpAMD64InvertFlags) @@ -8375,7 +7695,7 @@ func rewriteValueAMD64_OpAMD64CMPWconst(v *Value) bool { } // match: (CMPWconst l:(MOVWload {sym} [off] ptr mem) [c]) // cond: l.Uses == 1 && clobber(l) - // result: @l.Block (CMPWconstload {sym} [makeValAndOff32(int32(c),off)] ptr mem) + // result: @l.Block (CMPWconstload {sym} [makeValAndOff(int32(c),off)] ptr mem) for { c := auxIntToInt16(v.AuxInt) l := v_0 @@ -8392,7 +7712,7 @@ func rewriteValueAMD64_OpAMD64CMPWconst(v *Value) bool { b = l.Block v0 := b.NewValue0(l.Pos, OpAMD64CMPWconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) return true @@ -8497,8 +7817,7 @@ func rewriteValueAMD64_OpAMD64CMPWload(v *Value) bool { return true } // match: (CMPWload {sym} [off] ptr (MOVLconst [c]) mem) - // cond: validValAndOff(int64(int16(c)),int64(off)) - // result: (CMPWconstload {sym} [makeValAndOff32(int32(int16(c)),off)] ptr mem) + // result: (CMPWconstload {sym} [makeValAndOff(int32(int16(c)),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -8508,11 +7827,8 @@ func rewriteValueAMD64_OpAMD64CMPWload(v *Value) bool { } c := auxIntToInt32(v_1.AuxInt) mem := v_2 - if !(validValAndOff(int64(int16(c)), int64(off))) { - break - } v.reset(OpAMD64CMPWconstload) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(int16(c)), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(int16(c)), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -10656,7 +9972,7 @@ func rewriteValueAMD64_OpAMD64MOVBstore(v *Value) bool { return true } // match: (MOVBstore [off] {sym} ptr (MOVLconst [c]) mem) - // result: (MOVBstoreconst [makeValAndOff32(int32(int8(c)),off)] {sym} ptr mem) + // result: (MOVBstoreconst [makeValAndOff(int32(int8(c)),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -10667,13 +9983,13 @@ func rewriteValueAMD64_OpAMD64MOVBstore(v *Value) bool { c := auxIntToInt32(v_1.AuxInt) mem := v_2 v.reset(OpAMD64MOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(int8(c)), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(int8(c)), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVBstore [off] {sym} ptr (MOVQconst [c]) mem) - // result: (MOVBstoreconst [makeValAndOff32(int32(int8(c)),off)] {sym} ptr mem) + // result: (MOVBstoreconst [makeValAndOff(int32(int8(c)),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -10684,7 +10000,7 @@ func rewriteValueAMD64_OpAMD64MOVBstore(v *Value) bool { c := auxIntToInt64(v_1.AuxInt) mem := v_2 v.reset(OpAMD64MOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(int8(c)), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(int8(c)), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -11471,6 +10787,56 @@ func rewriteValueAMD64_OpAMD64MOVBstore(v *Value) bool { v.AddArg3(p0, w0, mem) return true } + // match: (MOVBstore [7] {s} p1 (SHRQconst [56] w) x1:(MOVWstore [5] {s} p1 (SHRQconst [40] w) x2:(MOVLstore [1] {s} p1 (SHRQconst [8] w) x3:(MOVBstore [0] {s} p1 w mem)))) + // cond: x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && clobber(x1, x2, x3) + // result: (MOVQstore {s} p1 w mem) + for { + if auxIntToInt32(v.AuxInt) != 7 { + break + } + s := auxToSym(v.Aux) + p1 := v_0 + if v_1.Op != OpAMD64SHRQconst || auxIntToInt8(v_1.AuxInt) != 56 { + break + } + w := v_1.Args[0] + x1 := v_2 + if x1.Op != OpAMD64MOVWstore || auxIntToInt32(x1.AuxInt) != 5 || auxToSym(x1.Aux) != s { + break + } + _ = x1.Args[2] + if p1 != x1.Args[0] { + break + } + x1_1 := x1.Args[1] + if x1_1.Op != OpAMD64SHRQconst || auxIntToInt8(x1_1.AuxInt) != 40 || w != x1_1.Args[0] { + break + } + x2 := x1.Args[2] + if x2.Op != OpAMD64MOVLstore || auxIntToInt32(x2.AuxInt) != 1 || auxToSym(x2.Aux) != s { + break + } + _ = x2.Args[2] + if p1 != x2.Args[0] { + break + } + x2_1 := x2.Args[1] + if x2_1.Op != OpAMD64SHRQconst || auxIntToInt8(x2_1.AuxInt) != 8 || w != x2_1.Args[0] { + break + } + x3 := x2.Args[2] + if x3.Op != OpAMD64MOVBstore || auxIntToInt32(x3.AuxInt) != 0 || auxToSym(x3.Aux) != s { + break + } + mem := x3.Args[2] + if p1 != x3.Args[0] || w != x3.Args[1] || !(x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && clobber(x1, x2, x3)) { + break + } + v.reset(OpAMD64MOVQstore) + v.Aux = symToAux(s) + v.AddArg3(p1, w, mem) + return true + } // match: (MOVBstore [i] {s} p x1:(MOVBload [j] {s2} p2 mem) mem2:(MOVBstore [i-1] {s} p x2:(MOVBload [j-1] {s2} p2 mem) mem)) // cond: x1.Uses == 1 && x2.Uses == 1 && mem2.Uses == 1 && clobber(x1, x2, mem2) // result: (MOVWstore [i-1] {s} p (MOVWload [j-1] {s2} p2 mem) mem) @@ -11607,7 +10973,7 @@ func rewriteValueAMD64_OpAMD64MOVBstoreconst(v *Value) bool { } // match: (MOVBstoreconst [c] {s} p x:(MOVBstoreconst [a] {s} p mem)) // cond: x.Uses == 1 && a.Off() + 1 == c.Off() && clobber(x) - // result: (MOVWstoreconst [makeValAndOff64(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) + // result: (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -11625,14 +10991,14 @@ func rewriteValueAMD64_OpAMD64MOVBstoreconst(v *Value) bool { break } v.reset(OpAMD64MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff64(a.Val()&0xff|c.Val()<<8, a.Off())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xff|c.Val()<<8, a.Off())) v.Aux = symToAux(s) v.AddArg2(p, mem) return true } // match: (MOVBstoreconst [a] {s} p x:(MOVBstoreconst [c] {s} p mem)) // cond: x.Uses == 1 && a.Off() + 1 == c.Off() && clobber(x) - // result: (MOVWstoreconst [makeValAndOff64(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) + // result: (MOVWstoreconst [makeValAndOff(a.Val()&0xff | c.Val()<<8, a.Off())] {s} p mem) for { a := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -11650,7 +11016,7 @@ func rewriteValueAMD64_OpAMD64MOVBstoreconst(v *Value) bool { break } v.reset(OpAMD64MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff64(a.Val()&0xff|c.Val()<<8, a.Off())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xff|c.Val()<<8, a.Off())) v.Aux = symToAux(s) v.AddArg2(p, mem) return true @@ -12264,7 +11630,7 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool { return true } // match: (MOVLstore [off] {sym} ptr (MOVLconst [c]) mem) - // result: (MOVLstoreconst [makeValAndOff32(int32(c),off)] {sym} ptr mem) + // result: (MOVLstoreconst [makeValAndOff(int32(c),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -12275,13 +11641,13 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool { c := auxIntToInt32(v_1.AuxInt) mem := v_2 v.reset(OpAMD64MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVLstore [off] {sym} ptr (MOVQconst [c]) mem) - // result: (MOVLstoreconst [makeValAndOff32(int32(c),off)] {sym} ptr mem) + // result: (MOVLstoreconst [makeValAndOff(int32(c),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -12292,7 +11658,7 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool { c := auxIntToInt64(v_1.AuxInt) mem := v_2 v.reset(OpAMD64MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -12769,99 +12135,9 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool { } break } - // match: (MOVLstore {sym} [off] ptr y:(BTCL l:(MOVLload [off] {sym} ptr mem) x) mem) - // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) - // result: (BTCLmodify [off] {sym} ptr (ANDLconst [31] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - y := v_1 - if y.Op != OpAMD64BTCL { - break - } - t := y.Type - x := y.Args[1] - l := y.Args[0] - if l.Op != OpAMD64MOVLload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l)) { - break - } - v.reset(OpAMD64BTCLmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(l.Pos, OpAMD64ANDLconst, t) - v0.AuxInt = int32ToAuxInt(31) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } - // match: (MOVLstore {sym} [off] ptr y:(BTRL l:(MOVLload [off] {sym} ptr mem) x) mem) - // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) - // result: (BTRLmodify [off] {sym} ptr (ANDLconst [31] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - y := v_1 - if y.Op != OpAMD64BTRL { - break - } - t := y.Type - x := y.Args[1] - l := y.Args[0] - if l.Op != OpAMD64MOVLload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l)) { - break - } - v.reset(OpAMD64BTRLmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(l.Pos, OpAMD64ANDLconst, t) - v0.AuxInt = int32ToAuxInt(31) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } - // match: (MOVLstore {sym} [off] ptr y:(BTSL l:(MOVLload [off] {sym} ptr mem) x) mem) - // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) - // result: (BTSLmodify [off] {sym} ptr (ANDLconst [31] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - y := v_1 - if y.Op != OpAMD64BTSL { - break - } - t := y.Type - x := y.Args[1] - l := y.Args[0] - if l.Op != OpAMD64MOVLload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l)) { - break - } - v.reset(OpAMD64BTSLmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(l.Pos, OpAMD64ANDLconst, t) - v0.AuxInt = int32ToAuxInt(31) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } // match: (MOVLstore [off] {sym} ptr a:(ADDLconst [c] l:(MOVLload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (ADDLconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) + // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a) + // result: (ADDLconstmodify {sym} [makeValAndOff(int32(c),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -12877,18 +12153,18 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool { } mem := l.Args[1] ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { + if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a)) { break } v.reset(OpAMD64ADDLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVLstore [off] {sym} ptr a:(ANDLconst [c] l:(MOVLload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (ANDLconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) + // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a) + // result: (ANDLconstmodify {sym} [makeValAndOff(int32(c),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -12904,18 +12180,18 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool { } mem := l.Args[1] ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { + if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a)) { break } v.reset(OpAMD64ANDLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVLstore [off] {sym} ptr a:(ORLconst [c] l:(MOVLload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (ORLconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) + // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a) + // result: (ORLconstmodify {sym} [makeValAndOff(int32(c),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -12931,18 +12207,18 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool { } mem := l.Args[1] ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { + if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a)) { break } v.reset(OpAMD64ORLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVLstore [off] {sym} ptr a:(XORLconst [c] l:(MOVLload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (XORLconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) + // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a) + // result: (XORLconstmodify {sym} [makeValAndOff(int32(c),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -12958,92 +12234,11 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool { } mem := l.Args[1] ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { + if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a)) { break } v.reset(OpAMD64XORLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) - v.Aux = symToAux(sym) - v.AddArg2(ptr, mem) - return true - } - // match: (MOVLstore [off] {sym} ptr a:(BTCLconst [c] l:(MOVLload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (BTCLconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - a := v_1 - if a.Op != OpAMD64BTCLconst { - break - } - c := auxIntToInt8(a.AuxInt) - l := a.Args[0] - if l.Op != OpAMD64MOVLload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { - break - } - v.reset(OpAMD64BTCLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) - v.Aux = symToAux(sym) - v.AddArg2(ptr, mem) - return true - } - // match: (MOVLstore [off] {sym} ptr a:(BTRLconst [c] l:(MOVLload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (BTRLconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - a := v_1 - if a.Op != OpAMD64BTRLconst { - break - } - c := auxIntToInt8(a.AuxInt) - l := a.Args[0] - if l.Op != OpAMD64MOVLload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { - break - } - v.reset(OpAMD64BTRLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) - v.Aux = symToAux(sym) - v.AddArg2(ptr, mem) - return true - } - // match: (MOVLstore [off] {sym} ptr a:(BTSLconst [c] l:(MOVLload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (BTSLconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - a := v_1 - if a.Op != OpAMD64BTSLconst { - break - } - c := auxIntToInt8(a.AuxInt) - l := a.Args[0] - if l.Op != OpAMD64MOVLload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { - break - } - v.reset(OpAMD64BTSLconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -13117,7 +12312,7 @@ func rewriteValueAMD64_OpAMD64MOVLstoreconst(v *Value) bool { } // match: (MOVLstoreconst [c] {s} p x:(MOVLstoreconst [a] {s} p mem)) // cond: x.Uses == 1 && a.Off() + 4 == c.Off() && clobber(x) - // result: (MOVQstore [a.Off32()] {s} p (MOVQconst [a.Val()&0xffffffff | c.Val()<<32]) mem) + // result: (MOVQstore [a.Off()] {s} p (MOVQconst [a.Val64()&0xffffffff | c.Val64()<<32]) mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -13135,16 +12330,16 @@ func rewriteValueAMD64_OpAMD64MOVLstoreconst(v *Value) bool { break } v.reset(OpAMD64MOVQstore) - v.AuxInt = int32ToAuxInt(a.Off32()) + v.AuxInt = int32ToAuxInt(a.Off()) v.Aux = symToAux(s) v0 := b.NewValue0(x.Pos, OpAMD64MOVQconst, typ.UInt64) - v0.AuxInt = int64ToAuxInt(a.Val()&0xffffffff | c.Val()<<32) + v0.AuxInt = int64ToAuxInt(a.Val64()&0xffffffff | c.Val64()<<32) v.AddArg3(p, v0, mem) return true } // match: (MOVLstoreconst [a] {s} p x:(MOVLstoreconst [c] {s} p mem)) // cond: x.Uses == 1 && a.Off() + 4 == c.Off() && clobber(x) - // result: (MOVQstore [a.Off32()] {s} p (MOVQconst [a.Val()&0xffffffff | c.Val()<<32]) mem) + // result: (MOVQstore [a.Off()] {s} p (MOVQconst [a.Val64()&0xffffffff | c.Val64()<<32]) mem) for { a := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -13162,10 +12357,10 @@ func rewriteValueAMD64_OpAMD64MOVLstoreconst(v *Value) bool { break } v.reset(OpAMD64MOVQstore) - v.AuxInt = int32ToAuxInt(a.Off32()) + v.AuxInt = int32ToAuxInt(a.Off()) v.Aux = symToAux(s) v0 := b.NewValue0(x.Pos, OpAMD64MOVQconst, typ.UInt64) - v0.AuxInt = int64ToAuxInt(a.Val()&0xffffffff | c.Val()<<32) + v0.AuxInt = int64ToAuxInt(a.Val64()&0xffffffff | c.Val64()<<32) v.AddArg3(p, v0, mem) return true } @@ -13597,7 +12792,6 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool { v_2 := v.Args[2] v_1 := v.Args[1] v_0 := v.Args[0] - b := v.Block // match: (MOVQstore [off1] {sym} (ADDQconst [off2] ptr) val mem) // cond: is32Bit(int64(off1)+int64(off2)) // result: (MOVQstore [off1+off2] {sym} ptr val mem) @@ -13622,7 +12816,7 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool { } // match: (MOVQstore [off] {sym} ptr (MOVQconst [c]) mem) // cond: validVal(c) - // result: (MOVQstoreconst [makeValAndOff32(int32(c),off)] {sym} ptr mem) + // result: (MOVQstoreconst [makeValAndOff(int32(c),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -13636,7 +12830,7 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool { break } v.reset(OpAMD64MOVQstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -13963,99 +13157,9 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool { } break } - // match: (MOVQstore {sym} [off] ptr y:(BTCQ l:(MOVQload [off] {sym} ptr mem) x) mem) - // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) - // result: (BTCQmodify [off] {sym} ptr (ANDQconst [63] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - y := v_1 - if y.Op != OpAMD64BTCQ { - break - } - t := y.Type - x := y.Args[1] - l := y.Args[0] - if l.Op != OpAMD64MOVQload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l)) { - break - } - v.reset(OpAMD64BTCQmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(l.Pos, OpAMD64ANDQconst, t) - v0.AuxInt = int32ToAuxInt(63) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } - // match: (MOVQstore {sym} [off] ptr y:(BTRQ l:(MOVQload [off] {sym} ptr mem) x) mem) - // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) - // result: (BTRQmodify [off] {sym} ptr (ANDQconst [63] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - y := v_1 - if y.Op != OpAMD64BTRQ { - break - } - t := y.Type - x := y.Args[1] - l := y.Args[0] - if l.Op != OpAMD64MOVQload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l)) { - break - } - v.reset(OpAMD64BTRQmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(l.Pos, OpAMD64ANDQconst, t) - v0.AuxInt = int32ToAuxInt(63) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } - // match: (MOVQstore {sym} [off] ptr y:(BTSQ l:(MOVQload [off] {sym} ptr mem) x) mem) - // cond: y.Uses==1 && l.Uses==1 && clobber(y, l) - // result: (BTSQmodify [off] {sym} ptr (ANDQconst [63] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - y := v_1 - if y.Op != OpAMD64BTSQ { - break - } - t := y.Type - x := y.Args[1] - l := y.Args[0] - if l.Op != OpAMD64MOVQload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - if ptr != l.Args[0] || mem != v_2 || !(y.Uses == 1 && l.Uses == 1 && clobber(y, l)) { - break - } - v.reset(OpAMD64BTSQmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(l.Pos, OpAMD64ANDQconst, t) - v0.AuxInt = int32ToAuxInt(63) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } // match: (MOVQstore [off] {sym} ptr a:(ADDQconst [c] l:(MOVQload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (ADDQconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) + // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a) + // result: (ADDQconstmodify {sym} [makeValAndOff(int32(c),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -14071,18 +13175,18 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool { } mem := l.Args[1] ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { + if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a)) { break } v.reset(OpAMD64ADDQconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVQstore [off] {sym} ptr a:(ANDQconst [c] l:(MOVQload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (ANDQconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) + // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a) + // result: (ANDQconstmodify {sym} [makeValAndOff(int32(c),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -14098,18 +13202,18 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool { } mem := l.Args[1] ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { + if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a)) { break } v.reset(OpAMD64ANDQconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVQstore [off] {sym} ptr a:(ORQconst [c] l:(MOVQload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (ORQconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) + // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a) + // result: (ORQconstmodify {sym} [makeValAndOff(int32(c),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -14125,18 +13229,18 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool { } mem := l.Args[1] ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { + if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a)) { break } v.reset(OpAMD64ORQconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVQstore [off] {sym} ptr a:(XORQconst [c] l:(MOVQload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (XORQconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) + // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a) + // result: (XORQconstmodify {sym} [makeValAndOff(int32(c),off)] ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -14152,92 +13256,11 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool { } mem := l.Args[1] ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { + if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && clobber(l, a)) { break } v.reset(OpAMD64XORQconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) - v.Aux = symToAux(sym) - v.AddArg2(ptr, mem) - return true - } - // match: (MOVQstore [off] {sym} ptr a:(BTCQconst [c] l:(MOVQload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (BTCQconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - a := v_1 - if a.Op != OpAMD64BTCQconst { - break - } - c := auxIntToInt8(a.AuxInt) - l := a.Args[0] - if l.Op != OpAMD64MOVQload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { - break - } - v.reset(OpAMD64BTCQconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) - v.Aux = symToAux(sym) - v.AddArg2(ptr, mem) - return true - } - // match: (MOVQstore [off] {sym} ptr a:(BTRQconst [c] l:(MOVQload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (BTRQconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - a := v_1 - if a.Op != OpAMD64BTRQconst { - break - } - c := auxIntToInt8(a.AuxInt) - l := a.Args[0] - if l.Op != OpAMD64MOVQload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { - break - } - v.reset(OpAMD64BTRQconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) - v.Aux = symToAux(sym) - v.AddArg2(ptr, mem) - return true - } - // match: (MOVQstore [off] {sym} ptr a:(BTSQconst [c] l:(MOVQload [off] {sym} ptr2 mem)) mem) - // cond: isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c),int64(off)) && clobber(l, a) - // result: (BTSQconstmodify {sym} [makeValAndOff32(int32(c),off)] ptr mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - a := v_1 - if a.Op != OpAMD64BTSQconst { - break - } - c := auxIntToInt8(a.AuxInt) - l := a.Args[0] - if l.Op != OpAMD64MOVQload || auxIntToInt32(l.AuxInt) != off || auxToSym(l.Aux) != sym { - break - } - mem := l.Args[1] - ptr2 := l.Args[0] - if mem != v_2 || !(isSamePtr(ptr, ptr2) && a.Uses == 1 && l.Uses == 1 && validValAndOff(int64(c), int64(off)) && clobber(l, a)) { - break - } - v.reset(OpAMD64BTSQconstmodify) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -14311,7 +13334,7 @@ func rewriteValueAMD64_OpAMD64MOVQstoreconst(v *Value) bool { } // match: (MOVQstoreconst [c] {s} p x:(MOVQstoreconst [c2] {s} p mem)) // cond: config.useSSE && x.Uses == 1 && c2.Off() + 8 == c.Off() && c.Val() == 0 && c2.Val() == 0 && clobber(x) - // result: (MOVOstore [c2.Off32()] {s} p (MOVOconst [0]) mem) + // result: (MOVOstorezero [c2.Off()] {s} p mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -14328,12 +13351,10 @@ func rewriteValueAMD64_OpAMD64MOVQstoreconst(v *Value) bool { if p != x.Args[0] || !(config.useSSE && x.Uses == 1 && c2.Off()+8 == c.Off() && c.Val() == 0 && c2.Val() == 0 && clobber(x)) { break } - v.reset(OpAMD64MOVOstore) - v.AuxInt = int32ToAuxInt(c2.Off32()) + v.reset(OpAMD64MOVOstorezero) + v.AuxInt = int32ToAuxInt(c2.Off()) v.Aux = symToAux(s) - v0 := b.NewValue0(x.Pos, OpAMD64MOVOconst, types.TypeInt128) - v0.AuxInt = int128ToAuxInt(0) - v.AddArg3(p, v0, mem) + v.AddArg2(p, mem) return true } // match: (MOVQstoreconst [sc] {sym1} (LEAL [off] {sym2} ptr) mem) @@ -15118,7 +14139,7 @@ func rewriteValueAMD64_OpAMD64MOVWstore(v *Value) bool { return true } // match: (MOVWstore [off] {sym} ptr (MOVLconst [c]) mem) - // result: (MOVWstoreconst [makeValAndOff32(int32(int16(c)),off)] {sym} ptr mem) + // result: (MOVWstoreconst [makeValAndOff(int32(int16(c)),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -15129,13 +14150,13 @@ func rewriteValueAMD64_OpAMD64MOVWstore(v *Value) bool { c := auxIntToInt32(v_1.AuxInt) mem := v_2 v.reset(OpAMD64MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(int16(c)), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(int16(c)), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true } // match: (MOVWstore [off] {sym} ptr (MOVQconst [c]) mem) - // result: (MOVWstoreconst [makeValAndOff32(int32(int16(c)),off)] {sym} ptr mem) + // result: (MOVWstoreconst [makeValAndOff(int32(int16(c)),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -15146,7 +14167,7 @@ func rewriteValueAMD64_OpAMD64MOVWstore(v *Value) bool { c := auxIntToInt64(v_1.AuxInt) mem := v_2 v.reset(OpAMD64MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(int16(c)), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(int16(c)), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -15528,7 +14549,7 @@ func rewriteValueAMD64_OpAMD64MOVWstoreconst(v *Value) bool { } // match: (MOVWstoreconst [c] {s} p x:(MOVWstoreconst [a] {s} p mem)) // cond: x.Uses == 1 && a.Off() + 2 == c.Off() && clobber(x) - // result: (MOVLstoreconst [makeValAndOff64(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) + // result: (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -15546,14 +14567,14 @@ func rewriteValueAMD64_OpAMD64MOVWstoreconst(v *Value) bool { break } v.reset(OpAMD64MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff64(a.Val()&0xffff|c.Val()<<16, a.Off())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xffff|c.Val()<<16, a.Off())) v.Aux = symToAux(s) v.AddArg2(p, mem) return true } // match: (MOVWstoreconst [a] {s} p x:(MOVWstoreconst [c] {s} p mem)) // cond: x.Uses == 1 && a.Off() + 2 == c.Off() && clobber(x) - // result: (MOVLstoreconst [makeValAndOff64(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) + // result: (MOVLstoreconst [makeValAndOff(a.Val()&0xffff | c.Val()<<16, a.Off())] {s} p mem) for { a := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -15571,7 +14592,7 @@ func rewriteValueAMD64_OpAMD64MOVWstoreconst(v *Value) bool { break } v.reset(OpAMD64MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff64(a.Val()&0xffff|c.Val()<<16, a.Off())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(a.Val()&0xffff|c.Val()<<16, a.Off())) v.Aux = symToAux(s) v.AddArg2(p, mem) return true @@ -18437,33 +17458,6 @@ func rewriteValueAMD64_OpAMD64ORLmodify(v *Value) bool { v_2 := v.Args[2] v_1 := v.Args[1] v_0 := v.Args[0] - b := v.Block - // match: (ORLmodify [off] {sym} ptr s:(SHLL (MOVLconst [1]) x) mem) - // result: (BTSLmodify [off] {sym} ptr (ANDLconst [31] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - s := v_1 - if s.Op != OpAMD64SHLL { - break - } - t := s.Type - x := s.Args[1] - s_0 := s.Args[0] - if s_0.Op != OpAMD64MOVLconst || auxIntToInt32(s_0.AuxInt) != 1 { - break - } - mem := v_2 - v.reset(OpAMD64BTSLmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(v.Pos, OpAMD64ANDLconst, t) - v0.AuxInt = int32ToAuxInt(31) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } // match: (ORLmodify [off1] {sym} (ADDQconst [off2] base) val mem) // cond: is32Bit(int64(off1)+int64(off2)) // result: (ORLmodify [off1+off2] {sym} base val mem) @@ -18815,6 +17809,54 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value) bool { } break } + // match: (ORQ (SHRQ lo bits) (SHLQ hi (NEGQ bits))) + // result: (SHRDQ lo hi bits) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHRQ { + continue + } + bits := v_0.Args[1] + lo := v_0.Args[0] + if v_1.Op != OpAMD64SHLQ { + continue + } + _ = v_1.Args[1] + hi := v_1.Args[0] + v_1_1 := v_1.Args[1] + if v_1_1.Op != OpAMD64NEGQ || bits != v_1_1.Args[0] { + continue + } + v.reset(OpAMD64SHRDQ) + v.AddArg3(lo, hi, bits) + return true + } + break + } + // match: (ORQ (SHLQ lo bits) (SHRQ hi (NEGQ bits))) + // result: (SHLDQ lo hi bits) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLQ { + continue + } + bits := v_0.Args[1] + lo := v_0.Args[0] + if v_1.Op != OpAMD64SHRQ { + continue + } + _ = v_1.Args[1] + hi := v_1.Args[0] + v_1_1 := v_1.Args[1] + if v_1_1.Op != OpAMD64NEGQ || bits != v_1_1.Args[0] { + continue + } + v.reset(OpAMD64SHLDQ) + v.AddArg3(lo, hi, bits) + return true + } + break + } // match: (ORQ (MOVQconst [c]) (MOVQconst [d])) // result: (MOVQconst [c|d]) for { @@ -20091,33 +19133,6 @@ func rewriteValueAMD64_OpAMD64ORQmodify(v *Value) bool { v_2 := v.Args[2] v_1 := v.Args[1] v_0 := v.Args[0] - b := v.Block - // match: (ORQmodify [off] {sym} ptr s:(SHLQ (MOVQconst [1]) x) mem) - // result: (BTSQmodify [off] {sym} ptr (ANDQconst [63] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - s := v_1 - if s.Op != OpAMD64SHLQ { - break - } - t := s.Type - x := s.Args[1] - s_0 := s.Args[0] - if s_0.Op != OpAMD64MOVQconst || auxIntToInt64(s_0.AuxInt) != 1 { - break - } - mem := v_2 - v.reset(OpAMD64BTSQmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(v.Pos, OpAMD64ANDQconst, t) - v0.AuxInt = int32ToAuxInt(63) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } // match: (ORQmodify [off1] {sym} (ADDQconst [off2] base) val mem) // cond: is32Bit(int64(off1)+int64(off2)) // result: (ORQmodify [off1+off2] {sym} base val mem) @@ -27187,8 +26202,8 @@ func rewriteValueAMD64_OpAMD64TESTB(v *Value) bool { break } // match: (TESTB l:(MOVBload {sym} [off] ptr mem) l2) - // cond: l == l2 && l.Uses == 2 && validValAndOff(0, int64(off)) && clobber(l) - // result: @l.Block (CMPBconstload {sym} [makeValAndOff64(0, int64(off))] ptr mem) + // cond: l == l2 && l.Uses == 2 && clobber(l) + // result: @l.Block (CMPBconstload {sym} [makeValAndOff(0, off)] ptr mem) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { l := v_0 @@ -27200,13 +26215,13 @@ func rewriteValueAMD64_OpAMD64TESTB(v *Value) bool { mem := l.Args[1] ptr := l.Args[0] l2 := v_1 - if !(l == l2 && l.Uses == 2 && validValAndOff(0, int64(off)) && clobber(l)) { + if !(l == l2 && l.Uses == 2 && clobber(l)) { continue } b = l.Block v0 := b.NewValue0(l.Pos, OpAMD64CMPBconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff64(0, int64(off))) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, off)) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) return true @@ -27255,8 +26270,8 @@ func rewriteValueAMD64_OpAMD64TESTL(v *Value) bool { break } // match: (TESTL l:(MOVLload {sym} [off] ptr mem) l2) - // cond: l == l2 && l.Uses == 2 && validValAndOff(0, int64(off)) && clobber(l) - // result: @l.Block (CMPLconstload {sym} [makeValAndOff64(0, int64(off))] ptr mem) + // cond: l == l2 && l.Uses == 2 && clobber(l) + // result: @l.Block (CMPLconstload {sym} [makeValAndOff(0, off)] ptr mem) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { l := v_0 @@ -27268,15 +26283,42 @@ func rewriteValueAMD64_OpAMD64TESTL(v *Value) bool { mem := l.Args[1] ptr := l.Args[0] l2 := v_1 - if !(l == l2 && l.Uses == 2 && validValAndOff(0, int64(off)) && clobber(l)) { + if !(l == l2 && l.Uses == 2 && clobber(l)) { continue } b = l.Block v0 := b.NewValue0(l.Pos, OpAMD64CMPLconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff64(0, int64(off))) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, off)) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + return true + } + break + } + // match: (TESTL a:(ANDLload [off] {sym} x ptr mem) a) + // cond: a.Uses == 2 && a.Block == v.Block && clobber(a) + // result: (TESTL (MOVLload [off] {sym} ptr mem) x) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + a := v_0 + if a.Op != OpAMD64ANDLload { + continue + } + off := auxIntToInt32(a.AuxInt) + sym := auxToSym(a.Aux) + mem := a.Args[2] + x := a.Args[0] + ptr := a.Args[1] + if a != v_1 || !(a.Uses == 2 && a.Block == v.Block && clobber(a)) { + continue + } + v.reset(OpAMD64TESTL) + v0 := b.NewValue0(a.Pos, OpAMD64MOVLload, a.Type) + v0.AuxInt = int32ToAuxInt(off) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) + v.AddArg2(v0, x) return true } break @@ -27360,8 +26402,8 @@ func rewriteValueAMD64_OpAMD64TESTQ(v *Value) bool { break } // match: (TESTQ l:(MOVQload {sym} [off] ptr mem) l2) - // cond: l == l2 && l.Uses == 2 && validValAndOff(0, int64(off)) && clobber(l) - // result: @l.Block (CMPQconstload {sym} [makeValAndOff64(0, int64(off))] ptr mem) + // cond: l == l2 && l.Uses == 2 && clobber(l) + // result: @l.Block (CMPQconstload {sym} [makeValAndOff(0, off)] ptr mem) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { l := v_0 @@ -27373,15 +26415,42 @@ func rewriteValueAMD64_OpAMD64TESTQ(v *Value) bool { mem := l.Args[1] ptr := l.Args[0] l2 := v_1 - if !(l == l2 && l.Uses == 2 && validValAndOff(0, int64(off)) && clobber(l)) { + if !(l == l2 && l.Uses == 2 && clobber(l)) { continue } b = l.Block v0 := b.NewValue0(l.Pos, OpAMD64CMPQconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff64(0, int64(off))) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, off)) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + return true + } + break + } + // match: (TESTQ a:(ANDQload [off] {sym} x ptr mem) a) + // cond: a.Uses == 2 && a.Block == v.Block && clobber(a) + // result: (TESTQ (MOVQload [off] {sym} ptr mem) x) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + a := v_0 + if a.Op != OpAMD64ANDQload { + continue + } + off := auxIntToInt32(a.AuxInt) + sym := auxToSym(a.Aux) + mem := a.Args[2] + x := a.Args[0] + ptr := a.Args[1] + if a != v_1 || !(a.Uses == 2 && a.Block == v.Block && clobber(a)) { + continue + } + v.reset(OpAMD64TESTQ) + v0 := b.NewValue0(a.Pos, OpAMD64MOVQload, a.Type) + v0.AuxInt = int32ToAuxInt(off) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) + v.AddArg2(v0, x) return true } break @@ -27473,8 +26542,8 @@ func rewriteValueAMD64_OpAMD64TESTW(v *Value) bool { break } // match: (TESTW l:(MOVWload {sym} [off] ptr mem) l2) - // cond: l == l2 && l.Uses == 2 && validValAndOff(0, int64(off)) && clobber(l) - // result: @l.Block (CMPWconstload {sym} [makeValAndOff64(0, int64(off))] ptr mem) + // cond: l == l2 && l.Uses == 2 && clobber(l) + // result: @l.Block (CMPWconstload {sym} [makeValAndOff(0, off)] ptr mem) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { l := v_0 @@ -27486,13 +26555,13 @@ func rewriteValueAMD64_OpAMD64TESTW(v *Value) bool { mem := l.Args[1] ptr := l.Args[0] l2 := v_1 - if !(l == l2 && l.Uses == 2 && validValAndOff(0, int64(off)) && clobber(l)) { + if !(l == l2 && l.Uses == 2 && clobber(l)) { continue } b = l.Block v0 := b.NewValue0(l.Pos, OpAMD64CMPWconstload, types.TypeFlags) v.copyOf(v0) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff64(0, int64(off))) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, off)) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) return true @@ -28153,33 +27222,6 @@ func rewriteValueAMD64_OpAMD64XORLmodify(v *Value) bool { v_2 := v.Args[2] v_1 := v.Args[1] v_0 := v.Args[0] - b := v.Block - // match: (XORLmodify [off] {sym} ptr s:(SHLL (MOVLconst [1]) x) mem) - // result: (BTCLmodify [off] {sym} ptr (ANDLconst [31] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - s := v_1 - if s.Op != OpAMD64SHLL { - break - } - t := s.Type - x := s.Args[1] - s_0 := s.Args[0] - if s_0.Op != OpAMD64MOVLconst || auxIntToInt32(s_0.AuxInt) != 1 { - break - } - mem := v_2 - v.reset(OpAMD64BTCLmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(v.Pos, OpAMD64ANDLconst, t) - v0.AuxInt = int32ToAuxInt(31) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } // match: (XORLmodify [off1] {sym} (ADDQconst [off2] base) val mem) // cond: is32Bit(int64(off1)+int64(off2)) // result: (XORLmodify [off1+off2] {sym} base val mem) @@ -28548,33 +27590,6 @@ func rewriteValueAMD64_OpAMD64XORQmodify(v *Value) bool { v_2 := v.Args[2] v_1 := v.Args[1] v_0 := v.Args[0] - b := v.Block - // match: (XORQmodify [off] {sym} ptr s:(SHLQ (MOVQconst [1]) x) mem) - // result: (BTCQmodify [off] {sym} ptr (ANDQconst [63] x) mem) - for { - off := auxIntToInt32(v.AuxInt) - sym := auxToSym(v.Aux) - ptr := v_0 - s := v_1 - if s.Op != OpAMD64SHLQ { - break - } - t := s.Type - x := s.Args[1] - s_0 := s.Args[0] - if s_0.Op != OpAMD64MOVQconst || auxIntToInt64(s_0.AuxInt) != 1 { - break - } - mem := v_2 - v.reset(OpAMD64BTCQmodify) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v0 := b.NewValue0(v.Pos, OpAMD64ANDQconst, t) - v0.AuxInt = int32ToAuxInt(63) - v0.AddArg(x) - v.AddArg3(ptr, v0, mem) - return true - } // match: (XORQmodify [off1] {sym} (ADDQconst [off2] base) val mem) // cond: is32Bit(int64(off1)+int64(off2)) // result: (XORQmodify [off1+off2] {sym} base val mem) @@ -30321,6 +29336,22 @@ func rewriteValueAMD64_OpFloor(v *Value) bool { return true } } +func rewriteValueAMD64_OpGetG(v *Value) bool { + v_0 := v.Args[0] + // match: (GetG mem) + // cond: !(buildcfg.Experiment.RegabiG && v.Block.Func.OwnAux.Fn.ABI() == obj.ABIInternal) + // result: (LoweredGetG mem) + for { + mem := v_0 + if !(!(buildcfg.Experiment.RegabiG && v.Block.Func.OwnAux.Fn.ABI() == obj.ABIInternal)) { + break + } + v.reset(OpAMD64LoweredGetG) + v.AddArg(mem) + return true + } + return false +} func rewriteValueAMD64_OpHasCPUFeature(v *Value) bool { b := v.Block typ := &b.Func.Config.Types @@ -33952,6 +32983,78 @@ func rewriteValueAMD64_OpSelect1(v *Value) bool { } return false } +func rewriteValueAMD64_OpSelectN(v *Value) bool { + v_0 := v.Args[0] + b := v.Block + config := b.Func.Config + // match: (SelectN [0] call:(CALLstatic {sym} s1:(MOVQstoreconst _ [sc] s2:(MOVQstore _ src s3:(MOVQstore _ dst mem))))) + // cond: sc.Val64() >= 0 && isSameCall(sym, "runtime.memmove") && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, sc.Val64(), config) && clobber(s1, s2, s3, call) + // result: (Move [sc.Val64()] dst src mem) + for { + if auxIntToInt64(v.AuxInt) != 0 { + break + } + call := v_0 + if call.Op != OpAMD64CALLstatic || len(call.Args) != 1 { + break + } + sym := auxToCall(call.Aux) + s1 := call.Args[0] + if s1.Op != OpAMD64MOVQstoreconst { + break + } + sc := auxIntToValAndOff(s1.AuxInt) + _ = s1.Args[1] + s2 := s1.Args[1] + if s2.Op != OpAMD64MOVQstore { + break + } + _ = s2.Args[2] + src := s2.Args[1] + s3 := s2.Args[2] + if s3.Op != OpAMD64MOVQstore { + break + } + mem := s3.Args[2] + dst := s3.Args[1] + if !(sc.Val64() >= 0 && isSameCall(sym, "runtime.memmove") && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, sc.Val64(), config) && clobber(s1, s2, s3, call)) { + break + } + v.reset(OpMove) + v.AuxInt = int64ToAuxInt(sc.Val64()) + v.AddArg3(dst, src, mem) + return true + } + // match: (SelectN [0] call:(CALLstatic {sym} dst src (MOVQconst [sz]) mem)) + // cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && call.Uses == 1 && isInlinableMemmove(dst, src, sz, config) && clobber(call) + // result: (Move [sz] dst src mem) + for { + if auxIntToInt64(v.AuxInt) != 0 { + break + } + call := v_0 + if call.Op != OpAMD64CALLstatic || len(call.Args) != 4 { + break + } + sym := auxToCall(call.Aux) + mem := call.Args[3] + dst := call.Args[0] + src := call.Args[1] + call_2 := call.Args[2] + if call_2.Op != OpAMD64MOVQconst { + break + } + sz := auxIntToInt64(call_2.AuxInt) + if !(sz >= 0 && isSameCall(sym, "runtime.memmove") && call.Uses == 1 && isInlinableMemmove(dst, src, sz, config) && clobber(call)) { + break + } + v.reset(OpMove) + v.AuxInt = int64ToAuxInt(sz) + v.AddArg3(dst, src, mem) + return true + } + return false +} func rewriteValueAMD64_OpSlicemask(v *Value) bool { v_0 := v.Args[0] b := v.Block @@ -34131,7 +33234,7 @@ func rewriteValueAMD64_OpZero(v *Value) bool { return true } // match: (Zero [1] destptr mem) - // result: (MOVBstoreconst [makeValAndOff32(0,0)] destptr mem) + // result: (MOVBstoreconst [makeValAndOff(0,0)] destptr mem) for { if auxIntToInt64(v.AuxInt) != 1 { break @@ -34139,12 +33242,12 @@ func rewriteValueAMD64_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpAMD64MOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v.AddArg2(destptr, mem) return true } // match: (Zero [2] destptr mem) - // result: (MOVWstoreconst [makeValAndOff32(0,0)] destptr mem) + // result: (MOVWstoreconst [makeValAndOff(0,0)] destptr mem) for { if auxIntToInt64(v.AuxInt) != 2 { break @@ -34152,12 +33255,12 @@ func rewriteValueAMD64_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpAMD64MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v.AddArg2(destptr, mem) return true } // match: (Zero [4] destptr mem) - // result: (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem) + // result: (MOVLstoreconst [makeValAndOff(0,0)] destptr mem) for { if auxIntToInt64(v.AuxInt) != 4 { break @@ -34165,12 +33268,12 @@ func rewriteValueAMD64_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpAMD64MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v.AddArg2(destptr, mem) return true } // match: (Zero [8] destptr mem) - // result: (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem) + // result: (MOVQstoreconst [makeValAndOff(0,0)] destptr mem) for { if auxIntToInt64(v.AuxInt) != 8 { break @@ -34178,12 +33281,12 @@ func rewriteValueAMD64_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpAMD64MOVQstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v.AddArg2(destptr, mem) return true } // match: (Zero [3] destptr mem) - // result: (MOVBstoreconst [makeValAndOff32(0,2)] destptr (MOVWstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVBstoreconst [makeValAndOff(0,2)] destptr (MOVWstoreconst [makeValAndOff(0,0)] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 3 { break @@ -34191,15 +33294,15 @@ func rewriteValueAMD64_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpAMD64MOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 2)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 2)) v0 := b.NewValue0(v.Pos, OpAMD64MOVWstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true } // match: (Zero [5] destptr mem) - // result: (MOVBstoreconst [makeValAndOff32(0,4)] destptr (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVBstoreconst [makeValAndOff(0,4)] destptr (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 5 { break @@ -34207,15 +33310,15 @@ func rewriteValueAMD64_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpAMD64MOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 4)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 4)) v0 := b.NewValue0(v.Pos, OpAMD64MOVLstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true } // match: (Zero [6] destptr mem) - // result: (MOVWstoreconst [makeValAndOff32(0,4)] destptr (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVWstoreconst [makeValAndOff(0,4)] destptr (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 6 { break @@ -34223,15 +33326,15 @@ func rewriteValueAMD64_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpAMD64MOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 4)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 4)) v0 := b.NewValue0(v.Pos, OpAMD64MOVLstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true } // match: (Zero [7] destptr mem) - // result: (MOVLstoreconst [makeValAndOff32(0,3)] destptr (MOVLstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVLstoreconst [makeValAndOff(0,3)] destptr (MOVLstoreconst [makeValAndOff(0,0)] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 7 { break @@ -34239,16 +33342,16 @@ func rewriteValueAMD64_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpAMD64MOVLstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 3)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 3)) v0 := b.NewValue0(v.Pos, OpAMD64MOVLstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true } // match: (Zero [s] destptr mem) // cond: s%8 != 0 && s > 8 && !config.useSSE - // result: (Zero [s-s%8] (OffPtr destptr [s%8]) (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (Zero [s-s%8] (OffPtr destptr [s%8]) (MOVQstoreconst [makeValAndOff(0,0)] destptr mem)) for { s := auxIntToInt64(v.AuxInt) destptr := v_0 @@ -34262,14 +33365,14 @@ func rewriteValueAMD64_OpZero(v *Value) bool { v0.AuxInt = int64ToAuxInt(s % 8) v0.AddArg(destptr) v1 := b.NewValue0(v.Pos, OpAMD64MOVQstoreconst, types.TypeMem) - v1.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v1.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v1.AddArg2(destptr, mem) v.AddArg2(v0, v1) return true } // match: (Zero [16] destptr mem) // cond: !config.useSSE - // result: (MOVQstoreconst [makeValAndOff32(0,8)] destptr (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVQstoreconst [makeValAndOff(0,8)] destptr (MOVQstoreconst [makeValAndOff(0,0)] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 16 { break @@ -34280,16 +33383,16 @@ func rewriteValueAMD64_OpZero(v *Value) bool { break } v.reset(OpAMD64MOVQstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 8)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 8)) v0 := b.NewValue0(v.Pos, OpAMD64MOVQstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true } // match: (Zero [24] destptr mem) // cond: !config.useSSE - // result: (MOVQstoreconst [makeValAndOff32(0,16)] destptr (MOVQstoreconst [makeValAndOff32(0,8)] destptr (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem))) + // result: (MOVQstoreconst [makeValAndOff(0,16)] destptr (MOVQstoreconst [makeValAndOff(0,8)] destptr (MOVQstoreconst [makeValAndOff(0,0)] destptr mem))) for { if auxIntToInt64(v.AuxInt) != 24 { break @@ -34300,11 +33403,11 @@ func rewriteValueAMD64_OpZero(v *Value) bool { break } v.reset(OpAMD64MOVQstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 16)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 16)) v0 := b.NewValue0(v.Pos, OpAMD64MOVQstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 8)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 8)) v1 := b.NewValue0(v.Pos, OpAMD64MOVQstoreconst, types.TypeMem) - v1.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v1.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v1.AddArg2(destptr, mem) v0.AddArg2(destptr, v1) v.AddArg2(destptr, v0) @@ -34312,7 +33415,7 @@ func rewriteValueAMD64_OpZero(v *Value) bool { } // match: (Zero [32] destptr mem) // cond: !config.useSSE - // result: (MOVQstoreconst [makeValAndOff32(0,24)] destptr (MOVQstoreconst [makeValAndOff32(0,16)] destptr (MOVQstoreconst [makeValAndOff32(0,8)] destptr (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem)))) + // result: (MOVQstoreconst [makeValAndOff(0,24)] destptr (MOVQstoreconst [makeValAndOff(0,16)] destptr (MOVQstoreconst [makeValAndOff(0,8)] destptr (MOVQstoreconst [makeValAndOff(0,0)] destptr mem)))) for { if auxIntToInt64(v.AuxInt) != 32 { break @@ -34323,13 +33426,13 @@ func rewriteValueAMD64_OpZero(v *Value) bool { break } v.reset(OpAMD64MOVQstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 24)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 24)) v0 := b.NewValue0(v.Pos, OpAMD64MOVQstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 16)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 16)) v1 := b.NewValue0(v.Pos, OpAMD64MOVQstoreconst, types.TypeMem) - v1.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 8)) + v1.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 8)) v2 := b.NewValue0(v.Pos, OpAMD64MOVQstoreconst, types.TypeMem) - v2.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v2.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v2.AddArg2(destptr, mem) v1.AddArg2(destptr, v2) v0.AddArg2(destptr, v1) @@ -34338,7 +33441,7 @@ func rewriteValueAMD64_OpZero(v *Value) bool { } // match: (Zero [s] destptr mem) // cond: s > 8 && s < 16 && config.useSSE - // result: (MOVQstoreconst [makeValAndOff32(0,int32(s-8))] destptr (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (MOVQstoreconst [makeValAndOff(0,int32(s-8))] destptr (MOVQstoreconst [makeValAndOff(0,0)] destptr mem)) for { s := auxIntToInt64(v.AuxInt) destptr := v_0 @@ -34347,16 +33450,16 @@ func rewriteValueAMD64_OpZero(v *Value) bool { break } v.reset(OpAMD64MOVQstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, int32(s-8))) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, int32(s-8))) v0 := b.NewValue0(v.Pos, OpAMD64MOVQstoreconst, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v0.AddArg2(destptr, mem) v.AddArg2(destptr, v0) return true } // match: (Zero [s] destptr mem) // cond: s%16 != 0 && s > 16 && s%16 > 8 && config.useSSE - // result: (Zero [s-s%16] (OffPtr destptr [s%16]) (MOVOstore destptr (MOVOconst [0]) mem)) + // result: (Zero [s-s%16] (OffPtr destptr [s%16]) (MOVOstorezero destptr mem)) for { s := auxIntToInt64(v.AuxInt) destptr := v_0 @@ -34369,16 +33472,14 @@ func rewriteValueAMD64_OpZero(v *Value) bool { v0 := b.NewValue0(v.Pos, OpOffPtr, destptr.Type) v0.AuxInt = int64ToAuxInt(s % 16) v0.AddArg(destptr) - v1 := b.NewValue0(v.Pos, OpAMD64MOVOstore, types.TypeMem) - v2 := b.NewValue0(v.Pos, OpAMD64MOVOconst, types.TypeInt128) - v2.AuxInt = int128ToAuxInt(0) - v1.AddArg3(destptr, v2, mem) + v1 := b.NewValue0(v.Pos, OpAMD64MOVOstorezero, types.TypeMem) + v1.AddArg2(destptr, mem) v.AddArg2(v0, v1) return true } // match: (Zero [s] destptr mem) // cond: s%16 != 0 && s > 16 && s%16 <= 8 && config.useSSE - // result: (Zero [s-s%16] (OffPtr destptr [s%16]) (MOVQstoreconst [makeValAndOff32(0,0)] destptr mem)) + // result: (Zero [s-s%16] (OffPtr destptr [s%16]) (MOVQstoreconst [makeValAndOff(0,0)] destptr mem)) for { s := auxIntToInt64(v.AuxInt) destptr := v_0 @@ -34392,14 +33493,14 @@ func rewriteValueAMD64_OpZero(v *Value) bool { v0.AuxInt = int64ToAuxInt(s % 16) v0.AddArg(destptr) v1 := b.NewValue0(v.Pos, OpAMD64MOVQstoreconst, types.TypeMem) - v1.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 0)) + v1.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 0)) v1.AddArg2(destptr, mem) v.AddArg2(v0, v1) return true } // match: (Zero [16] destptr mem) // cond: config.useSSE - // result: (MOVOstore destptr (MOVOconst [0]) mem) + // result: (MOVOstorezero destptr mem) for { if auxIntToInt64(v.AuxInt) != 16 { break @@ -34409,15 +33510,13 @@ func rewriteValueAMD64_OpZero(v *Value) bool { if !(config.useSSE) { break } - v.reset(OpAMD64MOVOstore) - v0 := b.NewValue0(v.Pos, OpAMD64MOVOconst, types.TypeInt128) - v0.AuxInt = int128ToAuxInt(0) - v.AddArg3(destptr, v0, mem) + v.reset(OpAMD64MOVOstorezero) + v.AddArg2(destptr, mem) return true } // match: (Zero [32] destptr mem) // cond: config.useSSE - // result: (MOVOstore (OffPtr destptr [16]) (MOVOconst [0]) (MOVOstore destptr (MOVOconst [0]) mem)) + // result: (MOVOstorezero (OffPtr destptr [16]) (MOVOstorezero destptr mem)) for { if auxIntToInt64(v.AuxInt) != 32 { break @@ -34427,20 +33526,18 @@ func rewriteValueAMD64_OpZero(v *Value) bool { if !(config.useSSE) { break } - v.reset(OpAMD64MOVOstore) + v.reset(OpAMD64MOVOstorezero) v0 := b.NewValue0(v.Pos, OpOffPtr, destptr.Type) v0.AuxInt = int64ToAuxInt(16) v0.AddArg(destptr) - v1 := b.NewValue0(v.Pos, OpAMD64MOVOconst, types.TypeInt128) - v1.AuxInt = int128ToAuxInt(0) - v2 := b.NewValue0(v.Pos, OpAMD64MOVOstore, types.TypeMem) - v2.AddArg3(destptr, v1, mem) - v.AddArg3(v0, v1, v2) + v1 := b.NewValue0(v.Pos, OpAMD64MOVOstorezero, types.TypeMem) + v1.AddArg2(destptr, mem) + v.AddArg2(v0, v1) return true } // match: (Zero [48] destptr mem) // cond: config.useSSE - // result: (MOVOstore (OffPtr destptr [32]) (MOVOconst [0]) (MOVOstore (OffPtr destptr [16]) (MOVOconst [0]) (MOVOstore destptr (MOVOconst [0]) mem))) + // result: (MOVOstorezero (OffPtr destptr [32]) (MOVOstorezero (OffPtr destptr [16]) (MOVOstorezero destptr mem))) for { if auxIntToInt64(v.AuxInt) != 48 { break @@ -34450,25 +33547,23 @@ func rewriteValueAMD64_OpZero(v *Value) bool { if !(config.useSSE) { break } - v.reset(OpAMD64MOVOstore) + v.reset(OpAMD64MOVOstorezero) v0 := b.NewValue0(v.Pos, OpOffPtr, destptr.Type) v0.AuxInt = int64ToAuxInt(32) v0.AddArg(destptr) - v1 := b.NewValue0(v.Pos, OpAMD64MOVOconst, types.TypeInt128) - v1.AuxInt = int128ToAuxInt(0) - v2 := b.NewValue0(v.Pos, OpAMD64MOVOstore, types.TypeMem) - v3 := b.NewValue0(v.Pos, OpOffPtr, destptr.Type) - v3.AuxInt = int64ToAuxInt(16) - v3.AddArg(destptr) - v4 := b.NewValue0(v.Pos, OpAMD64MOVOstore, types.TypeMem) - v4.AddArg3(destptr, v1, mem) - v2.AddArg3(v3, v1, v4) - v.AddArg3(v0, v1, v2) + v1 := b.NewValue0(v.Pos, OpAMD64MOVOstorezero, types.TypeMem) + v2 := b.NewValue0(v.Pos, OpOffPtr, destptr.Type) + v2.AuxInt = int64ToAuxInt(16) + v2.AddArg(destptr) + v3 := b.NewValue0(v.Pos, OpAMD64MOVOstorezero, types.TypeMem) + v3.AddArg2(destptr, mem) + v1.AddArg2(v2, v3) + v.AddArg2(v0, v1) return true } // match: (Zero [64] destptr mem) // cond: config.useSSE - // result: (MOVOstore (OffPtr destptr [48]) (MOVOconst [0]) (MOVOstore (OffPtr destptr [32]) (MOVOconst [0]) (MOVOstore (OffPtr destptr [16]) (MOVOconst [0]) (MOVOstore destptr (MOVOconst [0]) mem)))) + // result: (MOVOstorezero (OffPtr destptr [48]) (MOVOstorezero (OffPtr destptr [32]) (MOVOstorezero (OffPtr destptr [16]) (MOVOstorezero destptr mem)))) for { if auxIntToInt64(v.AuxInt) != 64 { break @@ -34478,30 +33573,28 @@ func rewriteValueAMD64_OpZero(v *Value) bool { if !(config.useSSE) { break } - v.reset(OpAMD64MOVOstore) + v.reset(OpAMD64MOVOstorezero) v0 := b.NewValue0(v.Pos, OpOffPtr, destptr.Type) v0.AuxInt = int64ToAuxInt(48) v0.AddArg(destptr) - v1 := b.NewValue0(v.Pos, OpAMD64MOVOconst, types.TypeInt128) - v1.AuxInt = int128ToAuxInt(0) - v2 := b.NewValue0(v.Pos, OpAMD64MOVOstore, types.TypeMem) - v3 := b.NewValue0(v.Pos, OpOffPtr, destptr.Type) - v3.AuxInt = int64ToAuxInt(32) - v3.AddArg(destptr) - v4 := b.NewValue0(v.Pos, OpAMD64MOVOstore, types.TypeMem) - v5 := b.NewValue0(v.Pos, OpOffPtr, destptr.Type) - v5.AuxInt = int64ToAuxInt(16) - v5.AddArg(destptr) - v6 := b.NewValue0(v.Pos, OpAMD64MOVOstore, types.TypeMem) - v6.AddArg3(destptr, v1, mem) - v4.AddArg3(v5, v1, v6) - v2.AddArg3(v3, v1, v4) - v.AddArg3(v0, v1, v2) + v1 := b.NewValue0(v.Pos, OpAMD64MOVOstorezero, types.TypeMem) + v2 := b.NewValue0(v.Pos, OpOffPtr, destptr.Type) + v2.AuxInt = int64ToAuxInt(32) + v2.AddArg(destptr) + v3 := b.NewValue0(v.Pos, OpAMD64MOVOstorezero, types.TypeMem) + v4 := b.NewValue0(v.Pos, OpOffPtr, destptr.Type) + v4.AuxInt = int64ToAuxInt(16) + v4.AddArg(destptr) + v5 := b.NewValue0(v.Pos, OpAMD64MOVOstorezero, types.TypeMem) + v5.AddArg2(destptr, mem) + v3.AddArg2(v4, v5) + v1.AddArg2(v2, v3) + v.AddArg2(v0, v1) return true } // match: (Zero [s] destptr mem) // cond: s > 64 && s <= 1024 && s%16 == 0 && !config.noDuffDevice - // result: (DUFFZERO [s] destptr (MOVOconst [0]) mem) + // result: (DUFFZERO [s] destptr mem) for { s := auxIntToInt64(v.AuxInt) destptr := v_0 @@ -34511,9 +33604,7 @@ func rewriteValueAMD64_OpZero(v *Value) bool { } v.reset(OpAMD64DUFFZERO) v.AuxInt = int64ToAuxInt(s) - v0 := b.NewValue0(v.Pos, OpAMD64MOVOconst, types.TypeInt128) - v0.AuxInt = int128ToAuxInt(0) - v.AddArg3(destptr, v0, mem) + v.AddArg2(destptr, mem) return true } // match: (Zero [s] destptr mem) diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64splitload.go b/src/cmd/compile/internal/ssa/rewriteAMD64splitload.go index 65bfec0f684fa24b9509b331d40fee675bfee2a7..ae50aaa466f214c74096cae7e194e266ee4290c5 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64splitload.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64splitload.go @@ -59,7 +59,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPBconstload(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPBconstload {sym} [vo] ptr mem) // cond: vo.Val() == 0 - // result: (TESTB x:(MOVBload {sym} [vo.Off32()] ptr mem) x) + // result: (TESTB x:(MOVBload {sym} [vo.Off()] ptr mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -70,7 +70,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPBconstload(v *Value) bool { } v.reset(OpAMD64TESTB) x := b.NewValue0(v.Pos, OpAMD64MOVBload, typ.UInt8) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg2(ptr, mem) v.AddArg2(x, x) @@ -78,7 +78,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPBconstload(v *Value) bool { } // match: (CMPBconstload {sym} [vo] ptr mem) // cond: vo.Val() != 0 - // result: (CMPBconst (MOVBload {sym} [vo.Off32()] ptr mem) [vo.Val8()]) + // result: (CMPBconst (MOVBload {sym} [vo.Off()] ptr mem) [vo.Val8()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -90,7 +90,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPBconstload(v *Value) bool { v.reset(OpAMD64CMPBconst) v.AuxInt = int8ToAuxInt(vo.Val8()) v0 := b.NewValue0(v.Pos, OpAMD64MOVBload, typ.UInt8) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) v.AddArg(v0) @@ -106,7 +106,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPBconstloadidx1(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPBconstloadidx1 {sym} [vo] ptr idx mem) // cond: vo.Val() == 0 - // result: (TESTB x:(MOVBloadidx1 {sym} [vo.Off32()] ptr idx mem) x) + // result: (TESTB x:(MOVBloadidx1 {sym} [vo.Off()] ptr idx mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -118,7 +118,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPBconstloadidx1(v *Value) bool { } v.reset(OpAMD64TESTB) x := b.NewValue0(v.Pos, OpAMD64MOVBloadidx1, typ.UInt8) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg3(ptr, idx, mem) v.AddArg2(x, x) @@ -126,7 +126,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPBconstloadidx1(v *Value) bool { } // match: (CMPBconstloadidx1 {sym} [vo] ptr idx mem) // cond: vo.Val() != 0 - // result: (CMPBconst (MOVBloadidx1 {sym} [vo.Off32()] ptr idx mem) [vo.Val8()]) + // result: (CMPBconst (MOVBloadidx1 {sym} [vo.Off()] ptr idx mem) [vo.Val8()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -139,7 +139,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPBconstloadidx1(v *Value) bool { v.reset(OpAMD64CMPBconst) v.AuxInt = int8ToAuxInt(vo.Val8()) v0 := b.NewValue0(v.Pos, OpAMD64MOVBloadidx1, typ.UInt8) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg3(ptr, idx, mem) v.AddArg(v0) @@ -202,7 +202,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstload(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPLconstload {sym} [vo] ptr mem) // cond: vo.Val() == 0 - // result: (TESTL x:(MOVLload {sym} [vo.Off32()] ptr mem) x) + // result: (TESTL x:(MOVLload {sym} [vo.Off()] ptr mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -213,7 +213,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstload(v *Value) bool { } v.reset(OpAMD64TESTL) x := b.NewValue0(v.Pos, OpAMD64MOVLload, typ.UInt32) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg2(ptr, mem) v.AddArg2(x, x) @@ -221,7 +221,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstload(v *Value) bool { } // match: (CMPLconstload {sym} [vo] ptr mem) // cond: vo.Val() != 0 - // result: (CMPLconst (MOVLload {sym} [vo.Off32()] ptr mem) [vo.Val32()]) + // result: (CMPLconst (MOVLload {sym} [vo.Off()] ptr mem) [vo.Val()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -231,9 +231,9 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstload(v *Value) bool { break } v.reset(OpAMD64CMPLconst) - v.AuxInt = int32ToAuxInt(vo.Val32()) + v.AuxInt = int32ToAuxInt(vo.Val()) v0 := b.NewValue0(v.Pos, OpAMD64MOVLload, typ.UInt32) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) v.AddArg(v0) @@ -249,7 +249,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstloadidx1(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPLconstloadidx1 {sym} [vo] ptr idx mem) // cond: vo.Val() == 0 - // result: (TESTL x:(MOVLloadidx1 {sym} [vo.Off32()] ptr idx mem) x) + // result: (TESTL x:(MOVLloadidx1 {sym} [vo.Off()] ptr idx mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -261,7 +261,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstloadidx1(v *Value) bool { } v.reset(OpAMD64TESTL) x := b.NewValue0(v.Pos, OpAMD64MOVLloadidx1, typ.UInt32) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg3(ptr, idx, mem) v.AddArg2(x, x) @@ -269,7 +269,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstloadidx1(v *Value) bool { } // match: (CMPLconstloadidx1 {sym} [vo] ptr idx mem) // cond: vo.Val() != 0 - // result: (CMPLconst (MOVLloadidx1 {sym} [vo.Off32()] ptr idx mem) [vo.Val32()]) + // result: (CMPLconst (MOVLloadidx1 {sym} [vo.Off()] ptr idx mem) [vo.Val()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -280,9 +280,9 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstloadidx1(v *Value) bool { break } v.reset(OpAMD64CMPLconst) - v.AuxInt = int32ToAuxInt(vo.Val32()) + v.AuxInt = int32ToAuxInt(vo.Val()) v0 := b.NewValue0(v.Pos, OpAMD64MOVLloadidx1, typ.UInt32) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg3(ptr, idx, mem) v.AddArg(v0) @@ -298,7 +298,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstloadidx4(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPLconstloadidx4 {sym} [vo] ptr idx mem) // cond: vo.Val() == 0 - // result: (TESTL x:(MOVLloadidx4 {sym} [vo.Off32()] ptr idx mem) x) + // result: (TESTL x:(MOVLloadidx4 {sym} [vo.Off()] ptr idx mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -310,7 +310,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstloadidx4(v *Value) bool { } v.reset(OpAMD64TESTL) x := b.NewValue0(v.Pos, OpAMD64MOVLloadidx4, typ.UInt32) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg3(ptr, idx, mem) v.AddArg2(x, x) @@ -318,7 +318,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstloadidx4(v *Value) bool { } // match: (CMPLconstloadidx4 {sym} [vo] ptr idx mem) // cond: vo.Val() != 0 - // result: (CMPLconst (MOVLloadidx4 {sym} [vo.Off32()] ptr idx mem) [vo.Val32()]) + // result: (CMPLconst (MOVLloadidx4 {sym} [vo.Off()] ptr idx mem) [vo.Val()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -329,9 +329,9 @@ func rewriteValueAMD64splitload_OpAMD64CMPLconstloadidx4(v *Value) bool { break } v.reset(OpAMD64CMPLconst) - v.AuxInt = int32ToAuxInt(vo.Val32()) + v.AuxInt = int32ToAuxInt(vo.Val()) v0 := b.NewValue0(v.Pos, OpAMD64MOVLloadidx4, typ.UInt32) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg3(ptr, idx, mem) v.AddArg(v0) @@ -419,7 +419,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstload(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPQconstload {sym} [vo] ptr mem) // cond: vo.Val() == 0 - // result: (TESTQ x:(MOVQload {sym} [vo.Off32()] ptr mem) x) + // result: (TESTQ x:(MOVQload {sym} [vo.Off()] ptr mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -430,7 +430,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstload(v *Value) bool { } v.reset(OpAMD64TESTQ) x := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg2(ptr, mem) v.AddArg2(x, x) @@ -438,7 +438,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstload(v *Value) bool { } // match: (CMPQconstload {sym} [vo] ptr mem) // cond: vo.Val() != 0 - // result: (CMPQconst (MOVQload {sym} [vo.Off32()] ptr mem) [vo.Val32()]) + // result: (CMPQconst (MOVQload {sym} [vo.Off()] ptr mem) [vo.Val()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -448,9 +448,9 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstload(v *Value) bool { break } v.reset(OpAMD64CMPQconst) - v.AuxInt = int32ToAuxInt(vo.Val32()) + v.AuxInt = int32ToAuxInt(vo.Val()) v0 := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) v.AddArg(v0) @@ -466,7 +466,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstloadidx1(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPQconstloadidx1 {sym} [vo] ptr idx mem) // cond: vo.Val() == 0 - // result: (TESTQ x:(MOVQloadidx1 {sym} [vo.Off32()] ptr idx mem) x) + // result: (TESTQ x:(MOVQloadidx1 {sym} [vo.Off()] ptr idx mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -478,7 +478,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstloadidx1(v *Value) bool { } v.reset(OpAMD64TESTQ) x := b.NewValue0(v.Pos, OpAMD64MOVQloadidx1, typ.UInt64) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg3(ptr, idx, mem) v.AddArg2(x, x) @@ -486,7 +486,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstloadidx1(v *Value) bool { } // match: (CMPQconstloadidx1 {sym} [vo] ptr idx mem) // cond: vo.Val() != 0 - // result: (CMPQconst (MOVQloadidx1 {sym} [vo.Off32()] ptr idx mem) [vo.Val32()]) + // result: (CMPQconst (MOVQloadidx1 {sym} [vo.Off()] ptr idx mem) [vo.Val()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -497,9 +497,9 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstloadidx1(v *Value) bool { break } v.reset(OpAMD64CMPQconst) - v.AuxInt = int32ToAuxInt(vo.Val32()) + v.AuxInt = int32ToAuxInt(vo.Val()) v0 := b.NewValue0(v.Pos, OpAMD64MOVQloadidx1, typ.UInt64) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg3(ptr, idx, mem) v.AddArg(v0) @@ -515,7 +515,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstloadidx8(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPQconstloadidx8 {sym} [vo] ptr idx mem) // cond: vo.Val() == 0 - // result: (TESTQ x:(MOVQloadidx8 {sym} [vo.Off32()] ptr idx mem) x) + // result: (TESTQ x:(MOVQloadidx8 {sym} [vo.Off()] ptr idx mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -527,7 +527,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstloadidx8(v *Value) bool { } v.reset(OpAMD64TESTQ) x := b.NewValue0(v.Pos, OpAMD64MOVQloadidx8, typ.UInt64) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg3(ptr, idx, mem) v.AddArg2(x, x) @@ -535,7 +535,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstloadidx8(v *Value) bool { } // match: (CMPQconstloadidx8 {sym} [vo] ptr idx mem) // cond: vo.Val() != 0 - // result: (CMPQconst (MOVQloadidx8 {sym} [vo.Off32()] ptr idx mem) [vo.Val32()]) + // result: (CMPQconst (MOVQloadidx8 {sym} [vo.Off()] ptr idx mem) [vo.Val()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -546,9 +546,9 @@ func rewriteValueAMD64splitload_OpAMD64CMPQconstloadidx8(v *Value) bool { break } v.reset(OpAMD64CMPQconst) - v.AuxInt = int32ToAuxInt(vo.Val32()) + v.AuxInt = int32ToAuxInt(vo.Val()) v0 := b.NewValue0(v.Pos, OpAMD64MOVQloadidx8, typ.UInt64) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg3(ptr, idx, mem) v.AddArg(v0) @@ -636,7 +636,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstload(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPWconstload {sym} [vo] ptr mem) // cond: vo.Val() == 0 - // result: (TESTW x:(MOVWload {sym} [vo.Off32()] ptr mem) x) + // result: (TESTW x:(MOVWload {sym} [vo.Off()] ptr mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -647,7 +647,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstload(v *Value) bool { } v.reset(OpAMD64TESTW) x := b.NewValue0(v.Pos, OpAMD64MOVWload, typ.UInt16) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg2(ptr, mem) v.AddArg2(x, x) @@ -655,7 +655,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstload(v *Value) bool { } // match: (CMPWconstload {sym} [vo] ptr mem) // cond: vo.Val() != 0 - // result: (CMPWconst (MOVWload {sym} [vo.Off32()] ptr mem) [vo.Val16()]) + // result: (CMPWconst (MOVWload {sym} [vo.Off()] ptr mem) [vo.Val16()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -667,7 +667,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstload(v *Value) bool { v.reset(OpAMD64CMPWconst) v.AuxInt = int16ToAuxInt(vo.Val16()) v0 := b.NewValue0(v.Pos, OpAMD64MOVWload, typ.UInt16) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg2(ptr, mem) v.AddArg(v0) @@ -683,7 +683,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstloadidx1(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPWconstloadidx1 {sym} [vo] ptr idx mem) // cond: vo.Val() == 0 - // result: (TESTW x:(MOVWloadidx1 {sym} [vo.Off32()] ptr idx mem) x) + // result: (TESTW x:(MOVWloadidx1 {sym} [vo.Off()] ptr idx mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -695,7 +695,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstloadidx1(v *Value) bool { } v.reset(OpAMD64TESTW) x := b.NewValue0(v.Pos, OpAMD64MOVWloadidx1, typ.UInt16) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg3(ptr, idx, mem) v.AddArg2(x, x) @@ -703,7 +703,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstloadidx1(v *Value) bool { } // match: (CMPWconstloadidx1 {sym} [vo] ptr idx mem) // cond: vo.Val() != 0 - // result: (CMPWconst (MOVWloadidx1 {sym} [vo.Off32()] ptr idx mem) [vo.Val16()]) + // result: (CMPWconst (MOVWloadidx1 {sym} [vo.Off()] ptr idx mem) [vo.Val16()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -716,7 +716,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstloadidx1(v *Value) bool { v.reset(OpAMD64CMPWconst) v.AuxInt = int16ToAuxInt(vo.Val16()) v0 := b.NewValue0(v.Pos, OpAMD64MOVWloadidx1, typ.UInt16) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg3(ptr, idx, mem) v.AddArg(v0) @@ -732,7 +732,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstloadidx2(v *Value) bool { typ := &b.Func.Config.Types // match: (CMPWconstloadidx2 {sym} [vo] ptr idx mem) // cond: vo.Val() == 0 - // result: (TESTW x:(MOVWloadidx2 {sym} [vo.Off32()] ptr idx mem) x) + // result: (TESTW x:(MOVWloadidx2 {sym} [vo.Off()] ptr idx mem) x) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -744,7 +744,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstloadidx2(v *Value) bool { } v.reset(OpAMD64TESTW) x := b.NewValue0(v.Pos, OpAMD64MOVWloadidx2, typ.UInt16) - x.AuxInt = int32ToAuxInt(vo.Off32()) + x.AuxInt = int32ToAuxInt(vo.Off()) x.Aux = symToAux(sym) x.AddArg3(ptr, idx, mem) v.AddArg2(x, x) @@ -752,7 +752,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstloadidx2(v *Value) bool { } // match: (CMPWconstloadidx2 {sym} [vo] ptr idx mem) // cond: vo.Val() != 0 - // result: (CMPWconst (MOVWloadidx2 {sym} [vo.Off32()] ptr idx mem) [vo.Val16()]) + // result: (CMPWconst (MOVWloadidx2 {sym} [vo.Off()] ptr idx mem) [vo.Val16()]) for { vo := auxIntToValAndOff(v.AuxInt) sym := auxToSym(v.Aux) @@ -765,7 +765,7 @@ func rewriteValueAMD64splitload_OpAMD64CMPWconstloadidx2(v *Value) bool { v.reset(OpAMD64CMPWconst) v.AuxInt = int16ToAuxInt(vo.Val16()) v0 := b.NewValue0(v.Pos, OpAMD64MOVWloadidx2, typ.UInt16) - v0.AuxInt = int32ToAuxInt(vo.Off32()) + v0.AuxInt = int32ToAuxInt(vo.Off()) v0.Aux = symToAux(sym) v0.AddArg3(ptr, idx, mem) v.AddArg(v0) @@ -847,7 +847,5 @@ func rewriteValueAMD64splitload_OpAMD64CMPWloadidx2(v *Value) bool { } } func rewriteBlockAMD64splitload(b *Block) bool { - switch b.Kind { - } return false } diff --git a/src/cmd/compile/internal/ssa/rewriteARM.go b/src/cmd/compile/internal/ssa/rewriteARM.go index d9d439fa63ee1d5c93930552d1c7aefeef7a83df..febb5566e338492cb6f42e30d013b454b579fa4a 100644 --- a/src/cmd/compile/internal/ssa/rewriteARM.go +++ b/src/cmd/compile/internal/ssa/rewriteARM.go @@ -3,7 +3,7 @@ package ssa -import "cmd/internal/objabi" +import "internal/buildcfg" import "cmd/compile/internal/types" func rewriteValueARM(v *Value) bool { @@ -202,6 +202,8 @@ func rewriteValueARM(v *Value) bool { return rewriteValueARM_OpARMMOVWloadshiftRA(v) case OpARMMOVWloadshiftRL: return rewriteValueARM_OpARMMOVWloadshiftRL(v) + case OpARMMOVWnop: + return rewriteValueARM_OpARMMOVWnop(v) case OpARMMOVWreg: return rewriteValueARM_OpARMMOVWreg(v) case OpARMMOVWstore: @@ -821,6 +823,9 @@ func rewriteValueARM(v *Value) bool { case OpSqrt: v.Op = OpARMSQRTD return true + case OpSqrt32: + v.Op = OpARMSQRTF + return true case OpStaticCall: v.Op = OpARMCALLstatic return true @@ -1470,7 +1475,7 @@ func rewriteValueARM_OpARMADDD(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (ADDD a (MULD x y)) - // cond: a.Uses == 1 && objabi.GOARM >= 6 + // cond: a.Uses == 1 && buildcfg.GOARM >= 6 // result: (MULAD a x y) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { @@ -1480,7 +1485,7 @@ func rewriteValueARM_OpARMADDD(v *Value) bool { } y := v_1.Args[1] x := v_1.Args[0] - if !(a.Uses == 1 && objabi.GOARM >= 6) { + if !(a.Uses == 1 && buildcfg.GOARM >= 6) { continue } v.reset(OpARMMULAD) @@ -1490,7 +1495,7 @@ func rewriteValueARM_OpARMADDD(v *Value) bool { break } // match: (ADDD a (NMULD x y)) - // cond: a.Uses == 1 && objabi.GOARM >= 6 + // cond: a.Uses == 1 && buildcfg.GOARM >= 6 // result: (MULSD a x y) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { @@ -1500,7 +1505,7 @@ func rewriteValueARM_OpARMADDD(v *Value) bool { } y := v_1.Args[1] x := v_1.Args[0] - if !(a.Uses == 1 && objabi.GOARM >= 6) { + if !(a.Uses == 1 && buildcfg.GOARM >= 6) { continue } v.reset(OpARMMULSD) @@ -1515,7 +1520,7 @@ func rewriteValueARM_OpARMADDF(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (ADDF a (MULF x y)) - // cond: a.Uses == 1 && objabi.GOARM >= 6 + // cond: a.Uses == 1 && buildcfg.GOARM >= 6 // result: (MULAF a x y) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { @@ -1525,7 +1530,7 @@ func rewriteValueARM_OpARMADDF(v *Value) bool { } y := v_1.Args[1] x := v_1.Args[0] - if !(a.Uses == 1 && objabi.GOARM >= 6) { + if !(a.Uses == 1 && buildcfg.GOARM >= 6) { continue } v.reset(OpARMMULAF) @@ -1535,7 +1540,7 @@ func rewriteValueARM_OpARMADDF(v *Value) bool { break } // match: (ADDF a (NMULF x y)) - // cond: a.Uses == 1 && objabi.GOARM >= 6 + // cond: a.Uses == 1 && buildcfg.GOARM >= 6 // result: (MULSF a x y) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { @@ -1545,7 +1550,7 @@ func rewriteValueARM_OpARMADDF(v *Value) bool { } y := v_1.Args[1] x := v_1.Args[0] - if !(a.Uses == 1 && objabi.GOARM >= 6) { + if !(a.Uses == 1 && buildcfg.GOARM >= 6) { continue } v.reset(OpARMMULSF) @@ -1941,12 +1946,12 @@ func rewriteValueARM_OpARMADDconst(v *Value) bool { return true } // match: (ADDconst [c] x) - // cond: objabi.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && uint32(-c)<=0xffff + // cond: buildcfg.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && uint32(-c)<=0xffff // result: (SUBconst [-c] x) for { c := auxIntToInt32(v.AuxInt) x := v_0 - if !(objabi.GOARM == 7 && !isARMImmRot(uint32(c)) && uint32(c) > 0xffff && uint32(-c) <= 0xffff) { + if !(buildcfg.GOARM == 7 && !isARMImmRot(uint32(c)) && uint32(c) > 0xffff && uint32(-c) <= 0xffff) { break } v.reset(OpARMSUBconst) @@ -2077,7 +2082,7 @@ func rewriteValueARM_OpARMADDshiftLL(v *Value) bool { return true } // match: (ADDshiftLL [8] (SRLconst [24] (SLLconst [16] x)) x) - // cond: objabi.GOARM>=6 + // cond: buildcfg.GOARM>=6 // result: (REV16 x) for { if v.Type != typ.UInt16 || auxIntToInt32(v.AuxInt) != 8 || v_0.Op != OpARMSRLconst || v_0.Type != typ.UInt16 || auxIntToInt32(v_0.AuxInt) != 24 { @@ -2088,7 +2093,7 @@ func rewriteValueARM_OpARMADDshiftLL(v *Value) bool { break } x := v_0_0.Args[0] - if x != v_1 || !(objabi.GOARM >= 6) { + if x != v_1 || !(buildcfg.GOARM >= 6) { break } v.reset(OpARMREV16) @@ -2533,12 +2538,12 @@ func rewriteValueARM_OpARMANDconst(v *Value) bool { return true } // match: (ANDconst [c] x) - // cond: objabi.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && ^uint32(c)<=0xffff + // cond: buildcfg.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && ^uint32(c)<=0xffff // result: (BICconst [int32(^uint32(c))] x) for { c := auxIntToInt32(v.AuxInt) x := v_0 - if !(objabi.GOARM == 7 && !isARMImmRot(uint32(c)) && uint32(c) > 0xffff && ^uint32(c) <= 0xffff) { + if !(buildcfg.GOARM == 7 && !isARMImmRot(uint32(c)) && uint32(c) > 0xffff && ^uint32(c) <= 0xffff) { break } v.reset(OpARMBICconst) @@ -3028,12 +3033,12 @@ func rewriteValueARM_OpARMBICconst(v *Value) bool { return true } // match: (BICconst [c] x) - // cond: objabi.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && ^uint32(c)<=0xffff + // cond: buildcfg.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && ^uint32(c)<=0xffff // result: (ANDconst [int32(^uint32(c))] x) for { c := auxIntToInt32(v.AuxInt) x := v_0 - if !(objabi.GOARM == 7 && !isARMImmRot(uint32(c)) && uint32(c) > 0xffff && ^uint32(c) <= 0xffff) { + if !(buildcfg.GOARM == 7 && !isARMImmRot(uint32(c)) && uint32(c) > 0xffff && ^uint32(c) <= 0xffff) { break } v.reset(OpARMANDconst) @@ -3728,12 +3733,12 @@ func rewriteValueARM_OpARMCMP(v *Value) bool { return true } // match: (CMP x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMP y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpARMInvertFlags) @@ -6501,6 +6506,21 @@ func rewriteValueARM_OpARMMOVWloadshiftRL(v *Value) bool { } return false } +func rewriteValueARM_OpARMMOVWnop(v *Value) bool { + v_0 := v.Args[0] + // match: (MOVWnop (MOVWconst [c])) + // result: (MOVWconst [c]) + for { + if v_0.Op != OpARMMOVWconst { + break + } + c := auxIntToInt32(v_0.AuxInt) + v.reset(OpARMMOVWconst) + v.AuxInt = int32ToAuxInt(c) + return true + } + return false +} func rewriteValueARM_OpARMMOVWreg(v *Value) bool { v_0 := v.Args[0] // match: (MOVWreg x) @@ -7521,7 +7541,7 @@ func rewriteValueARM_OpARMMULD(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (MULD (NEGD x) y) - // cond: objabi.GOARM >= 6 + // cond: buildcfg.GOARM >= 6 // result: (NMULD x y) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { @@ -7530,7 +7550,7 @@ func rewriteValueARM_OpARMMULD(v *Value) bool { } x := v_0.Args[0] y := v_1 - if !(objabi.GOARM >= 6) { + if !(buildcfg.GOARM >= 6) { continue } v.reset(OpARMNMULD) @@ -7545,7 +7565,7 @@ func rewriteValueARM_OpARMMULF(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (MULF (NEGF x) y) - // cond: objabi.GOARM >= 6 + // cond: buildcfg.GOARM >= 6 // result: (NMULF x y) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { @@ -7554,7 +7574,7 @@ func rewriteValueARM_OpARMMULF(v *Value) bool { } x := v_0.Args[0] y := v_1 - if !(objabi.GOARM >= 6) { + if !(buildcfg.GOARM >= 6) { continue } v.reset(OpARMNMULF) @@ -8166,7 +8186,7 @@ func rewriteValueARM_OpARMMVNshiftRLreg(v *Value) bool { func rewriteValueARM_OpARMNEGD(v *Value) bool { v_0 := v.Args[0] // match: (NEGD (MULD x y)) - // cond: objabi.GOARM >= 6 + // cond: buildcfg.GOARM >= 6 // result: (NMULD x y) for { if v_0.Op != OpARMMULD { @@ -8174,7 +8194,7 @@ func rewriteValueARM_OpARMNEGD(v *Value) bool { } y := v_0.Args[1] x := v_0.Args[0] - if !(objabi.GOARM >= 6) { + if !(buildcfg.GOARM >= 6) { break } v.reset(OpARMNMULD) @@ -8186,7 +8206,7 @@ func rewriteValueARM_OpARMNEGD(v *Value) bool { func rewriteValueARM_OpARMNEGF(v *Value) bool { v_0 := v.Args[0] // match: (NEGF (MULF x y)) - // cond: objabi.GOARM >= 6 + // cond: buildcfg.GOARM >= 6 // result: (NMULF x y) for { if v_0.Op != OpARMMULF { @@ -8194,7 +8214,7 @@ func rewriteValueARM_OpARMNEGF(v *Value) bool { } y := v_0.Args[1] x := v_0.Args[0] - if !(objabi.GOARM >= 6) { + if !(buildcfg.GOARM >= 6) { break } v.reset(OpARMNMULF) @@ -8518,7 +8538,7 @@ func rewriteValueARM_OpARMORshiftLL(v *Value) bool { return true } // match: (ORshiftLL [8] (SRLconst [24] (SLLconst [16] x)) x) - // cond: objabi.GOARM>=6 + // cond: buildcfg.GOARM>=6 // result: (REV16 x) for { if v.Type != typ.UInt16 || auxIntToInt32(v.AuxInt) != 8 || v_0.Op != OpARMSRLconst || v_0.Type != typ.UInt16 || auxIntToInt32(v_0.AuxInt) != 24 { @@ -8529,7 +8549,7 @@ func rewriteValueARM_OpARMORshiftLL(v *Value) bool { break } x := v_0_0.Args[0] - if x != v_1 || !(objabi.GOARM >= 6) { + if x != v_1 || !(buildcfg.GOARM >= 6) { break } v.reset(OpARMREV16) @@ -8993,7 +9013,7 @@ func rewriteValueARM_OpARMRSB(v *Value) bool { return true } // match: (RSB (MUL x y) a) - // cond: objabi.GOARM == 7 + // cond: buildcfg.GOARM == 7 // result: (MULS x y a) for { if v_0.Op != OpARMMUL { @@ -9002,7 +9022,7 @@ func rewriteValueARM_OpARMRSB(v *Value) bool { y := v_0.Args[1] x := v_0.Args[0] a := v_1 - if !(objabi.GOARM == 7) { + if !(buildcfg.GOARM == 7) { break } v.reset(OpARMMULS) @@ -10429,7 +10449,7 @@ func rewriteValueARM_OpARMSRAconst(v *Value) bool { return true } // match: (SRAconst (SLLconst x [c]) [d]) - // cond: objabi.GOARM==7 && uint64(d)>=uint64(c) && uint64(d)<=31 + // cond: buildcfg.GOARM==7 && uint64(d)>=uint64(c) && uint64(d)<=31 // result: (BFX [(d-c)|(32-d)<<8] x) for { d := auxIntToInt32(v.AuxInt) @@ -10438,7 +10458,7 @@ func rewriteValueARM_OpARMSRAconst(v *Value) bool { } c := auxIntToInt32(v_0.AuxInt) x := v_0.Args[0] - if !(objabi.GOARM == 7 && uint64(d) >= uint64(c) && uint64(d) <= 31) { + if !(buildcfg.GOARM == 7 && uint64(d) >= uint64(c) && uint64(d) <= 31) { break } v.reset(OpARMBFX) @@ -10481,7 +10501,7 @@ func rewriteValueARM_OpARMSRLconst(v *Value) bool { return true } // match: (SRLconst (SLLconst x [c]) [d]) - // cond: objabi.GOARM==7 && uint64(d)>=uint64(c) && uint64(d)<=31 + // cond: buildcfg.GOARM==7 && uint64(d)>=uint64(c) && uint64(d)<=31 // result: (BFXU [(d-c)|(32-d)<<8] x) for { d := auxIntToInt32(v.AuxInt) @@ -10490,7 +10510,7 @@ func rewriteValueARM_OpARMSRLconst(v *Value) bool { } c := auxIntToInt32(v_0.AuxInt) x := v_0.Args[0] - if !(objabi.GOARM == 7 && uint64(d) >= uint64(c) && uint64(d) <= 31) { + if !(buildcfg.GOARM == 7 && uint64(d) >= uint64(c) && uint64(d) <= 31) { break } v.reset(OpARMBFXU) @@ -10703,7 +10723,7 @@ func rewriteValueARM_OpARMSUB(v *Value) bool { return true } // match: (SUB a (MUL x y)) - // cond: objabi.GOARM == 7 + // cond: buildcfg.GOARM == 7 // result: (MULS x y a) for { a := v_0 @@ -10712,7 +10732,7 @@ func rewriteValueARM_OpARMSUB(v *Value) bool { } y := v_1.Args[1] x := v_1.Args[0] - if !(objabi.GOARM == 7) { + if !(buildcfg.GOARM == 7) { break } v.reset(OpARMMULS) @@ -10725,7 +10745,7 @@ func rewriteValueARM_OpARMSUBD(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (SUBD a (MULD x y)) - // cond: a.Uses == 1 && objabi.GOARM >= 6 + // cond: a.Uses == 1 && buildcfg.GOARM >= 6 // result: (MULSD a x y) for { a := v_0 @@ -10734,7 +10754,7 @@ func rewriteValueARM_OpARMSUBD(v *Value) bool { } y := v_1.Args[1] x := v_1.Args[0] - if !(a.Uses == 1 && objabi.GOARM >= 6) { + if !(a.Uses == 1 && buildcfg.GOARM >= 6) { break } v.reset(OpARMMULSD) @@ -10742,7 +10762,7 @@ func rewriteValueARM_OpARMSUBD(v *Value) bool { return true } // match: (SUBD a (NMULD x y)) - // cond: a.Uses == 1 && objabi.GOARM >= 6 + // cond: a.Uses == 1 && buildcfg.GOARM >= 6 // result: (MULAD a x y) for { a := v_0 @@ -10751,7 +10771,7 @@ func rewriteValueARM_OpARMSUBD(v *Value) bool { } y := v_1.Args[1] x := v_1.Args[0] - if !(a.Uses == 1 && objabi.GOARM >= 6) { + if !(a.Uses == 1 && buildcfg.GOARM >= 6) { break } v.reset(OpARMMULAD) @@ -10764,7 +10784,7 @@ func rewriteValueARM_OpARMSUBF(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (SUBF a (MULF x y)) - // cond: a.Uses == 1 && objabi.GOARM >= 6 + // cond: a.Uses == 1 && buildcfg.GOARM >= 6 // result: (MULSF a x y) for { a := v_0 @@ -10773,7 +10793,7 @@ func rewriteValueARM_OpARMSUBF(v *Value) bool { } y := v_1.Args[1] x := v_1.Args[0] - if !(a.Uses == 1 && objabi.GOARM >= 6) { + if !(a.Uses == 1 && buildcfg.GOARM >= 6) { break } v.reset(OpARMMULSF) @@ -10781,7 +10801,7 @@ func rewriteValueARM_OpARMSUBF(v *Value) bool { return true } // match: (SUBF a (NMULF x y)) - // cond: a.Uses == 1 && objabi.GOARM >= 6 + // cond: a.Uses == 1 && buildcfg.GOARM >= 6 // result: (MULAF a x y) for { a := v_0 @@ -10790,7 +10810,7 @@ func rewriteValueARM_OpARMSUBF(v *Value) bool { } y := v_1.Args[1] x := v_1.Args[0] - if !(a.Uses == 1 && objabi.GOARM >= 6) { + if !(a.Uses == 1 && buildcfg.GOARM >= 6) { break } v.reset(OpARMMULAF) @@ -11244,12 +11264,12 @@ func rewriteValueARM_OpARMSUBconst(v *Value) bool { return true } // match: (SUBconst [c] x) - // cond: objabi.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && uint32(-c)<=0xffff + // cond: buildcfg.GOARM==7 && !isARMImmRot(uint32(c)) && uint32(c)>0xffff && uint32(-c)<=0xffff // result: (ADDconst [-c] x) for { c := auxIntToInt32(v.AuxInt) x := v_0 - if !(objabi.GOARM == 7 && !isARMImmRot(uint32(c)) && uint32(c) > 0xffff && uint32(-c) <= 0xffff) { + if !(buildcfg.GOARM == 7 && !isARMImmRot(uint32(c)) && uint32(c) > 0xffff && uint32(-c) <= 0xffff) { break } v.reset(OpARMADDconst) @@ -12557,7 +12577,7 @@ func rewriteValueARM_OpARMXORshiftLL(v *Value) bool { return true } // match: (XORshiftLL [8] (SRLconst [24] (SLLconst [16] x)) x) - // cond: objabi.GOARM>=6 + // cond: buildcfg.GOARM>=6 // result: (REV16 x) for { if v.Type != typ.UInt16 || auxIntToInt32(v.AuxInt) != 8 || v_0.Op != OpARMSRLconst || v_0.Type != typ.UInt16 || auxIntToInt32(v_0.AuxInt) != 24 { @@ -12568,7 +12588,7 @@ func rewriteValueARM_OpARMXORshiftLL(v *Value) bool { break } x := v_0_0.Args[0] - if x != v_1 || !(objabi.GOARM >= 6) { + if x != v_1 || !(buildcfg.GOARM >= 6) { break } v.reset(OpARMREV16) @@ -12919,12 +12939,12 @@ func rewriteValueARM_OpBswap32(v *Value) bool { v_0 := v.Args[0] b := v.Block // match: (Bswap32 x) - // cond: objabi.GOARM==5 + // cond: buildcfg.GOARM==5 // result: (XOR (SRLconst (BICconst (XOR x (SRRconst [16] x)) [0xff0000]) [8]) (SRRconst x [8])) for { t := v.Type x := v_0 - if !(objabi.GOARM == 5) { + if !(buildcfg.GOARM == 5) { break } v.reset(OpARMXOR) @@ -12947,11 +12967,11 @@ func rewriteValueARM_OpBswap32(v *Value) bool { return true } // match: (Bswap32 x) - // cond: objabi.GOARM>=6 + // cond: buildcfg.GOARM>=6 // result: (REV x) for { x := v_0 - if !(objabi.GOARM >= 6) { + if !(buildcfg.GOARM >= 6) { break } v.reset(OpARMREV) @@ -13011,12 +13031,12 @@ func rewriteValueARM_OpConst8(v *Value) bool { } } func rewriteValueARM_OpConstBool(v *Value) bool { - // match: (ConstBool [b]) - // result: (MOVWconst [b2i32(b)]) + // match: (ConstBool [t]) + // result: (MOVWconst [b2i32(t)]) for { - b := auxIntToBool(v.AuxInt) + t := auxIntToBool(v.AuxInt) v.reset(OpARMMOVWconst) - v.AuxInt = int32ToAuxInt(b2i32(b)) + v.AuxInt = int32ToAuxInt(b2i32(t)) return true } } @@ -13034,12 +13054,12 @@ func rewriteValueARM_OpCtz16(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (Ctz16 x) - // cond: objabi.GOARM<=6 + // cond: buildcfg.GOARM<=6 // result: (RSBconst [32] (CLZ (SUBconst (AND (ORconst [0x10000] x) (RSBconst [0] (ORconst [0x10000] x))) [1]))) for { t := v.Type x := v_0 - if !(objabi.GOARM <= 6) { + if !(buildcfg.GOARM <= 6) { break } v.reset(OpARMRSBconst) @@ -13061,12 +13081,12 @@ func rewriteValueARM_OpCtz16(v *Value) bool { return true } // match: (Ctz16 x) - // cond: objabi.GOARM==7 + // cond: buildcfg.GOARM==7 // result: (CLZ (RBIT (ORconst [0x10000] x))) for { t := v.Type x := v_0 - if !(objabi.GOARM == 7) { + if !(buildcfg.GOARM == 7) { break } v.reset(OpARMCLZ) @@ -13085,12 +13105,12 @@ func rewriteValueARM_OpCtz32(v *Value) bool { v_0 := v.Args[0] b := v.Block // match: (Ctz32 x) - // cond: objabi.GOARM<=6 + // cond: buildcfg.GOARM<=6 // result: (RSBconst [32] (CLZ (SUBconst (AND x (RSBconst [0] x)) [1]))) for { t := v.Type x := v_0 - if !(objabi.GOARM <= 6) { + if !(buildcfg.GOARM <= 6) { break } v.reset(OpARMRSBconst) @@ -13109,12 +13129,12 @@ func rewriteValueARM_OpCtz32(v *Value) bool { return true } // match: (Ctz32 x) - // cond: objabi.GOARM==7 + // cond: buildcfg.GOARM==7 // result: (CLZ (RBIT x)) for { t := v.Type x := v_0 - if !(objabi.GOARM == 7) { + if !(buildcfg.GOARM == 7) { break } v.reset(OpARMCLZ) @@ -13131,12 +13151,12 @@ func rewriteValueARM_OpCtz8(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (Ctz8 x) - // cond: objabi.GOARM<=6 + // cond: buildcfg.GOARM<=6 // result: (RSBconst [32] (CLZ (SUBconst (AND (ORconst [0x100] x) (RSBconst [0] (ORconst [0x100] x))) [1]))) for { t := v.Type x := v_0 - if !(objabi.GOARM <= 6) { + if !(buildcfg.GOARM <= 6) { break } v.reset(OpARMRSBconst) @@ -13158,12 +13178,12 @@ func rewriteValueARM_OpCtz8(v *Value) bool { return true } // match: (Ctz8 x) - // cond: objabi.GOARM==7 + // cond: buildcfg.GOARM==7 // result: (CLZ (RBIT (ORconst [0x100] x))) for { t := v.Type x := v_0 - if !(objabi.GOARM == 7) { + if !(buildcfg.GOARM == 7) { break } v.reset(OpARMCLZ) diff --git a/src/cmd/compile/internal/ssa/rewriteARM64.go b/src/cmd/compile/internal/ssa/rewriteARM64.go index 5d5e526add31fbd8ca8fafd4f1e7861b750bfbab..3cdc4d36cb5a400fbb419aa5b896074bc5243d61 100644 --- a/src/cmd/compile/internal/ssa/rewriteARM64.go +++ b/src/cmd/compile/internal/ssa/rewriteARM64.go @@ -69,6 +69,14 @@ func rewriteValueARM64(v *Value) bool { return rewriteValueARM64_OpARM64CSEL(v) case OpARM64CSEL0: return rewriteValueARM64_OpARM64CSEL0(v) + case OpARM64CSETM: + return rewriteValueARM64_OpARM64CSETM(v) + case OpARM64CSINC: + return rewriteValueARM64_OpARM64CSINC(v) + case OpARM64CSINV: + return rewriteValueARM64_OpARM64CSINV(v) + case OpARM64CSNEG: + return rewriteValueARM64_OpARM64CSNEG(v) case OpARM64DIV: return rewriteValueARM64_OpARM64DIV(v) case OpARM64DIVW: @@ -99,18 +107,26 @@ func rewriteValueARM64(v *Value) bool { return rewriteValueARM64_OpARM64FMOVDload(v) case OpARM64FMOVDloadidx: return rewriteValueARM64_OpARM64FMOVDloadidx(v) + case OpARM64FMOVDloadidx8: + return rewriteValueARM64_OpARM64FMOVDloadidx8(v) case OpARM64FMOVDstore: return rewriteValueARM64_OpARM64FMOVDstore(v) case OpARM64FMOVDstoreidx: return rewriteValueARM64_OpARM64FMOVDstoreidx(v) + case OpARM64FMOVDstoreidx8: + return rewriteValueARM64_OpARM64FMOVDstoreidx8(v) case OpARM64FMOVSload: return rewriteValueARM64_OpARM64FMOVSload(v) case OpARM64FMOVSloadidx: return rewriteValueARM64_OpARM64FMOVSloadidx(v) + case OpARM64FMOVSloadidx4: + return rewriteValueARM64_OpARM64FMOVSloadidx4(v) case OpARM64FMOVSstore: return rewriteValueARM64_OpARM64FMOVSstore(v) case OpARM64FMOVSstoreidx: return rewriteValueARM64_OpARM64FMOVSstoreidx(v) + case OpARM64FMOVSstoreidx4: + return rewriteValueARM64_OpARM64FMOVSstoreidx4(v) case OpARM64FMULD: return rewriteValueARM64_OpARM64FMULD(v) case OpARM64FMULS: @@ -189,6 +205,8 @@ func rewriteValueARM64(v *Value) bool { return rewriteValueARM64_OpARM64MOVDloadidx(v) case OpARM64MOVDloadidx8: return rewriteValueARM64_OpARM64MOVDloadidx8(v) + case OpARM64MOVDnop: + return rewriteValueARM64_OpARM64MOVDnop(v) case OpARM64MOVDreg: return rewriteValueARM64_OpARM64MOVDreg(v) case OpARM64MOVDstore: @@ -966,6 +984,8 @@ func rewriteValueARM64(v *Value) bool { return rewriteValueARM64_OpSelect0(v) case OpSelect1: return rewriteValueARM64_OpSelect1(v) + case OpSelectN: + return rewriteValueARM64_OpSelectN(v) case OpSignExt16to32: v.Op = OpARM64MOVHreg return true @@ -989,6 +1009,9 @@ func rewriteValueARM64(v *Value) bool { case OpSqrt: v.Op = OpARM64FSQRTD return true + case OpSqrt32: + v.Op = OpARM64FSQRTS + return true case OpStaticCall: v.Op = OpARM64CALLstatic return true @@ -1751,6 +1774,81 @@ func rewriteValueARM64_OpARM64ADDshiftLL(v *Value) bool { v.AddArg(x) return true } + // match: (ADDshiftLL [8] (UBFX [armBFAuxInt(8, 24)] (ANDconst [c1] x)) (ANDconst [c2] x)) + // cond: uint32(c1) == 0xff00ff00 && uint32(c2) == 0x00ff00ff + // result: (REV16W x) + for { + if auxIntToInt64(v.AuxInt) != 8 || v_0.Op != OpARM64UBFX || auxIntToArm64BitField(v_0.AuxInt) != armBFAuxInt(8, 24) { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpARM64ANDconst { + break + } + c1 := auxIntToInt64(v_0_0.AuxInt) + x := v_0_0.Args[0] + if v_1.Op != OpARM64ANDconst { + break + } + c2 := auxIntToInt64(v_1.AuxInt) + if x != v_1.Args[0] || !(uint32(c1) == 0xff00ff00 && uint32(c2) == 0x00ff00ff) { + break + } + v.reset(OpARM64REV16W) + v.AddArg(x) + return true + } + // match: (ADDshiftLL [8] (SRLconst [8] (ANDconst [c1] x)) (ANDconst [c2] x)) + // cond: (uint64(c1) == 0xff00ff00ff00ff00 && uint64(c2) == 0x00ff00ff00ff00ff) + // result: (REV16 x) + for { + if auxIntToInt64(v.AuxInt) != 8 || v_0.Op != OpARM64SRLconst || auxIntToInt64(v_0.AuxInt) != 8 { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpARM64ANDconst { + break + } + c1 := auxIntToInt64(v_0_0.AuxInt) + x := v_0_0.Args[0] + if v_1.Op != OpARM64ANDconst { + break + } + c2 := auxIntToInt64(v_1.AuxInt) + if x != v_1.Args[0] || !(uint64(c1) == 0xff00ff00ff00ff00 && uint64(c2) == 0x00ff00ff00ff00ff) { + break + } + v.reset(OpARM64REV16) + v.AddArg(x) + return true + } + // match: (ADDshiftLL [8] (SRLconst [8] (ANDconst [c1] x)) (ANDconst [c2] x)) + // cond: (uint64(c1) == 0xff00ff00 && uint64(c2) == 0x00ff00ff) + // result: (REV16 (ANDconst [0xffffffff] x)) + for { + if auxIntToInt64(v.AuxInt) != 8 || v_0.Op != OpARM64SRLconst || auxIntToInt64(v_0.AuxInt) != 8 { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpARM64ANDconst { + break + } + c1 := auxIntToInt64(v_0_0.AuxInt) + x := v_0_0.Args[0] + if v_1.Op != OpARM64ANDconst { + break + } + c2 := auxIntToInt64(v_1.AuxInt) + if x != v_1.Args[0] || !(uint64(c1) == 0xff00ff00 && uint64(c2) == 0x00ff00ff) { + break + } + v.reset(OpARM64REV16) + v0 := b.NewValue0(v.Pos, OpARM64ANDconst, x.Type) + v0.AuxInt = int64ToAuxInt(0xffffffff) + v0.AddArg(x) + v.AddArg(v0) + return true + } // match: (ADDshiftLL [c] (SRLconst x [64-c]) x2) // result: (EXTRconst [64-c] x2 x) for { @@ -2772,12 +2870,12 @@ func rewriteValueARM64_OpARM64CMP(v *Value) bool { return true } // match: (CMP x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMP y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpARM64InvertFlags) @@ -2941,12 +3039,12 @@ func rewriteValueARM64_OpARM64CMPW(v *Value) bool { return true } // match: (CMPW x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPW y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpARM64InvertFlags) @@ -3202,6 +3300,32 @@ func rewriteValueARM64_OpARM64CSEL(v *Value) bool { v_2 := v.Args[2] v_1 := v.Args[1] v_0 := v.Args[0] + // match: (CSEL [cc] (MOVDconst [-1]) (MOVDconst [0]) flag) + // result: (CSETM [cc] flag) + for { + cc := auxIntToOp(v.AuxInt) + if v_0.Op != OpARM64MOVDconst || auxIntToInt64(v_0.AuxInt) != -1 || v_1.Op != OpARM64MOVDconst || auxIntToInt64(v_1.AuxInt) != 0 { + break + } + flag := v_2 + v.reset(OpARM64CSETM) + v.AuxInt = opToAuxInt(cc) + v.AddArg(flag) + return true + } + // match: (CSEL [cc] (MOVDconst [0]) (MOVDconst [-1]) flag) + // result: (CSETM [arm64Negate(cc)] flag) + for { + cc := auxIntToOp(v.AuxInt) + if v_0.Op != OpARM64MOVDconst || auxIntToInt64(v_0.AuxInt) != 0 || v_1.Op != OpARM64MOVDconst || auxIntToInt64(v_1.AuxInt) != -1 { + break + } + flag := v_2 + v.reset(OpARM64CSETM) + v.AuxInt = opToAuxInt(arm64Negate(cc)) + v.AddArg(flag) + return true + } // match: (CSEL [cc] x (MOVDconst [0]) flag) // result: (CSEL0 [cc] x flag) for { @@ -3230,6 +3354,96 @@ func rewriteValueARM64_OpARM64CSEL(v *Value) bool { v.AddArg2(y, flag) return true } + // match: (CSEL [cc] x (ADDconst [1] a) flag) + // result: (CSINC [cc] x a flag) + for { + cc := auxIntToOp(v.AuxInt) + x := v_0 + if v_1.Op != OpARM64ADDconst || auxIntToInt64(v_1.AuxInt) != 1 { + break + } + a := v_1.Args[0] + flag := v_2 + v.reset(OpARM64CSINC) + v.AuxInt = opToAuxInt(cc) + v.AddArg3(x, a, flag) + return true + } + // match: (CSEL [cc] (ADDconst [1] a) x flag) + // result: (CSINC [arm64Negate(cc)] x a flag) + for { + cc := auxIntToOp(v.AuxInt) + if v_0.Op != OpARM64ADDconst || auxIntToInt64(v_0.AuxInt) != 1 { + break + } + a := v_0.Args[0] + x := v_1 + flag := v_2 + v.reset(OpARM64CSINC) + v.AuxInt = opToAuxInt(arm64Negate(cc)) + v.AddArg3(x, a, flag) + return true + } + // match: (CSEL [cc] x (MVN a) flag) + // result: (CSINV [cc] x a flag) + for { + cc := auxIntToOp(v.AuxInt) + x := v_0 + if v_1.Op != OpARM64MVN { + break + } + a := v_1.Args[0] + flag := v_2 + v.reset(OpARM64CSINV) + v.AuxInt = opToAuxInt(cc) + v.AddArg3(x, a, flag) + return true + } + // match: (CSEL [cc] (MVN a) x flag) + // result: (CSINV [arm64Negate(cc)] x a flag) + for { + cc := auxIntToOp(v.AuxInt) + if v_0.Op != OpARM64MVN { + break + } + a := v_0.Args[0] + x := v_1 + flag := v_2 + v.reset(OpARM64CSINV) + v.AuxInt = opToAuxInt(arm64Negate(cc)) + v.AddArg3(x, a, flag) + return true + } + // match: (CSEL [cc] x (NEG a) flag) + // result: (CSNEG [cc] x a flag) + for { + cc := auxIntToOp(v.AuxInt) + x := v_0 + if v_1.Op != OpARM64NEG { + break + } + a := v_1.Args[0] + flag := v_2 + v.reset(OpARM64CSNEG) + v.AuxInt = opToAuxInt(cc) + v.AddArg3(x, a, flag) + return true + } + // match: (CSEL [cc] (NEG a) x flag) + // result: (CSNEG [arm64Negate(cc)] x a flag) + for { + cc := auxIntToOp(v.AuxInt) + if v_0.Op != OpARM64NEG { + break + } + a := v_0.Args[0] + x := v_1 + flag := v_2 + v.reset(OpARM64CSNEG) + v.AuxInt = opToAuxInt(arm64Negate(cc)) + v.AddArg3(x, a, flag) + return true + } // match: (CSEL [cc] x y (InvertFlags cmp)) // result: (CSEL [arm64Invert(cc)] x y cmp) for { @@ -3392,6 +3606,194 @@ func rewriteValueARM64_OpARM64CSEL0(v *Value) bool { } return false } +func rewriteValueARM64_OpARM64CSETM(v *Value) bool { + v_0 := v.Args[0] + // match: (CSETM [cc] (InvertFlags cmp)) + // result: (CSETM [arm64Invert(cc)] cmp) + for { + cc := auxIntToOp(v.AuxInt) + if v_0.Op != OpARM64InvertFlags { + break + } + cmp := v_0.Args[0] + v.reset(OpARM64CSETM) + v.AuxInt = opToAuxInt(arm64Invert(cc)) + v.AddArg(cmp) + return true + } + // match: (CSETM [cc] flag) + // cond: ccARM64Eval(cc, flag) > 0 + // result: (MOVDconst [-1]) + for { + cc := auxIntToOp(v.AuxInt) + flag := v_0 + if !(ccARM64Eval(cc, flag) > 0) { + break + } + v.reset(OpARM64MOVDconst) + v.AuxInt = int64ToAuxInt(-1) + return true + } + // match: (CSETM [cc] flag) + // cond: ccARM64Eval(cc, flag) < 0 + // result: (MOVDconst [0]) + for { + cc := auxIntToOp(v.AuxInt) + flag := v_0 + if !(ccARM64Eval(cc, flag) < 0) { + break + } + v.reset(OpARM64MOVDconst) + v.AuxInt = int64ToAuxInt(0) + return true + } + return false +} +func rewriteValueARM64_OpARM64CSINC(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + // match: (CSINC [cc] x y (InvertFlags cmp)) + // result: (CSINC [arm64Invert(cc)] x y cmp) + for { + cc := auxIntToOp(v.AuxInt) + x := v_0 + y := v_1 + if v_2.Op != OpARM64InvertFlags { + break + } + cmp := v_2.Args[0] + v.reset(OpARM64CSINC) + v.AuxInt = opToAuxInt(arm64Invert(cc)) + v.AddArg3(x, y, cmp) + return true + } + // match: (CSINC [cc] x _ flag) + // cond: ccARM64Eval(cc, flag) > 0 + // result: x + for { + cc := auxIntToOp(v.AuxInt) + x := v_0 + flag := v_2 + if !(ccARM64Eval(cc, flag) > 0) { + break + } + v.copyOf(x) + return true + } + // match: (CSINC [cc] _ y flag) + // cond: ccARM64Eval(cc, flag) < 0 + // result: (ADDconst [1] y) + for { + cc := auxIntToOp(v.AuxInt) + y := v_1 + flag := v_2 + if !(ccARM64Eval(cc, flag) < 0) { + break + } + v.reset(OpARM64ADDconst) + v.AuxInt = int64ToAuxInt(1) + v.AddArg(y) + return true + } + return false +} +func rewriteValueARM64_OpARM64CSINV(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + // match: (CSINV [cc] x y (InvertFlags cmp)) + // result: (CSINV [arm64Invert(cc)] x y cmp) + for { + cc := auxIntToOp(v.AuxInt) + x := v_0 + y := v_1 + if v_2.Op != OpARM64InvertFlags { + break + } + cmp := v_2.Args[0] + v.reset(OpARM64CSINV) + v.AuxInt = opToAuxInt(arm64Invert(cc)) + v.AddArg3(x, y, cmp) + return true + } + // match: (CSINV [cc] x _ flag) + // cond: ccARM64Eval(cc, flag) > 0 + // result: x + for { + cc := auxIntToOp(v.AuxInt) + x := v_0 + flag := v_2 + if !(ccARM64Eval(cc, flag) > 0) { + break + } + v.copyOf(x) + return true + } + // match: (CSINV [cc] _ y flag) + // cond: ccARM64Eval(cc, flag) < 0 + // result: (Not y) + for { + cc := auxIntToOp(v.AuxInt) + y := v_1 + flag := v_2 + if !(ccARM64Eval(cc, flag) < 0) { + break + } + v.reset(OpNot) + v.AddArg(y) + return true + } + return false +} +func rewriteValueARM64_OpARM64CSNEG(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + // match: (CSNEG [cc] x y (InvertFlags cmp)) + // result: (CSNEG [arm64Invert(cc)] x y cmp) + for { + cc := auxIntToOp(v.AuxInt) + x := v_0 + y := v_1 + if v_2.Op != OpARM64InvertFlags { + break + } + cmp := v_2.Args[0] + v.reset(OpARM64CSNEG) + v.AuxInt = opToAuxInt(arm64Invert(cc)) + v.AddArg3(x, y, cmp) + return true + } + // match: (CSNEG [cc] x _ flag) + // cond: ccARM64Eval(cc, flag) > 0 + // result: x + for { + cc := auxIntToOp(v.AuxInt) + x := v_0 + flag := v_2 + if !(ccARM64Eval(cc, flag) > 0) { + break + } + v.copyOf(x) + return true + } + // match: (CSNEG [cc] _ y flag) + // cond: ccARM64Eval(cc, flag) < 0 + // result: (NEG y) + for { + cc := auxIntToOp(v.AuxInt) + y := v_1 + flag := v_2 + if !(ccARM64Eval(cc, flag) < 0) { + break + } + v.reset(OpARM64NEG) + v.AddArg(y) + return true + } + return false +} func rewriteValueARM64_OpARM64DIV(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] @@ -3898,6 +4300,25 @@ func rewriteValueARM64_OpARM64FMOVDload(v *Value) bool { v.AddArg3(ptr, idx, mem) return true } + // match: (FMOVDload [off] {sym} (ADDshiftLL [3] ptr idx) mem) + // cond: off == 0 && sym == nil + // result: (FMOVDloadidx8 ptr idx mem) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpARM64ADDshiftLL || auxIntToInt64(v_0.AuxInt) != 3 { + break + } + idx := v_0.Args[1] + ptr := v_0.Args[0] + mem := v_1 + if !(off == 0 && sym == nil) { + break + } + v.reset(OpARM64FMOVDloadidx8) + v.AddArg3(ptr, idx, mem) + return true + } // match: (FMOVDload [off1] {sym1} (MOVDaddr [off2] {sym2} ptr) mem) // cond: canMergeSym(sym1,sym2) && is32Bit(int64(off1)+int64(off2)) && (ptr.Op != OpSB || !config.ctxt.Flag_shared) // result: (FMOVDload [off1+off2] {mergeSym(sym1,sym2)} ptr mem) @@ -3962,6 +4383,56 @@ func rewriteValueARM64_OpARM64FMOVDloadidx(v *Value) bool { v.AddArg2(ptr, mem) return true } + // match: (FMOVDloadidx ptr (SLLconst [3] idx) mem) + // result: (FMOVDloadidx8 ptr idx mem) + for { + ptr := v_0 + if v_1.Op != OpARM64SLLconst || auxIntToInt64(v_1.AuxInt) != 3 { + break + } + idx := v_1.Args[0] + mem := v_2 + v.reset(OpARM64FMOVDloadidx8) + v.AddArg3(ptr, idx, mem) + return true + } + // match: (FMOVDloadidx (SLLconst [3] idx) ptr mem) + // result: (FMOVDloadidx8 ptr idx mem) + for { + if v_0.Op != OpARM64SLLconst || auxIntToInt64(v_0.AuxInt) != 3 { + break + } + idx := v_0.Args[0] + ptr := v_1 + mem := v_2 + v.reset(OpARM64FMOVDloadidx8) + v.AddArg3(ptr, idx, mem) + return true + } + return false +} +func rewriteValueARM64_OpARM64FMOVDloadidx8(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + // match: (FMOVDloadidx8 ptr (MOVDconst [c]) mem) + // cond: is32Bit(c<<3) + // result: (FMOVDload ptr [int32(c)<<3] mem) + for { + ptr := v_0 + if v_1.Op != OpARM64MOVDconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + mem := v_2 + if !(is32Bit(c << 3)) { + break + } + v.reset(OpARM64FMOVDload) + v.AuxInt = int32ToAuxInt(int32(c) << 3) + v.AddArg2(ptr, mem) + return true + } return false } func rewriteValueARM64_OpARM64FMOVDstore(v *Value) bool { @@ -4029,6 +4500,26 @@ func rewriteValueARM64_OpARM64FMOVDstore(v *Value) bool { v.AddArg4(ptr, idx, val, mem) return true } + // match: (FMOVDstore [off] {sym} (ADDshiftLL [3] ptr idx) val mem) + // cond: off == 0 && sym == nil + // result: (FMOVDstoreidx8 ptr idx val mem) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpARM64ADDshiftLL || auxIntToInt64(v_0.AuxInt) != 3 { + break + } + idx := v_0.Args[1] + ptr := v_0.Args[0] + val := v_1 + mem := v_2 + if !(off == 0 && sym == nil) { + break + } + v.reset(OpARM64FMOVDstoreidx8) + v.AddArg4(ptr, idx, val, mem) + return true + } // match: (FMOVDstore [off1] {sym1} (MOVDaddr [off2] {sym2} ptr) val mem) // cond: canMergeSym(sym1,sym2) && is32Bit(int64(off1)+int64(off2)) && (ptr.Op != OpSB || !config.ctxt.Flag_shared) // result: (FMOVDstore [off1+off2] {mergeSym(sym1,sym2)} ptr val mem) @@ -4097,6 +4588,60 @@ func rewriteValueARM64_OpARM64FMOVDstoreidx(v *Value) bool { v.AddArg3(idx, val, mem) return true } + // match: (FMOVDstoreidx ptr (SLLconst [3] idx) val mem) + // result: (FMOVDstoreidx8 ptr idx val mem) + for { + ptr := v_0 + if v_1.Op != OpARM64SLLconst || auxIntToInt64(v_1.AuxInt) != 3 { + break + } + idx := v_1.Args[0] + val := v_2 + mem := v_3 + v.reset(OpARM64FMOVDstoreidx8) + v.AddArg4(ptr, idx, val, mem) + return true + } + // match: (FMOVDstoreidx (SLLconst [3] idx) ptr val mem) + // result: (FMOVDstoreidx8 ptr idx val mem) + for { + if v_0.Op != OpARM64SLLconst || auxIntToInt64(v_0.AuxInt) != 3 { + break + } + idx := v_0.Args[0] + ptr := v_1 + val := v_2 + mem := v_3 + v.reset(OpARM64FMOVDstoreidx8) + v.AddArg4(ptr, idx, val, mem) + return true + } + return false +} +func rewriteValueARM64_OpARM64FMOVDstoreidx8(v *Value) bool { + v_3 := v.Args[3] + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + // match: (FMOVDstoreidx8 ptr (MOVDconst [c]) val mem) + // cond: is32Bit(c<<3) + // result: (FMOVDstore [int32(c)<<3] ptr val mem) + for { + ptr := v_0 + if v_1.Op != OpARM64MOVDconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + val := v_2 + mem := v_3 + if !(is32Bit(c << 3)) { + break + } + v.reset(OpARM64FMOVDstore) + v.AuxInt = int32ToAuxInt(int32(c) << 3) + v.AddArg3(ptr, val, mem) + return true + } return false } func rewriteValueARM64_OpARM64FMOVSload(v *Value) bool { @@ -4161,6 +4706,25 @@ func rewriteValueARM64_OpARM64FMOVSload(v *Value) bool { v.AddArg3(ptr, idx, mem) return true } + // match: (FMOVSload [off] {sym} (ADDshiftLL [2] ptr idx) mem) + // cond: off == 0 && sym == nil + // result: (FMOVSloadidx4 ptr idx mem) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpARM64ADDshiftLL || auxIntToInt64(v_0.AuxInt) != 2 { + break + } + idx := v_0.Args[1] + ptr := v_0.Args[0] + mem := v_1 + if !(off == 0 && sym == nil) { + break + } + v.reset(OpARM64FMOVSloadidx4) + v.AddArg3(ptr, idx, mem) + return true + } // match: (FMOVSload [off1] {sym1} (MOVDaddr [off2] {sym2} ptr) mem) // cond: canMergeSym(sym1,sym2) && is32Bit(int64(off1)+int64(off2)) && (ptr.Op != OpSB || !config.ctxt.Flag_shared) // result: (FMOVSload [off1+off2] {mergeSym(sym1,sym2)} ptr mem) @@ -4225,6 +4789,56 @@ func rewriteValueARM64_OpARM64FMOVSloadidx(v *Value) bool { v.AddArg2(ptr, mem) return true } + // match: (FMOVSloadidx ptr (SLLconst [2] idx) mem) + // result: (FMOVSloadidx4 ptr idx mem) + for { + ptr := v_0 + if v_1.Op != OpARM64SLLconst || auxIntToInt64(v_1.AuxInt) != 2 { + break + } + idx := v_1.Args[0] + mem := v_2 + v.reset(OpARM64FMOVSloadidx4) + v.AddArg3(ptr, idx, mem) + return true + } + // match: (FMOVSloadidx (SLLconst [2] idx) ptr mem) + // result: (FMOVSloadidx4 ptr idx mem) + for { + if v_0.Op != OpARM64SLLconst || auxIntToInt64(v_0.AuxInt) != 2 { + break + } + idx := v_0.Args[0] + ptr := v_1 + mem := v_2 + v.reset(OpARM64FMOVSloadidx4) + v.AddArg3(ptr, idx, mem) + return true + } + return false +} +func rewriteValueARM64_OpARM64FMOVSloadidx4(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + // match: (FMOVSloadidx4 ptr (MOVDconst [c]) mem) + // cond: is32Bit(c<<2) + // result: (FMOVSload ptr [int32(c)<<2] mem) + for { + ptr := v_0 + if v_1.Op != OpARM64MOVDconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + mem := v_2 + if !(is32Bit(c << 2)) { + break + } + v.reset(OpARM64FMOVSload) + v.AuxInt = int32ToAuxInt(int32(c) << 2) + v.AddArg2(ptr, mem) + return true + } return false } func rewriteValueARM64_OpARM64FMOVSstore(v *Value) bool { @@ -4292,6 +4906,26 @@ func rewriteValueARM64_OpARM64FMOVSstore(v *Value) bool { v.AddArg4(ptr, idx, val, mem) return true } + // match: (FMOVSstore [off] {sym} (ADDshiftLL [2] ptr idx) val mem) + // cond: off == 0 && sym == nil + // result: (FMOVSstoreidx4 ptr idx val mem) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpARM64ADDshiftLL || auxIntToInt64(v_0.AuxInt) != 2 { + break + } + idx := v_0.Args[1] + ptr := v_0.Args[0] + val := v_1 + mem := v_2 + if !(off == 0 && sym == nil) { + break + } + v.reset(OpARM64FMOVSstoreidx4) + v.AddArg4(ptr, idx, val, mem) + return true + } // match: (FMOVSstore [off1] {sym1} (MOVDaddr [off2] {sym2} ptr) val mem) // cond: canMergeSym(sym1,sym2) && is32Bit(int64(off1)+int64(off2)) && (ptr.Op != OpSB || !config.ctxt.Flag_shared) // result: (FMOVSstore [off1+off2] {mergeSym(sym1,sym2)} ptr val mem) @@ -4360,6 +4994,60 @@ func rewriteValueARM64_OpARM64FMOVSstoreidx(v *Value) bool { v.AddArg3(idx, val, mem) return true } + // match: (FMOVSstoreidx ptr (SLLconst [2] idx) val mem) + // result: (FMOVSstoreidx4 ptr idx val mem) + for { + ptr := v_0 + if v_1.Op != OpARM64SLLconst || auxIntToInt64(v_1.AuxInt) != 2 { + break + } + idx := v_1.Args[0] + val := v_2 + mem := v_3 + v.reset(OpARM64FMOVSstoreidx4) + v.AddArg4(ptr, idx, val, mem) + return true + } + // match: (FMOVSstoreidx (SLLconst [2] idx) ptr val mem) + // result: (FMOVSstoreidx4 ptr idx val mem) + for { + if v_0.Op != OpARM64SLLconst || auxIntToInt64(v_0.AuxInt) != 2 { + break + } + idx := v_0.Args[0] + ptr := v_1 + val := v_2 + mem := v_3 + v.reset(OpARM64FMOVSstoreidx4) + v.AddArg4(ptr, idx, val, mem) + return true + } + return false +} +func rewriteValueARM64_OpARM64FMOVSstoreidx4(v *Value) bool { + v_3 := v.Args[3] + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + // match: (FMOVSstoreidx4 ptr (MOVDconst [c]) val mem) + // cond: is32Bit(c<<2) + // result: (FMOVSstore [int32(c)<<2] ptr val mem) + for { + ptr := v_0 + if v_1.Op != OpARM64MOVDconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + val := v_2 + mem := v_3 + if !(is32Bit(c << 2)) { + break + } + v.reset(OpARM64FMOVSstore) + v.AuxInt = int32ToAuxInt(int32(c) << 2) + v.AddArg3(ptr, val, mem) + return true + } return false } func rewriteValueARM64_OpARM64FMULD(v *Value) bool { @@ -6450,6 +7138,21 @@ func rewriteValueARM64_OpARM64MOVBUreg(v *Value) bool { v.AddArg(x) return true } + // match: (MOVBUreg (SLLconst [lc] x)) + // cond: lc >= 8 + // result: (MOVDconst [0]) + for { + if v_0.Op != OpARM64SLLconst { + break + } + lc := auxIntToInt64(v_0.AuxInt) + if !(lc >= 8) { + break + } + v.reset(OpARM64MOVDconst) + v.AuxInt = int64ToAuxInt(0) + return true + } // match: (MOVBUreg (SLLconst [sc] x)) // cond: isARM64BFMask(sc, 1<<8-1, sc) // result: (UBFIZ [armBFAuxInt(sc, arm64BFWidth(1<<8-1, sc))] x) @@ -9011,6 +9714,21 @@ func rewriteValueARM64_OpARM64MOVDloadidx8(v *Value) bool { } return false } +func rewriteValueARM64_OpARM64MOVDnop(v *Value) bool { + v_0 := v.Args[0] + // match: (MOVDnop (MOVDconst [c])) + // result: (MOVDconst [c]) + for { + if v_0.Op != OpARM64MOVDconst { + break + } + c := auxIntToInt64(v_0.AuxInt) + v.reset(OpARM64MOVDconst) + v.AuxInt = int64ToAuxInt(c) + return true + } + return false +} func rewriteValueARM64_OpARM64MOVDreg(v *Value) bool { v_0 := v.Args[0] // match: (MOVDreg x) @@ -9932,6 +10650,21 @@ func rewriteValueARM64_OpARM64MOVHUreg(v *Value) bool { v.AuxInt = int64ToAuxInt(int64(uint16(c))) return true } + // match: (MOVHUreg (SLLconst [lc] x)) + // cond: lc >= 16 + // result: (MOVDconst [0]) + for { + if v_0.Op != OpARM64SLLconst { + break + } + lc := auxIntToInt64(v_0.AuxInt) + if !(lc >= 16) { + break + } + v.reset(OpARM64MOVDconst) + v.AuxInt = int64ToAuxInt(0) + return true + } // match: (MOVHUreg (SLLconst [sc] x)) // cond: isARM64BFMask(sc, 1<<16-1, sc) // result: (UBFIZ [armBFAuxInt(sc, arm64BFWidth(1<<16-1, sc))] x) @@ -12029,6 +12762,21 @@ func rewriteValueARM64_OpARM64MOVWUreg(v *Value) bool { v.AuxInt = int64ToAuxInt(int64(uint32(c))) return true } + // match: (MOVWUreg (SLLconst [lc] x)) + // cond: lc >= 32 + // result: (MOVDconst [0]) + for { + if v_0.Op != OpARM64SLLconst { + break + } + lc := auxIntToInt64(v_0.AuxInt) + if !(lc >= 32) { + break + } + v.reset(OpARM64MOVDconst) + v.AuxInt = int64ToAuxInt(0) + return true + } // match: (MOVWUreg (SLLconst [sc] x)) // cond: isARM64BFMask(sc, 1<<32-1, sc) // result: (UBFIZ [armBFAuxInt(sc, arm64BFWidth(1<<32-1, sc))] x) @@ -17332,6 +18080,81 @@ func rewriteValueARM64_OpARM64ORshiftLL(v *Value) bool { v.AddArg(x) return true } + // match: (ORshiftLL [8] (UBFX [armBFAuxInt(8, 24)] (ANDconst [c1] x)) (ANDconst [c2] x)) + // cond: uint32(c1) == 0xff00ff00 && uint32(c2) == 0x00ff00ff + // result: (REV16W x) + for { + if auxIntToInt64(v.AuxInt) != 8 || v_0.Op != OpARM64UBFX || auxIntToArm64BitField(v_0.AuxInt) != armBFAuxInt(8, 24) { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpARM64ANDconst { + break + } + c1 := auxIntToInt64(v_0_0.AuxInt) + x := v_0_0.Args[0] + if v_1.Op != OpARM64ANDconst { + break + } + c2 := auxIntToInt64(v_1.AuxInt) + if x != v_1.Args[0] || !(uint32(c1) == 0xff00ff00 && uint32(c2) == 0x00ff00ff) { + break + } + v.reset(OpARM64REV16W) + v.AddArg(x) + return true + } + // match: (ORshiftLL [8] (SRLconst [8] (ANDconst [c1] x)) (ANDconst [c2] x)) + // cond: (uint64(c1) == 0xff00ff00ff00ff00 && uint64(c2) == 0x00ff00ff00ff00ff) + // result: (REV16 x) + for { + if auxIntToInt64(v.AuxInt) != 8 || v_0.Op != OpARM64SRLconst || auxIntToInt64(v_0.AuxInt) != 8 { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpARM64ANDconst { + break + } + c1 := auxIntToInt64(v_0_0.AuxInt) + x := v_0_0.Args[0] + if v_1.Op != OpARM64ANDconst { + break + } + c2 := auxIntToInt64(v_1.AuxInt) + if x != v_1.Args[0] || !(uint64(c1) == 0xff00ff00ff00ff00 && uint64(c2) == 0x00ff00ff00ff00ff) { + break + } + v.reset(OpARM64REV16) + v.AddArg(x) + return true + } + // match: (ORshiftLL [8] (SRLconst [8] (ANDconst [c1] x)) (ANDconst [c2] x)) + // cond: (uint64(c1) == 0xff00ff00 && uint64(c2) == 0x00ff00ff) + // result: (REV16 (ANDconst [0xffffffff] x)) + for { + if auxIntToInt64(v.AuxInt) != 8 || v_0.Op != OpARM64SRLconst || auxIntToInt64(v_0.AuxInt) != 8 { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpARM64ANDconst { + break + } + c1 := auxIntToInt64(v_0_0.AuxInt) + x := v_0_0.Args[0] + if v_1.Op != OpARM64ANDconst { + break + } + c2 := auxIntToInt64(v_1.AuxInt) + if x != v_1.Args[0] || !(uint64(c1) == 0xff00ff00 && uint64(c2) == 0x00ff00ff) { + break + } + v.reset(OpARM64REV16) + v0 := b.NewValue0(v.Pos, OpARM64ANDconst, x.Type) + v0.AuxInt = int64ToAuxInt(0xffffffff) + v0.AddArg(x) + v.AddArg(v0) + return true + } // match: ( ORshiftLL [c] (SRLconst x [64-c]) x2) // result: (EXTRconst [64-c] x2 x) for { @@ -19457,6 +20280,51 @@ func rewriteValueARM64_OpARM64SRLconst(v *Value) bool { v.AddArg(x) return true } + // match: (SRLconst [rc] (MOVWUreg x)) + // cond: rc >= 32 + // result: (MOVDconst [0]) + for { + rc := auxIntToInt64(v.AuxInt) + if v_0.Op != OpARM64MOVWUreg { + break + } + if !(rc >= 32) { + break + } + v.reset(OpARM64MOVDconst) + v.AuxInt = int64ToAuxInt(0) + return true + } + // match: (SRLconst [rc] (MOVHUreg x)) + // cond: rc >= 16 + // result: (MOVDconst [0]) + for { + rc := auxIntToInt64(v.AuxInt) + if v_0.Op != OpARM64MOVHUreg { + break + } + if !(rc >= 16) { + break + } + v.reset(OpARM64MOVDconst) + v.AuxInt = int64ToAuxInt(0) + return true + } + // match: (SRLconst [rc] (MOVBUreg x)) + // cond: rc >= 8 + // result: (MOVDconst [0]) + for { + rc := auxIntToInt64(v.AuxInt) + if v_0.Op != OpARM64MOVBUreg { + break + } + if !(rc >= 8) { + break + } + v.reset(OpARM64MOVDconst) + v.AuxInt = int64ToAuxInt(0) + return true + } // match: (SRLconst [rc] (SLLconst [lc] x)) // cond: lc > rc // result: (UBFIZ [armBFAuxInt(lc-rc, 64-lc)] x) @@ -21205,6 +22073,81 @@ func rewriteValueARM64_OpARM64XORshiftLL(v *Value) bool { v.AddArg(x) return true } + // match: (XORshiftLL [8] (UBFX [armBFAuxInt(8, 24)] (ANDconst [c1] x)) (ANDconst [c2] x)) + // cond: uint32(c1) == 0xff00ff00 && uint32(c2) == 0x00ff00ff + // result: (REV16W x) + for { + if auxIntToInt64(v.AuxInt) != 8 || v_0.Op != OpARM64UBFX || auxIntToArm64BitField(v_0.AuxInt) != armBFAuxInt(8, 24) { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpARM64ANDconst { + break + } + c1 := auxIntToInt64(v_0_0.AuxInt) + x := v_0_0.Args[0] + if v_1.Op != OpARM64ANDconst { + break + } + c2 := auxIntToInt64(v_1.AuxInt) + if x != v_1.Args[0] || !(uint32(c1) == 0xff00ff00 && uint32(c2) == 0x00ff00ff) { + break + } + v.reset(OpARM64REV16W) + v.AddArg(x) + return true + } + // match: (XORshiftLL [8] (SRLconst [8] (ANDconst [c1] x)) (ANDconst [c2] x)) + // cond: (uint64(c1) == 0xff00ff00ff00ff00 && uint64(c2) == 0x00ff00ff00ff00ff) + // result: (REV16 x) + for { + if auxIntToInt64(v.AuxInt) != 8 || v_0.Op != OpARM64SRLconst || auxIntToInt64(v_0.AuxInt) != 8 { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpARM64ANDconst { + break + } + c1 := auxIntToInt64(v_0_0.AuxInt) + x := v_0_0.Args[0] + if v_1.Op != OpARM64ANDconst { + break + } + c2 := auxIntToInt64(v_1.AuxInt) + if x != v_1.Args[0] || !(uint64(c1) == 0xff00ff00ff00ff00 && uint64(c2) == 0x00ff00ff00ff00ff) { + break + } + v.reset(OpARM64REV16) + v.AddArg(x) + return true + } + // match: (XORshiftLL [8] (SRLconst [8] (ANDconst [c1] x)) (ANDconst [c2] x)) + // cond: (uint64(c1) == 0xff00ff00 && uint64(c2) == 0x00ff00ff) + // result: (REV16 (ANDconst [0xffffffff] x)) + for { + if auxIntToInt64(v.AuxInt) != 8 || v_0.Op != OpARM64SRLconst || auxIntToInt64(v_0.AuxInt) != 8 { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpARM64ANDconst { + break + } + c1 := auxIntToInt64(v_0_0.AuxInt) + x := v_0_0.Args[0] + if v_1.Op != OpARM64ANDconst { + break + } + c2 := auxIntToInt64(v_1.AuxInt) + if x != v_1.Args[0] || !(uint64(c1) == 0xff00ff00 && uint64(c2) == 0x00ff00ff) { + break + } + v.reset(OpARM64REV16) + v0 := b.NewValue0(v.Pos, OpARM64ANDconst, x.Type) + v0.AuxInt = int64ToAuxInt(0xffffffff) + v0.AddArg(x) + v.AddArg(v0) + return true + } // match: (XORshiftLL [c] (SRLconst x [64-c]) x2) // result: (EXTRconst [64-c] x2 x) for { @@ -21735,12 +22678,12 @@ func rewriteValueARM64_OpConst8(v *Value) bool { } } func rewriteValueARM64_OpConstBool(v *Value) bool { - // match: (ConstBool [b]) - // result: (MOVDconst [b2i(b)]) + // match: (ConstBool [t]) + // result: (MOVDconst [b2i(t)]) for { - b := auxIntToBool(v.AuxInt) + t := auxIntToBool(v.AuxInt) v.reset(OpARM64MOVDconst) - v.AuxInt = int64ToAuxInt(b2i(b)) + v.AuxInt = int64ToAuxInt(b2i(t)) return true } } @@ -25042,6 +25985,54 @@ func rewriteValueARM64_OpSelect1(v *Value) bool { } return false } +func rewriteValueARM64_OpSelectN(v *Value) bool { + v_0 := v.Args[0] + b := v.Block + config := b.Func.Config + // match: (SelectN [0] call:(CALLstatic {sym} s1:(MOVDstore _ (MOVDconst [sz]) s2:(MOVDstore _ src s3:(MOVDstore {t} _ dst mem))))) + // cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, sz, config) && clobber(s1, s2, s3, call) + // result: (Move [sz] dst src mem) + for { + if auxIntToInt64(v.AuxInt) != 0 { + break + } + call := v_0 + if call.Op != OpARM64CALLstatic { + break + } + sym := auxToCall(call.Aux) + s1 := call.Args[0] + if s1.Op != OpARM64MOVDstore { + break + } + _ = s1.Args[2] + s1_1 := s1.Args[1] + if s1_1.Op != OpARM64MOVDconst { + break + } + sz := auxIntToInt64(s1_1.AuxInt) + s2 := s1.Args[2] + if s2.Op != OpARM64MOVDstore { + break + } + _ = s2.Args[2] + src := s2.Args[1] + s3 := s2.Args[2] + if s3.Op != OpARM64MOVDstore { + break + } + mem := s3.Args[2] + dst := s3.Args[1] + if !(sz >= 0 && isSameCall(sym, "runtime.memmove") && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, sz, config) && clobber(s1, s2, s3, call)) { + break + } + v.reset(OpMove) + v.AuxInt = int64ToAuxInt(sz) + v.AddArg3(dst, src, mem) + return true + } + return false +} func rewriteValueARM64_OpSlicemask(v *Value) bool { v_0 := v.Args[0] b := v.Block diff --git a/src/cmd/compile/internal/ssa/rewriteMIPS.go b/src/cmd/compile/internal/ssa/rewriteMIPS.go index 3fc552795507c256582aec4deb41a53dabf80b26..429369d631eb37bf910c4b6a845fbac06da68463 100644 --- a/src/cmd/compile/internal/ssa/rewriteMIPS.go +++ b/src/cmd/compile/internal/ssa/rewriteMIPS.go @@ -297,6 +297,8 @@ func rewriteValueMIPS(v *Value) bool { return rewriteValueMIPS_OpMIPSMOVHstorezero(v) case OpMIPSMOVWload: return rewriteValueMIPS_OpMIPSMOVWload(v) + case OpMIPSMOVWnop: + return rewriteValueMIPS_OpMIPSMOVWnop(v) case OpMIPSMOVWreg: return rewriteValueMIPS_OpMIPSMOVWreg(v) case OpMIPSMOVWstore: @@ -514,6 +516,9 @@ func rewriteValueMIPS(v *Value) bool { case OpSqrt: v.Op = OpMIPSSQRTD return true + case OpSqrt32: + v.Op = OpMIPSSQRTF + return true case OpStaticCall: v.Op = OpMIPSCALLstatic return true @@ -867,12 +872,12 @@ func rewriteValueMIPS_OpConst8(v *Value) bool { } } func rewriteValueMIPS_OpConstBool(v *Value) bool { - // match: (ConstBool [b]) - // result: (MOVWconst [b2i32(b)]) + // match: (ConstBool [t]) + // result: (MOVWconst [b2i32(t)]) for { - b := auxIntToBool(v.AuxInt) + t := auxIntToBool(v.AuxInt) v.reset(OpMIPSMOVWconst) - v.AuxInt = int32ToAuxInt(b2i32(b)) + v.AuxInt = int32ToAuxInt(b2i32(t)) return true } } @@ -3647,6 +3652,21 @@ func rewriteValueMIPS_OpMIPSMOVWload(v *Value) bool { } return false } +func rewriteValueMIPS_OpMIPSMOVWnop(v *Value) bool { + v_0 := v.Args[0] + // match: (MOVWnop (MOVWconst [c])) + // result: (MOVWconst [c]) + for { + if v_0.Op != OpMIPSMOVWconst { + break + } + c := auxIntToInt32(v_0.AuxInt) + v.reset(OpMIPSMOVWconst) + v.AuxInt = int32ToAuxInt(c) + return true + } + return false +} func rewriteValueMIPS_OpMIPSMOVWreg(v *Value) bool { v_0 := v.Args[0] // match: (MOVWreg x) diff --git a/src/cmd/compile/internal/ssa/rewriteMIPS64.go b/src/cmd/compile/internal/ssa/rewriteMIPS64.go index d78f6089afff28f780226dbb8afe515d69d14363..772d7b66efeebdf3bd3048c2d5285be80ea69b03 100644 --- a/src/cmd/compile/internal/ssa/rewriteMIPS64.go +++ b/src/cmd/compile/internal/ssa/rewriteMIPS64.go @@ -339,6 +339,8 @@ func rewriteValueMIPS64(v *Value) bool { return rewriteValueMIPS64_OpMIPS64MOVHstorezero(v) case OpMIPS64MOVVload: return rewriteValueMIPS64_OpMIPS64MOVVload(v) + case OpMIPS64MOVVnop: + return rewriteValueMIPS64_OpMIPS64MOVVnop(v) case OpMIPS64MOVVreg: return rewriteValueMIPS64_OpMIPS64MOVVreg(v) case OpMIPS64MOVVstore: @@ -594,6 +596,9 @@ func rewriteValueMIPS64(v *Value) bool { case OpSqrt: v.Op = OpMIPS64SQRTD return true + case OpSqrt32: + v.Op = OpMIPS64SQRTF + return true case OpStaticCall: v.Op = OpMIPS64CALLstatic return true @@ -830,12 +835,12 @@ func rewriteValueMIPS64_OpConst8(v *Value) bool { } } func rewriteValueMIPS64_OpConstBool(v *Value) bool { - // match: (ConstBool [b]) - // result: (MOVVconst [int64(b2i(b))]) + // match: (ConstBool [t]) + // result: (MOVVconst [int64(b2i(t))]) for { - b := auxIntToBool(v.AuxInt) + t := auxIntToBool(v.AuxInt) v.reset(OpMIPS64MOVVconst) - v.AuxInt = int64ToAuxInt(int64(b2i(b))) + v.AuxInt = int64ToAuxInt(int64(b2i(t))) return true } } @@ -2663,6 +2668,19 @@ func rewriteValueMIPS64_OpMIPS64MOVBload(v *Value) bool { v.AddArg2(ptr, mem) return true } + // match: (MOVBload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(read8(sym, int64(off)))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpMIPS64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(read8(sym, int64(off)))) + return true + } return false } func rewriteValueMIPS64_OpMIPS64MOVBreg(v *Value) bool { @@ -3227,6 +3245,8 @@ func rewriteValueMIPS64_OpMIPS64MOVHUreg(v *Value) bool { func rewriteValueMIPS64_OpMIPS64MOVHload(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] + b := v.Block + config := b.Func.Config // match: (MOVHload [off1] {sym} (ADDVconst [off2] ptr) mem) // cond: is32Bit(int64(off1)+off2) // result: (MOVHload [off1+int32(off2)] {sym} ptr mem) @@ -3270,6 +3290,19 @@ func rewriteValueMIPS64_OpMIPS64MOVHload(v *Value) bool { v.AddArg2(ptr, mem) return true } + // match: (MOVHload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(read16(sym, int64(off), config.ctxt.Arch.ByteOrder))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpMIPS64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(read16(sym, int64(off), config.ctxt.Arch.ByteOrder))) + return true + } return false } func rewriteValueMIPS64_OpMIPS64MOVHreg(v *Value) bool { @@ -3539,6 +3572,8 @@ func rewriteValueMIPS64_OpMIPS64MOVHstorezero(v *Value) bool { func rewriteValueMIPS64_OpMIPS64MOVVload(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] + b := v.Block + config := b.Func.Config // match: (MOVVload [off1] {sym} (ADDVconst [off2] ptr) mem) // cond: is32Bit(int64(off1)+off2) // result: (MOVVload [off1+int32(off2)] {sym} ptr mem) @@ -3582,6 +3617,34 @@ func rewriteValueMIPS64_OpMIPS64MOVVload(v *Value) bool { v.AddArg2(ptr, mem) return true } + // match: (MOVVload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(read64(sym, int64(off), config.ctxt.Arch.ByteOrder))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpMIPS64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(read64(sym, int64(off), config.ctxt.Arch.ByteOrder))) + return true + } + return false +} +func rewriteValueMIPS64_OpMIPS64MOVVnop(v *Value) bool { + v_0 := v.Args[0] + // match: (MOVVnop (MOVVconst [c])) + // result: (MOVVconst [c]) + for { + if v_0.Op != OpMIPS64MOVVconst { + break + } + c := auxIntToInt64(v_0.AuxInt) + v.reset(OpMIPS64MOVVconst) + v.AuxInt = int64ToAuxInt(c) + return true + } return false } func rewriteValueMIPS64_OpMIPS64MOVVreg(v *Value) bool { @@ -3858,6 +3921,8 @@ func rewriteValueMIPS64_OpMIPS64MOVWUreg(v *Value) bool { func rewriteValueMIPS64_OpMIPS64MOVWload(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] + b := v.Block + config := b.Func.Config // match: (MOVWload [off1] {sym} (ADDVconst [off2] ptr) mem) // cond: is32Bit(int64(off1)+off2) // result: (MOVWload [off1+int32(off2)] {sym} ptr mem) @@ -3901,6 +3966,19 @@ func rewriteValueMIPS64_OpMIPS64MOVWload(v *Value) bool { v.AddArg2(ptr, mem) return true } + // match: (MOVWload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(read32(sym, int64(off), config.ctxt.Arch.ByteOrder))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpMIPS64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(read32(sym, int64(off), config.ctxt.Arch.ByteOrder))) + return true + } return false } func rewriteValueMIPS64_OpMIPS64MOVWreg(v *Value) bool { diff --git a/src/cmd/compile/internal/ssa/rewritePPC64.go b/src/cmd/compile/internal/ssa/rewritePPC64.go index 455f9b138859b0e0e31231b644e6584ed9b2841c..96dee0bd21baf6d9f753dcbf615fee2797506fee 100644 --- a/src/cmd/compile/internal/ssa/rewritePPC64.go +++ b/src/cmd/compile/internal/ssa/rewritePPC64.go @@ -3,8 +3,8 @@ package ssa +import "internal/buildcfg" import "math" -import "cmd/internal/objabi" import "cmd/compile/internal/types" func rewriteValuePPC64(v *Value) bool { @@ -743,6 +743,9 @@ func rewriteValuePPC64(v *Value) bool { case OpSqrt: v.Op = OpPPC64FSQRT return true + case OpSqrt32: + v.Op = OpPPC64FSQRTS + return true case OpStaticCall: v.Op = OpPPC64CALLstatic return true @@ -1231,12 +1234,12 @@ func rewriteValuePPC64_OpConst8(v *Value) bool { } } func rewriteValuePPC64_OpConstBool(v *Value) bool { - // match: (ConstBool [b]) - // result: (MOVDconst [b2i(b)]) + // match: (ConstBool [t]) + // result: (MOVDconst [b2i(t)]) for { - b := auxIntToBool(v.AuxInt) + t := auxIntToBool(v.AuxInt) v.reset(OpPPC64MOVDconst) - v.AuxInt = int64ToAuxInt(b2i(b)) + v.AuxInt = int64ToAuxInt(b2i(t)) return true } } @@ -1287,11 +1290,11 @@ func rewriteValuePPC64_OpCtz32(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (Ctz32 x) - // cond: objabi.GOPPC64<=8 + // cond: buildcfg.GOPPC64<=8 // result: (POPCNTW (MOVWZreg (ANDN (ADDconst [-1] x) x))) for { x := v_0 - if !(objabi.GOPPC64 <= 8) { + if !(buildcfg.GOPPC64 <= 8) { break } v.reset(OpPPC64POPCNTW) @@ -1321,11 +1324,11 @@ func rewriteValuePPC64_OpCtz64(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (Ctz64 x) - // cond: objabi.GOPPC64<=8 + // cond: buildcfg.GOPPC64<=8 // result: (POPCNTD (ANDN (ADDconst [-1] x) x)) for { x := v_0 - if !(objabi.GOPPC64 <= 8) { + if !(buildcfg.GOPPC64 <= 8) { break } v.reset(OpPPC64POPCNTD) @@ -3283,12 +3286,12 @@ func rewriteValuePPC64_OpMod32(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (Mod32 x y) - // cond: objabi.GOPPC64 >= 9 + // cond: buildcfg.GOPPC64 >= 9 // result: (MODSW x y) for { x := v_0 y := v_1 - if !(objabi.GOPPC64 >= 9) { + if !(buildcfg.GOPPC64 >= 9) { break } v.reset(OpPPC64MODSW) @@ -3296,12 +3299,12 @@ func rewriteValuePPC64_OpMod32(v *Value) bool { return true } // match: (Mod32 x y) - // cond: objabi.GOPPC64 <= 8 + // cond: buildcfg.GOPPC64 <= 8 // result: (SUB x (MULLW y (DIVW x y))) for { x := v_0 y := v_1 - if !(objabi.GOPPC64 <= 8) { + if !(buildcfg.GOPPC64 <= 8) { break } v.reset(OpPPC64SUB) @@ -3320,12 +3323,12 @@ func rewriteValuePPC64_OpMod32u(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (Mod32u x y) - // cond: objabi.GOPPC64 >= 9 + // cond: buildcfg.GOPPC64 >= 9 // result: (MODUW x y) for { x := v_0 y := v_1 - if !(objabi.GOPPC64 >= 9) { + if !(buildcfg.GOPPC64 >= 9) { break } v.reset(OpPPC64MODUW) @@ -3333,12 +3336,12 @@ func rewriteValuePPC64_OpMod32u(v *Value) bool { return true } // match: (Mod32u x y) - // cond: objabi.GOPPC64 <= 8 + // cond: buildcfg.GOPPC64 <= 8 // result: (SUB x (MULLW y (DIVWU x y))) for { x := v_0 y := v_1 - if !(objabi.GOPPC64 <= 8) { + if !(buildcfg.GOPPC64 <= 8) { break } v.reset(OpPPC64SUB) @@ -3357,12 +3360,12 @@ func rewriteValuePPC64_OpMod64(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (Mod64 x y) - // cond: objabi.GOPPC64 >=9 + // cond: buildcfg.GOPPC64 >=9 // result: (MODSD x y) for { x := v_0 y := v_1 - if !(objabi.GOPPC64 >= 9) { + if !(buildcfg.GOPPC64 >= 9) { break } v.reset(OpPPC64MODSD) @@ -3370,12 +3373,12 @@ func rewriteValuePPC64_OpMod64(v *Value) bool { return true } // match: (Mod64 x y) - // cond: objabi.GOPPC64 <=8 + // cond: buildcfg.GOPPC64 <=8 // result: (SUB x (MULLD y (DIVD x y))) for { x := v_0 y := v_1 - if !(objabi.GOPPC64 <= 8) { + if !(buildcfg.GOPPC64 <= 8) { break } v.reset(OpPPC64SUB) @@ -3394,12 +3397,12 @@ func rewriteValuePPC64_OpMod64u(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (Mod64u x y) - // cond: objabi.GOPPC64 >= 9 + // cond: buildcfg.GOPPC64 >= 9 // result: (MODUD x y) for { x := v_0 y := v_1 - if !(objabi.GOPPC64 >= 9) { + if !(buildcfg.GOPPC64 >= 9) { break } v.reset(OpPPC64MODUD) @@ -3407,12 +3410,12 @@ func rewriteValuePPC64_OpMod64u(v *Value) bool { return true } // match: (Mod64u x y) - // cond: objabi.GOPPC64 <= 8 + // cond: buildcfg.GOPPC64 <= 8 // result: (SUB x (MULLD y (DIVDU x y))) for { x := v_0 y := v_1 - if !(objabi.GOPPC64 <= 8) { + if !(buildcfg.GOPPC64 <= 8) { break } v.reset(OpPPC64SUB) @@ -3525,46 +3528,20 @@ func rewriteValuePPC64_OpMove(v *Value) bool { return true } // match: (Move [8] {t} dst src mem) - // cond: t.Alignment()%4 == 0 // result: (MOVDstore dst (MOVDload src mem) mem) for { if auxIntToInt64(v.AuxInt) != 8 { break } - t := auxToType(v.Aux) dst := v_0 src := v_1 mem := v_2 - if !(t.Alignment()%4 == 0) { - break - } v.reset(OpPPC64MOVDstore) v0 := b.NewValue0(v.Pos, OpPPC64MOVDload, typ.Int64) v0.AddArg2(src, mem) v.AddArg3(dst, v0, mem) return true } - // match: (Move [8] dst src mem) - // result: (MOVWstore [4] dst (MOVWZload [4] src mem) (MOVWstore dst (MOVWZload src mem) mem)) - for { - if auxIntToInt64(v.AuxInt) != 8 { - break - } - dst := v_0 - src := v_1 - mem := v_2 - v.reset(OpPPC64MOVWstore) - v.AuxInt = int32ToAuxInt(4) - v0 := b.NewValue0(v.Pos, OpPPC64MOVWZload, typ.UInt32) - v0.AuxInt = int32ToAuxInt(4) - v0.AddArg2(src, mem) - v1 := b.NewValue0(v.Pos, OpPPC64MOVWstore, types.TypeMem) - v2 := b.NewValue0(v.Pos, OpPPC64MOVWZload, typ.UInt32) - v2.AddArg2(src, mem) - v1.AddArg3(dst, v2, mem) - v.AddArg3(dst, v0, v1) - return true - } // match: (Move [3] dst src mem) // result: (MOVBstore [2] dst (MOVBZload [2] src mem) (MOVHstore dst (MOVHload src mem) mem)) for { @@ -3656,14 +3633,14 @@ func rewriteValuePPC64_OpMove(v *Value) bool { return true } // match: (Move [s] dst src mem) - // cond: s > 8 && objabi.GOPPC64 <= 8 && logLargeCopy(v, s) + // cond: s > 8 && buildcfg.GOPPC64 <= 8 && logLargeCopy(v, s) // result: (LoweredMove [s] dst src mem) for { s := auxIntToInt64(v.AuxInt) dst := v_0 src := v_1 mem := v_2 - if !(s > 8 && objabi.GOPPC64 <= 8 && logLargeCopy(v, s)) { + if !(s > 8 && buildcfg.GOPPC64 <= 8 && logLargeCopy(v, s)) { break } v.reset(OpPPC64LoweredMove) @@ -3672,14 +3649,14 @@ func rewriteValuePPC64_OpMove(v *Value) bool { return true } // match: (Move [s] dst src mem) - // cond: s > 8 && s <= 64 && objabi.GOPPC64 >= 9 + // cond: s > 8 && s <= 64 && buildcfg.GOPPC64 >= 9 // result: (LoweredQuadMoveShort [s] dst src mem) for { s := auxIntToInt64(v.AuxInt) dst := v_0 src := v_1 mem := v_2 - if !(s > 8 && s <= 64 && objabi.GOPPC64 >= 9) { + if !(s > 8 && s <= 64 && buildcfg.GOPPC64 >= 9) { break } v.reset(OpPPC64LoweredQuadMoveShort) @@ -3688,14 +3665,14 @@ func rewriteValuePPC64_OpMove(v *Value) bool { return true } // match: (Move [s] dst src mem) - // cond: s > 8 && objabi.GOPPC64 >= 9 && logLargeCopy(v, s) + // cond: s > 8 && buildcfg.GOPPC64 >= 9 && logLargeCopy(v, s) // result: (LoweredQuadMove [s] dst src mem) for { s := auxIntToInt64(v.AuxInt) dst := v_0 src := v_1 mem := v_2 - if !(s > 8 && objabi.GOPPC64 >= 9 && logLargeCopy(v, s)) { + if !(s > 8 && buildcfg.GOPPC64 >= 9 && logLargeCopy(v, s)) { break } v.reset(OpPPC64LoweredQuadMove) @@ -3905,7 +3882,7 @@ func rewriteValuePPC64_OpPPC64ADD(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (ADD l:(MULLD x y) z) - // cond: objabi.GOPPC64 >= 9 && l.Uses == 1 && clobber(l) + // cond: buildcfg.GOPPC64 >= 9 && l.Uses == 1 && clobber(l) // result: (MADDLD x y z) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { @@ -3916,7 +3893,7 @@ func rewriteValuePPC64_OpPPC64ADD(v *Value) bool { y := l.Args[1] x := l.Args[0] z := v_1 - if !(objabi.GOPPC64 >= 9 && l.Uses == 1 && clobber(l)) { + if !(buildcfg.GOPPC64 >= 9 && l.Uses == 1 && clobber(l)) { continue } v.reset(OpPPC64MADDLD) @@ -4777,12 +4754,12 @@ func rewriteValuePPC64_OpPPC64CMP(v *Value) bool { return true } // match: (CMP x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMP y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpPPC64InvertFlags) @@ -4834,12 +4811,12 @@ func rewriteValuePPC64_OpPPC64CMPU(v *Value) bool { return true } // match: (CMPU x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPU y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpPPC64InvertFlags) @@ -4964,12 +4941,12 @@ func rewriteValuePPC64_OpPPC64CMPW(v *Value) bool { return true } // match: (CMPW x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPW y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpPPC64InvertFlags) @@ -5045,12 +5022,12 @@ func rewriteValuePPC64_OpPPC64CMPWU(v *Value) bool { return true } // match: (CMPWU x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPWU y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpPPC64InvertFlags) @@ -7878,7 +7855,7 @@ func rewriteValuePPC64_OpPPC64MOVBstore(v *Value) bool { return true } // match: (MOVBstore [i7] {s} p (SRDconst w [56]) x0:(MOVBstore [i6] {s} p (SRDconst w [48]) x1:(MOVBstore [i5] {s} p (SRDconst w [40]) x2:(MOVBstore [i4] {s} p (SRDconst w [32]) x3:(MOVWstore [i0] {s} p w mem))))) - // cond: !config.BigEndian && i0%4 == 0 && x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && i4 == i0+4 && i5 == i0+5 && i6 == i0+6 && i7 == i0+7 && clobber(x0, x1, x2, x3) + // cond: !config.BigEndian && x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && i4 == i0+4 && i5 == i0+5 && i6 == i0+6 && i7 == i0+7 && clobber(x0, x1, x2, x3) // result: (MOVDstore [i0] {s} p w mem) for { i7 := auxIntToInt32(v.AuxInt) @@ -7945,7 +7922,7 @@ func rewriteValuePPC64_OpPPC64MOVBstore(v *Value) bool { break } mem := x3.Args[2] - if p != x3.Args[0] || w != x3.Args[1] || !(!config.BigEndian && i0%4 == 0 && x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && i4 == i0+4 && i5 == i0+5 && i6 == i0+6 && i7 == i0+7 && clobber(x0, x1, x2, x3)) { + if p != x3.Args[0] || w != x3.Args[1] || !(!config.BigEndian && x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && i4 == i0+4 && i5 == i0+5 && i6 == i0+6 && i7 == i0+7 && clobber(x0, x1, x2, x3)) { break } v.reset(OpPPC64MOVDstore) @@ -8389,7 +8366,7 @@ func rewriteValuePPC64_OpPPC64MOVDload(v *Value) bool { return true } // match: (MOVDload [off1] {sym1} p:(MOVDaddr [off2] {sym2} ptr) mem) - // cond: canMergeSym(sym1,sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0 + // cond: canMergeSym(sym1,sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) // result: (MOVDload [off1+off2] {mergeSym(sym1,sym2)} ptr mem) for { off1 := auxIntToInt32(v.AuxInt) @@ -8402,7 +8379,7 @@ func rewriteValuePPC64_OpPPC64MOVDload(v *Value) bool { sym2 := auxToSym(p.Aux) ptr := p.Args[0] mem := v_1 - if !(canMergeSym(sym1, sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0) { + if !(canMergeSym(sym1, sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1)) { break } v.reset(OpPPC64MOVDload) @@ -8412,7 +8389,7 @@ func rewriteValuePPC64_OpPPC64MOVDload(v *Value) bool { return true } // match: (MOVDload [off1] {sym} (ADDconst [off2] x) mem) - // cond: is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0 + // cond: is16Bit(int64(off1)+off2) // result: (MOVDload [off1+int32(off2)] {sym} x mem) for { off1 := auxIntToInt32(v.AuxInt) @@ -8423,7 +8400,7 @@ func rewriteValuePPC64_OpPPC64MOVDload(v *Value) bool { off2 := auxIntToInt64(v_0.AuxInt) x := v_0.Args[0] mem := v_1 - if !(is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0) { + if !(is16Bit(int64(off1) + off2)) { break } v.reset(OpPPC64MOVDload) @@ -8520,7 +8497,7 @@ func rewriteValuePPC64_OpPPC64MOVDstore(v *Value) bool { return true } // match: (MOVDstore [off1] {sym} (ADDconst [off2] x) val mem) - // cond: is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0 + // cond: is16Bit(int64(off1)+off2) // result: (MOVDstore [off1+int32(off2)] {sym} x val mem) for { off1 := auxIntToInt32(v.AuxInt) @@ -8532,7 +8509,7 @@ func rewriteValuePPC64_OpPPC64MOVDstore(v *Value) bool { x := v_0.Args[0] val := v_1 mem := v_2 - if !(is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0) { + if !(is16Bit(int64(off1) + off2)) { break } v.reset(OpPPC64MOVDstore) @@ -8542,7 +8519,7 @@ func rewriteValuePPC64_OpPPC64MOVDstore(v *Value) bool { return true } // match: (MOVDstore [off1] {sym1} p:(MOVDaddr [off2] {sym2} ptr) val mem) - // cond: canMergeSym(sym1,sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0 + // cond: canMergeSym(sym1,sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) // result: (MOVDstore [off1+off2] {mergeSym(sym1,sym2)} ptr val mem) for { off1 := auxIntToInt32(v.AuxInt) @@ -8556,7 +8533,7 @@ func rewriteValuePPC64_OpPPC64MOVDstore(v *Value) bool { ptr := p.Args[0] val := v_1 mem := v_2 - if !(canMergeSym(sym1, sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0) { + if !(canMergeSym(sym1, sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1)) { break } v.reset(OpPPC64MOVDstore) @@ -8655,7 +8632,7 @@ func rewriteValuePPC64_OpPPC64MOVDstorezero(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (MOVDstorezero [off1] {sym} (ADDconst [off2] x) mem) - // cond: is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0 + // cond: is16Bit(int64(off1)+off2) // result: (MOVDstorezero [off1+int32(off2)] {sym} x mem) for { off1 := auxIntToInt32(v.AuxInt) @@ -8666,7 +8643,7 @@ func rewriteValuePPC64_OpPPC64MOVDstorezero(v *Value) bool { off2 := auxIntToInt64(v_0.AuxInt) x := v_0.Args[0] mem := v_1 - if !(is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0) { + if !(is16Bit(int64(off1) + off2)) { break } v.reset(OpPPC64MOVDstorezero) @@ -8676,7 +8653,7 @@ func rewriteValuePPC64_OpPPC64MOVDstorezero(v *Value) bool { return true } // match: (MOVDstorezero [off1] {sym1} p:(MOVDaddr [off2] {sym2} x) mem) - // cond: canMergeSym(sym1,sym2) && (x.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0 + // cond: canMergeSym(sym1,sym2) && (x.Op != OpSB || p.Uses == 1) // result: (MOVDstorezero [off1+off2] {mergeSym(sym1,sym2)} x mem) for { off1 := auxIntToInt32(v.AuxInt) @@ -8689,7 +8666,7 @@ func rewriteValuePPC64_OpPPC64MOVDstorezero(v *Value) bool { sym2 := auxToSym(p.Aux) x := p.Args[0] mem := v_1 - if !(canMergeSym(sym1, sym2) && (x.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0) { + if !(canMergeSym(sym1, sym2) && (x.Op != OpSB || p.Uses == 1)) { break } v.reset(OpPPC64MOVDstorezero) @@ -10595,7 +10572,7 @@ func rewriteValuePPC64_OpPPC64MOVWload(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (MOVWload [off1] {sym1} p:(MOVDaddr [off2] {sym2} ptr) mem) - // cond: canMergeSym(sym1,sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0 + // cond: canMergeSym(sym1,sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) // result: (MOVWload [off1+off2] {mergeSym(sym1,sym2)} ptr mem) for { off1 := auxIntToInt32(v.AuxInt) @@ -10608,7 +10585,7 @@ func rewriteValuePPC64_OpPPC64MOVWload(v *Value) bool { sym2 := auxToSym(p.Aux) ptr := p.Args[0] mem := v_1 - if !(canMergeSym(sym1, sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1) && (off1+off2)%4 == 0) { + if !(canMergeSym(sym1, sym2) && is16Bit(int64(off1+off2)) && (ptr.Op != OpSB || p.Uses == 1)) { break } v.reset(OpPPC64MOVWload) @@ -10618,7 +10595,7 @@ func rewriteValuePPC64_OpPPC64MOVWload(v *Value) bool { return true } // match: (MOVWload [off1] {sym} (ADDconst [off2] x) mem) - // cond: is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0 + // cond: is16Bit(int64(off1)+off2) // result: (MOVWload [off1+int32(off2)] {sym} x mem) for { off1 := auxIntToInt32(v.AuxInt) @@ -10629,7 +10606,7 @@ func rewriteValuePPC64_OpPPC64MOVWload(v *Value) bool { off2 := auxIntToInt64(v_0.AuxInt) x := v_0.Args[0] mem := v_1 - if !(is16Bit(int64(off1)+off2) && (int64(off1)+off2)%4 == 0) { + if !(is16Bit(int64(off1) + off2)) { break } v.reset(OpPPC64MOVWload) @@ -12501,7 +12478,7 @@ func rewriteValuePPC64_OpPPC64OR(v *Value) bool { break } // match: (OR s6:(SLDconst x7:(MOVBZload [i7] {s} p mem) [56]) o5:(OR s5:(SLDconst x6:(MOVBZload [i6] {s} p mem) [48]) o4:(OR s4:(SLDconst x5:(MOVBZload [i5] {s} p mem) [40]) o3:(OR s3:(SLDconst x4:(MOVBZload [i4] {s} p mem) [32]) x0:(MOVWZload {s} [i0] p mem))))) - // cond: !config.BigEndian && i0%4 == 0 && i4 == i0+4 && i5 == i0+5 && i6 == i0+6 && i7 == i0+7 && x0.Uses == 1 && x4.Uses == 1 && x5.Uses == 1 && x6.Uses ==1 && x7.Uses == 1 && o3.Uses == 1 && o4.Uses == 1 && o5.Uses == 1 && s3.Uses == 1 && s4.Uses == 1 && s5.Uses == 1 && s6.Uses == 1 && mergePoint(b, x0, x4, x5, x6, x7) != nil && clobber(x0, x4, x5, x6, x7, s3, s4, s5, s6, o3, o4, o5) + // cond: !config.BigEndian && i4 == i0+4 && i5 == i0+5 && i6 == i0+6 && i7 == i0+7 && x0.Uses == 1 && x4.Uses == 1 && x5.Uses == 1 && x6.Uses ==1 && x7.Uses == 1 && o3.Uses == 1 && o4.Uses == 1 && o5.Uses == 1 && s3.Uses == 1 && s4.Uses == 1 && s5.Uses == 1 && s6.Uses == 1 && mergePoint(b, x0, x4, x5, x6, x7) != nil && clobber(x0, x4, x5, x6, x7, s3, s4, s5, s6, o3, o4, o5) // result: @mergePoint(b,x0,x4,x5,x6,x7) (MOVDload {s} [i0] p mem) for { t := v.Type @@ -12599,7 +12576,7 @@ func rewriteValuePPC64_OpPPC64OR(v *Value) bool { continue } _ = x0.Args[1] - if p != x0.Args[0] || mem != x0.Args[1] || !(!config.BigEndian && i0%4 == 0 && i4 == i0+4 && i5 == i0+5 && i6 == i0+6 && i7 == i0+7 && x0.Uses == 1 && x4.Uses == 1 && x5.Uses == 1 && x6.Uses == 1 && x7.Uses == 1 && o3.Uses == 1 && o4.Uses == 1 && o5.Uses == 1 && s3.Uses == 1 && s4.Uses == 1 && s5.Uses == 1 && s6.Uses == 1 && mergePoint(b, x0, x4, x5, x6, x7) != nil && clobber(x0, x4, x5, x6, x7, s3, s4, s5, s6, o3, o4, o5)) { + if p != x0.Args[0] || mem != x0.Args[1] || !(!config.BigEndian && i4 == i0+4 && i5 == i0+5 && i6 == i0+6 && i7 == i0+7 && x0.Uses == 1 && x4.Uses == 1 && x5.Uses == 1 && x6.Uses == 1 && x7.Uses == 1 && o3.Uses == 1 && o4.Uses == 1 && o5.Uses == 1 && s3.Uses == 1 && s4.Uses == 1 && s5.Uses == 1 && s6.Uses == 1 && mergePoint(b, x0, x4, x5, x6, x7) != nil && clobber(x0, x4, x5, x6, x7, s3, s4, s5, s6, o3, o4, o5)) { continue } b = mergePoint(b, x0, x4, x5, x6, x7) @@ -13264,7 +13241,7 @@ func rewriteValuePPC64_OpPPC64SLDconst(v *Value) bool { break } // match: (SLDconst [c] z:(MOVWreg x)) - // cond: c < 32 && objabi.GOPPC64 >= 9 + // cond: c < 32 && buildcfg.GOPPC64 >= 9 // result: (EXTSWSLconst [c] x) for { c := auxIntToInt64(v.AuxInt) @@ -13273,7 +13250,7 @@ func rewriteValuePPC64_OpPPC64SLDconst(v *Value) bool { break } x := z.Args[0] - if !(c < 32 && objabi.GOPPC64 >= 9) { + if !(c < 32 && buildcfg.GOPPC64 >= 9) { break } v.reset(OpPPC64EXTSWSLconst) @@ -13387,7 +13364,7 @@ func rewriteValuePPC64_OpPPC64SLWconst(v *Value) bool { break } // match: (SLWconst [c] z:(MOVWreg x)) - // cond: c < 32 && objabi.GOPPC64 >= 9 + // cond: c < 32 && buildcfg.GOPPC64 >= 9 // result: (EXTSWSLconst [c] x) for { c := auxIntToInt64(v.AuxInt) @@ -13396,7 +13373,7 @@ func rewriteValuePPC64_OpPPC64SLWconst(v *Value) bool { break } x := z.Args[0] - if !(c < 32 && objabi.GOPPC64 >= 9) { + if !(c < 32 && buildcfg.GOPPC64 >= 9) { break } v.reset(OpPPC64EXTSWSLconst) @@ -16844,51 +16821,25 @@ func rewriteValuePPC64_OpZero(v *Value) bool { return true } // match: (Zero [8] {t} destptr mem) - // cond: t.Alignment()%4 == 0 // result: (MOVDstorezero destptr mem) for { if auxIntToInt64(v.AuxInt) != 8 { break } - t := auxToType(v.Aux) destptr := v_0 mem := v_1 - if !(t.Alignment()%4 == 0) { - break - } v.reset(OpPPC64MOVDstorezero) v.AddArg2(destptr, mem) return true } - // match: (Zero [8] destptr mem) - // result: (MOVWstorezero [4] destptr (MOVWstorezero [0] destptr mem)) - for { - if auxIntToInt64(v.AuxInt) != 8 { - break - } - destptr := v_0 - mem := v_1 - v.reset(OpPPC64MOVWstorezero) - v.AuxInt = int32ToAuxInt(4) - v0 := b.NewValue0(v.Pos, OpPPC64MOVWstorezero, types.TypeMem) - v0.AuxInt = int32ToAuxInt(0) - v0.AddArg2(destptr, mem) - v.AddArg2(destptr, v0) - return true - } // match: (Zero [12] {t} destptr mem) - // cond: t.Alignment()%4 == 0 // result: (MOVWstorezero [8] destptr (MOVDstorezero [0] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 12 { break } - t := auxToType(v.Aux) destptr := v_0 mem := v_1 - if !(t.Alignment()%4 == 0) { - break - } v.reset(OpPPC64MOVWstorezero) v.AuxInt = int32ToAuxInt(8) v0 := b.NewValue0(v.Pos, OpPPC64MOVDstorezero, types.TypeMem) @@ -16898,18 +16849,13 @@ func rewriteValuePPC64_OpZero(v *Value) bool { return true } // match: (Zero [16] {t} destptr mem) - // cond: t.Alignment()%4 == 0 // result: (MOVDstorezero [8] destptr (MOVDstorezero [0] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 16 { break } - t := auxToType(v.Aux) destptr := v_0 mem := v_1 - if !(t.Alignment()%4 == 0) { - break - } v.reset(OpPPC64MOVDstorezero) v.AuxInt = int32ToAuxInt(8) v0 := b.NewValue0(v.Pos, OpPPC64MOVDstorezero, types.TypeMem) @@ -16919,18 +16865,13 @@ func rewriteValuePPC64_OpZero(v *Value) bool { return true } // match: (Zero [24] {t} destptr mem) - // cond: t.Alignment()%4 == 0 // result: (MOVDstorezero [16] destptr (MOVDstorezero [8] destptr (MOVDstorezero [0] destptr mem))) for { if auxIntToInt64(v.AuxInt) != 24 { break } - t := auxToType(v.Aux) destptr := v_0 mem := v_1 - if !(t.Alignment()%4 == 0) { - break - } v.reset(OpPPC64MOVDstorezero) v.AuxInt = int32ToAuxInt(16) v0 := b.NewValue0(v.Pos, OpPPC64MOVDstorezero, types.TypeMem) @@ -16943,18 +16884,13 @@ func rewriteValuePPC64_OpZero(v *Value) bool { return true } // match: (Zero [32] {t} destptr mem) - // cond: t.Alignment()%4 == 0 // result: (MOVDstorezero [24] destptr (MOVDstorezero [16] destptr (MOVDstorezero [8] destptr (MOVDstorezero [0] destptr mem)))) for { if auxIntToInt64(v.AuxInt) != 32 { break } - t := auxToType(v.Aux) destptr := v_0 mem := v_1 - if !(t.Alignment()%4 == 0) { - break - } v.reset(OpPPC64MOVDstorezero) v.AuxInt = int32ToAuxInt(24) v0 := b.NewValue0(v.Pos, OpPPC64MOVDstorezero, types.TypeMem) @@ -16970,13 +16906,13 @@ func rewriteValuePPC64_OpZero(v *Value) bool { return true } // match: (Zero [s] ptr mem) - // cond: objabi.GOPPC64 <= 8 && s < 64 + // cond: buildcfg.GOPPC64 <= 8 && s < 64 // result: (LoweredZeroShort [s] ptr mem) for { s := auxIntToInt64(v.AuxInt) ptr := v_0 mem := v_1 - if !(objabi.GOPPC64 <= 8 && s < 64) { + if !(buildcfg.GOPPC64 <= 8 && s < 64) { break } v.reset(OpPPC64LoweredZeroShort) @@ -16985,13 +16921,13 @@ func rewriteValuePPC64_OpZero(v *Value) bool { return true } // match: (Zero [s] ptr mem) - // cond: objabi.GOPPC64 <= 8 + // cond: buildcfg.GOPPC64 <= 8 // result: (LoweredZero [s] ptr mem) for { s := auxIntToInt64(v.AuxInt) ptr := v_0 mem := v_1 - if !(objabi.GOPPC64 <= 8) { + if !(buildcfg.GOPPC64 <= 8) { break } v.reset(OpPPC64LoweredZero) @@ -17000,13 +16936,13 @@ func rewriteValuePPC64_OpZero(v *Value) bool { return true } // match: (Zero [s] ptr mem) - // cond: s < 128 && objabi.GOPPC64 >= 9 + // cond: s < 128 && buildcfg.GOPPC64 >= 9 // result: (LoweredQuadZeroShort [s] ptr mem) for { s := auxIntToInt64(v.AuxInt) ptr := v_0 mem := v_1 - if !(s < 128 && objabi.GOPPC64 >= 9) { + if !(s < 128 && buildcfg.GOPPC64 >= 9) { break } v.reset(OpPPC64LoweredQuadZeroShort) @@ -17015,13 +16951,13 @@ func rewriteValuePPC64_OpZero(v *Value) bool { return true } // match: (Zero [s] ptr mem) - // cond: objabi.GOPPC64 >= 9 + // cond: buildcfg.GOPPC64 >= 9 // result: (LoweredQuadZero [s] ptr mem) for { s := auxIntToInt64(v.AuxInt) ptr := v_0 mem := v_1 - if !(objabi.GOPPC64 >= 9) { + if !(buildcfg.GOPPC64 >= 9) { break } v.reset(OpPPC64LoweredQuadZero) diff --git a/src/cmd/compile/internal/ssa/rewriteRISCV64.go b/src/cmd/compile/internal/ssa/rewriteRISCV64.go index fb507b65c4ce9a5e9be8d4b17c772d66535fecf6..431fb1aaf66e0dd288704707d9ed909780533d4b 100644 --- a/src/cmd/compile/internal/ssa/rewriteRISCV64.go +++ b/src/cmd/compile/internal/ssa/rewriteRISCV64.go @@ -52,6 +52,11 @@ func rewriteValueRISCV64(v *Value) bool { case OpAtomicAdd64: v.Op = OpRISCV64LoweredAtomicAdd64 return true + case OpAtomicAnd32: + v.Op = OpRISCV64LoweredAtomicAnd32 + return true + case OpAtomicAnd8: + return rewriteValueRISCV64_OpAtomicAnd8(v) case OpAtomicCompareAndSwap32: v.Op = OpRISCV64LoweredAtomicCas32 return true @@ -76,6 +81,11 @@ func rewriteValueRISCV64(v *Value) bool { case OpAtomicLoadPtr: v.Op = OpRISCV64LoweredAtomicLoad64 return true + case OpAtomicOr32: + v.Op = OpRISCV64LoweredAtomicOr32 + return true + case OpAtomicOr8: + return rewriteValueRISCV64_OpAtomicOr8(v) case OpAtomicStore32: v.Op = OpRISCV64LoweredAtomicStore32 return true @@ -106,21 +116,17 @@ func rewriteValueRISCV64(v *Value) bool { v.Op = OpRISCV64NOT return true case OpConst16: - v.Op = OpRISCV64MOVHconst - return true + return rewriteValueRISCV64_OpConst16(v) case OpConst32: - v.Op = OpRISCV64MOVWconst - return true + return rewriteValueRISCV64_OpConst32(v) case OpConst32F: return rewriteValueRISCV64_OpConst32F(v) case OpConst64: - v.Op = OpRISCV64MOVDconst - return true + return rewriteValueRISCV64_OpConst64(v) case OpConst64F: return rewriteValueRISCV64_OpConst64F(v) case OpConst8: - v.Op = OpRISCV64MOVBconst - return true + return rewriteValueRISCV64_OpConst8(v) case OpConstBool: return rewriteValueRISCV64_OpConstBool(v) case OpConstNil: @@ -229,7 +235,8 @@ func rewriteValueRISCV64(v *Value) bool { v.Op = OpLess64U return true case OpIsNonNil: - return rewriteValueRISCV64_OpIsNonNil(v) + v.Op = OpRISCV64SNEZ + return true case OpIsSliceInBounds: v.Op = OpLeq64U return true @@ -431,10 +438,10 @@ func rewriteValueRISCV64(v *Value) bool { return rewriteValueRISCV64_OpRISCV64MOVBstore(v) case OpRISCV64MOVBstorezero: return rewriteValueRISCV64_OpRISCV64MOVBstorezero(v) - case OpRISCV64MOVDconst: - return rewriteValueRISCV64_OpRISCV64MOVDconst(v) case OpRISCV64MOVDload: return rewriteValueRISCV64_OpRISCV64MOVDload(v) + case OpRISCV64MOVDnop: + return rewriteValueRISCV64_OpRISCV64MOVDnop(v) case OpRISCV64MOVDreg: return rewriteValueRISCV64_OpRISCV64MOVDreg(v) case OpRISCV64MOVDstore: @@ -580,6 +587,9 @@ func rewriteValueRISCV64(v *Value) bool { case OpSqrt: v.Op = OpRISCV64FSQRTD return true + case OpSqrt32: + v.Op = OpRISCV64FSQRTS + return true case OpStaticCall: v.Op = OpRISCV64CALLstatic return true @@ -676,6 +686,71 @@ func rewriteValueRISCV64_OpAddr(v *Value) bool { return true } } +func rewriteValueRISCV64_OpAtomicAnd8(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + typ := &b.Func.Config.Types + // match: (AtomicAnd8 ptr val mem) + // result: (LoweredAtomicAnd32 (ANDI [^3] ptr) (NOT (SLL (XORI [0xff] (ZeroExt8to32 val)) (SLLI [3] (ANDI [3] ptr)))) mem) + for { + ptr := v_0 + val := v_1 + mem := v_2 + v.reset(OpRISCV64LoweredAtomicAnd32) + v0 := b.NewValue0(v.Pos, OpRISCV64ANDI, typ.Uintptr) + v0.AuxInt = int64ToAuxInt(^3) + v0.AddArg(ptr) + v1 := b.NewValue0(v.Pos, OpRISCV64NOT, typ.UInt32) + v2 := b.NewValue0(v.Pos, OpRISCV64SLL, typ.UInt32) + v3 := b.NewValue0(v.Pos, OpRISCV64XORI, typ.UInt32) + v3.AuxInt = int64ToAuxInt(0xff) + v4 := b.NewValue0(v.Pos, OpZeroExt8to32, typ.UInt32) + v4.AddArg(val) + v3.AddArg(v4) + v5 := b.NewValue0(v.Pos, OpRISCV64SLLI, typ.UInt64) + v5.AuxInt = int64ToAuxInt(3) + v6 := b.NewValue0(v.Pos, OpRISCV64ANDI, typ.UInt64) + v6.AuxInt = int64ToAuxInt(3) + v6.AddArg(ptr) + v5.AddArg(v6) + v2.AddArg2(v3, v5) + v1.AddArg(v2) + v.AddArg3(v0, v1, mem) + return true + } +} +func rewriteValueRISCV64_OpAtomicOr8(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + typ := &b.Func.Config.Types + // match: (AtomicOr8 ptr val mem) + // result: (LoweredAtomicOr32 (ANDI [^3] ptr) (SLL (ZeroExt8to32 val) (SLLI [3] (ANDI [3] ptr))) mem) + for { + ptr := v_0 + val := v_1 + mem := v_2 + v.reset(OpRISCV64LoweredAtomicOr32) + v0 := b.NewValue0(v.Pos, OpRISCV64ANDI, typ.Uintptr) + v0.AuxInt = int64ToAuxInt(^3) + v0.AddArg(ptr) + v1 := b.NewValue0(v.Pos, OpRISCV64SLL, typ.UInt32) + v2 := b.NewValue0(v.Pos, OpZeroExt8to32, typ.UInt32) + v2.AddArg(val) + v3 := b.NewValue0(v.Pos, OpRISCV64SLLI, typ.UInt64) + v3.AuxInt = int64ToAuxInt(3) + v4 := b.NewValue0(v.Pos, OpRISCV64ANDI, typ.UInt64) + v4.AuxInt = int64ToAuxInt(3) + v4.AddArg(ptr) + v3.AddArg(v4) + v1.AddArg2(v2, v3) + v.AddArg3(v0, v1, mem) + return true + } +} func rewriteValueRISCV64_OpAvg64u(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] @@ -704,20 +779,50 @@ func rewriteValueRISCV64_OpAvg64u(v *Value) bool { return true } } +func rewriteValueRISCV64_OpConst16(v *Value) bool { + // match: (Const16 [val]) + // result: (MOVDconst [int64(val)]) + for { + val := auxIntToInt16(v.AuxInt) + v.reset(OpRISCV64MOVDconst) + v.AuxInt = int64ToAuxInt(int64(val)) + return true + } +} +func rewriteValueRISCV64_OpConst32(v *Value) bool { + // match: (Const32 [val]) + // result: (MOVDconst [int64(val)]) + for { + val := auxIntToInt32(v.AuxInt) + v.reset(OpRISCV64MOVDconst) + v.AuxInt = int64ToAuxInt(int64(val)) + return true + } +} func rewriteValueRISCV64_OpConst32F(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (Const32F [val]) - // result: (FMVSX (MOVWconst [int32(math.Float32bits(val))])) + // result: (FMVSX (MOVDconst [int64(math.Float32bits(val))])) for { val := auxIntToFloat32(v.AuxInt) v.reset(OpRISCV64FMVSX) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVWconst, typ.UInt32) - v0.AuxInt = int32ToAuxInt(int32(math.Float32bits(val))) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(int64(math.Float32bits(val))) v.AddArg(v0) return true } } +func rewriteValueRISCV64_OpConst64(v *Value) bool { + // match: (Const64 [val]) + // result: (MOVDconst [int64(val)]) + for { + val := auxIntToInt64(v.AuxInt) + v.reset(OpRISCV64MOVDconst) + v.AuxInt = int64ToAuxInt(int64(val)) + return true + } +} func rewriteValueRISCV64_OpConst64F(v *Value) bool { b := v.Block typ := &b.Func.Config.Types @@ -732,13 +837,23 @@ func rewriteValueRISCV64_OpConst64F(v *Value) bool { return true } } +func rewriteValueRISCV64_OpConst8(v *Value) bool { + // match: (Const8 [val]) + // result: (MOVDconst [int64(val)]) + for { + val := auxIntToInt8(v.AuxInt) + v.reset(OpRISCV64MOVDconst) + v.AuxInt = int64ToAuxInt(int64(val)) + return true + } +} func rewriteValueRISCV64_OpConstBool(v *Value) bool { // match: (ConstBool [val]) - // result: (MOVBconst [int8(b2i(val))]) + // result: (MOVDconst [int64(b2i(val))]) for { val := auxIntToBool(v.AuxInt) - v.reset(OpRISCV64MOVBconst) - v.AuxInt = int8ToAuxInt(int8(b2i(val))) + v.reset(OpRISCV64MOVDconst) + v.AuxInt = int64ToAuxInt(int64(b2i(val))) return true } } @@ -890,14 +1005,19 @@ func rewriteValueRISCV64_OpEq32(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + typ := &b.Func.Config.Types // match: (Eq32 x y) - // result: (SEQZ (SUBW x y)) + // result: (SEQZ (SUB (ZeroExt32to64 x) (ZeroExt32to64 y))) for { x := v_0 y := v_1 v.reset(OpRISCV64SEQZ) - v0 := b.NewValue0(v.Pos, OpRISCV64SUBW, x.Type) - v0.AddArg2(x, y) + v0 := b.NewValue0(v.Pos, OpRISCV64SUB, x.Type) + v1 := b.NewValue0(v.Pos, OpZeroExt32to64, typ.UInt64) + v1.AddArg(x) + v2 := b.NewValue0(v.Pos, OpZeroExt32to64, typ.UInt64) + v2.AddArg(y) + v0.AddArg2(v1, v2) v.AddArg(v0) return true } @@ -1016,21 +1136,6 @@ func rewriteValueRISCV64_OpHmul32u(v *Value) bool { return true } } -func rewriteValueRISCV64_OpIsNonNil(v *Value) bool { - v_0 := v.Args[0] - b := v.Block - typ := &b.Func.Config.Types - // match: (IsNonNil p) - // result: (NeqPtr (MOVDconst [0]) p) - for { - p := v_0 - v.reset(OpNeqPtr) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) - v0.AuxInt = int64ToAuxInt(0) - v.AddArg2(v0, p) - return true - } -} func rewriteValueRISCV64_OpLeq16(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] @@ -2466,14 +2571,19 @@ func rewriteValueRISCV64_OpNeq32(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + typ := &b.Func.Config.Types // match: (Neq32 x y) - // result: (SNEZ (SUBW x y)) + // result: (SNEZ (SUB (ZeroExt32to64 x) (ZeroExt32to64 y))) for { x := v_0 y := v_1 v.reset(OpRISCV64SNEZ) - v0 := b.NewValue0(v.Pos, OpRISCV64SUBW, x.Type) - v0.AddArg2(x, y) + v0 := b.NewValue0(v.Pos, OpRISCV64SUB, x.Type) + v1 := b.NewValue0(v.Pos, OpZeroExt32to64, typ.UInt64) + v1.AddArg(x) + v2 := b.NewValue0(v.Pos, OpZeroExt32to64, typ.UInt64) + v2.AddArg(y) + v0.AddArg2(v1, v2) v.AddArg(v0) return true } @@ -2632,54 +2742,6 @@ func rewriteValueRISCV64_OpPanicBounds(v *Value) bool { func rewriteValueRISCV64_OpRISCV64ADD(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] - // match: (ADD (MOVBconst [val]) x) - // result: (ADDI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVBconst { - continue - } - val := auxIntToInt8(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64ADDI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } - // match: (ADD (MOVHconst [val]) x) - // result: (ADDI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVHconst { - continue - } - val := auxIntToInt16(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64ADDI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } - // match: (ADD (MOVWconst [val]) x) - // result: (ADDI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVWconst { - continue - } - val := auxIntToInt32(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64ADDI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } // match: (ADD (MOVDconst [val]) x) // cond: is32Bit(val) // result: (ADDI [val] x) @@ -2739,54 +2801,6 @@ func rewriteValueRISCV64_OpRISCV64ADDI(v *Value) bool { func rewriteValueRISCV64_OpRISCV64AND(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] - // match: (AND (MOVBconst [val]) x) - // result: (ANDI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVBconst { - continue - } - val := auxIntToInt8(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64ANDI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } - // match: (AND (MOVHconst [val]) x) - // result: (ANDI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVHconst { - continue - } - val := auxIntToInt16(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64ANDI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } - // match: (AND (MOVWconst [val]) x) - // result: (ANDI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVWconst { - continue - } - val := auxIntToInt32(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64ANDI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } // match: (AND (MOVDconst [val]) x) // cond: is32Bit(val) // result: (ANDI [val] x) @@ -2860,13 +2874,13 @@ func rewriteValueRISCV64_OpRISCV64MOVBUload(v *Value) bool { func rewriteValueRISCV64_OpRISCV64MOVBUreg(v *Value) bool { v_0 := v.Args[0] b := v.Block - // match: (MOVBUreg (MOVBconst [c])) + // match: (MOVBUreg (MOVDconst [c])) // result: (MOVDconst [int64(uint8(c))]) for { - if v_0.Op != OpRISCV64MOVBconst { + if v_0.Op != OpRISCV64MOVDconst { break } - c := auxIntToInt8(v_0.AuxInt) + c := auxIntToInt64(v_0.AuxInt) v.reset(OpRISCV64MOVDconst) v.AuxInt = int64ToAuxInt(int64(uint8(c))) return true @@ -2970,15 +2984,15 @@ func rewriteValueRISCV64_OpRISCV64MOVBload(v *Value) bool { func rewriteValueRISCV64_OpRISCV64MOVBreg(v *Value) bool { v_0 := v.Args[0] b := v.Block - // match: (MOVBreg (MOVBconst [c])) - // result: (MOVDconst [int64(c)]) + // match: (MOVBreg (MOVDconst [c])) + // result: (MOVDconst [int64(int8(c))]) for { - if v_0.Op != OpRISCV64MOVBconst { + if v_0.Op != OpRISCV64MOVDconst { break } - c := auxIntToInt8(v_0.AuxInt) + c := auxIntToInt64(v_0.AuxInt) v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(c)) + v.AuxInt = int64ToAuxInt(int64(int8(c))) return true } // match: (MOVBreg x:(MOVBload _ _)) @@ -3078,13 +3092,13 @@ func rewriteValueRISCV64_OpRISCV64MOVBstore(v *Value) bool { v.AddArg3(base, val, mem) return true } - // match: (MOVBstore [off] {sym} ptr (MOVBconst [0]) mem) + // match: (MOVBstore [off] {sym} ptr (MOVDconst [0]) mem) // result: (MOVBstorezero [off] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) ptr := v_0 - if v_1.Op != OpRISCV64MOVBconst || auxIntToInt8(v_1.AuxInt) != 0 { + if v_1.Op != OpRISCV64MOVDconst || auxIntToInt64(v_1.AuxInt) != 0 { break } mem := v_2 @@ -3246,51 +3260,6 @@ func rewriteValueRISCV64_OpRISCV64MOVBstorezero(v *Value) bool { } return false } -func rewriteValueRISCV64_OpRISCV64MOVDconst(v *Value) bool { - b := v.Block - typ := &b.Func.Config.Types - // match: (MOVDconst [c]) - // cond: !is32Bit(c) && int32(c) < 0 - // result: (ADD (SLLI [32] (MOVDconst [c>>32+1])) (MOVDconst [int64(int32(c))])) - for { - t := v.Type - c := auxIntToInt64(v.AuxInt) - if !(!is32Bit(c) && int32(c) < 0) { - break - } - v.reset(OpRISCV64ADD) - v0 := b.NewValue0(v.Pos, OpRISCV64SLLI, t) - v0.AuxInt = int64ToAuxInt(32) - v1 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) - v1.AuxInt = int64ToAuxInt(c>>32 + 1) - v0.AddArg(v1) - v2 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) - v2.AuxInt = int64ToAuxInt(int64(int32(c))) - v.AddArg2(v0, v2) - return true - } - // match: (MOVDconst [c]) - // cond: !is32Bit(c) && int32(c) >= 0 - // result: (ADD (SLLI [32] (MOVDconst [c>>32+0])) (MOVDconst [int64(int32(c))])) - for { - t := v.Type - c := auxIntToInt64(v.AuxInt) - if !(!is32Bit(c) && int32(c) >= 0) { - break - } - v.reset(OpRISCV64ADD) - v0 := b.NewValue0(v.Pos, OpRISCV64SLLI, t) - v0.AuxInt = int64ToAuxInt(32) - v1 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) - v1.AuxInt = int64ToAuxInt(c>>32 + 0) - v0.AddArg(v1) - v2 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) - v2.AuxInt = int64ToAuxInt(int64(int32(c))) - v.AddArg2(v0, v2) - return true - } - return false -} func rewriteValueRISCV64_OpRISCV64MOVDload(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] @@ -3339,6 +3308,21 @@ func rewriteValueRISCV64_OpRISCV64MOVDload(v *Value) bool { } return false } +func rewriteValueRISCV64_OpRISCV64MOVDnop(v *Value) bool { + v_0 := v.Args[0] + // match: (MOVDnop (MOVDconst [c])) + // result: (MOVDconst [c]) + for { + if v_0.Op != OpRISCV64MOVDconst { + break + } + c := auxIntToInt64(v_0.AuxInt) + v.reset(OpRISCV64MOVDconst) + v.AuxInt = int64ToAuxInt(c) + return true + } + return false +} func rewriteValueRISCV64_OpRISCV64MOVDreg(v *Value) bool { v_0 := v.Args[0] // match: (MOVDreg x) @@ -3521,24 +3505,13 @@ func rewriteValueRISCV64_OpRISCV64MOVHUload(v *Value) bool { func rewriteValueRISCV64_OpRISCV64MOVHUreg(v *Value) bool { v_0 := v.Args[0] b := v.Block - // match: (MOVHUreg (MOVBconst [c])) - // result: (MOVDconst [int64(uint16(c))]) - for { - if v_0.Op != OpRISCV64MOVBconst { - break - } - c := auxIntToInt8(v_0.AuxInt) - v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(uint16(c))) - return true - } - // match: (MOVHUreg (MOVHconst [c])) + // match: (MOVHUreg (MOVDconst [c])) // result: (MOVDconst [int64(uint16(c))]) for { - if v_0.Op != OpRISCV64MOVHconst { + if v_0.Op != OpRISCV64MOVDconst { break } - c := auxIntToInt16(v_0.AuxInt) + c := auxIntToInt64(v_0.AuxInt) v.reset(OpRISCV64MOVDconst) v.AuxInt = int64ToAuxInt(int64(uint16(c))) return true @@ -3664,26 +3637,15 @@ func rewriteValueRISCV64_OpRISCV64MOVHload(v *Value) bool { func rewriteValueRISCV64_OpRISCV64MOVHreg(v *Value) bool { v_0 := v.Args[0] b := v.Block - // match: (MOVHreg (MOVBconst [c])) - // result: (MOVDconst [int64(c)]) - for { - if v_0.Op != OpRISCV64MOVBconst { - break - } - c := auxIntToInt8(v_0.AuxInt) - v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(c)) - return true - } - // match: (MOVHreg (MOVHconst [c])) - // result: (MOVDconst [int64(c)]) + // match: (MOVHreg (MOVDconst [c])) + // result: (MOVDconst [int64(int16(c))]) for { - if v_0.Op != OpRISCV64MOVHconst { + if v_0.Op != OpRISCV64MOVDconst { break } - c := auxIntToInt16(v_0.AuxInt) + c := auxIntToInt64(v_0.AuxInt) v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(c)) + v.AuxInt = int64ToAuxInt(int64(int16(c))) return true } // match: (MOVHreg x:(MOVBload _ _)) @@ -3827,13 +3789,13 @@ func rewriteValueRISCV64_OpRISCV64MOVHstore(v *Value) bool { v.AddArg3(base, val, mem) return true } - // match: (MOVHstore [off] {sym} ptr (MOVHconst [0]) mem) + // match: (MOVHstore [off] {sym} ptr (MOVDconst [0]) mem) // result: (MOVHstorezero [off] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) ptr := v_0 - if v_1.Op != OpRISCV64MOVHconst || auxIntToInt16(v_1.AuxInt) != 0 { + if v_1.Op != OpRISCV64MOVDconst || auxIntToInt64(v_1.AuxInt) != 0 { break } mem := v_2 @@ -4012,35 +3974,13 @@ func rewriteValueRISCV64_OpRISCV64MOVWUload(v *Value) bool { func rewriteValueRISCV64_OpRISCV64MOVWUreg(v *Value) bool { v_0 := v.Args[0] b := v.Block - // match: (MOVWUreg (MOVBconst [c])) + // match: (MOVWUreg (MOVDconst [c])) // result: (MOVDconst [int64(uint32(c))]) for { - if v_0.Op != OpRISCV64MOVBconst { + if v_0.Op != OpRISCV64MOVDconst { break } - c := auxIntToInt8(v_0.AuxInt) - v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(uint32(c))) - return true - } - // match: (MOVWUreg (MOVHconst [c])) - // result: (MOVDconst [int64(uint32(c))]) - for { - if v_0.Op != OpRISCV64MOVHconst { - break - } - c := auxIntToInt16(v_0.AuxInt) - v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(uint32(c))) - return true - } - // match: (MOVWUreg (MOVWconst [c])) - // result: (MOVDconst [int64(uint32(c))]) - for { - if v_0.Op != OpRISCV64MOVWconst { - break - } - c := auxIntToInt32(v_0.AuxInt) + c := auxIntToInt64(v_0.AuxInt) v.reset(OpRISCV64MOVDconst) v.AuxInt = int64ToAuxInt(int64(uint32(c))) return true @@ -4188,37 +4128,15 @@ func rewriteValueRISCV64_OpRISCV64MOVWload(v *Value) bool { func rewriteValueRISCV64_OpRISCV64MOVWreg(v *Value) bool { v_0 := v.Args[0] b := v.Block - // match: (MOVWreg (MOVBconst [c])) - // result: (MOVDconst [int64(c)]) - for { - if v_0.Op != OpRISCV64MOVBconst { - break - } - c := auxIntToInt8(v_0.AuxInt) - v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(c)) - return true - } - // match: (MOVWreg (MOVHconst [c])) - // result: (MOVDconst [int64(c)]) - for { - if v_0.Op != OpRISCV64MOVHconst { - break - } - c := auxIntToInt16(v_0.AuxInt) - v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(c)) - return true - } - // match: (MOVWreg (MOVWconst [c])) - // result: (MOVDconst [int64(c)]) + // match: (MOVWreg (MOVDconst [c])) + // result: (MOVDconst [int64(int32(c))]) for { - if v_0.Op != OpRISCV64MOVWconst { + if v_0.Op != OpRISCV64MOVDconst { break } - c := auxIntToInt32(v_0.AuxInt) + c := auxIntToInt64(v_0.AuxInt) v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(c)) + v.AuxInt = int64ToAuxInt(int64(int32(c))) return true } // match: (MOVWreg x:(MOVBload _ _)) @@ -4395,13 +4313,13 @@ func rewriteValueRISCV64_OpRISCV64MOVWstore(v *Value) bool { v.AddArg3(base, val, mem) return true } - // match: (MOVWstore [off] {sym} ptr (MOVWconst [0]) mem) + // match: (MOVWstore [off] {sym} ptr (MOVDconst [0]) mem) // result: (MOVWstorezero [off] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) ptr := v_0 - if v_1.Op != OpRISCV64MOVWconst || auxIntToInt32(v_1.AuxInt) != 0 { + if v_1.Op != OpRISCV64MOVDconst || auxIntToInt64(v_1.AuxInt) != 0 { break } mem := v_2 @@ -4498,54 +4416,6 @@ func rewriteValueRISCV64_OpRISCV64MOVWstorezero(v *Value) bool { func rewriteValueRISCV64_OpRISCV64OR(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] - // match: (OR (MOVBconst [val]) x) - // result: (ORI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVBconst { - continue - } - val := auxIntToInt8(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64ORI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } - // match: (OR (MOVHconst [val]) x) - // result: (ORI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVHconst { - continue - } - val := auxIntToInt16(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64ORI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } - // match: (OR (MOVWconst [val]) x) - // result: (ORI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVWconst { - continue - } - val := auxIntToInt32(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64ORI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } // match: (OR (MOVDconst [val]) x) // cond: is32Bit(val) // result: (ORI [val] x) @@ -4571,45 +4441,6 @@ func rewriteValueRISCV64_OpRISCV64OR(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SLL(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] - // match: (SLL x (MOVBconst [val])) - // result: (SLLI [int64(val&63)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVBconst { - break - } - val := auxIntToInt8(v_1.AuxInt) - v.reset(OpRISCV64SLLI) - v.AuxInt = int64ToAuxInt(int64(val & 63)) - v.AddArg(x) - return true - } - // match: (SLL x (MOVHconst [val])) - // result: (SLLI [int64(val&63)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVHconst { - break - } - val := auxIntToInt16(v_1.AuxInt) - v.reset(OpRISCV64SLLI) - v.AuxInt = int64ToAuxInt(int64(val & 63)) - v.AddArg(x) - return true - } - // match: (SLL x (MOVWconst [val])) - // result: (SLLI [int64(val&63)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVWconst { - break - } - val := auxIntToInt32(v_1.AuxInt) - v.reset(OpRISCV64SLLI) - v.AuxInt = int64ToAuxInt(int64(val & 63)) - v.AddArg(x) - return true - } // match: (SLL x (MOVDconst [val])) // result: (SLLI [int64(val&63)] x) for { @@ -4628,45 +4459,6 @@ func rewriteValueRISCV64_OpRISCV64SLL(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SRA(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] - // match: (SRA x (MOVBconst [val])) - // result: (SRAI [int64(val&63)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVBconst { - break - } - val := auxIntToInt8(v_1.AuxInt) - v.reset(OpRISCV64SRAI) - v.AuxInt = int64ToAuxInt(int64(val & 63)) - v.AddArg(x) - return true - } - // match: (SRA x (MOVHconst [val])) - // result: (SRAI [int64(val&63)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVHconst { - break - } - val := auxIntToInt16(v_1.AuxInt) - v.reset(OpRISCV64SRAI) - v.AuxInt = int64ToAuxInt(int64(val & 63)) - v.AddArg(x) - return true - } - // match: (SRA x (MOVWconst [val])) - // result: (SRAI [int64(val&63)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVWconst { - break - } - val := auxIntToInt32(v_1.AuxInt) - v.reset(OpRISCV64SRAI) - v.AuxInt = int64ToAuxInt(int64(val & 63)) - v.AddArg(x) - return true - } // match: (SRA x (MOVDconst [val])) // result: (SRAI [int64(val&63)] x) for { @@ -4685,45 +4477,6 @@ func rewriteValueRISCV64_OpRISCV64SRA(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SRL(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] - // match: (SRL x (MOVBconst [val])) - // result: (SRLI [int64(val&63)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVBconst { - break - } - val := auxIntToInt8(v_1.AuxInt) - v.reset(OpRISCV64SRLI) - v.AuxInt = int64ToAuxInt(int64(val & 63)) - v.AddArg(x) - return true - } - // match: (SRL x (MOVHconst [val])) - // result: (SRLI [int64(val&63)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVHconst { - break - } - val := auxIntToInt16(v_1.AuxInt) - v.reset(OpRISCV64SRLI) - v.AuxInt = int64ToAuxInt(int64(val & 63)) - v.AddArg(x) - return true - } - // match: (SRL x (MOVWconst [val])) - // result: (SRLI [int64(val&63)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVWconst { - break - } - val := auxIntToInt32(v_1.AuxInt) - v.reset(OpRISCV64SRLI) - v.AuxInt = int64ToAuxInt(int64(val & 63)) - v.AddArg(x) - return true - } // match: (SRL x (MOVDconst [val])) // result: (SRLI [int64(val&63)] x) for { @@ -4742,49 +4495,6 @@ func rewriteValueRISCV64_OpRISCV64SRL(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SUB(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] - // match: (SUB x (MOVBconst [val])) - // result: (ADDI [-int64(val)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVBconst { - break - } - val := auxIntToInt8(v_1.AuxInt) - v.reset(OpRISCV64ADDI) - v.AuxInt = int64ToAuxInt(-int64(val)) - v.AddArg(x) - return true - } - // match: (SUB x (MOVHconst [val])) - // result: (ADDI [-int64(val)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVHconst { - break - } - val := auxIntToInt16(v_1.AuxInt) - v.reset(OpRISCV64ADDI) - v.AuxInt = int64ToAuxInt(-int64(val)) - v.AddArg(x) - return true - } - // match: (SUB x (MOVWconst [val])) - // cond: is32Bit(-int64(val)) - // result: (ADDI [-int64(val)] x) - for { - x := v_0 - if v_1.Op != OpRISCV64MOVWconst { - break - } - val := auxIntToInt32(v_1.AuxInt) - if !(is32Bit(-int64(val))) { - break - } - v.reset(OpRISCV64ADDI) - v.AuxInt = int64ToAuxInt(-int64(val)) - v.AddArg(x) - return true - } // match: (SUB x (MOVDconst [val])) // cond: is32Bit(-val) // result: (ADDI [-val] x) @@ -4802,36 +4512,6 @@ func rewriteValueRISCV64_OpRISCV64SUB(v *Value) bool { v.AddArg(x) return true } - // match: (SUB x (MOVBconst [0])) - // result: x - for { - x := v_0 - if v_1.Op != OpRISCV64MOVBconst || auxIntToInt8(v_1.AuxInt) != 0 { - break - } - v.copyOf(x) - return true - } - // match: (SUB x (MOVHconst [0])) - // result: x - for { - x := v_0 - if v_1.Op != OpRISCV64MOVHconst || auxIntToInt16(v_1.AuxInt) != 0 { - break - } - v.copyOf(x) - return true - } - // match: (SUB x (MOVWconst [0])) - // result: x - for { - x := v_0 - if v_1.Op != OpRISCV64MOVWconst || auxIntToInt32(v_1.AuxInt) != 0 { - break - } - v.copyOf(x) - return true - } // match: (SUB x (MOVDconst [0])) // result: x for { @@ -4842,39 +4522,6 @@ func rewriteValueRISCV64_OpRISCV64SUB(v *Value) bool { v.copyOf(x) return true } - // match: (SUB (MOVBconst [0]) x) - // result: (NEG x) - for { - if v_0.Op != OpRISCV64MOVBconst || auxIntToInt8(v_0.AuxInt) != 0 { - break - } - x := v_1 - v.reset(OpRISCV64NEG) - v.AddArg(x) - return true - } - // match: (SUB (MOVHconst [0]) x) - // result: (NEG x) - for { - if v_0.Op != OpRISCV64MOVHconst || auxIntToInt16(v_0.AuxInt) != 0 { - break - } - x := v_1 - v.reset(OpRISCV64NEG) - v.AddArg(x) - return true - } - // match: (SUB (MOVWconst [0]) x) - // result: (NEG x) - for { - if v_0.Op != OpRISCV64MOVWconst || auxIntToInt32(v_0.AuxInt) != 0 { - break - } - x := v_1 - v.reset(OpRISCV64NEG) - v.AddArg(x) - return true - } // match: (SUB (MOVDconst [0]) x) // result: (NEG x) for { @@ -4891,11 +4538,11 @@ func rewriteValueRISCV64_OpRISCV64SUB(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SUBW(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] - // match: (SUBW x (MOVWconst [0])) + // match: (SUBW x (MOVDconst [0])) // result: (ADDIW [0] x) for { x := v_0 - if v_1.Op != OpRISCV64MOVWconst || auxIntToInt32(v_1.AuxInt) != 0 { + if v_1.Op != OpRISCV64MOVDconst || auxIntToInt64(v_1.AuxInt) != 0 { break } v.reset(OpRISCV64ADDIW) @@ -4919,54 +4566,6 @@ func rewriteValueRISCV64_OpRISCV64SUBW(v *Value) bool { func rewriteValueRISCV64_OpRISCV64XOR(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] - // match: (XOR (MOVBconst [val]) x) - // result: (XORI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVBconst { - continue - } - val := auxIntToInt8(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64XORI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } - // match: (XOR (MOVHconst [val]) x) - // result: (XORI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVHconst { - continue - } - val := auxIntToInt16(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64XORI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } - // match: (XOR (MOVWconst [val]) x) - // result: (XORI [int64(val)] x) - for { - for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { - if v_0.Op != OpRISCV64MOVWconst { - continue - } - val := auxIntToInt32(v_0.AuxInt) - x := v_1 - v.reset(OpRISCV64XORI) - v.AuxInt = int64ToAuxInt(int64(val)) - v.AddArg(x) - return true - } - break - } // match: (XOR (MOVDconst [val]) x) // cond: is32Bit(val) // result: (XORI [val] x) @@ -4994,23 +4593,23 @@ func rewriteValueRISCV64_OpRotateLeft16(v *Value) bool { v_0 := v.Args[0] b := v.Block typ := &b.Func.Config.Types - // match: (RotateLeft16 x (MOVHconst [c])) - // result: (Or16 (Lsh16x64 x (MOVHconst [c&15])) (Rsh16Ux64 x (MOVHconst [-c&15]))) + // match: (RotateLeft16 x (MOVDconst [c])) + // result: (Or16 (Lsh16x64 x (MOVDconst [c&15])) (Rsh16Ux64 x (MOVDconst [-c&15]))) for { t := v.Type x := v_0 - if v_1.Op != OpRISCV64MOVHconst { + if v_1.Op != OpRISCV64MOVDconst { break } - c := auxIntToInt16(v_1.AuxInt) + c := auxIntToInt64(v_1.AuxInt) v.reset(OpOr16) v0 := b.NewValue0(v.Pos, OpLsh16x64, t) - v1 := b.NewValue0(v.Pos, OpRISCV64MOVHconst, typ.UInt16) - v1.AuxInt = int16ToAuxInt(c & 15) + v1 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v1.AuxInt = int64ToAuxInt(c & 15) v0.AddArg2(x, v1) v2 := b.NewValue0(v.Pos, OpRsh16Ux64, t) - v3 := b.NewValue0(v.Pos, OpRISCV64MOVHconst, typ.UInt16) - v3.AuxInt = int16ToAuxInt(-c & 15) + v3 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v3.AuxInt = int64ToAuxInt(-c & 15) v2.AddArg2(x, v3) v.AddArg2(v0, v2) return true @@ -5022,23 +4621,23 @@ func rewriteValueRISCV64_OpRotateLeft32(v *Value) bool { v_0 := v.Args[0] b := v.Block typ := &b.Func.Config.Types - // match: (RotateLeft32 x (MOVWconst [c])) - // result: (Or32 (Lsh32x64 x (MOVWconst [c&31])) (Rsh32Ux64 x (MOVWconst [-c&31]))) + // match: (RotateLeft32 x (MOVDconst [c])) + // result: (Or32 (Lsh32x64 x (MOVDconst [c&31])) (Rsh32Ux64 x (MOVDconst [-c&31]))) for { t := v.Type x := v_0 - if v_1.Op != OpRISCV64MOVWconst { + if v_1.Op != OpRISCV64MOVDconst { break } - c := auxIntToInt32(v_1.AuxInt) + c := auxIntToInt64(v_1.AuxInt) v.reset(OpOr32) v0 := b.NewValue0(v.Pos, OpLsh32x64, t) - v1 := b.NewValue0(v.Pos, OpRISCV64MOVWconst, typ.UInt32) - v1.AuxInt = int32ToAuxInt(c & 31) + v1 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v1.AuxInt = int64ToAuxInt(c & 31) v0.AddArg2(x, v1) v2 := b.NewValue0(v.Pos, OpRsh32Ux64, t) - v3 := b.NewValue0(v.Pos, OpRISCV64MOVWconst, typ.UInt32) - v3.AuxInt = int32ToAuxInt(-c & 31) + v3 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v3.AuxInt = int64ToAuxInt(-c & 31) v2.AddArg2(x, v3) v.AddArg2(v0, v2) return true @@ -5078,23 +4677,23 @@ func rewriteValueRISCV64_OpRotateLeft8(v *Value) bool { v_0 := v.Args[0] b := v.Block typ := &b.Func.Config.Types - // match: (RotateLeft8 x (MOVBconst [c])) - // result: (Or8 (Lsh8x64 x (MOVBconst [c&7])) (Rsh8Ux64 x (MOVBconst [-c&7]))) + // match: (RotateLeft8 x (MOVDconst [c])) + // result: (Or8 (Lsh8x64 x (MOVDconst [c&7])) (Rsh8Ux64 x (MOVDconst [-c&7]))) for { t := v.Type x := v_0 - if v_1.Op != OpRISCV64MOVBconst { + if v_1.Op != OpRISCV64MOVDconst { break } - c := auxIntToInt8(v_1.AuxInt) + c := auxIntToInt64(v_1.AuxInt) v.reset(OpOr8) v0 := b.NewValue0(v.Pos, OpLsh8x64, t) - v1 := b.NewValue0(v.Pos, OpRISCV64MOVBconst, typ.UInt8) - v1.AuxInt = int8ToAuxInt(c & 7) + v1 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v1.AuxInt = int64ToAuxInt(c & 7) v0.AddArg2(x, v1) v2 := b.NewValue0(v.Pos, OpRsh8Ux64, t) - v3 := b.NewValue0(v.Pos, OpRISCV64MOVBconst, typ.UInt8) - v3.AuxInt = int8ToAuxInt(-c & 7) + v3 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v3.AuxInt = int64ToAuxInt(-c & 7) v2.AddArg2(x, v3) v.AddArg2(v0, v2) return true @@ -6095,7 +5694,7 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { return true } // match: (Zero [1] ptr mem) - // result: (MOVBstore ptr (MOVBconst [0]) mem) + // result: (MOVBstore ptr (MOVDconst [0]) mem) for { if auxIntToInt64(v.AuxInt) != 1 { break @@ -6103,14 +5702,14 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { ptr := v_0 mem := v_1 v.reset(OpRISCV64MOVBstore) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVBconst, typ.UInt8) - v0.AuxInt = int8ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v.AddArg3(ptr, v0, mem) return true } // match: (Zero [2] {t} ptr mem) // cond: t.Alignment()%2 == 0 - // result: (MOVHstore ptr (MOVHconst [0]) mem) + // result: (MOVHstore ptr (MOVDconst [0]) mem) for { if auxIntToInt64(v.AuxInt) != 2 { break @@ -6122,13 +5721,13 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { break } v.reset(OpRISCV64MOVHstore) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVHconst, typ.UInt16) - v0.AuxInt = int16ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v.AddArg3(ptr, v0, mem) return true } // match: (Zero [2] ptr mem) - // result: (MOVBstore [1] ptr (MOVBconst [0]) (MOVBstore ptr (MOVBconst [0]) mem)) + // result: (MOVBstore [1] ptr (MOVDconst [0]) (MOVBstore ptr (MOVDconst [0]) mem)) for { if auxIntToInt64(v.AuxInt) != 2 { break @@ -6137,8 +5736,8 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { mem := v_1 v.reset(OpRISCV64MOVBstore) v.AuxInt = int32ToAuxInt(1) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVBconst, typ.UInt8) - v0.AuxInt = int8ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v1 := b.NewValue0(v.Pos, OpRISCV64MOVBstore, types.TypeMem) v1.AddArg3(ptr, v0, mem) v.AddArg3(ptr, v0, v1) @@ -6146,7 +5745,7 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { } // match: (Zero [4] {t} ptr mem) // cond: t.Alignment()%4 == 0 - // result: (MOVWstore ptr (MOVWconst [0]) mem) + // result: (MOVWstore ptr (MOVDconst [0]) mem) for { if auxIntToInt64(v.AuxInt) != 4 { break @@ -6158,14 +5757,14 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { break } v.reset(OpRISCV64MOVWstore) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVWconst, typ.UInt32) - v0.AuxInt = int32ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v.AddArg3(ptr, v0, mem) return true } // match: (Zero [4] {t} ptr mem) // cond: t.Alignment()%2 == 0 - // result: (MOVHstore [2] ptr (MOVHconst [0]) (MOVHstore ptr (MOVHconst [0]) mem)) + // result: (MOVHstore [2] ptr (MOVDconst [0]) (MOVHstore ptr (MOVDconst [0]) mem)) for { if auxIntToInt64(v.AuxInt) != 4 { break @@ -6178,15 +5777,15 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { } v.reset(OpRISCV64MOVHstore) v.AuxInt = int32ToAuxInt(2) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVHconst, typ.UInt16) - v0.AuxInt = int16ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v1 := b.NewValue0(v.Pos, OpRISCV64MOVHstore, types.TypeMem) v1.AddArg3(ptr, v0, mem) v.AddArg3(ptr, v0, v1) return true } // match: (Zero [4] ptr mem) - // result: (MOVBstore [3] ptr (MOVBconst [0]) (MOVBstore [2] ptr (MOVBconst [0]) (MOVBstore [1] ptr (MOVBconst [0]) (MOVBstore ptr (MOVBconst [0]) mem)))) + // result: (MOVBstore [3] ptr (MOVDconst [0]) (MOVBstore [2] ptr (MOVDconst [0]) (MOVBstore [1] ptr (MOVDconst [0]) (MOVBstore ptr (MOVDconst [0]) mem)))) for { if auxIntToInt64(v.AuxInt) != 4 { break @@ -6195,8 +5794,8 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { mem := v_1 v.reset(OpRISCV64MOVBstore) v.AuxInt = int32ToAuxInt(3) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVBconst, typ.UInt8) - v0.AuxInt = int8ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v1 := b.NewValue0(v.Pos, OpRISCV64MOVBstore, types.TypeMem) v1.AuxInt = int32ToAuxInt(2) v2 := b.NewValue0(v.Pos, OpRISCV64MOVBstore, types.TypeMem) @@ -6229,7 +5828,7 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { } // match: (Zero [8] {t} ptr mem) // cond: t.Alignment()%4 == 0 - // result: (MOVWstore [4] ptr (MOVWconst [0]) (MOVWstore ptr (MOVWconst [0]) mem)) + // result: (MOVWstore [4] ptr (MOVDconst [0]) (MOVWstore ptr (MOVDconst [0]) mem)) for { if auxIntToInt64(v.AuxInt) != 8 { break @@ -6242,8 +5841,8 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { } v.reset(OpRISCV64MOVWstore) v.AuxInt = int32ToAuxInt(4) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVWconst, typ.UInt32) - v0.AuxInt = int32ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v1 := b.NewValue0(v.Pos, OpRISCV64MOVWstore, types.TypeMem) v1.AddArg3(ptr, v0, mem) v.AddArg3(ptr, v0, v1) @@ -6251,7 +5850,7 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { } // match: (Zero [8] {t} ptr mem) // cond: t.Alignment()%2 == 0 - // result: (MOVHstore [6] ptr (MOVHconst [0]) (MOVHstore [4] ptr (MOVHconst [0]) (MOVHstore [2] ptr (MOVHconst [0]) (MOVHstore ptr (MOVHconst [0]) mem)))) + // result: (MOVHstore [6] ptr (MOVDconst [0]) (MOVHstore [4] ptr (MOVDconst [0]) (MOVHstore [2] ptr (MOVDconst [0]) (MOVHstore ptr (MOVDconst [0]) mem)))) for { if auxIntToInt64(v.AuxInt) != 8 { break @@ -6264,8 +5863,8 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { } v.reset(OpRISCV64MOVHstore) v.AuxInt = int32ToAuxInt(6) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVHconst, typ.UInt16) - v0.AuxInt = int16ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v1 := b.NewValue0(v.Pos, OpRISCV64MOVHstore, types.TypeMem) v1.AuxInt = int32ToAuxInt(4) v2 := b.NewValue0(v.Pos, OpRISCV64MOVHstore, types.TypeMem) @@ -6278,7 +5877,7 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { return true } // match: (Zero [3] ptr mem) - // result: (MOVBstore [2] ptr (MOVBconst [0]) (MOVBstore [1] ptr (MOVBconst [0]) (MOVBstore ptr (MOVBconst [0]) mem))) + // result: (MOVBstore [2] ptr (MOVDconst [0]) (MOVBstore [1] ptr (MOVDconst [0]) (MOVBstore ptr (MOVDconst [0]) mem))) for { if auxIntToInt64(v.AuxInt) != 3 { break @@ -6287,8 +5886,8 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { mem := v_1 v.reset(OpRISCV64MOVBstore) v.AuxInt = int32ToAuxInt(2) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVBconst, typ.UInt8) - v0.AuxInt = int8ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v1 := b.NewValue0(v.Pos, OpRISCV64MOVBstore, types.TypeMem) v1.AuxInt = int32ToAuxInt(1) v2 := b.NewValue0(v.Pos, OpRISCV64MOVBstore, types.TypeMem) @@ -6299,7 +5898,7 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { } // match: (Zero [6] {t} ptr mem) // cond: t.Alignment()%2 == 0 - // result: (MOVHstore [4] ptr (MOVHconst [0]) (MOVHstore [2] ptr (MOVHconst [0]) (MOVHstore ptr (MOVHconst [0]) mem))) + // result: (MOVHstore [4] ptr (MOVDconst [0]) (MOVHstore [2] ptr (MOVDconst [0]) (MOVHstore ptr (MOVDconst [0]) mem))) for { if auxIntToInt64(v.AuxInt) != 6 { break @@ -6312,8 +5911,8 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { } v.reset(OpRISCV64MOVHstore) v.AuxInt = int32ToAuxInt(4) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVHconst, typ.UInt16) - v0.AuxInt = int16ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v1 := b.NewValue0(v.Pos, OpRISCV64MOVHstore, types.TypeMem) v1.AuxInt = int32ToAuxInt(2) v2 := b.NewValue0(v.Pos, OpRISCV64MOVHstore, types.TypeMem) @@ -6324,7 +5923,7 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { } // match: (Zero [12] {t} ptr mem) // cond: t.Alignment()%4 == 0 - // result: (MOVWstore [8] ptr (MOVWconst [0]) (MOVWstore [4] ptr (MOVWconst [0]) (MOVWstore ptr (MOVWconst [0]) mem))) + // result: (MOVWstore [8] ptr (MOVDconst [0]) (MOVWstore [4] ptr (MOVDconst [0]) (MOVWstore ptr (MOVDconst [0]) mem))) for { if auxIntToInt64(v.AuxInt) != 12 { break @@ -6337,8 +5936,8 @@ func rewriteValueRISCV64_OpZero(v *Value) bool { } v.reset(OpRISCV64MOVWstore) v.AuxInt = int32ToAuxInt(8) - v0 := b.NewValue0(v.Pos, OpRISCV64MOVWconst, typ.UInt32) - v0.AuxInt = int32ToAuxInt(0) + v0 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64) + v0.AuxInt = int64ToAuxInt(0) v1 := b.NewValue0(v.Pos, OpRISCV64MOVWstore, types.TypeMem) v1.AuxInt = int32ToAuxInt(4) v2 := b.NewValue0(v.Pos, OpRISCV64MOVWstore, types.TypeMem) diff --git a/src/cmd/compile/internal/ssa/rewriteS390X.go b/src/cmd/compile/internal/ssa/rewriteS390X.go index a9722b820c90d3d23a8ec2eda67cb6b846f969ef..8b41d62c315bd25de6fa2adbdd553c17e1ca2a3e 100644 --- a/src/cmd/compile/internal/ssa/rewriteS390X.go +++ b/src/cmd/compile/internal/ssa/rewriteS390X.go @@ -792,6 +792,9 @@ func rewriteValueS390X(v *Value) bool { case OpSqrt: v.Op = OpS390XFSQRT return true + case OpSqrt32: + v.Op = OpS390XFSQRTS + return true case OpStaticCall: v.Op = OpS390XCALLstatic return true @@ -1318,12 +1321,12 @@ func rewriteValueS390X_OpConst8(v *Value) bool { } } func rewriteValueS390X_OpConstBool(v *Value) bool { - // match: (ConstBool [b]) - // result: (MOVDconst [b2i(b)]) + // match: (ConstBool [t]) + // result: (MOVDconst [b2i(t)]) for { - b := auxIntToBool(v.AuxInt) + t := auxIntToBool(v.AuxInt) v.reset(OpS390XMOVDconst) - v.AuxInt = int64ToAuxInt(b2i(b)) + v.AuxInt = int64ToAuxInt(b2i(t)) return true } } @@ -3430,7 +3433,7 @@ func rewriteValueS390X_OpMove(v *Value) bool { } // match: (Move [s] dst src mem) // cond: s > 0 && s <= 256 && logLargeCopy(v, s) - // result: (MVC [makeValAndOff32(int32(s), 0)] dst src mem) + // result: (MVC [makeValAndOff(int32(s), 0)] dst src mem) for { s := auxIntToInt64(v.AuxInt) dst := v_0 @@ -3440,13 +3443,13 @@ func rewriteValueS390X_OpMove(v *Value) bool { break } v.reset(OpS390XMVC) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(s), 0)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(s), 0)) v.AddArg3(dst, src, mem) return true } // match: (Move [s] dst src mem) // cond: s > 256 && s <= 512 && logLargeCopy(v, s) - // result: (MVC [makeValAndOff32(int32(s)-256, 256)] dst src (MVC [makeValAndOff32(256, 0)] dst src mem)) + // result: (MVC [makeValAndOff(int32(s)-256, 256)] dst src (MVC [makeValAndOff(256, 0)] dst src mem)) for { s := auxIntToInt64(v.AuxInt) dst := v_0 @@ -3456,16 +3459,16 @@ func rewriteValueS390X_OpMove(v *Value) bool { break } v.reset(OpS390XMVC) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(s)-256, 256)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(s)-256, 256)) v0 := b.NewValue0(v.Pos, OpS390XMVC, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(256, 0)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(256, 0)) v0.AddArg3(dst, src, mem) v.AddArg3(dst, src, v0) return true } // match: (Move [s] dst src mem) // cond: s > 512 && s <= 768 && logLargeCopy(v, s) - // result: (MVC [makeValAndOff32(int32(s)-512, 512)] dst src (MVC [makeValAndOff32(256, 256)] dst src (MVC [makeValAndOff32(256, 0)] dst src mem))) + // result: (MVC [makeValAndOff(int32(s)-512, 512)] dst src (MVC [makeValAndOff(256, 256)] dst src (MVC [makeValAndOff(256, 0)] dst src mem))) for { s := auxIntToInt64(v.AuxInt) dst := v_0 @@ -3475,11 +3478,11 @@ func rewriteValueS390X_OpMove(v *Value) bool { break } v.reset(OpS390XMVC) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(s)-512, 512)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(s)-512, 512)) v0 := b.NewValue0(v.Pos, OpS390XMVC, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(256, 256)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(256, 256)) v1 := b.NewValue0(v.Pos, OpS390XMVC, types.TypeMem) - v1.AuxInt = valAndOffToAuxInt(makeValAndOff32(256, 0)) + v1.AuxInt = valAndOffToAuxInt(makeValAndOff(256, 0)) v1.AddArg3(dst, src, mem) v0.AddArg3(dst, src, v1) v.AddArg3(dst, src, v0) @@ -3487,7 +3490,7 @@ func rewriteValueS390X_OpMove(v *Value) bool { } // match: (Move [s] dst src mem) // cond: s > 768 && s <= 1024 && logLargeCopy(v, s) - // result: (MVC [makeValAndOff32(int32(s)-768, 768)] dst src (MVC [makeValAndOff32(256, 512)] dst src (MVC [makeValAndOff32(256, 256)] dst src (MVC [makeValAndOff32(256, 0)] dst src mem)))) + // result: (MVC [makeValAndOff(int32(s)-768, 768)] dst src (MVC [makeValAndOff(256, 512)] dst src (MVC [makeValAndOff(256, 256)] dst src (MVC [makeValAndOff(256, 0)] dst src mem)))) for { s := auxIntToInt64(v.AuxInt) dst := v_0 @@ -3497,13 +3500,13 @@ func rewriteValueS390X_OpMove(v *Value) bool { break } v.reset(OpS390XMVC) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(s)-768, 768)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(s)-768, 768)) v0 := b.NewValue0(v.Pos, OpS390XMVC, types.TypeMem) - v0.AuxInt = valAndOffToAuxInt(makeValAndOff32(256, 512)) + v0.AuxInt = valAndOffToAuxInt(makeValAndOff(256, 512)) v1 := b.NewValue0(v.Pos, OpS390XMVC, types.TypeMem) - v1.AuxInt = valAndOffToAuxInt(makeValAndOff32(256, 256)) + v1.AuxInt = valAndOffToAuxInt(makeValAndOff(256, 256)) v2 := b.NewValue0(v.Pos, OpS390XMVC, types.TypeMem) - v2.AuxInt = valAndOffToAuxInt(makeValAndOff32(256, 0)) + v2.AuxInt = valAndOffToAuxInt(makeValAndOff(256, 0)) v2.AddArg3(dst, src, mem) v1.AddArg3(dst, src, v2) v0.AddArg3(dst, src, v1) @@ -6332,12 +6335,12 @@ func rewriteValueS390X_OpS390XCMP(v *Value) bool { return true } // match: (CMP x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMP y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpS390XInvertFlags) @@ -6389,12 +6392,12 @@ func rewriteValueS390X_OpS390XCMPU(v *Value) bool { return true } // match: (CMPU x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPU y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpS390XInvertFlags) @@ -6624,12 +6627,12 @@ func rewriteValueS390X_OpS390XCMPW(v *Value) bool { return true } // match: (CMPW x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPW y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpS390XInvertFlags) @@ -6721,12 +6724,12 @@ func rewriteValueS390X_OpS390XCMPWU(v *Value) bool { return true } // match: (CMPWU x y) - // cond: x.ID > y.ID + // cond: canonLessThan(x,y) // result: (InvertFlags (CMPWU y x)) for { x := v_0 y := v_1 - if !(x.ID > y.ID) { + if !(canonLessThan(x, y)) { break } v.reset(OpS390XInvertFlags) @@ -8614,7 +8617,7 @@ func rewriteValueS390X_OpS390XMOVBstore(v *Value) bool { } // match: (MOVBstore [off] {sym} ptr (MOVDconst [c]) mem) // cond: is20Bit(int64(off)) && ptr.Op != OpSB - // result: (MOVBstoreconst [makeValAndOff32(int32(int8(c)),off)] {sym} ptr mem) + // result: (MOVBstoreconst [makeValAndOff(int32(int8(c)),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -8628,7 +8631,7 @@ func rewriteValueS390X_OpS390XMOVBstore(v *Value) bool { break } v.reset(OpS390XMOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(int8(c)), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(int8(c)), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -8880,13 +8883,63 @@ func rewriteValueS390X_OpS390XMOVBstore(v *Value) bool { v.AddArg3(p, w0, mem) return true } + // match: (MOVBstore [7] {s} p1 (SRDconst w) x1:(MOVHBRstore [5] {s} p1 (SRDconst w) x2:(MOVWBRstore [1] {s} p1 (SRDconst w) x3:(MOVBstore [0] {s} p1 w mem)))) + // cond: x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && clobber(x1, x2, x3) + // result: (MOVDBRstore {s} p1 w mem) + for { + if auxIntToInt32(v.AuxInt) != 7 { + break + } + s := auxToSym(v.Aux) + p1 := v_0 + if v_1.Op != OpS390XSRDconst { + break + } + w := v_1.Args[0] + x1 := v_2 + if x1.Op != OpS390XMOVHBRstore || auxIntToInt32(x1.AuxInt) != 5 || auxToSym(x1.Aux) != s { + break + } + _ = x1.Args[2] + if p1 != x1.Args[0] { + break + } + x1_1 := x1.Args[1] + if x1_1.Op != OpS390XSRDconst || w != x1_1.Args[0] { + break + } + x2 := x1.Args[2] + if x2.Op != OpS390XMOVWBRstore || auxIntToInt32(x2.AuxInt) != 1 || auxToSym(x2.Aux) != s { + break + } + _ = x2.Args[2] + if p1 != x2.Args[0] { + break + } + x2_1 := x2.Args[1] + if x2_1.Op != OpS390XSRDconst || w != x2_1.Args[0] { + break + } + x3 := x2.Args[2] + if x3.Op != OpS390XMOVBstore || auxIntToInt32(x3.AuxInt) != 0 || auxToSym(x3.Aux) != s { + break + } + mem := x3.Args[2] + if p1 != x3.Args[0] || w != x3.Args[1] || !(x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && clobber(x1, x2, x3)) { + break + } + v.reset(OpS390XMOVDBRstore) + v.Aux = symToAux(s) + v.AddArg3(p1, w, mem) + return true + } return false } func rewriteValueS390X_OpS390XMOVBstoreconst(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (MOVBstoreconst [sc] {s} (ADDconst [off] ptr) mem) - // cond: is20Bit(sc.Off()+int64(off)) + // cond: is20Bit(sc.Off64()+int64(off)) // result: (MOVBstoreconst [sc.addOffset32(off)] {s} ptr mem) for { sc := auxIntToValAndOff(v.AuxInt) @@ -8897,7 +8950,7 @@ func rewriteValueS390X_OpS390XMOVBstoreconst(v *Value) bool { off := auxIntToInt32(v_0.AuxInt) ptr := v_0.Args[0] mem := v_1 - if !(is20Bit(sc.Off() + int64(off))) { + if !(is20Bit(sc.Off64() + int64(off))) { break } v.reset(OpS390XMOVBstoreconst) @@ -8930,7 +8983,7 @@ func rewriteValueS390X_OpS390XMOVBstoreconst(v *Value) bool { } // match: (MOVBstoreconst [c] {s} p x:(MOVBstoreconst [a] {s} p mem)) // cond: p.Op != OpSB && x.Uses == 1 && a.Off() + 1 == c.Off() && clobber(x) - // result: (MOVHstoreconst [makeValAndOff32(c.Val32()&0xff | a.Val32()<<8, a.Off32())] {s} p mem) + // result: (MOVHstoreconst [makeValAndOff(c.Val()&0xff | a.Val()<<8, a.Off())] {s} p mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -8948,7 +9001,7 @@ func rewriteValueS390X_OpS390XMOVBstoreconst(v *Value) bool { break } v.reset(OpS390XMOVHstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(c.Val32()&0xff|a.Val32()<<8, a.Off32())) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(c.Val()&0xff|a.Val()<<8, a.Off())) v.Aux = symToAux(s) v.AddArg2(p, mem) return true @@ -9160,7 +9213,7 @@ func rewriteValueS390X_OpS390XMOVDstore(v *Value) bool { } // match: (MOVDstore [off] {sym} ptr (MOVDconst [c]) mem) // cond: is16Bit(c) && isU12Bit(int64(off)) && ptr.Op != OpSB - // result: (MOVDstoreconst [makeValAndOff32(int32(c),off)] {sym} ptr mem) + // result: (MOVDstoreconst [makeValAndOff(int32(c),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -9174,7 +9227,7 @@ func rewriteValueS390X_OpS390XMOVDstore(v *Value) bool { break } v.reset(OpS390XMOVDstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -9290,7 +9343,7 @@ func rewriteValueS390X_OpS390XMOVDstoreconst(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] // match: (MOVDstoreconst [sc] {s} (ADDconst [off] ptr) mem) - // cond: isU12Bit(sc.Off()+int64(off)) + // cond: isU12Bit(sc.Off64()+int64(off)) // result: (MOVDstoreconst [sc.addOffset32(off)] {s} ptr mem) for { sc := auxIntToValAndOff(v.AuxInt) @@ -9301,7 +9354,7 @@ func rewriteValueS390X_OpS390XMOVDstoreconst(v *Value) bool { off := auxIntToInt32(v_0.AuxInt) ptr := v_0.Args[0] mem := v_1 - if !(isU12Bit(sc.Off() + int64(off))) { + if !(isU12Bit(sc.Off64() + int64(off))) { break } v.reset(OpS390XMOVDstoreconst) @@ -10026,7 +10079,7 @@ func rewriteValueS390X_OpS390XMOVHstore(v *Value) bool { } // match: (MOVHstore [off] {sym} ptr (MOVDconst [c]) mem) // cond: isU12Bit(int64(off)) && ptr.Op != OpSB - // result: (MOVHstoreconst [makeValAndOff32(int32(int16(c)),off)] {sym} ptr mem) + // result: (MOVHstoreconst [makeValAndOff(int32(int16(c)),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -10040,7 +10093,7 @@ func rewriteValueS390X_OpS390XMOVHstore(v *Value) bool { break } v.reset(OpS390XMOVHstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(int16(c)), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(int16(c)), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -10191,7 +10244,7 @@ func rewriteValueS390X_OpS390XMOVHstoreconst(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (MOVHstoreconst [sc] {s} (ADDconst [off] ptr) mem) - // cond: isU12Bit(sc.Off()+int64(off)) + // cond: isU12Bit(sc.Off64()+int64(off)) // result: (MOVHstoreconst [sc.addOffset32(off)] {s} ptr mem) for { sc := auxIntToValAndOff(v.AuxInt) @@ -10202,7 +10255,7 @@ func rewriteValueS390X_OpS390XMOVHstoreconst(v *Value) bool { off := auxIntToInt32(v_0.AuxInt) ptr := v_0.Args[0] mem := v_1 - if !(isU12Bit(sc.Off() + int64(off))) { + if !(isU12Bit(sc.Off64() + int64(off))) { break } v.reset(OpS390XMOVHstoreconst) @@ -10235,7 +10288,7 @@ func rewriteValueS390X_OpS390XMOVHstoreconst(v *Value) bool { } // match: (MOVHstoreconst [c] {s} p x:(MOVHstoreconst [a] {s} p mem)) // cond: p.Op != OpSB && x.Uses == 1 && a.Off() + 2 == c.Off() && clobber(x) - // result: (MOVWstore [a.Off32()] {s} p (MOVDconst [int64(c.Val32()&0xffff | a.Val32()<<16)]) mem) + // result: (MOVWstore [a.Off()] {s} p (MOVDconst [int64(c.Val()&0xffff | a.Val()<<16)]) mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -10253,10 +10306,10 @@ func rewriteValueS390X_OpS390XMOVHstoreconst(v *Value) bool { break } v.reset(OpS390XMOVWstore) - v.AuxInt = int32ToAuxInt(a.Off32()) + v.AuxInt = int32ToAuxInt(a.Off()) v.Aux = symToAux(s) v0 := b.NewValue0(x.Pos, OpS390XMOVDconst, typ.UInt64) - v0.AuxInt = int64ToAuxInt(int64(c.Val32()&0xffff | a.Val32()<<16)) + v0.AuxInt = int64ToAuxInt(int64(c.Val()&0xffff | a.Val()<<16)) v.AddArg3(p, v0, mem) return true } @@ -10864,7 +10917,7 @@ func rewriteValueS390X_OpS390XMOVWstore(v *Value) bool { } // match: (MOVWstore [off] {sym} ptr (MOVDconst [c]) mem) // cond: is16Bit(c) && isU12Bit(int64(off)) && ptr.Op != OpSB - // result: (MOVWstoreconst [makeValAndOff32(int32(c),off)] {sym} ptr mem) + // result: (MOVWstoreconst [makeValAndOff(int32(c),off)] {sym} ptr mem) for { off := auxIntToInt32(v.AuxInt) sym := auxToSym(v.Aux) @@ -10878,7 +10931,7 @@ func rewriteValueS390X_OpS390XMOVWstore(v *Value) bool { break } v.reset(OpS390XMOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(c), off)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(c), off)) v.Aux = symToAux(sym) v.AddArg2(ptr, mem) return true @@ -11052,7 +11105,7 @@ func rewriteValueS390X_OpS390XMOVWstoreconst(v *Value) bool { b := v.Block typ := &b.Func.Config.Types // match: (MOVWstoreconst [sc] {s} (ADDconst [off] ptr) mem) - // cond: isU12Bit(sc.Off()+int64(off)) + // cond: isU12Bit(sc.Off64()+int64(off)) // result: (MOVWstoreconst [sc.addOffset32(off)] {s} ptr mem) for { sc := auxIntToValAndOff(v.AuxInt) @@ -11063,7 +11116,7 @@ func rewriteValueS390X_OpS390XMOVWstoreconst(v *Value) bool { off := auxIntToInt32(v_0.AuxInt) ptr := v_0.Args[0] mem := v_1 - if !(isU12Bit(sc.Off() + int64(off))) { + if !(isU12Bit(sc.Off64() + int64(off))) { break } v.reset(OpS390XMOVWstoreconst) @@ -11096,7 +11149,7 @@ func rewriteValueS390X_OpS390XMOVWstoreconst(v *Value) bool { } // match: (MOVWstoreconst [c] {s} p x:(MOVWstoreconst [a] {s} p mem)) // cond: p.Op != OpSB && x.Uses == 1 && a.Off() + 4 == c.Off() && clobber(x) - // result: (MOVDstore [a.Off32()] {s} p (MOVDconst [c.Val()&0xffffffff | a.Val()<<32]) mem) + // result: (MOVDstore [a.Off()] {s} p (MOVDconst [c.Val64()&0xffffffff | a.Val64()<<32]) mem) for { c := auxIntToValAndOff(v.AuxInt) s := auxToSym(v.Aux) @@ -11114,10 +11167,10 @@ func rewriteValueS390X_OpS390XMOVWstoreconst(v *Value) bool { break } v.reset(OpS390XMOVDstore) - v.AuxInt = int32ToAuxInt(a.Off32()) + v.AuxInt = int32ToAuxInt(a.Off()) v.Aux = symToAux(s) v0 := b.NewValue0(x.Pos, OpS390XMOVDconst, typ.UInt64) - v0.AuxInt = int64ToAuxInt(c.Val()&0xffffffff | a.Val()<<32) + v0.AuxInt = int64ToAuxInt(c.Val64()&0xffffffff | a.Val64()<<32) v.AddArg3(p, v0, mem) return true } @@ -15865,7 +15918,7 @@ func rewriteValueS390X_OpZero(v *Value) bool { return true } // match: (Zero [3] destptr mem) - // result: (MOVBstoreconst [makeValAndOff32(0,2)] destptr (MOVHstoreconst [0] destptr mem)) + // result: (MOVBstoreconst [makeValAndOff(0,2)] destptr (MOVHstoreconst [0] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 3 { break @@ -15873,7 +15926,7 @@ func rewriteValueS390X_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpS390XMOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 2)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 2)) v0 := b.NewValue0(v.Pos, OpS390XMOVHstoreconst, types.TypeMem) v0.AuxInt = valAndOffToAuxInt(0) v0.AddArg2(destptr, mem) @@ -15881,7 +15934,7 @@ func rewriteValueS390X_OpZero(v *Value) bool { return true } // match: (Zero [5] destptr mem) - // result: (MOVBstoreconst [makeValAndOff32(0,4)] destptr (MOVWstoreconst [0] destptr mem)) + // result: (MOVBstoreconst [makeValAndOff(0,4)] destptr (MOVWstoreconst [0] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 5 { break @@ -15889,7 +15942,7 @@ func rewriteValueS390X_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpS390XMOVBstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 4)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 4)) v0 := b.NewValue0(v.Pos, OpS390XMOVWstoreconst, types.TypeMem) v0.AuxInt = valAndOffToAuxInt(0) v0.AddArg2(destptr, mem) @@ -15897,7 +15950,7 @@ func rewriteValueS390X_OpZero(v *Value) bool { return true } // match: (Zero [6] destptr mem) - // result: (MOVHstoreconst [makeValAndOff32(0,4)] destptr (MOVWstoreconst [0] destptr mem)) + // result: (MOVHstoreconst [makeValAndOff(0,4)] destptr (MOVWstoreconst [0] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 6 { break @@ -15905,7 +15958,7 @@ func rewriteValueS390X_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpS390XMOVHstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 4)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 4)) v0 := b.NewValue0(v.Pos, OpS390XMOVWstoreconst, types.TypeMem) v0.AuxInt = valAndOffToAuxInt(0) v0.AddArg2(destptr, mem) @@ -15913,7 +15966,7 @@ func rewriteValueS390X_OpZero(v *Value) bool { return true } // match: (Zero [7] destptr mem) - // result: (MOVWstoreconst [makeValAndOff32(0,3)] destptr (MOVWstoreconst [0] destptr mem)) + // result: (MOVWstoreconst [makeValAndOff(0,3)] destptr (MOVWstoreconst [0] destptr mem)) for { if auxIntToInt64(v.AuxInt) != 7 { break @@ -15921,7 +15974,7 @@ func rewriteValueS390X_OpZero(v *Value) bool { destptr := v_0 mem := v_1 v.reset(OpS390XMOVWstoreconst) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(0, 3)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(0, 3)) v0 := b.NewValue0(v.Pos, OpS390XMOVWstoreconst, types.TypeMem) v0.AuxInt = valAndOffToAuxInt(0) v0.AddArg2(destptr, mem) @@ -15930,7 +15983,7 @@ func rewriteValueS390X_OpZero(v *Value) bool { } // match: (Zero [s] destptr mem) // cond: s > 0 && s <= 1024 - // result: (CLEAR [makeValAndOff32(int32(s), 0)] destptr mem) + // result: (CLEAR [makeValAndOff(int32(s), 0)] destptr mem) for { s := auxIntToInt64(v.AuxInt) destptr := v_0 @@ -15939,7 +15992,7 @@ func rewriteValueS390X_OpZero(v *Value) bool { break } v.reset(OpS390XCLEAR) - v.AuxInt = valAndOffToAuxInt(makeValAndOff32(int32(s), 0)) + v.AuxInt = valAndOffToAuxInt(makeValAndOff(int32(s), 0)) v.AddArg2(destptr, mem) return true } diff --git a/src/cmd/compile/internal/ssa/rewriteWasm.go b/src/cmd/compile/internal/ssa/rewriteWasm.go index c8ecefc7367cb8883c8f434a8d3330839efa6ce9..5dab09f85b35780f7113c4cb5e89e70337c7ae49 100644 --- a/src/cmd/compile/internal/ssa/rewriteWasm.go +++ b/src/cmd/compile/internal/ssa/rewriteWasm.go @@ -3,8 +3,8 @@ package ssa +import "internal/buildcfg" import "math" -import "cmd/internal/objabi" import "cmd/compile/internal/types" func rewriteValueWasm(v *Value) bool { @@ -527,6 +527,9 @@ func rewriteValueWasm(v *Value) bool { case OpSqrt: v.Op = OpWasmF64Sqrt return true + case OpSqrt32: + v.Op = OpWasmF32Sqrt + return true case OpStaticCall: v.Op = OpWasmLoweredStaticCall return true @@ -3190,11 +3193,11 @@ func rewriteValueWasm_OpSignExt16to32(v *Value) bool { return true } // match: (SignExt16to32 x) - // cond: objabi.GOWASM.SignExt + // cond: buildcfg.GOWASM.SignExt // result: (I64Extend16S x) for { x := v_0 - if !(objabi.GOWASM.SignExt) { + if !(buildcfg.GOWASM.SignExt) { break } v.reset(OpWasmI64Extend16S) @@ -3229,11 +3232,11 @@ func rewriteValueWasm_OpSignExt16to64(v *Value) bool { return true } // match: (SignExt16to64 x) - // cond: objabi.GOWASM.SignExt + // cond: buildcfg.GOWASM.SignExt // result: (I64Extend16S x) for { x := v_0 - if !(objabi.GOWASM.SignExt) { + if !(buildcfg.GOWASM.SignExt) { break } v.reset(OpWasmI64Extend16S) @@ -3268,11 +3271,11 @@ func rewriteValueWasm_OpSignExt32to64(v *Value) bool { return true } // match: (SignExt32to64 x) - // cond: objabi.GOWASM.SignExt + // cond: buildcfg.GOWASM.SignExt // result: (I64Extend32S x) for { x := v_0 - if !(objabi.GOWASM.SignExt) { + if !(buildcfg.GOWASM.SignExt) { break } v.reset(OpWasmI64Extend32S) @@ -3307,11 +3310,11 @@ func rewriteValueWasm_OpSignExt8to16(v *Value) bool { return true } // match: (SignExt8to16 x) - // cond: objabi.GOWASM.SignExt + // cond: buildcfg.GOWASM.SignExt // result: (I64Extend8S x) for { x := v_0 - if !(objabi.GOWASM.SignExt) { + if !(buildcfg.GOWASM.SignExt) { break } v.reset(OpWasmI64Extend8S) @@ -3346,11 +3349,11 @@ func rewriteValueWasm_OpSignExt8to32(v *Value) bool { return true } // match: (SignExt8to32 x) - // cond: objabi.GOWASM.SignExt + // cond: buildcfg.GOWASM.SignExt // result: (I64Extend8S x) for { x := v_0 - if !(objabi.GOWASM.SignExt) { + if !(buildcfg.GOWASM.SignExt) { break } v.reset(OpWasmI64Extend8S) @@ -3385,11 +3388,11 @@ func rewriteValueWasm_OpSignExt8to64(v *Value) bool { return true } // match: (SignExt8to64 x) - // cond: objabi.GOWASM.SignExt + // cond: buildcfg.GOWASM.SignExt // result: (I64Extend8S x) for { x := v_0 - if !(objabi.GOWASM.SignExt) { + if !(buildcfg.GOWASM.SignExt) { break } v.reset(OpWasmI64Extend8S) @@ -4899,7 +4902,5 @@ func rewriteValueWasm_OpZeroExt8to64(v *Value) bool { } } func rewriteBlockWasm(b *Block) bool { - switch b.Kind { - } return false } diff --git a/src/cmd/compile/internal/ssa/rewrite_test.go b/src/cmd/compile/internal/ssa/rewrite_test.go index 6fe429e85a67f3c509f1cdc5dafdbfdb55027882..357fe1183fad56da349da9af4de47cdff074c59b 100644 --- a/src/cmd/compile/internal/ssa/rewrite_test.go +++ b/src/cmd/compile/internal/ssa/rewrite_test.go @@ -13,7 +13,7 @@ func TestMove(t *testing.T) { copy(x[1:], x[:]) for i := 1; i < len(x); i++ { if int(x[i]) != i { - t.Errorf("Memmove got converted to OpMove in alias-unsafe way. Got %d insted of %d in position %d", int(x[i]), i, i+1) + t.Errorf("Memmove got converted to OpMove in alias-unsafe way. Got %d instead of %d in position %d", int(x[i]), i, i+1) } } } @@ -205,6 +205,7 @@ func TestMergePPC64AndSrwi(t *testing.T) { {0x00000000, 4, false, 0, 0}, {0xF0000000, 4, false, 0, 0}, {0xF0000000, 32, false, 0, 0}, + {0xFFFFFFFF, 0, true, 0, 0xFFFFFFFF}, } for i, v := range tests { result := mergePPC64AndSrwi(v.and, v.srw) diff --git a/src/cmd/compile/internal/ssa/rewritedec.go b/src/cmd/compile/internal/ssa/rewritedec.go index e0fa9768d959bc70b15691965c024ea45c473b1b..2a73a5ddc830d3885c6a36f02232b380917213c5 100644 --- a/src/cmd/compile/internal/ssa/rewritedec.go +++ b/src/cmd/compile/internal/ssa/rewritedec.go @@ -23,6 +23,8 @@ func rewriteValuedec(v *Value) bool { return rewriteValuedec_OpSliceLen(v) case OpSlicePtr: return rewriteValuedec_OpSlicePtr(v) + case OpSlicePtrUnchecked: + return rewriteValuedec_OpSlicePtrUnchecked(v) case OpStore: return rewriteValuedec_OpStore(v) case OpStringLen: @@ -248,6 +250,20 @@ func rewriteValuedec_OpSlicePtr(v *Value) bool { } return false } +func rewriteValuedec_OpSlicePtrUnchecked(v *Value) bool { + v_0 := v.Args[0] + // match: (SlicePtrUnchecked (SliceMake ptr _ _ )) + // result: ptr + for { + if v_0.Op != OpSliceMake { + break + } + ptr := v_0.Args[0] + v.copyOf(ptr) + return true + } + return false +} func rewriteValuedec_OpStore(v *Value) bool { v_2 := v.Args[2] v_1 := v.Args[1] @@ -409,7 +425,5 @@ func rewriteValuedec_OpStringPtr(v *Value) bool { return false } func rewriteBlockdec(b *Block) bool { - switch b.Kind { - } return false } diff --git a/src/cmd/compile/internal/ssa/rewritedec64.go b/src/cmd/compile/internal/ssa/rewritedec64.go index c49bc8043e7f2b584a5bd19266b55a6a10514b58..7d9656a4c827a1024c8adf96c3db03b40c19a2e4 100644 --- a/src/cmd/compile/internal/ssa/rewritedec64.go +++ b/src/cmd/compile/internal/ssa/rewritedec64.go @@ -184,12 +184,12 @@ func rewriteValuedec64_OpArg(v *Value) bool { config := b.Func.Config typ := &b.Func.Config.Types // match: (Arg {n} [off]) - // cond: is64BitInt(v.Type) && !config.BigEndian && v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin") + // cond: is64BitInt(v.Type) && !config.BigEndian && v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin") // result: (Int64Make (Arg {n} [off+4]) (Arg {n} [off])) for { off := auxIntToInt32(v.AuxInt) n := auxToSym(v.Aux) - if !(is64BitInt(v.Type) && !config.BigEndian && v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin")) { + if !(is64BitInt(v.Type) && !config.BigEndian && v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin")) { break } v.reset(OpInt64Make) @@ -203,12 +203,12 @@ func rewriteValuedec64_OpArg(v *Value) bool { return true } // match: (Arg {n} [off]) - // cond: is64BitInt(v.Type) && !config.BigEndian && !v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin") + // cond: is64BitInt(v.Type) && !config.BigEndian && !v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin") // result: (Int64Make (Arg {n} [off+4]) (Arg {n} [off])) for { off := auxIntToInt32(v.AuxInt) n := auxToSym(v.Aux) - if !(is64BitInt(v.Type) && !config.BigEndian && !v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin")) { + if !(is64BitInt(v.Type) && !config.BigEndian && !v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin")) { break } v.reset(OpInt64Make) @@ -222,12 +222,12 @@ func rewriteValuedec64_OpArg(v *Value) bool { return true } // match: (Arg {n} [off]) - // cond: is64BitInt(v.Type) && config.BigEndian && v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin") + // cond: is64BitInt(v.Type) && config.BigEndian && v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin") // result: (Int64Make (Arg {n} [off]) (Arg {n} [off+4])) for { off := auxIntToInt32(v.AuxInt) n := auxToSym(v.Aux) - if !(is64BitInt(v.Type) && config.BigEndian && v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin")) { + if !(is64BitInt(v.Type) && config.BigEndian && v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin")) { break } v.reset(OpInt64Make) @@ -241,12 +241,12 @@ func rewriteValuedec64_OpArg(v *Value) bool { return true } // match: (Arg {n} [off]) - // cond: is64BitInt(v.Type) && config.BigEndian && !v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin") + // cond: is64BitInt(v.Type) && config.BigEndian && !v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin") // result: (Int64Make (Arg {n} [off]) (Arg {n} [off+4])) for { off := auxIntToInt32(v.AuxInt) n := auxToSym(v.Aux) - if !(is64BitInt(v.Type) && config.BigEndian && !v.Type.IsSigned() && !(go116lateCallExpansion && b.Func.pass.name == "decompose builtin")) { + if !(is64BitInt(v.Type) && config.BigEndian && !v.Type.IsSigned() && !(b.Func.pass.name == "decompose builtin")) { break } v.reset(OpInt64Make) @@ -2458,7 +2458,5 @@ func rewriteValuedec64_OpZeroExt8to64(v *Value) bool { } } func rewriteBlockdec64(b *Block) bool { - switch b.Kind { - } return false } diff --git a/src/cmd/compile/internal/ssa/rewritedecArgs.go b/src/cmd/compile/internal/ssa/rewritedecArgs.go deleted file mode 100644 index 23ff417eee930335610ddf2f04eb9538617853d6..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/ssa/rewritedecArgs.go +++ /dev/null @@ -1,247 +0,0 @@ -// Code generated from gen/decArgs.rules; DO NOT EDIT. -// generated with: cd gen; go run *.go - -package ssa - -func rewriteValuedecArgs(v *Value) bool { - switch v.Op { - case OpArg: - return rewriteValuedecArgs_OpArg(v) - } - return false -} -func rewriteValuedecArgs_OpArg(v *Value) bool { - b := v.Block - config := b.Func.Config - fe := b.Func.fe - typ := &b.Func.Config.Types - // match: (Arg {n} [off]) - // cond: v.Type.IsString() - // result: (StringMake (Arg {n} [off]) (Arg {n} [off+int32(config.PtrSize)])) - for { - off := auxIntToInt32(v.AuxInt) - n := auxToSym(v.Aux) - if !(v.Type.IsString()) { - break - } - v.reset(OpStringMake) - v0 := b.NewValue0(v.Pos, OpArg, typ.BytePtr) - v0.AuxInt = int32ToAuxInt(off) - v0.Aux = symToAux(n) - v1 := b.NewValue0(v.Pos, OpArg, typ.Int) - v1.AuxInt = int32ToAuxInt(off + int32(config.PtrSize)) - v1.Aux = symToAux(n) - v.AddArg2(v0, v1) - return true - } - // match: (Arg {n} [off]) - // cond: v.Type.IsSlice() - // result: (SliceMake (Arg {n} [off]) (Arg {n} [off+int32(config.PtrSize)]) (Arg {n} [off+2*int32(config.PtrSize)])) - for { - off := auxIntToInt32(v.AuxInt) - n := auxToSym(v.Aux) - if !(v.Type.IsSlice()) { - break - } - v.reset(OpSliceMake) - v0 := b.NewValue0(v.Pos, OpArg, v.Type.Elem().PtrTo()) - v0.AuxInt = int32ToAuxInt(off) - v0.Aux = symToAux(n) - v1 := b.NewValue0(v.Pos, OpArg, typ.Int) - v1.AuxInt = int32ToAuxInt(off + int32(config.PtrSize)) - v1.Aux = symToAux(n) - v2 := b.NewValue0(v.Pos, OpArg, typ.Int) - v2.AuxInt = int32ToAuxInt(off + 2*int32(config.PtrSize)) - v2.Aux = symToAux(n) - v.AddArg3(v0, v1, v2) - return true - } - // match: (Arg {n} [off]) - // cond: v.Type.IsInterface() - // result: (IMake (Arg {n} [off]) (Arg {n} [off+int32(config.PtrSize)])) - for { - off := auxIntToInt32(v.AuxInt) - n := auxToSym(v.Aux) - if !(v.Type.IsInterface()) { - break - } - v.reset(OpIMake) - v0 := b.NewValue0(v.Pos, OpArg, typ.Uintptr) - v0.AuxInt = int32ToAuxInt(off) - v0.Aux = symToAux(n) - v1 := b.NewValue0(v.Pos, OpArg, typ.BytePtr) - v1.AuxInt = int32ToAuxInt(off + int32(config.PtrSize)) - v1.Aux = symToAux(n) - v.AddArg2(v0, v1) - return true - } - // match: (Arg {n} [off]) - // cond: v.Type.IsComplex() && v.Type.Size() == 16 - // result: (ComplexMake (Arg {n} [off]) (Arg {n} [off+8])) - for { - off := auxIntToInt32(v.AuxInt) - n := auxToSym(v.Aux) - if !(v.Type.IsComplex() && v.Type.Size() == 16) { - break - } - v.reset(OpComplexMake) - v0 := b.NewValue0(v.Pos, OpArg, typ.Float64) - v0.AuxInt = int32ToAuxInt(off) - v0.Aux = symToAux(n) - v1 := b.NewValue0(v.Pos, OpArg, typ.Float64) - v1.AuxInt = int32ToAuxInt(off + 8) - v1.Aux = symToAux(n) - v.AddArg2(v0, v1) - return true - } - // match: (Arg {n} [off]) - // cond: v.Type.IsComplex() && v.Type.Size() == 8 - // result: (ComplexMake (Arg {n} [off]) (Arg {n} [off+4])) - for { - off := auxIntToInt32(v.AuxInt) - n := auxToSym(v.Aux) - if !(v.Type.IsComplex() && v.Type.Size() == 8) { - break - } - v.reset(OpComplexMake) - v0 := b.NewValue0(v.Pos, OpArg, typ.Float32) - v0.AuxInt = int32ToAuxInt(off) - v0.Aux = symToAux(n) - v1 := b.NewValue0(v.Pos, OpArg, typ.Float32) - v1.AuxInt = int32ToAuxInt(off + 4) - v1.Aux = symToAux(n) - v.AddArg2(v0, v1) - return true - } - // match: (Arg ) - // cond: t.IsStruct() && t.NumFields() == 0 && fe.CanSSA(t) - // result: (StructMake0) - for { - t := v.Type - if !(t.IsStruct() && t.NumFields() == 0 && fe.CanSSA(t)) { - break - } - v.reset(OpStructMake0) - return true - } - // match: (Arg {n} [off]) - // cond: t.IsStruct() && t.NumFields() == 1 && fe.CanSSA(t) - // result: (StructMake1 (Arg {n} [off+int32(t.FieldOff(0))])) - for { - t := v.Type - off := auxIntToInt32(v.AuxInt) - n := auxToSym(v.Aux) - if !(t.IsStruct() && t.NumFields() == 1 && fe.CanSSA(t)) { - break - } - v.reset(OpStructMake1) - v0 := b.NewValue0(v.Pos, OpArg, t.FieldType(0)) - v0.AuxInt = int32ToAuxInt(off + int32(t.FieldOff(0))) - v0.Aux = symToAux(n) - v.AddArg(v0) - return true - } - // match: (Arg {n} [off]) - // cond: t.IsStruct() && t.NumFields() == 2 && fe.CanSSA(t) - // result: (StructMake2 (Arg {n} [off+int32(t.FieldOff(0))]) (Arg {n} [off+int32(t.FieldOff(1))])) - for { - t := v.Type - off := auxIntToInt32(v.AuxInt) - n := auxToSym(v.Aux) - if !(t.IsStruct() && t.NumFields() == 2 && fe.CanSSA(t)) { - break - } - v.reset(OpStructMake2) - v0 := b.NewValue0(v.Pos, OpArg, t.FieldType(0)) - v0.AuxInt = int32ToAuxInt(off + int32(t.FieldOff(0))) - v0.Aux = symToAux(n) - v1 := b.NewValue0(v.Pos, OpArg, t.FieldType(1)) - v1.AuxInt = int32ToAuxInt(off + int32(t.FieldOff(1))) - v1.Aux = symToAux(n) - v.AddArg2(v0, v1) - return true - } - // match: (Arg {n} [off]) - // cond: t.IsStruct() && t.NumFields() == 3 && fe.CanSSA(t) - // result: (StructMake3 (Arg {n} [off+int32(t.FieldOff(0))]) (Arg {n} [off+int32(t.FieldOff(1))]) (Arg {n} [off+int32(t.FieldOff(2))])) - for { - t := v.Type - off := auxIntToInt32(v.AuxInt) - n := auxToSym(v.Aux) - if !(t.IsStruct() && t.NumFields() == 3 && fe.CanSSA(t)) { - break - } - v.reset(OpStructMake3) - v0 := b.NewValue0(v.Pos, OpArg, t.FieldType(0)) - v0.AuxInt = int32ToAuxInt(off + int32(t.FieldOff(0))) - v0.Aux = symToAux(n) - v1 := b.NewValue0(v.Pos, OpArg, t.FieldType(1)) - v1.AuxInt = int32ToAuxInt(off + int32(t.FieldOff(1))) - v1.Aux = symToAux(n) - v2 := b.NewValue0(v.Pos, OpArg, t.FieldType(2)) - v2.AuxInt = int32ToAuxInt(off + int32(t.FieldOff(2))) - v2.Aux = symToAux(n) - v.AddArg3(v0, v1, v2) - return true - } - // match: (Arg {n} [off]) - // cond: t.IsStruct() && t.NumFields() == 4 && fe.CanSSA(t) - // result: (StructMake4 (Arg {n} [off+int32(t.FieldOff(0))]) (Arg {n} [off+int32(t.FieldOff(1))]) (Arg {n} [off+int32(t.FieldOff(2))]) (Arg {n} [off+int32(t.FieldOff(3))])) - for { - t := v.Type - off := auxIntToInt32(v.AuxInt) - n := auxToSym(v.Aux) - if !(t.IsStruct() && t.NumFields() == 4 && fe.CanSSA(t)) { - break - } - v.reset(OpStructMake4) - v0 := b.NewValue0(v.Pos, OpArg, t.FieldType(0)) - v0.AuxInt = int32ToAuxInt(off + int32(t.FieldOff(0))) - v0.Aux = symToAux(n) - v1 := b.NewValue0(v.Pos, OpArg, t.FieldType(1)) - v1.AuxInt = int32ToAuxInt(off + int32(t.FieldOff(1))) - v1.Aux = symToAux(n) - v2 := b.NewValue0(v.Pos, OpArg, t.FieldType(2)) - v2.AuxInt = int32ToAuxInt(off + int32(t.FieldOff(2))) - v2.Aux = symToAux(n) - v3 := b.NewValue0(v.Pos, OpArg, t.FieldType(3)) - v3.AuxInt = int32ToAuxInt(off + int32(t.FieldOff(3))) - v3.Aux = symToAux(n) - v.AddArg4(v0, v1, v2, v3) - return true - } - // match: (Arg ) - // cond: t.IsArray() && t.NumElem() == 0 - // result: (ArrayMake0) - for { - t := v.Type - if !(t.IsArray() && t.NumElem() == 0) { - break - } - v.reset(OpArrayMake0) - return true - } - // match: (Arg {n} [off]) - // cond: t.IsArray() && t.NumElem() == 1 && fe.CanSSA(t) - // result: (ArrayMake1 (Arg {n} [off])) - for { - t := v.Type - off := auxIntToInt32(v.AuxInt) - n := auxToSym(v.Aux) - if !(t.IsArray() && t.NumElem() == 1 && fe.CanSSA(t)) { - break - } - v.reset(OpArrayMake1) - v0 := b.NewValue0(v.Pos, OpArg, t.Elem()) - v0.AuxInt = int32ToAuxInt(off) - v0.Aux = symToAux(n) - v.AddArg(v0) - return true - } - return false -} -func rewriteBlockdecArgs(b *Block) bool { - switch b.Kind { - } - return false -} diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index 958e24d29f02e167c473fec5b3061c44ba145419..52258201ca105163e20197a200794c7604176ad2 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -122,8 +122,6 @@ func rewriteValuegeneric(v *Value) bool { return rewriteValuegeneric_OpEqSlice(v) case OpIMake: return rewriteValuegeneric_OpIMake(v) - case OpInterCall: - return rewriteValuegeneric_OpInterCall(v) case OpInterLECall: return rewriteValuegeneric_OpInterLECall(v) case OpIsInBounds: @@ -392,8 +390,6 @@ func rewriteValuegeneric(v *Value) bool { return rewriteValuegeneric_OpSlicemask(v) case OpSqrt: return rewriteValuegeneric_OpSqrt(v) - case OpStaticCall: - return rewriteValuegeneric_OpStaticCall(v) case OpStaticLECall: return rewriteValuegeneric_OpStaticLECall(v) case OpStore: @@ -4089,6 +4085,26 @@ func rewriteValuegeneric_OpCvt64Fto32F(v *Value) bool { v.AuxInt = float32ToAuxInt(float32(c)) return true } + // match: (Cvt64Fto32F sqrt0:(Sqrt (Cvt32Fto64F x))) + // cond: sqrt0.Uses==1 + // result: (Sqrt32 x) + for { + sqrt0 := v_0 + if sqrt0.Op != OpSqrt { + break + } + sqrt0_0 := sqrt0.Args[0] + if sqrt0_0.Op != OpCvt32Fto64F { + break + } + x := sqrt0_0.Args[0] + if !(sqrt0.Uses == 1) { + break + } + v.reset(OpSqrt32) + v.AddArg(x) + return true + } return false } func rewriteValuegeneric_OpCvt64Fto64(v *Value) bool { @@ -8136,32 +8152,32 @@ func rewriteValuegeneric_OpEqPtr(v *Value) bool { v.AuxInt = boolToAuxInt(true) return true } - // match: (EqPtr (Addr {a} _) (Addr {b} _)) - // result: (ConstBool [a == b]) + // match: (EqPtr (Addr {x} _) (Addr {y} _)) + // result: (ConstBool [x == y]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpAddr { continue } - a := auxToSym(v_0.Aux) + x := auxToSym(v_0.Aux) if v_1.Op != OpAddr { continue } - b := auxToSym(v_1.Aux) + y := auxToSym(v_1.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a == b) + v.AuxInt = boolToAuxInt(x == y) return true } break } - // match: (EqPtr (Addr {a} _) (OffPtr [o] (Addr {b} _))) - // result: (ConstBool [a == b && o == 0]) + // match: (EqPtr (Addr {x} _) (OffPtr [o] (Addr {y} _))) + // result: (ConstBool [x == y && o == 0]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpAddr { continue } - a := auxToSym(v_0.Aux) + x := auxToSym(v_0.Aux) if v_1.Op != OpOffPtr { continue } @@ -8170,15 +8186,15 @@ func rewriteValuegeneric_OpEqPtr(v *Value) bool { if v_1_0.Op != OpAddr { continue } - b := auxToSym(v_1_0.Aux) + y := auxToSym(v_1_0.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a == b && o == 0) + v.AuxInt = boolToAuxInt(x == y && o == 0) return true } break } - // match: (EqPtr (OffPtr [o1] (Addr {a} _)) (OffPtr [o2] (Addr {b} _))) - // result: (ConstBool [a == b && o1 == o2]) + // match: (EqPtr (OffPtr [o1] (Addr {x} _)) (OffPtr [o2] (Addr {y} _))) + // result: (ConstBool [x == y && o1 == o2]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpOffPtr { @@ -8189,7 +8205,7 @@ func rewriteValuegeneric_OpEqPtr(v *Value) bool { if v_0_0.Op != OpAddr { continue } - a := auxToSym(v_0_0.Aux) + x := auxToSym(v_0_0.Aux) if v_1.Op != OpOffPtr { continue } @@ -8198,39 +8214,39 @@ func rewriteValuegeneric_OpEqPtr(v *Value) bool { if v_1_0.Op != OpAddr { continue } - b := auxToSym(v_1_0.Aux) + y := auxToSym(v_1_0.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a == b && o1 == o2) + v.AuxInt = boolToAuxInt(x == y && o1 == o2) return true } break } - // match: (EqPtr (LocalAddr {a} _ _) (LocalAddr {b} _ _)) - // result: (ConstBool [a == b]) + // match: (EqPtr (LocalAddr {x} _ _) (LocalAddr {y} _ _)) + // result: (ConstBool [x == y]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpLocalAddr { continue } - a := auxToSym(v_0.Aux) + x := auxToSym(v_0.Aux) if v_1.Op != OpLocalAddr { continue } - b := auxToSym(v_1.Aux) + y := auxToSym(v_1.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a == b) + v.AuxInt = boolToAuxInt(x == y) return true } break } - // match: (EqPtr (LocalAddr {a} _ _) (OffPtr [o] (LocalAddr {b} _ _))) - // result: (ConstBool [a == b && o == 0]) + // match: (EqPtr (LocalAddr {x} _ _) (OffPtr [o] (LocalAddr {y} _ _))) + // result: (ConstBool [x == y && o == 0]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpLocalAddr { continue } - a := auxToSym(v_0.Aux) + x := auxToSym(v_0.Aux) if v_1.Op != OpOffPtr { continue } @@ -8239,15 +8255,15 @@ func rewriteValuegeneric_OpEqPtr(v *Value) bool { if v_1_0.Op != OpLocalAddr { continue } - b := auxToSym(v_1_0.Aux) + y := auxToSym(v_1_0.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a == b && o == 0) + v.AuxInt = boolToAuxInt(x == y && o == 0) return true } break } - // match: (EqPtr (OffPtr [o1] (LocalAddr {a} _ _)) (OffPtr [o2] (LocalAddr {b} _ _))) - // result: (ConstBool [a == b && o1 == o2]) + // match: (EqPtr (OffPtr [o1] (LocalAddr {x} _ _)) (OffPtr [o2] (LocalAddr {y} _ _))) + // result: (ConstBool [x == y && o1 == o2]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpOffPtr { @@ -8258,7 +8274,7 @@ func rewriteValuegeneric_OpEqPtr(v *Value) bool { if v_0_0.Op != OpLocalAddr { continue } - a := auxToSym(v_0_0.Aux) + x := auxToSym(v_0_0.Aux) if v_1.Op != OpOffPtr { continue } @@ -8267,9 +8283,9 @@ func rewriteValuegeneric_OpEqPtr(v *Value) bool { if v_1_0.Op != OpLocalAddr { continue } - b := auxToSym(v_1_0.Aux) + y := auxToSym(v_1_0.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a == b && o1 == o2) + v.AuxInt = boolToAuxInt(x == y && o1 == o2) return true } break @@ -8516,74 +8532,28 @@ func rewriteValuegeneric_OpEqSlice(v *Value) bool { func rewriteValuegeneric_OpIMake(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] - // match: (IMake typ (StructMake1 val)) - // result: (IMake typ val) + // match: (IMake _typ (StructMake1 val)) + // result: (IMake _typ val) for { - typ := v_0 + _typ := v_0 if v_1.Op != OpStructMake1 { break } val := v_1.Args[0] v.reset(OpIMake) - v.AddArg2(typ, val) + v.AddArg2(_typ, val) return true } - // match: (IMake typ (ArrayMake1 val)) - // result: (IMake typ val) + // match: (IMake _typ (ArrayMake1 val)) + // result: (IMake _typ val) for { - typ := v_0 + _typ := v_0 if v_1.Op != OpArrayMake1 { break } val := v_1.Args[0] v.reset(OpIMake) - v.AddArg2(typ, val) - return true - } - return false -} -func rewriteValuegeneric_OpInterCall(v *Value) bool { - v_1 := v.Args[1] - v_0 := v.Args[0] - // match: (InterCall [argsize] {auxCall} (Load (OffPtr [off] (ITab (IMake (Addr {itab} (SB)) _))) _) mem) - // cond: devirt(v, auxCall, itab, off) != nil - // result: (StaticCall [int32(argsize)] {devirt(v, auxCall, itab, off)} mem) - for { - argsize := auxIntToInt32(v.AuxInt) - auxCall := auxToCall(v.Aux) - if v_0.Op != OpLoad { - break - } - v_0_0 := v_0.Args[0] - if v_0_0.Op != OpOffPtr { - break - } - off := auxIntToInt64(v_0_0.AuxInt) - v_0_0_0 := v_0_0.Args[0] - if v_0_0_0.Op != OpITab { - break - } - v_0_0_0_0 := v_0_0_0.Args[0] - if v_0_0_0_0.Op != OpIMake { - break - } - v_0_0_0_0_0 := v_0_0_0_0.Args[0] - if v_0_0_0_0_0.Op != OpAddr { - break - } - itab := auxToSym(v_0_0_0_0_0.Aux) - v_0_0_0_0_0_0 := v_0_0_0_0_0.Args[0] - if v_0_0_0_0_0_0.Op != OpSB { - break - } - mem := v_1 - if !(devirt(v, auxCall, itab, off) != nil) { - break - } - v.reset(OpStaticCall) - v.AuxInt = int32ToAuxInt(int32(argsize)) - v.Aux = callToAux(devirt(v, auxCall, itab, off)) - v.AddArg(mem) + v.AddArg2(_typ, val) return true } return false @@ -15740,32 +15710,32 @@ func rewriteValuegeneric_OpNeqPtr(v *Value) bool { v.AuxInt = boolToAuxInt(false) return true } - // match: (NeqPtr (Addr {a} _) (Addr {b} _)) - // result: (ConstBool [a != b]) + // match: (NeqPtr (Addr {x} _) (Addr {y} _)) + // result: (ConstBool [x != y]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpAddr { continue } - a := auxToSym(v_0.Aux) + x := auxToSym(v_0.Aux) if v_1.Op != OpAddr { continue } - b := auxToSym(v_1.Aux) + y := auxToSym(v_1.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a != b) + v.AuxInt = boolToAuxInt(x != y) return true } break } - // match: (NeqPtr (Addr {a} _) (OffPtr [o] (Addr {b} _))) - // result: (ConstBool [a != b || o != 0]) + // match: (NeqPtr (Addr {x} _) (OffPtr [o] (Addr {y} _))) + // result: (ConstBool [x != y || o != 0]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpAddr { continue } - a := auxToSym(v_0.Aux) + x := auxToSym(v_0.Aux) if v_1.Op != OpOffPtr { continue } @@ -15774,15 +15744,15 @@ func rewriteValuegeneric_OpNeqPtr(v *Value) bool { if v_1_0.Op != OpAddr { continue } - b := auxToSym(v_1_0.Aux) + y := auxToSym(v_1_0.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a != b || o != 0) + v.AuxInt = boolToAuxInt(x != y || o != 0) return true } break } - // match: (NeqPtr (OffPtr [o1] (Addr {a} _)) (OffPtr [o2] (Addr {b} _))) - // result: (ConstBool [a != b || o1 != o2]) + // match: (NeqPtr (OffPtr [o1] (Addr {x} _)) (OffPtr [o2] (Addr {y} _))) + // result: (ConstBool [x != y || o1 != o2]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpOffPtr { @@ -15793,7 +15763,7 @@ func rewriteValuegeneric_OpNeqPtr(v *Value) bool { if v_0_0.Op != OpAddr { continue } - a := auxToSym(v_0_0.Aux) + x := auxToSym(v_0_0.Aux) if v_1.Op != OpOffPtr { continue } @@ -15802,39 +15772,39 @@ func rewriteValuegeneric_OpNeqPtr(v *Value) bool { if v_1_0.Op != OpAddr { continue } - b := auxToSym(v_1_0.Aux) + y := auxToSym(v_1_0.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a != b || o1 != o2) + v.AuxInt = boolToAuxInt(x != y || o1 != o2) return true } break } - // match: (NeqPtr (LocalAddr {a} _ _) (LocalAddr {b} _ _)) - // result: (ConstBool [a != b]) + // match: (NeqPtr (LocalAddr {x} _ _) (LocalAddr {y} _ _)) + // result: (ConstBool [x != y]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpLocalAddr { continue } - a := auxToSym(v_0.Aux) + x := auxToSym(v_0.Aux) if v_1.Op != OpLocalAddr { continue } - b := auxToSym(v_1.Aux) + y := auxToSym(v_1.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a != b) + v.AuxInt = boolToAuxInt(x != y) return true } break } - // match: (NeqPtr (LocalAddr {a} _ _) (OffPtr [o] (LocalAddr {b} _ _))) - // result: (ConstBool [a != b || o != 0]) + // match: (NeqPtr (LocalAddr {x} _ _) (OffPtr [o] (LocalAddr {y} _ _))) + // result: (ConstBool [x != y || o != 0]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpLocalAddr { continue } - a := auxToSym(v_0.Aux) + x := auxToSym(v_0.Aux) if v_1.Op != OpOffPtr { continue } @@ -15843,15 +15813,15 @@ func rewriteValuegeneric_OpNeqPtr(v *Value) bool { if v_1_0.Op != OpLocalAddr { continue } - b := auxToSym(v_1_0.Aux) + y := auxToSym(v_1_0.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a != b || o != 0) + v.AuxInt = boolToAuxInt(x != y || o != 0) return true } break } - // match: (NeqPtr (OffPtr [o1] (LocalAddr {a} _ _)) (OffPtr [o2] (LocalAddr {b} _ _))) - // result: (ConstBool [a != b || o1 != o2]) + // match: (NeqPtr (OffPtr [o1] (LocalAddr {x} _ _)) (OffPtr [o2] (LocalAddr {y} _ _))) + // result: (ConstBool [x != y || o1 != o2]) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { if v_0.Op != OpOffPtr { @@ -15862,7 +15832,7 @@ func rewriteValuegeneric_OpNeqPtr(v *Value) bool { if v_0_0.Op != OpLocalAddr { continue } - a := auxToSym(v_0_0.Aux) + x := auxToSym(v_0_0.Aux) if v_1.Op != OpOffPtr { continue } @@ -15871,9 +15841,9 @@ func rewriteValuegeneric_OpNeqPtr(v *Value) bool { if v_1_0.Op != OpLocalAddr { continue } - b := auxToSym(v_1_0.Aux) + y := auxToSym(v_1_0.Aux) v.reset(OpConstBool) - v.AuxInt = boolToAuxInt(a != b || o1 != o2) + v.AuxInt = boolToAuxInt(x != y || o1 != o2) return true } break @@ -16113,7 +16083,6 @@ func rewriteValuegeneric_OpNilCheck(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block - config := b.Func.Config fe := b.Func.fe // match: (NilCheck (GetG mem) mem) // result: mem @@ -16128,67 +16097,7 @@ func rewriteValuegeneric_OpNilCheck(v *Value) bool { v.copyOf(mem) return true } - // match: (NilCheck (Load (OffPtr [c] (SP)) (StaticCall {sym} _)) _) - // cond: isSameCall(sym, "runtime.newobject") && c == config.ctxt.FixedFrameSize() + config.RegSize && warnRule(fe.Debug_checknil(), v, "removed nil check") - // result: (Invalid) - for { - if v_0.Op != OpLoad { - break - } - _ = v_0.Args[1] - v_0_0 := v_0.Args[0] - if v_0_0.Op != OpOffPtr { - break - } - c := auxIntToInt64(v_0_0.AuxInt) - v_0_0_0 := v_0_0.Args[0] - if v_0_0_0.Op != OpSP { - break - } - v_0_1 := v_0.Args[1] - if v_0_1.Op != OpStaticCall { - break - } - sym := auxToCall(v_0_1.Aux) - if !(isSameCall(sym, "runtime.newobject") && c == config.ctxt.FixedFrameSize()+config.RegSize && warnRule(fe.Debug_checknil(), v, "removed nil check")) { - break - } - v.reset(OpInvalid) - return true - } - // match: (NilCheck (OffPtr (Load (OffPtr [c] (SP)) (StaticCall {sym} _))) _) - // cond: isSameCall(sym, "runtime.newobject") && c == config.ctxt.FixedFrameSize() + config.RegSize && warnRule(fe.Debug_checknil(), v, "removed nil check") - // result: (Invalid) - for { - if v_0.Op != OpOffPtr { - break - } - v_0_0 := v_0.Args[0] - if v_0_0.Op != OpLoad { - break - } - _ = v_0_0.Args[1] - v_0_0_0 := v_0_0.Args[0] - if v_0_0_0.Op != OpOffPtr { - break - } - c := auxIntToInt64(v_0_0_0.AuxInt) - v_0_0_0_0 := v_0_0_0.Args[0] - if v_0_0_0_0.Op != OpSP { - break - } - v_0_0_1 := v_0_0.Args[1] - if v_0_0_1.Op != OpStaticCall { - break - } - sym := auxToCall(v_0_0_1.Aux) - if !(isSameCall(sym, "runtime.newobject") && c == config.ctxt.FixedFrameSize()+config.RegSize && warnRule(fe.Debug_checknil(), v, "removed nil check")) { - break - } - v.reset(OpInvalid) - return true - } - // match: (NilCheck (SelectN [0] call:(StaticLECall _ _)) (SelectN [1] call)) + // match: (NilCheck (SelectN [0] call:(StaticLECall _ _)) _) // cond: isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check") // result: (Invalid) for { @@ -16196,13 +16105,13 @@ func rewriteValuegeneric_OpNilCheck(v *Value) bool { break } call := v_0.Args[0] - if call.Op != OpStaticLECall || len(call.Args) != 2 || v_1.Op != OpSelectN || auxIntToInt64(v_1.AuxInt) != 1 || call != v_1.Args[0] || !(isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check")) { + if call.Op != OpStaticLECall || len(call.Args) != 2 || !(isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check")) { break } v.reset(OpInvalid) return true } - // match: (NilCheck (OffPtr (SelectN [0] call:(StaticLECall _ _))) (SelectN [1] call)) + // match: (NilCheck (OffPtr (SelectN [0] call:(StaticLECall _ _))) _) // cond: isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check") // result: (Invalid) for { @@ -16214,7 +16123,7 @@ func rewriteValuegeneric_OpNilCheck(v *Value) bool { break } call := v_0_0.Args[0] - if call.Op != OpStaticLECall || len(call.Args) != 2 || v_1.Op != OpSelectN || auxIntToInt64(v_1.AuxInt) != 1 || call != v_1.Args[0] || !(isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check")) { + if call.Op != OpStaticLECall || len(call.Args) != 2 || !(isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check")) { break } v.reset(OpInvalid) @@ -16623,17 +16532,17 @@ func rewriteValuegeneric_OpNot(v *Value) bool { } func rewriteValuegeneric_OpOffPtr(v *Value) bool { v_0 := v.Args[0] - // match: (OffPtr (OffPtr p [b]) [a]) - // result: (OffPtr p [a+b]) + // match: (OffPtr (OffPtr p [y]) [x]) + // result: (OffPtr p [x+y]) for { - a := auxIntToInt64(v.AuxInt) + x := auxIntToInt64(v.AuxInt) if v_0.Op != OpOffPtr { break } - b := auxIntToInt64(v_0.AuxInt) + y := auxIntToInt64(v_0.AuxInt) p := v_0.Args[0] v.reset(OpOffPtr) - v.AuxInt = int64ToAuxInt(a + b) + v.AuxInt = int64ToAuxInt(x + y) v.AddArg(p) return true } @@ -20769,34 +20678,180 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool { v_0 := v.Args[0] b := v.Block config := b.Func.Config - // match: (SelectN [0] (MakeResult a ___)) - // result: a + // match: (SelectN [0] (MakeResult x ___)) + // result: x for { if auxIntToInt64(v.AuxInt) != 0 || v_0.Op != OpMakeResult || len(v_0.Args) < 1 { break } - a := v_0.Args[0] - v.copyOf(a) + x := v_0.Args[0] + v.copyOf(x) return true } - // match: (SelectN [1] (MakeResult a b ___)) - // result: b + // match: (SelectN [1] (MakeResult x y ___)) + // result: y for { if auxIntToInt64(v.AuxInt) != 1 || v_0.Op != OpMakeResult || len(v_0.Args) < 2 { break } - b := v_0.Args[1] - v.copyOf(b) + y := v_0.Args[1] + v.copyOf(y) return true } - // match: (SelectN [2] (MakeResult a b c ___)) - // result: c + // match: (SelectN [2] (MakeResult x y z ___)) + // result: z for { if auxIntToInt64(v.AuxInt) != 2 || v_0.Op != OpMakeResult || len(v_0.Args) < 3 { break } - c := v_0.Args[2] - v.copyOf(c) + z := v_0.Args[2] + v.copyOf(z) + return true + } + // match: (SelectN [0] call:(StaticCall {sym} s1:(Store _ (Const64 [sz]) s2:(Store _ src s3:(Store {t} _ dst mem))))) + // cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3, call) + // result: (Move {t.Elem()} [int64(sz)] dst src mem) + for { + if auxIntToInt64(v.AuxInt) != 0 { + break + } + call := v_0 + if call.Op != OpStaticCall || len(call.Args) != 1 { + break + } + sym := auxToCall(call.Aux) + s1 := call.Args[0] + if s1.Op != OpStore { + break + } + _ = s1.Args[2] + s1_1 := s1.Args[1] + if s1_1.Op != OpConst64 { + break + } + sz := auxIntToInt64(s1_1.AuxInt) + s2 := s1.Args[2] + if s2.Op != OpStore { + break + } + _ = s2.Args[2] + src := s2.Args[1] + s3 := s2.Args[2] + if s3.Op != OpStore { + break + } + t := auxToType(s3.Aux) + mem := s3.Args[2] + dst := s3.Args[1] + if !(sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3, call)) { + break + } + v.reset(OpMove) + v.AuxInt = int64ToAuxInt(int64(sz)) + v.Aux = typeToAux(t.Elem()) + v.AddArg3(dst, src, mem) + return true + } + // match: (SelectN [0] call:(StaticCall {sym} s1:(Store _ (Const32 [sz]) s2:(Store _ src s3:(Store {t} _ dst mem))))) + // cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3, call) + // result: (Move {t.Elem()} [int64(sz)] dst src mem) + for { + if auxIntToInt64(v.AuxInt) != 0 { + break + } + call := v_0 + if call.Op != OpStaticCall || len(call.Args) != 1 { + break + } + sym := auxToCall(call.Aux) + s1 := call.Args[0] + if s1.Op != OpStore { + break + } + _ = s1.Args[2] + s1_1 := s1.Args[1] + if s1_1.Op != OpConst32 { + break + } + sz := auxIntToInt32(s1_1.AuxInt) + s2 := s1.Args[2] + if s2.Op != OpStore { + break + } + _ = s2.Args[2] + src := s2.Args[1] + s3 := s2.Args[2] + if s3.Op != OpStore { + break + } + t := auxToType(s3.Aux) + mem := s3.Args[2] + dst := s3.Args[1] + if !(sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3, call)) { + break + } + v.reset(OpMove) + v.AuxInt = int64ToAuxInt(int64(sz)) + v.Aux = typeToAux(t.Elem()) + v.AddArg3(dst, src, mem) + return true + } + // match: (SelectN [0] call:(StaticCall {sym} dst src (Const64 [sz]) mem)) + // cond: sz >= 0 && call.Uses == 1 && isSameCall(sym, "runtime.memmove") && dst.Type.IsPtr() && isInlinableMemmove(dst, src, int64(sz), config) && clobber(call) + // result: (Move {dst.Type.Elem()} [int64(sz)] dst src mem) + for { + if auxIntToInt64(v.AuxInt) != 0 { + break + } + call := v_0 + if call.Op != OpStaticCall || len(call.Args) != 4 { + break + } + sym := auxToCall(call.Aux) + mem := call.Args[3] + dst := call.Args[0] + src := call.Args[1] + call_2 := call.Args[2] + if call_2.Op != OpConst64 { + break + } + sz := auxIntToInt64(call_2.AuxInt) + if !(sz >= 0 && call.Uses == 1 && isSameCall(sym, "runtime.memmove") && dst.Type.IsPtr() && isInlinableMemmove(dst, src, int64(sz), config) && clobber(call)) { + break + } + v.reset(OpMove) + v.AuxInt = int64ToAuxInt(int64(sz)) + v.Aux = typeToAux(dst.Type.Elem()) + v.AddArg3(dst, src, mem) + return true + } + // match: (SelectN [0] call:(StaticCall {sym} dst src (Const32 [sz]) mem)) + // cond: sz >= 0 && call.Uses == 1 && isSameCall(sym, "runtime.memmove") && dst.Type.IsPtr() && isInlinableMemmove(dst, src, int64(sz), config) && clobber(call) + // result: (Move {dst.Type.Elem()} [int64(sz)] dst src mem) + for { + if auxIntToInt64(v.AuxInt) != 0 { + break + } + call := v_0 + if call.Op != OpStaticCall || len(call.Args) != 4 { + break + } + sym := auxToCall(call.Aux) + mem := call.Args[3] + dst := call.Args[0] + src := call.Args[1] + call_2 := call.Args[2] + if call_2.Op != OpConst32 { + break + } + sz := auxIntToInt32(call_2.AuxInt) + if !(sz >= 0 && call.Uses == 1 && isSameCall(sym, "runtime.memmove") && dst.Type.IsPtr() && isInlinableMemmove(dst, src, int64(sz), config) && clobber(call)) { + break + } + v.reset(OpMove) + v.AuxInt = int64ToAuxInt(int64(sz)) + v.Aux = typeToAux(dst.Type.Elem()) + v.AddArg3(dst, src, mem) return true } // match: (SelectN [0] call:(StaticLECall {sym} dst src (Const64 [sz]) mem)) @@ -20857,6 +20912,44 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool { v.AddArg3(dst, src, mem) return true } + // match: (SelectN [0] call:(StaticLECall {sym} a x)) + // cond: needRaceCleanup(sym, call) && clobber(call) + // result: x + for { + if auxIntToInt64(v.AuxInt) != 0 { + break + } + call := v_0 + if call.Op != OpStaticLECall || len(call.Args) != 2 { + break + } + sym := auxToCall(call.Aux) + x := call.Args[1] + if !(needRaceCleanup(sym, call) && clobber(call)) { + break + } + v.copyOf(x) + return true + } + // match: (SelectN [0] call:(StaticLECall {sym} x)) + // cond: needRaceCleanup(sym, call) && clobber(call) + // result: x + for { + if auxIntToInt64(v.AuxInt) != 0 { + break + } + call := v_0 + if call.Op != OpStaticLECall || len(call.Args) != 1 { + break + } + sym := auxToCall(call.Aux) + x := call.Args[0] + if !(needRaceCleanup(sym, call) && clobber(call)) { + break + } + v.copyOf(x) + return true + } return false } func rewriteValuegeneric_OpSignExt16to32(v *Value) bool { @@ -21307,98 +21400,6 @@ func rewriteValuegeneric_OpSqrt(v *Value) bool { } return false } -func rewriteValuegeneric_OpStaticCall(v *Value) bool { - v_0 := v.Args[0] - b := v.Block - config := b.Func.Config - // match: (StaticCall {sym} s1:(Store _ (Const64 [sz]) s2:(Store _ src s3:(Store {t} _ dst mem)))) - // cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3) - // result: (Move {t.Elem()} [int64(sz)] dst src mem) - for { - sym := auxToCall(v.Aux) - s1 := v_0 - if s1.Op != OpStore { - break - } - _ = s1.Args[2] - s1_1 := s1.Args[1] - if s1_1.Op != OpConst64 { - break - } - sz := auxIntToInt64(s1_1.AuxInt) - s2 := s1.Args[2] - if s2.Op != OpStore { - break - } - _ = s2.Args[2] - src := s2.Args[1] - s3 := s2.Args[2] - if s3.Op != OpStore { - break - } - t := auxToType(s3.Aux) - mem := s3.Args[2] - dst := s3.Args[1] - if !(sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3)) { - break - } - v.reset(OpMove) - v.AuxInt = int64ToAuxInt(int64(sz)) - v.Aux = typeToAux(t.Elem()) - v.AddArg3(dst, src, mem) - return true - } - // match: (StaticCall {sym} s1:(Store _ (Const32 [sz]) s2:(Store _ src s3:(Store {t} _ dst mem)))) - // cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3) - // result: (Move {t.Elem()} [int64(sz)] dst src mem) - for { - sym := auxToCall(v.Aux) - s1 := v_0 - if s1.Op != OpStore { - break - } - _ = s1.Args[2] - s1_1 := s1.Args[1] - if s1_1.Op != OpConst32 { - break - } - sz := auxIntToInt32(s1_1.AuxInt) - s2 := s1.Args[2] - if s2.Op != OpStore { - break - } - _ = s2.Args[2] - src := s2.Args[1] - s3 := s2.Args[2] - if s3.Op != OpStore { - break - } - t := auxToType(s3.Aux) - mem := s3.Args[2] - dst := s3.Args[1] - if !(sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3)) { - break - } - v.reset(OpMove) - v.AuxInt = int64ToAuxInt(int64(sz)) - v.Aux = typeToAux(t.Elem()) - v.AddArg3(dst, src, mem) - return true - } - // match: (StaticCall {sym} x) - // cond: needRaceCleanup(sym, v) - // result: x - for { - sym := auxToCall(v.Aux) - x := v_0 - if !(needRaceCleanup(sym, v)) { - break - } - v.copyOf(x) - return true - } - return false -} func rewriteValuegeneric_OpStaticLECall(v *Value) bool { b := v.Block typ := &b.Func.Config.Types @@ -21442,7 +21443,6 @@ func rewriteValuegeneric_OpStore(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block - config := b.Func.Config fe := b.Func.fe // match: (Store {t1} p1 (Load p2 mem) mem) // cond: isSamePtr(p1, p2) && t2.Size() == t1.Size() @@ -21890,58 +21890,6 @@ func rewriteValuegeneric_OpStore(v *Value) bool { v.AddArg3(dst, e, mem) return true } - // match: (Store (Load (OffPtr [c] (SP)) mem) x mem) - // cond: isConstZero(x) && mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize() + config.RegSize - // result: mem - for { - if v_0.Op != OpLoad { - break - } - mem := v_0.Args[1] - v_0_0 := v_0.Args[0] - if v_0_0.Op != OpOffPtr { - break - } - c := auxIntToInt64(v_0_0.AuxInt) - v_0_0_0 := v_0_0.Args[0] - if v_0_0_0.Op != OpSP { - break - } - x := v_1 - if mem != v_2 || !(isConstZero(x) && mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize()+config.RegSize) { - break - } - v.copyOf(mem) - return true - } - // match: (Store (OffPtr (Load (OffPtr [c] (SP)) mem)) x mem) - // cond: isConstZero(x) && mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize() + config.RegSize - // result: mem - for { - if v_0.Op != OpOffPtr { - break - } - v_0_0 := v_0.Args[0] - if v_0_0.Op != OpLoad { - break - } - mem := v_0_0.Args[1] - v_0_0_0 := v_0_0.Args[0] - if v_0_0_0.Op != OpOffPtr { - break - } - c := auxIntToInt64(v_0_0_0.AuxInt) - v_0_0_0_0 := v_0_0_0.Args[0] - if v_0_0_0_0.Op != OpSP { - break - } - x := v_1 - if mem != v_2 || !(isConstZero(x) && mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize()+config.RegSize) { - break - } - v.copyOf(mem) - return true - } // match: (Store (SelectN [0] call:(StaticLECall _ _)) x mem:(SelectN [1] call)) // cond: isConstZero(x) && isSameCall(call.Aux, "runtime.newobject") // result: mem @@ -24660,27 +24608,6 @@ func rewriteValuegeneric_OpZero(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block - config := b.Func.Config - // match: (Zero (Load (OffPtr [c] (SP)) mem) mem) - // cond: mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize() + config.RegSize - // result: mem - for { - if v_0.Op != OpLoad { - break - } - mem := v_0.Args[1] - v_0_0 := v_0.Args[0] - if v_0_0.Op != OpOffPtr { - break - } - c := auxIntToInt64(v_0_0.AuxInt) - v_0_0_0 := v_0_0.Args[0] - if v_0_0_0.Op != OpSP || mem != v_1 || !(mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize()+config.RegSize) { - break - } - v.copyOf(mem) - return true - } // match: (Zero (SelectN [0] call:(StaticLECall _ _)) mem:(SelectN [1] call)) // cond: isSameCall(call.Aux, "runtime.newobject") // result: mem diff --git a/src/cmd/compile/internal/ssa/schedule.go b/src/cmd/compile/internal/ssa/schedule.go index 8facb91100c5e79de0337d8de96a9608464a996c..4e3e5e75e358d7f1d75a0a5c2888efdfe8fe398a 100644 --- a/src/cmd/compile/internal/ssa/schedule.go +++ b/src/cmd/compile/internal/ssa/schedule.go @@ -137,6 +137,13 @@ func schedule(f *Func) { case v.Op == OpVarDef: // We want all the vardefs next. score[v.ID] = ScoreVarDef + case v.Op == OpArgIntReg || v.Op == OpArgFloatReg: + // In-register args must be scheduled as early as possible to ensure that the + // context register is not stomped. They should only appear in the entry block. + if b != f.Entry { + f.Fatalf("%s appeared outside of entry block, b=%s", v.Op, b.String()) + } + score[v.ID] = ScorePhi case v.Op == OpArg: // We want all the args as early as possible, for better debugging. score[v.ID] = ScoreArg @@ -145,7 +152,7 @@ func schedule(f *Func) { // reduce register pressure. It also helps make sure // VARDEF ops are scheduled before the corresponding LEA. score[v.ID] = ScoreMemory - case v.Op == OpSelect0 || v.Op == OpSelect1: + case v.Op == OpSelect0 || v.Op == OpSelect1 || v.Op == OpSelectN: // Schedule the pseudo-op of reading part of a tuple // immediately after the tuple-generating op, since // this value is already live. This also removes its @@ -270,6 +277,20 @@ func schedule(f *Func) { tuples[v.Args[0].ID] = make([]*Value, 2) } tuples[v.Args[0].ID][1] = v + case v.Op == OpSelectN: + if tuples[v.Args[0].ID] == nil { + tuples[v.Args[0].ID] = make([]*Value, v.Args[0].Type.NumFields()) + } + tuples[v.Args[0].ID][v.AuxInt] = v + case v.Type.IsResults() && tuples[v.ID] != nil: + tup := tuples[v.ID] + for i := len(tup) - 1; i >= 0; i-- { + if tup[i] != nil { + order = append(order, tup[i]) + } + } + delete(tuples, v.ID) + order = append(order, v) case v.Type.IsTuple() && tuples[v.ID] != nil: if tuples[v.ID][1] != nil { order = append(order, tuples[v.ID][1]) diff --git a/src/cmd/compile/internal/ssa/shortcircuit.go b/src/cmd/compile/internal/ssa/shortcircuit.go index 4dd86ec74f64362f6a591a60e795e7f6f5443d2f..29abf3c591f902748ef2affd7869d8636e053d12 100644 --- a/src/cmd/compile/internal/ssa/shortcircuit.go +++ b/src/cmd/compile/internal/ssa/shortcircuit.go @@ -284,6 +284,13 @@ func shortcircuitPhiPlan(b *Block, ctl *Value, cidx int, ti int64) func(*Value, // u is the "untaken" branch: the successor we never go to when coming in from p. u := b.Succs[1^ti].b + // In the following CFG matching, ensure that b's preds are entirely distinct from b's succs. + // This is probably a stronger condition than required, but this happens extremely rarely, + // and it makes it easier to avoid getting deceived by pretty ASCII charts. See #44465. + if p0, p1 := b.Preds[0].b, b.Preds[1].b; p0 == t || p1 == t || p0 == u || p1 == u { + return nil + } + // Look for some common CFG structures // in which the outbound paths from b merge, // with no other preds joining them. diff --git a/src/cmd/compile/internal/ssa/sizeof_test.go b/src/cmd/compile/internal/ssa/sizeof_test.go index 60ada011e3e0b7dc552383442226509d3b883710..a27002ee3ac3b26a557decab61d33de604665398 100644 --- a/src/cmd/compile/internal/ssa/sizeof_test.go +++ b/src/cmd/compile/internal/ssa/sizeof_test.go @@ -22,7 +22,7 @@ func TestSizeof(t *testing.T) { }{ {Value{}, 72, 112}, {Block{}, 164, 304}, - {LocalSlot{}, 32, 48}, + {LocalSlot{}, 28, 40}, {valState{}, 28, 40}, } diff --git a/src/cmd/compile/internal/ssa/sparsetree.go b/src/cmd/compile/internal/ssa/sparsetree.go index 1be20b2cdaedca53759d6eae787d8839f5b3e81f..be914c8644de42a0d70fc1474bb267a5fa5ed1d2 100644 --- a/src/cmd/compile/internal/ssa/sparsetree.go +++ b/src/cmd/compile/internal/ssa/sparsetree.go @@ -178,6 +178,12 @@ func (t SparseTree) Child(x *Block) *Block { return t[x.ID].child } +// Parent returns the parent of x in the dominator tree, or +// nil if x is the function's entry. +func (t SparseTree) Parent(x *Block) *Block { + return t[x.ID].parent +} + // isAncestorEq reports whether x is an ancestor of or equal to y. func (t SparseTree) IsAncestorEq(x, y *Block) bool { if x == y { diff --git a/src/cmd/compile/internal/ssa/sparsetreemap.go b/src/cmd/compile/internal/ssa/sparsetreemap.go deleted file mode 100644 index d26467517e193dfda3829ae33e54f2811f3bc4f0..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/ssa/sparsetreemap.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssa - -import "fmt" - -// A SparseTreeMap encodes a subset of nodes within a tree -// used for sparse-ancestor queries. -// -// Combined with a SparseTreeHelper, this supports an Insert -// to add a tree node to the set and a Find operation to locate -// the nearest tree ancestor of a given node such that the -// ancestor is also in the set. -// -// Given a set of blocks {B1, B2, B3} within the dominator tree, established -// by stm.Insert()ing B1, B2, B3, etc, a query at block B -// (performed with stm.Find(stm, B, adjust, helper)) -// will return the member of the set that is the nearest strict -// ancestor of B within the dominator tree, or nil if none exists. -// The expected complexity of this operation is the log of the size -// the set, given certain assumptions about sparsity (the log complexity -// could be guaranteed with additional data structures whose constant- -// factor overhead has not yet been justified.) -// -// The adjust parameter allows positioning of the insertion -// and lookup points within a block -- one of -// AdjustBefore, AdjustWithin, AdjustAfter, -// where lookups at AdjustWithin can find insertions at -// AdjustBefore in the same block, and lookups at AdjustAfter -// can find insertions at either AdjustBefore or AdjustWithin -// in the same block. (Note that this assumes a gappy numbering -// such that exit number or exit number is separated from its -// nearest neighbor by at least 3). -// -// The Sparse Tree lookup algorithm is described by -// Paul F. Dietz. Maintaining order in a linked list. In -// Proceedings of the Fourteenth Annual ACM Symposium on -// Theory of Computing, pages 122–127, May 1982. -// and by -// Ben Wegbreit. Faster retrieval from context trees. -// Communications of the ACM, 19(9):526–529, September 1976. -type SparseTreeMap RBTint32 - -// A SparseTreeHelper contains indexing and allocation data -// structures common to a collection of SparseTreeMaps, as well -// as exposing some useful control-flow-related data to other -// packages, such as gc. -type SparseTreeHelper struct { - Sdom []SparseTreeNode // indexed by block.ID - Po []*Block // exported data; the blocks, in a post-order - Dom []*Block // exported data; the dominator of this block. - Ponums []int32 // exported data; Po[Ponums[b.ID]] == b; the index of b in Po -} - -// NewSparseTreeHelper returns a SparseTreeHelper for use -// in the gc package, for example in phi-function placement. -func NewSparseTreeHelper(f *Func) *SparseTreeHelper { - dom := f.Idom() - ponums := make([]int32, f.NumBlocks()) - po := postorderWithNumbering(f, ponums) - return makeSparseTreeHelper(newSparseTree(f, dom), dom, po, ponums) -} - -func (h *SparseTreeHelper) NewTree() *SparseTreeMap { - return &SparseTreeMap{} -} - -func makeSparseTreeHelper(sdom SparseTree, dom, po []*Block, ponums []int32) *SparseTreeHelper { - helper := &SparseTreeHelper{Sdom: []SparseTreeNode(sdom), - Dom: dom, - Po: po, - Ponums: ponums, - } - return helper -} - -// A sparseTreeMapEntry contains the data stored in a binary search -// data structure indexed by (dominator tree walk) entry and exit numbers. -// Each entry is added twice, once keyed by entry-1/entry/entry+1 and -// once keyed by exit+1/exit/exit-1. -// -// Within a sparse tree, the two entries added bracket all their descendant -// entries within the tree; the first insertion is keyed by entry number, -// which comes before all the entry and exit numbers of descendants, and -// the second insertion is keyed by exit number, which comes after all the -// entry and exit numbers of the descendants. -type sparseTreeMapEntry struct { - index *SparseTreeNode // references the entry and exit numbers for a block in the sparse tree - block *Block // TODO: store this in a separate index. - data interface{} - sparseParent *sparseTreeMapEntry // references the nearest ancestor of this block in the sparse tree. - adjust int32 // at what adjustment was this node entered into the sparse tree? The same block may be entered more than once, but at different adjustments. -} - -// Insert creates a definition within b with data x. -// adjust indicates where in the block should be inserted: -// AdjustBefore means defined at a phi function (visible Within or After in the same block) -// AdjustWithin means defined within the block (visible After in the same block) -// AdjustAfter means after the block (visible within child blocks) -func (m *SparseTreeMap) Insert(b *Block, adjust int32, x interface{}, helper *SparseTreeHelper) { - rbtree := (*RBTint32)(m) - blockIndex := &helper.Sdom[b.ID] - if blockIndex.entry == 0 { - // assert unreachable - return - } - // sp will be the sparse parent in this sparse tree (nearest ancestor in the larger tree that is also in this sparse tree) - sp := m.findEntry(b, adjust, helper) - entry := &sparseTreeMapEntry{index: blockIndex, block: b, data: x, sparseParent: sp, adjust: adjust} - - right := blockIndex.exit - adjust - _ = rbtree.Insert(right, entry) - - left := blockIndex.entry + adjust - _ = rbtree.Insert(left, entry) - - // This newly inserted block may now be the sparse parent of some existing nodes (the new sparse children of this block) - // Iterate over nodes bracketed by this new node to correct their parent, but not over the proper sparse descendants of those nodes. - _, d := rbtree.Lub(left) // Lub (not EQ) of left is either right or a sparse child - for tme := d.(*sparseTreeMapEntry); tme != entry; tme = d.(*sparseTreeMapEntry) { - tme.sparseParent = entry - // all descendants of tme are unchanged; - // next sparse sibling (or right-bracketing sparse parent == entry) is first node after tme.index.exit - tme.adjust - _, d = rbtree.Lub(tme.index.exit - tme.adjust) - } -} - -// Find returns the definition visible from block b, or nil if none can be found. -// Adjust indicates where the block should be searched. -// AdjustBefore searches before the phi functions of b. -// AdjustWithin searches starting at the phi functions of b. -// AdjustAfter searches starting at the exit from the block, including normal within-block definitions. -// -// Note that Finds are properly nested with Inserts: -// m.Insert(b, a) followed by m.Find(b, a) will not return the result of the insert, -// but m.Insert(b, AdjustBefore) followed by m.Find(b, AdjustWithin) will. -// -// Another way to think of this is that Find searches for inputs, Insert defines outputs. -func (m *SparseTreeMap) Find(b *Block, adjust int32, helper *SparseTreeHelper) interface{} { - v := m.findEntry(b, adjust, helper) - if v == nil { - return nil - } - return v.data -} - -func (m *SparseTreeMap) findEntry(b *Block, adjust int32, helper *SparseTreeHelper) *sparseTreeMapEntry { - rbtree := (*RBTint32)(m) - if rbtree == nil { - return nil - } - blockIndex := &helper.Sdom[b.ID] - - // The Glb (not EQ) of this probe is either the entry-indexed end of a sparse parent - // or the exit-indexed end of a sparse sibling - _, v := rbtree.Glb(blockIndex.entry + adjust) - - if v == nil { - return nil - } - - otherEntry := v.(*sparseTreeMapEntry) - if otherEntry.index.exit >= blockIndex.exit { // otherEntry exit after blockIndex exit; therefore, brackets - return otherEntry - } - // otherEntry is a sparse Sibling, and shares the same sparse parent (nearest ancestor within larger tree) - sp := otherEntry.sparseParent - if sp != nil { - if sp.index.exit < blockIndex.exit { // no ancestor found - return nil - } - return sp - } - return nil -} - -func (m *SparseTreeMap) String() string { - tree := (*RBTint32)(m) - return tree.String() -} - -func (e *sparseTreeMapEntry) String() string { - if e == nil { - return "nil" - } - return fmt.Sprintf("(index=%v, block=%v, data=%v)->%v", e.index, e.block, e.data, e.sparseParent) -} diff --git a/src/cmd/compile/internal/ssa/stackalloc.go b/src/cmd/compile/internal/ssa/stackalloc.go index 406a3c3ea53faa46455823ee34309ddaa16be37f..d41f3996af792fbaaf17a8e09cb872600912f976 100644 --- a/src/cmd/compile/internal/ssa/stackalloc.go +++ b/src/cmd/compile/internal/ssa/stackalloc.go @@ -7,6 +7,7 @@ package ssa import ( + "cmd/compile/internal/ir" "cmd/compile/internal/types" "cmd/internal/src" "fmt" @@ -111,7 +112,7 @@ func (s *stackAllocState) init(f *Func, spillLive [][]ID) { for _, v := range b.Values { s.values[v.ID].typ = v.Type s.values[v.ID].needSlot = !v.Type.IsMemory() && !v.Type.IsVoid() && !v.Type.IsFlags() && f.getHome(v.ID) == nil && !v.rematerializeable() && !v.OnWasmStack - s.values[v.ID].isArg = v.Op == OpArg + s.values[v.ID].isArg = hasAnyArgOp(v) if f.pass.debug > stackDebug && s.values[v.ID].needSlot { fmt.Printf("%s needs a stack slot\n", v) } @@ -140,27 +141,72 @@ func (s *stackAllocState) stackalloc() { s.names = make([]LocalSlot, n) } names := s.names + empty := LocalSlot{} for _, name := range f.Names { // Note: not "range f.NamedValues" above, because // that would be nondeterministic. - for _, v := range f.NamedValues[name] { - names[v.ID] = name + for _, v := range f.NamedValues[*name] { + if v.Op == OpArgIntReg || v.Op == OpArgFloatReg { + aux := v.Aux.(*AuxNameOffset) + // Never let an arg be bound to a differently named thing. + if name.N != aux.Name || name.Off != aux.Offset { + if f.pass.debug > stackDebug { + fmt.Printf("stackalloc register arg %s skipping name %s\n", v, name) + } + continue + } + } else if name.N.Class == ir.PPARAM && v.Op != OpArg { + // PPARAM's only bind to OpArg + if f.pass.debug > stackDebug { + fmt.Printf("stackalloc PPARAM name %s skipping non-Arg %s\n", name, v) + } + continue + } + + if names[v.ID] == empty { + if f.pass.debug > stackDebug { + fmt.Printf("stackalloc value %s to name %s\n", v, *name) + } + names[v.ID] = *name + } } } // Allocate args to their assigned locations. for _, v := range f.Entry.Values { - if v.Op != OpArg { + if !hasAnyArgOp(v) { continue } if v.Aux == nil { f.Fatalf("%s has nil Aux\n", v.LongString()) } - loc := LocalSlot{N: v.Aux.(GCNode), Type: v.Type, Off: v.AuxInt} - if f.pass.debug > stackDebug { - fmt.Printf("stackalloc %s to %s\n", v, loc) + if v.Op == OpArg { + loc := LocalSlot{N: v.Aux.(*ir.Name), Type: v.Type, Off: v.AuxInt} + if f.pass.debug > stackDebug { + fmt.Printf("stackalloc OpArg %s to %s\n", v, loc) + } + f.setHome(v, loc) + continue } - f.setHome(v, loc) + // You might think this below would be the right idea, but you would be wrong. + // It almost works; as of 105a6e9518 - 2021-04-23, + // GOSSAHASH=11011011001011111 == cmd/compile/internal/noder.(*noder).embedded + // is compiled incorrectly. I believe the cause is one of those SSA-to-registers + // puzzles that the register allocator untangles; in the event that a register + // parameter does not end up bound to a name, "fixing" it is a bad idea. + // + //if f.DebugTest { + // if v.Op == OpArgIntReg || v.Op == OpArgFloatReg { + // aux := v.Aux.(*AuxNameOffset) + // loc := LocalSlot{N: aux.Name, Type: v.Type, Off: aux.Offset} + // if f.pass.debug > stackDebug { + // fmt.Printf("stackalloc Op%s %s to %s\n", v.Op, v, loc) + // } + // names[v.ID] = loc + // continue + // } + //} + } // For each type, we keep track of all the stack slots we @@ -197,7 +243,7 @@ func (s *stackAllocState) stackalloc() { s.nNotNeed++ continue } - if v.Op == OpArg { + if hasAnyArgOp(v) { s.nArgSlot++ continue // already picked } @@ -384,7 +430,7 @@ func (s *stackAllocState) buildInterferenceGraph() { for _, id := range live.contents() { // Note: args can have different types and still interfere // (with each other or with other values). See issue 23522. - if s.values[v.ID].typ.Compare(s.values[id].typ) == types.CMPeq || v.Op == OpArg || s.values[id].isArg { + if s.values[v.ID].typ.Compare(s.values[id].typ) == types.CMPeq || hasAnyArgOp(v) || s.values[id].isArg { s.interfere[v.ID] = append(s.interfere[v.ID], id) s.interfere[id] = append(s.interfere[id], v.ID) } @@ -395,13 +441,15 @@ func (s *stackAllocState) buildInterferenceGraph() { live.add(a.ID) } } - if v.Op == OpArg && s.values[v.ID].needSlot { + if hasAnyArgOp(v) && s.values[v.ID].needSlot { // OpArg is an input argument which is pre-spilled. // We add back v.ID here because we want this value // to appear live even before this point. Being live // all the way to the start of the entry block prevents other // values from being allocated to the same slot and clobbering // the input value before we have a chance to load it. + + // TODO(register args) this is apparently not wrong for register args -- is it necessary? live.add(v.ID) } } @@ -418,3 +466,7 @@ func (s *stackAllocState) buildInterferenceGraph() { } } } + +func hasAnyArgOp(v *Value) bool { + return v.Op == OpArg || v.Op == OpArgIntReg || v.Op == OpArgFloatReg +} diff --git a/src/cmd/compile/internal/ssa/stmtlines_test.go b/src/cmd/compile/internal/ssa/stmtlines_test.go index f5ff3a59272dbff75ee2d34b3683c5ca1290cad7..a510d0b3d0607c268d76b444790ed7744bd1a802 100644 --- a/src/cmd/compile/internal/ssa/stmtlines_test.go +++ b/src/cmd/compile/internal/ssa/stmtlines_test.go @@ -117,6 +117,7 @@ func TestStmtLines(t *testing.T) { } else if len(nonStmtLines)*100 > 2*len(lines) { // expect 98% elsewhere. t.Errorf("Saw too many (not amd64, > 2%%) lines without statement marks, total=%d, nostmt=%d ('-run TestStmtLines -v' lists failing lines)\n", len(lines), len(nonStmtLines)) } + t.Logf("Saw %d out of %d lines without statement marks", len(nonStmtLines), len(lines)) if testing.Verbose() { sort.Slice(nonStmtLines, func(i, j int) bool { if nonStmtLines[i].File != nonStmtLines[j].File { diff --git a/src/cmd/compile/internal/ssa/tighten.go b/src/cmd/compile/internal/ssa/tighten.go index 5dfc45364954c12ea1971b138a0e570865aca361..214bf628bd80b1a65a90a66723f7c7a4c67f4b1c 100644 --- a/src/cmd/compile/internal/ssa/tighten.go +++ b/src/cmd/compile/internal/ssa/tighten.go @@ -18,10 +18,11 @@ func tighten(f *Func) { continue } switch v.Op { - case OpPhi, OpArg, OpSelect0, OpSelect1: + case OpPhi, OpArg, OpArgIntReg, OpArgFloatReg, OpSelect0, OpSelect1, OpSelectN: // Phis need to stay in their block. // Arg must stay in the entry block. // Tuple selectors must stay with the tuple generator. + // SelectN is typically, ultimately, a register. continue } if v.MemoryArg() != nil { diff --git a/src/cmd/compile/internal/ssa/tuple.go b/src/cmd/compile/internal/ssa/tuple.go index 38deabf83d24b24a5c97dd139759dc99410b509d..289df40431a7a7b4ce2cb3083d7e24332d45af07 100644 --- a/src/cmd/compile/internal/ssa/tuple.go +++ b/src/cmd/compile/internal/ssa/tuple.go @@ -4,8 +4,8 @@ package ssa -// tightenTupleSelectors ensures that tuple selectors (Select0 and -// Select1 ops) are in the same block as their tuple generator. The +// tightenTupleSelectors ensures that tuple selectors (Select0, Select1, +// and SelectN ops) are in the same block as their tuple generator. The // function also ensures that there are no duplicate tuple selectors. // These properties are expected by the scheduler but may not have // been maintained by the optimization pipeline up to this point. @@ -13,28 +13,40 @@ package ssa // See issues 16741 and 39472. func tightenTupleSelectors(f *Func) { selectors := make(map[struct { - id ID - op Op + id ID + which int }]*Value) for _, b := range f.Blocks { for _, selector := range b.Values { - if selector.Op != OpSelect0 && selector.Op != OpSelect1 { + // Key fields for de-duplication + var tuple *Value + idx := 0 + switch selector.Op { + default: continue - } - - // Get the tuple generator to use as a key for de-duplication. - tuple := selector.Args[0] - if !tuple.Type.IsTuple() { - f.Fatalf("arg of tuple selector %s is not a tuple: %s", selector.String(), tuple.LongString()) + case OpSelect1: + idx = 1 + fallthrough + case OpSelect0: + tuple = selector.Args[0] + if !tuple.Type.IsTuple() { + f.Fatalf("arg of tuple selector %s is not a tuple: %s", selector.String(), tuple.LongString()) + } + case OpSelectN: + tuple = selector.Args[0] + idx = int(selector.AuxInt) + if !tuple.Type.IsResults() { + f.Fatalf("arg of result selector %s is not a results: %s", selector.String(), tuple.LongString()) + } } // If there is a pre-existing selector in the target block then // use that. Do this even if the selector is already in the // target block to avoid duplicate tuple selectors. key := struct { - id ID - op Op - }{tuple.ID, selector.Op} + id ID + which int + }{tuple.ID, idx} if t := selectors[key]; t != nil { if selector != t { selector.copyOf(t) diff --git a/src/cmd/compile/internal/ssa/value.go b/src/cmd/compile/internal/ssa/value.go index edc43aaae72185217487cc7fc68c3ad43d197ad7..630e4814b99f1b38c182d684bb2b6c16f30de704 100644 --- a/src/cmd/compile/internal/ssa/value.go +++ b/src/cmd/compile/internal/ssa/value.go @@ -5,6 +5,7 @@ package ssa import ( + "cmd/compile/internal/ir" "cmd/compile/internal/types" "cmd/internal/src" "fmt" @@ -36,7 +37,7 @@ type Value struct { // Users of AuxInt which interpret AuxInt as unsigned (e.g. shifts) must be careful. // Use Value.AuxUnsigned to get the zero-extended value of AuxInt. AuxInt int64 - Aux interface{} + Aux Aux // Arguments of this value Args []*Value @@ -77,7 +78,7 @@ func (v *Value) String() string { } func (v *Value) AuxInt8() int8 { - if opcodeTable[v.Op].auxType != auxInt8 { + if opcodeTable[v.Op].auxType != auxInt8 && opcodeTable[v.Op].auxType != auxNameOffsetInt8 { v.Fatalf("op %s doesn't have an int8 aux field", v.Op) } return int8(v.AuxInt) @@ -138,6 +139,9 @@ func (v *Value) AuxArm64BitField() arm64BitField { // long form print. v# = opcode [aux] args [: reg] (names) func (v *Value) LongString() string { + if v == nil { + return "" + } s := fmt.Sprintf("v%d = %s", v.ID, v.Op) s += " <" + v.Type.String() + ">" s += v.auxString() @@ -197,12 +201,12 @@ func (v *Value) auxString() string { if v.Aux != nil { return fmt.Sprintf(" {%v}", v.Aux) } - case auxSymOff, auxCallOff, auxTypSize: + case auxSymOff, auxCallOff, auxTypSize, auxNameOffsetInt8: s := "" if v.Aux != nil { s = fmt.Sprintf(" {%v}", v.Aux) } - if v.AuxInt != 0 { + if v.AuxInt != 0 || opcodeTable[v.Op].auxType == auxNameOffsetInt8 { s += fmt.Sprintf(" [%v]", v.AuxInt) } return s @@ -344,6 +348,35 @@ func (v *Value) reset(op Op) { v.Aux = nil } +// invalidateRecursively marks a value as invalid (unused) +// and after decrementing reference counts on its Args, +// also recursively invalidates any of those whose use +// count goes to zero. +// +// BEWARE of doing this *before* you've applied intended +// updates to SSA. +func (v *Value) invalidateRecursively() { + if v.InCache { + v.Block.Func.unCache(v) + } + v.Op = OpInvalid + + for _, a := range v.Args { + a.Uses-- + if a.Uses == 0 { + a.invalidateRecursively() + } + } + + v.argstorage[0] = nil + v.argstorage[1] = nil + v.argstorage[2] = nil + v.Args = v.argstorage[:0] + + v.AuxInt = 0 + v.Aux = nil +} + // copyOf is called from rewrite rules. // It modifies v to be (Copy a). //go:noinline @@ -410,6 +443,23 @@ func (v *Value) isGenericIntConst() bool { return v != nil && (v.Op == OpConst64 || v.Op == OpConst32 || v.Op == OpConst16 || v.Op == OpConst8) } +// ResultReg returns the result register assigned to v, in cmd/internal/obj/$ARCH numbering. +// It is similar to Reg and Reg0, except that it is usable interchangeably for all Value Ops. +// If you know v.Op, using Reg or Reg0 (as appropriate) will be more efficient. +func (v *Value) ResultReg() int16 { + reg := v.Block.Func.RegAlloc[v.ID] + if reg == nil { + v.Fatalf("nil reg for value: %s\n%s\n", v.LongString(), v.Block.Func) + } + if pair, ok := reg.(LocPair); ok { + reg = pair[0] + } + if reg == nil { + v.Fatalf("nil reg0 for value: %s\n%s\n", v.LongString(), v.Block.Func) + } + return reg.(*Register).objNum +} + // Reg returns the register assigned to v, in cmd/internal/obj/$ARCH numbering. func (v *Value) Reg() int16 { reg := v.Block.Func.RegAlloc[v.ID] @@ -481,9 +531,9 @@ func (v *Value) removeable() bool { return false } if v.Type.IsMemory() { - // All memory ops aren't needed here, but we do need + // We don't need to preserve all memory ops, but we do need // to keep calls at least (because they might have - // syncronization operations we can't see). + // synchronization operations we can't see). return false } if v.Op.HasSideEffects() { @@ -492,3 +542,20 @@ func (v *Value) removeable() bool { } return true } + +// TODO(mdempsky): Shouldn't be necessary; see discussion at golang.org/cl/275756 +func (*Value) CanBeAnSSAAux() {} + +// AutoVar returns a *Name and int64 representing the auto variable and offset within it +// where v should be spilled. +func AutoVar(v *Value) (*ir.Name, int64) { + if loc, ok := v.Block.Func.RegAlloc[v.ID].(LocalSlot); ok { + if v.Type.Size() > loc.Type.Size() { + v.Fatalf("spill/restore type %s doesn't fit in slot type %s", v.Type, loc.Type) + } + return loc.N, loc.Off + } + // Assume it is a register, return its spill slot, which needs to be live + nameOff := v.Aux.(*AuxNameOffset) + return nameOff.Name, nameOff.Offset +} diff --git a/src/cmd/compile/internal/ssa/writebarrier.go b/src/cmd/compile/internal/ssa/writebarrier.go index 849c9e8967c60f73a60c2f2d8606c5ba68158f86..419d91d0d367e609dcf9874cced1a58c04acdb4a 100644 --- a/src/cmd/compile/internal/ssa/writebarrier.go +++ b/src/cmd/compile/internal/ssa/writebarrier.go @@ -5,6 +5,7 @@ package ssa import ( + "cmd/compile/internal/reflectdata" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/objabi" @@ -37,9 +38,11 @@ func needwb(v *Value, zeroes map[ID]ZeroRegion) bool { if IsStackAddr(v.Args[0]) { return false // write on stack doesn't need write barrier } - if v.Op == OpMove && IsReadOnlyGlobalAddr(v.Args[1]) && IsNewObject(v.Args[0], v.MemoryArg()) { - // Copying data from readonly memory into a fresh object doesn't need a write barrier. - return false + if v.Op == OpMove && IsReadOnlyGlobalAddr(v.Args[1]) { + if mem, ok := IsNewObject(v.Args[0]); ok && mem == v.MemoryArg() { + // Copying data from readonly memory into a fresh object doesn't need a write barrier. + return false + } } if v.Op == OpStore && IsGlobalAddr(v.Args[1]) { // Storing pointers to non-heap locations into zeroed memory doesn't need a write barrier. @@ -270,11 +273,11 @@ func writebarrier(f *Func) { case OpMoveWB: fn = typedmemmove val = w.Args[1] - typ = w.Aux.(*types.Type).Symbol() + typ = reflectdata.TypeLinksym(w.Aux.(*types.Type)) nWBops-- case OpZeroWB: fn = typedmemclr - typ = w.Aux.(*types.Type).Symbol() + typ = reflectdata.TypeLinksym(w.Aux.(*types.Type)) nWBops-- case OpVarDef, OpVarLive, OpVarKill: } @@ -388,11 +391,7 @@ func (f *Func) computeZeroMap() map[ID]ZeroRegion { // Find new objects. for _, b := range f.Blocks { for _, v := range b.Values { - if v.Op != OpLoad { - continue - } - mem := v.MemoryArg() - if IsNewObject(v, mem) { + if mem, ok := IsNewObject(v); ok { nptr := v.Type.Elem().Size() / ptrSize if nptr > 64 { nptr = 64 @@ -482,38 +481,56 @@ func (f *Func) computeZeroMap() map[ID]ZeroRegion { func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Value) *Value { config := b.Func.Config + var wbargs []*Value + // TODO (register args) this is a bit of a hack. + inRegs := b.Func.ABIDefault == b.Func.ABI1 && len(config.intParamRegs) >= 3 + // put arguments on stack off := config.ctxt.FixedFrameSize() - var ACArgs []Param + var argTypes []*types.Type if typ != nil { // for typedmemmove taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb) + argTypes = append(argTypes, b.Func.Config.Types.Uintptr) off = round(off, taddr.Type.Alignment()) - arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp) - mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, taddr, mem) - ACArgs = append(ACArgs, Param{Type: b.Func.Config.Types.Uintptr, Offset: int32(off)}) + if inRegs { + wbargs = append(wbargs, taddr) + } else { + arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp) + mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, taddr, mem) + } off += taddr.Type.Size() } + argTypes = append(argTypes, ptr.Type) off = round(off, ptr.Type.Alignment()) - arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp) - mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, ptr, mem) - ACArgs = append(ACArgs, Param{Type: ptr.Type, Offset: int32(off)}) + if inRegs { + wbargs = append(wbargs, ptr) + } else { + arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp) + mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, ptr, mem) + } off += ptr.Type.Size() if val != nil { + argTypes = append(argTypes, val.Type) off = round(off, val.Type.Alignment()) - arg = b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp) - mem = b.NewValue3A(pos, OpStore, types.TypeMem, val.Type, arg, val, mem) - ACArgs = append(ACArgs, Param{Type: val.Type, Offset: int32(off)}) + if inRegs { + wbargs = append(wbargs, val) + } else { + arg := b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp) + mem = b.NewValue3A(pos, OpStore, types.TypeMem, val.Type, arg, val, mem) + } off += val.Type.Size() } off = round(off, config.PtrSize) + wbargs = append(wbargs, mem) // issue call - mem = b.NewValue1A(pos, OpStaticCall, types.TypeMem, StaticAuxCall(fn, ACArgs, nil), mem) - mem.AuxInt = off - config.ctxt.FixedFrameSize() - return mem + call := b.NewValue0A(pos, OpStaticCall, types.TypeResultMem, StaticAuxCall(fn, b.Func.ABIDefault.ABIAnalyzeTypes(nil, argTypes, nil))) + call.AddArgs(wbargs...) + call.AuxInt = off - config.ctxt.FixedFrameSize() + return b.NewValue1I(pos, OpSelectN, types.TypeMem, 0, call) } // round to a multiple of r, r is a power of 2 @@ -559,31 +576,60 @@ func IsReadOnlyGlobalAddr(v *Value) bool { return false } -// IsNewObject reports whether v is a pointer to a freshly allocated & zeroed object at memory state mem. -func IsNewObject(v *Value, mem *Value) bool { - if v.Op != OpLoad { - return false +// IsNewObject reports whether v is a pointer to a freshly allocated & zeroed object, +// if so, also returns the memory state mem at which v is zero. +func IsNewObject(v *Value) (mem *Value, ok bool) { + f := v.Block.Func + c := f.Config + if f.ABIDefault == f.ABI1 && len(c.intParamRegs) >= 1 { + if v.Op != OpSelectN || v.AuxInt != 0 { + return nil, false + } + // Find the memory + for _, w := range v.Block.Values { + if w.Op == OpSelectN && w.AuxInt == 1 && w.Args[0] == v.Args[0] { + mem = w + break + } + } + if mem == nil { + return nil, false + } + } else { + if v.Op != OpLoad { + return nil, false + } + mem = v.MemoryArg() + if mem.Op != OpSelectN { + return nil, false + } + if mem.Type != types.TypeMem { + return nil, false + } // assume it is the right selection if true } - if v.MemoryArg() != mem { - return false + call := mem.Args[0] + if call.Op != OpStaticCall { + return nil, false } - if mem.Op != OpStaticCall { - return false + if !isSameCall(call.Aux, "runtime.newobject") { + return nil, false } - if !isSameCall(mem.Aux, "runtime.newobject") { - return false + if f.ABIDefault == f.ABI1 && len(c.intParamRegs) >= 1 { + if v.Args[0] == call { + return mem, true + } + return nil, false } if v.Args[0].Op != OpOffPtr { - return false + return nil, false } if v.Args[0].Args[0].Op != OpSP { - return false + return nil, false } - c := v.Block.Func.Config if v.Args[0].AuxInt != c.ctxt.FixedFrameSize()+c.RegSize { // offset of return value - return false + return nil, false } - return true + return mem, true } // IsSanitizerSafeAddr reports whether v is known to be an address diff --git a/src/cmd/compile/internal/ssa/zcse.go b/src/cmd/compile/internal/ssa/zcse.go index ec38b7d1ba4ae2abd55dfa045b3283cd008b2642..e08272c34580173c60929b7c9edc4183d80050f4 100644 --- a/src/cmd/compile/internal/ssa/zcse.go +++ b/src/cmd/compile/internal/ssa/zcse.go @@ -57,7 +57,7 @@ func zcse(f *Func) { type vkey struct { op Op ai int64 // aux int - ax interface{} // aux + ax Aux // aux t *types.Type // type } diff --git a/src/cmd/compile/internal/ssagen/abi.go b/src/cmd/compile/internal/ssagen/abi.go new file mode 100644 index 0000000000000000000000000000000000000000..e460adaf95d143f2403f40a0ab0607a68ef2ffb1 --- /dev/null +++ b/src/cmd/compile/internal/ssagen/abi.go @@ -0,0 +1,456 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssagen + +import ( + "fmt" + "internal/buildcfg" + "io/ioutil" + "log" + "os" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/staticdata" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/objabi" +) + +// SymABIs records information provided by the assembler about symbol +// definition ABIs and reference ABIs. +type SymABIs struct { + defs map[string]obj.ABI + refs map[string]obj.ABISet + + localPrefix string +} + +func NewSymABIs(myimportpath string) *SymABIs { + var localPrefix string + if myimportpath != "" { + localPrefix = objabi.PathToPrefix(myimportpath) + "." + } + + return &SymABIs{ + defs: make(map[string]obj.ABI), + refs: make(map[string]obj.ABISet), + localPrefix: localPrefix, + } +} + +// canonicalize returns the canonical name used for a linker symbol in +// s's maps. Symbols in this package may be written either as "".X or +// with the package's import path already in the symbol. This rewrites +// both to `"".`, which matches compiler-generated linker symbol names. +func (s *SymABIs) canonicalize(linksym string) string { + // If the symbol is already prefixed with localPrefix, + // rewrite it to start with "" so it matches the + // compiler's internal symbol names. + if s.localPrefix != "" && strings.HasPrefix(linksym, s.localPrefix) { + return `"".` + linksym[len(s.localPrefix):] + } + return linksym +} + +// ReadSymABIs reads a symabis file that specifies definitions and +// references of text symbols by ABI. +// +// The symabis format is a set of lines, where each line is a sequence +// of whitespace-separated fields. The first field is a verb and is +// either "def" for defining a symbol ABI or "ref" for referencing a +// symbol using an ABI. For both "def" and "ref", the second field is +// the symbol name and the third field is the ABI name, as one of the +// named cmd/internal/obj.ABI constants. +func (s *SymABIs) ReadSymABIs(file string) { + data, err := ioutil.ReadFile(file) + if err != nil { + log.Fatalf("-symabis: %v", err) + } + + for lineNum, line := range strings.Split(string(data), "\n") { + lineNum++ // 1-based + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + parts := strings.Fields(line) + switch parts[0] { + case "def", "ref": + // Parse line. + if len(parts) != 3 { + log.Fatalf(`%s:%d: invalid symabi: syntax is "%s sym abi"`, file, lineNum, parts[0]) + } + sym, abistr := parts[1], parts[2] + abi, valid := obj.ParseABI(abistr) + if !valid { + log.Fatalf(`%s:%d: invalid symabi: unknown abi "%s"`, file, lineNum, abistr) + } + + sym = s.canonicalize(sym) + + // Record for later. + if parts[0] == "def" { + s.defs[sym] = abi + } else { + s.refs[sym] |= obj.ABISetOf(abi) + } + default: + log.Fatalf(`%s:%d: invalid symabi type "%s"`, file, lineNum, parts[0]) + } + } +} + +// GenABIWrappers applies ABI information to Funcs and generates ABI +// wrapper functions where necessary. +func (s *SymABIs) GenABIWrappers() { + // For cgo exported symbols, we tell the linker to export the + // definition ABI to C. That also means that we don't want to + // create ABI wrappers even if there's a linkname. + // + // TODO(austin): Maybe we want to create the ABI wrappers, but + // ensure the linker exports the right ABI definition under + // the unmangled name? + cgoExports := make(map[string][]*[]string) + for i, prag := range typecheck.Target.CgoPragmas { + switch prag[0] { + case "cgo_export_static", "cgo_export_dynamic": + symName := s.canonicalize(prag[1]) + pprag := &typecheck.Target.CgoPragmas[i] + cgoExports[symName] = append(cgoExports[symName], pprag) + } + } + + // Apply ABI defs and refs to Funcs and generate wrappers. + // + // This may generate new decls for the wrappers, but we + // specifically *don't* want to visit those, lest we create + // wrappers for wrappers. + for _, fn := range typecheck.Target.Decls { + if fn.Op() != ir.ODCLFUNC { + continue + } + fn := fn.(*ir.Func) + nam := fn.Nname + if ir.IsBlank(nam) { + continue + } + sym := nam.Sym() + var symName string + if sym.Linkname != "" { + symName = s.canonicalize(sym.Linkname) + } else { + // These names will already be canonical. + symName = sym.Pkg.Prefix + "." + sym.Name + } + + // Apply definitions. + defABI, hasDefABI := s.defs[symName] + if hasDefABI { + fn.ABI = defABI + } + + if fn.Pragma&ir.CgoUnsafeArgs != 0 { + // CgoUnsafeArgs indicates the function (or its callee) uses + // offsets to dispatch arguments, which currently using ABI0 + // frame layout. Pin it to ABI0. + fn.ABI = obj.ABI0 + } + + // If cgo-exported, add the definition ABI to the cgo + // pragmas. + cgoExport := cgoExports[symName] + for _, pprag := range cgoExport { + // The export pragmas have the form: + // + // cgo_export_* [] + // + // If is omitted, it's the same as + // . + // + // Expand to + // + // cgo_export_* + if len(*pprag) == 2 { + *pprag = append(*pprag, (*pprag)[1]) + } + // Add the ABI argument. + *pprag = append(*pprag, fn.ABI.String()) + } + + // Apply references. + if abis, ok := s.refs[symName]; ok { + fn.ABIRefs |= abis + } + // Assume all functions are referenced at least as + // ABIInternal, since they may be referenced from + // other packages. + fn.ABIRefs.Set(obj.ABIInternal, true) + + // If a symbol is defined in this package (either in + // Go or assembly) and given a linkname, it may be + // referenced from another package, so make it + // callable via any ABI. It's important that we know + // it's defined in this package since other packages + // may "pull" symbols using linkname and we don't want + // to create duplicate ABI wrappers. + // + // However, if it's given a linkname for exporting to + // C, then we don't make ABI wrappers because the cgo + // tool wants the original definition. + hasBody := len(fn.Body) != 0 + if sym.Linkname != "" && (hasBody || hasDefABI) && len(cgoExport) == 0 { + fn.ABIRefs |= obj.ABISetCallable + } + + // Double check that cgo-exported symbols don't get + // any wrappers. + if len(cgoExport) > 0 && fn.ABIRefs&^obj.ABISetOf(fn.ABI) != 0 { + base.Fatalf("cgo exported function %s cannot have ABI wrappers", fn) + } + + if !buildcfg.Experiment.RegabiWrappers { + // We'll generate ABI aliases instead of + // wrappers once we have LSyms in InitLSym. + continue + } + + forEachWrapperABI(fn, makeABIWrapper) + } +} + +// InitLSym defines f's obj.LSym and initializes it based on the +// properties of f. This includes setting the symbol flags and ABI and +// creating and initializing related DWARF symbols. +// +// InitLSym must be called exactly once per function and must be +// called for both functions with bodies and functions without bodies. +// For body-less functions, we only create the LSym; for functions +// with bodies call a helper to setup up / populate the LSym. +func InitLSym(f *ir.Func, hasBody bool) { + if f.LSym != nil { + base.FatalfAt(f.Pos(), "InitLSym called twice on %v", f) + } + + if nam := f.Nname; !ir.IsBlank(nam) { + f.LSym = nam.LinksymABI(f.ABI) + if f.Pragma&ir.Systemstack != 0 { + f.LSym.Set(obj.AttrCFunc, true) + } + if f.ABI == obj.ABIInternal || !buildcfg.Experiment.RegabiWrappers { + // Function values can only point to + // ABIInternal entry points. This will create + // the funcsym for either the defining + // function or its wrapper as appropriate. + // + // If we're using ABI aliases instead of + // wrappers, we only InitLSym for the defining + // ABI of a function, so we make the funcsym + // when we see that. + staticdata.NeedFuncSym(f) + } + if !buildcfg.Experiment.RegabiWrappers { + // Create ABI aliases instead of wrappers. + forEachWrapperABI(f, makeABIAlias) + } + } + if hasBody { + setupTextLSym(f, 0) + } +} + +func forEachWrapperABI(fn *ir.Func, cb func(fn *ir.Func, wrapperABI obj.ABI)) { + need := fn.ABIRefs &^ obj.ABISetOf(fn.ABI) + if need == 0 { + return + } + + for wrapperABI := obj.ABI(0); wrapperABI < obj.ABICount; wrapperABI++ { + if !need.Get(wrapperABI) { + continue + } + cb(fn, wrapperABI) + } +} + +// makeABIAlias creates a new ABI alias so calls to f via wrapperABI +// will be resolved directly to f's ABI by the linker. +func makeABIAlias(f *ir.Func, wrapperABI obj.ABI) { + // These LSyms have the same name as the native function, so + // we create them directly rather than looking them up. + // The uniqueness of f.lsym ensures uniqueness of asym. + asym := &obj.LSym{ + Name: f.LSym.Name, + Type: objabi.SABIALIAS, + R: []obj.Reloc{{Sym: f.LSym}}, // 0 size, so "informational" + } + asym.SetABI(wrapperABI) + asym.Set(obj.AttrDuplicateOK, true) + base.Ctxt.ABIAliases = append(base.Ctxt.ABIAliases, asym) +} + +// makeABIWrapper creates a new function that will be called with +// wrapperABI and calls "f" using f.ABI. +func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) { + if base.Debug.ABIWrap != 0 { + fmt.Fprintf(os.Stderr, "=-= %v to %v wrapper for %v\n", wrapperABI, f.ABI, f) + } + + // Q: is this needed? + savepos := base.Pos + savedclcontext := typecheck.DeclContext + savedcurfn := ir.CurFunc + + base.Pos = base.AutogeneratedPos + typecheck.DeclContext = ir.PEXTERN + + // At the moment we don't support wrapping a method, we'd need machinery + // below to handle the receiver. Panic if we see this scenario. + ft := f.Nname.Type() + if ft.NumRecvs() != 0 { + panic("makeABIWrapper support for wrapping methods not implemented") + } + + // Manufacture a new func type to use for the wrapper. + var noReceiver *ir.Field + tfn := ir.NewFuncType(base.Pos, + noReceiver, + typecheck.NewFuncParams(ft.Params(), true), + typecheck.NewFuncParams(ft.Results(), false)) + + // Reuse f's types.Sym to create a new ODCLFUNC/function. + fn := typecheck.DeclFunc(f.Nname.Sym(), tfn) + fn.ABI = wrapperABI + + fn.SetABIWrapper(true) + fn.SetDupok(true) + + // ABI0-to-ABIInternal wrappers will be mainly loading params from + // stack into registers (and/or storing stack locations back to + // registers after the wrapped call); in most cases they won't + // need to allocate stack space, so it should be OK to mark them + // as NOSPLIT in these cases. In addition, my assumption is that + // functions written in assembly are NOSPLIT in most (but not all) + // cases. In the case of an ABIInternal target that has too many + // parameters to fit into registers, the wrapper would need to + // allocate stack space, but this seems like an unlikely scenario. + // Hence: mark these wrappers NOSPLIT. + // + // ABIInternal-to-ABI0 wrappers on the other hand will be taking + // things in registers and pushing them onto the stack prior to + // the ABI0 call, meaning that they will always need to allocate + // stack space. If the compiler marks them as NOSPLIT this seems + // as though it could lead to situations where the linker's + // nosplit-overflow analysis would trigger a link failure. On the + // other hand if they not tagged NOSPLIT then this could cause + // problems when building the runtime (since there may be calls to + // asm routine in cases where it's not safe to grow the stack). In + // most cases the wrapper would be (in effect) inlined, but are + // there (perhaps) indirect calls from the runtime that could run + // into trouble here. + // FIXME: at the moment all.bash does not pass when I leave out + // NOSPLIT for these wrappers, so all are currently tagged with NOSPLIT. + fn.Pragma |= ir.Nosplit + + // Generate call. Use tail call if no params and no returns, + // but a regular call otherwise. + // + // Note: ideally we would be using a tail call in cases where + // there are params but no returns for ABI0->ABIInternal wrappers, + // provided that all params fit into registers (e.g. we don't have + // to allocate any stack space). Doing this will require some + // extra work in typecheck/walk/ssa, might want to add a new node + // OTAILCALL or something to this effect. + tailcall := tfn.Type().NumResults() == 0 && tfn.Type().NumParams() == 0 && tfn.Type().NumRecvs() == 0 + if base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink { + // cannot tailcall on PPC64 with dynamic linking, as we need + // to restore R2 after call. + tailcall = false + } + if base.Ctxt.Arch.Name == "amd64" && wrapperABI == obj.ABIInternal { + // cannot tailcall from ABIInternal to ABI0 on AMD64, as we need + // to special registers (X15) when returning to ABIInternal. + tailcall = false + } + + var tail ir.Node + if tailcall { + tail = ir.NewTailCallStmt(base.Pos, f.Nname) + } else { + call := ir.NewCallExpr(base.Pos, ir.OCALL, f.Nname, nil) + call.Args = ir.ParamNames(tfn.Type()) + call.IsDDD = tfn.Type().IsVariadic() + tail = call + if tfn.Type().NumResults() > 0 { + n := ir.NewReturnStmt(base.Pos, nil) + n.Results = []ir.Node{call} + tail = n + } + } + fn.Body.Append(tail) + + typecheck.FinishFuncBody() + if base.Debug.DclStack != 0 { + types.CheckDclstack() + } + + typecheck.Func(fn) + ir.CurFunc = fn + typecheck.Stmts(fn.Body) + + typecheck.Target.Decls = append(typecheck.Target.Decls, fn) + + // Restore previous context. + base.Pos = savepos + typecheck.DeclContext = savedclcontext + ir.CurFunc = savedcurfn +} + +// setupTextLsym initializes the LSym for a with-body text symbol. +func setupTextLSym(f *ir.Func, flag int) { + if f.Dupok() { + flag |= obj.DUPOK + } + if f.Wrapper() { + flag |= obj.WRAPPER + } + if f.ABIWrapper() { + flag |= obj.ABIWRAPPER + } + if f.Needctxt() { + flag |= obj.NEEDCTXT + } + if f.Pragma&ir.Nosplit != 0 { + flag |= obj.NOSPLIT + } + if f.ReflectMethod() { + flag |= obj.REFLECTMETHOD + } + + // Clumsy but important. + // For functions that could be on the path of invoking a deferred + // function that can recover (runtime.reflectcall, reflect.callReflect, + // and reflect.callMethod), we want the panic+recover special handling. + // See test/recover.go for test cases and src/reflect/value.go + // for the actual functions being considered. + // + // runtime.reflectcall is an assembly function which tailcalls + // WRAPPER functions (runtime.callNN). Its ABI wrapper needs WRAPPER + // flag as well. + fnname := f.Sym().Name + if base.Ctxt.Pkgpath == "runtime" && fnname == "reflectcall" { + flag |= obj.WRAPPER + } else if base.Ctxt.Pkgpath == "reflect" { + switch fnname { + case "callReflect", "callMethod": + flag |= obj.WRAPPER + } + } + + base.Ctxt.InitTextSym(f.LSym, flag) +} diff --git a/src/cmd/compile/internal/ssagen/arch.go b/src/cmd/compile/internal/ssagen/arch.go new file mode 100644 index 0000000000000000000000000000000000000000..7215f42c059a147272500b7916df51287e7bdd7b --- /dev/null +++ b/src/cmd/compile/internal/ssagen/arch.go @@ -0,0 +1,52 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssagen + +import ( + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/ssa" + "cmd/compile/internal/types" + "cmd/internal/obj" +) + +var Arch ArchInfo + +// interface to back end + +type ArchInfo struct { + LinkArch *obj.LinkArch + + REGSP int + MAXWIDTH int64 + SoftFloat bool + + PadFrame func(int64) int64 + + // ZeroRange zeroes a range of memory on stack. It is only inserted + // at function entry, and it is ok to clobber registers. + ZeroRange func(*objw.Progs, *obj.Prog, int64, int64, *uint32) *obj.Prog + + Ginsnop func(*objw.Progs) *obj.Prog + Ginsnopdefer func(*objw.Progs) *obj.Prog // special ginsnop for deferreturn + + // SSAMarkMoves marks any MOVXconst ops that need to avoid clobbering flags. + SSAMarkMoves func(*State, *ssa.Block) + + // SSAGenValue emits Prog(s) for the Value. + SSAGenValue func(*State, *ssa.Value) + + // SSAGenBlock emits end-of-block Progs. SSAGenValue should be called + // for all values in the block before SSAGenBlock. + SSAGenBlock func(s *State, b, next *ssa.Block) + + // LoadRegResults emits instructions that loads register-assigned results + // into registers. They are already in memory (PPARAMOUT nodes). + // Used in open-coded defer return path. + LoadRegResults func(s *State, f *ssa.Func) + + // SpillArgReg emits instructions that spill reg to n+off. + SpillArgReg func(pp *objw.Progs, p *obj.Prog, f *ssa.Func, t *types.Type, reg int16, n *ir.Name, off int64) *obj.Prog +} diff --git a/src/cmd/compile/internal/ssagen/nowb.go b/src/cmd/compile/internal/ssagen/nowb.go new file mode 100644 index 0000000000000000000000000000000000000000..1fbc6a847d0f0712f0c399a9722660493b8c140f --- /dev/null +++ b/src/cmd/compile/internal/ssagen/nowb.go @@ -0,0 +1,206 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssagen + +import ( + "bytes" + "fmt" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/src" +) + +func EnableNoWriteBarrierRecCheck() { + nowritebarrierrecCheck = newNowritebarrierrecChecker() +} + +func NoWriteBarrierRecCheck() { + // Write barriers are now known. Check the + // call graph. + nowritebarrierrecCheck.check() + nowritebarrierrecCheck = nil +} + +var nowritebarrierrecCheck *nowritebarrierrecChecker + +type nowritebarrierrecChecker struct { + // extraCalls contains extra function calls that may not be + // visible during later analysis. It maps from the ODCLFUNC of + // the caller to a list of callees. + extraCalls map[*ir.Func][]nowritebarrierrecCall + + // curfn is the current function during AST walks. + curfn *ir.Func +} + +type nowritebarrierrecCall struct { + target *ir.Func // caller or callee + lineno src.XPos // line of call +} + +// newNowritebarrierrecChecker creates a nowritebarrierrecChecker. It +// must be called before walk +func newNowritebarrierrecChecker() *nowritebarrierrecChecker { + c := &nowritebarrierrecChecker{ + extraCalls: make(map[*ir.Func][]nowritebarrierrecCall), + } + + // Find all systemstack calls and record their targets. In + // general, flow analysis can't see into systemstack, but it's + // important to handle it for this check, so we model it + // directly. This has to happen before transforming closures in walk since + // it's a lot harder to work out the argument after. + for _, n := range typecheck.Target.Decls { + if n.Op() != ir.ODCLFUNC { + continue + } + c.curfn = n.(*ir.Func) + if c.curfn.ABIWrapper() { + // We only want "real" calls to these + // functions, not the generated ones within + // their own ABI wrappers. + continue + } + ir.Visit(n, c.findExtraCalls) + } + c.curfn = nil + return c +} + +func (c *nowritebarrierrecChecker) findExtraCalls(nn ir.Node) { + if nn.Op() != ir.OCALLFUNC { + return + } + n := nn.(*ir.CallExpr) + if n.X == nil || n.X.Op() != ir.ONAME { + return + } + fn := n.X.(*ir.Name) + if fn.Class != ir.PFUNC || fn.Defn == nil { + return + } + if !types.IsRuntimePkg(fn.Sym().Pkg) || fn.Sym().Name != "systemstack" { + return + } + + var callee *ir.Func + arg := n.Args[0] + switch arg.Op() { + case ir.ONAME: + arg := arg.(*ir.Name) + callee = arg.Defn.(*ir.Func) + case ir.OCLOSURE: + arg := arg.(*ir.ClosureExpr) + callee = arg.Func + default: + base.Fatalf("expected ONAME or OCLOSURE node, got %+v", arg) + } + if callee.Op() != ir.ODCLFUNC { + base.Fatalf("expected ODCLFUNC node, got %+v", callee) + } + c.extraCalls[c.curfn] = append(c.extraCalls[c.curfn], nowritebarrierrecCall{callee, n.Pos()}) +} + +// recordCall records a call from ODCLFUNC node "from", to function +// symbol "to" at position pos. +// +// This should be done as late as possible during compilation to +// capture precise call graphs. The target of the call is an LSym +// because that's all we know after we start SSA. +// +// This can be called concurrently for different from Nodes. +func (c *nowritebarrierrecChecker) recordCall(fn *ir.Func, to *obj.LSym, pos src.XPos) { + // We record this information on the *Func so this is concurrent-safe. + if fn.NWBRCalls == nil { + fn.NWBRCalls = new([]ir.SymAndPos) + } + *fn.NWBRCalls = append(*fn.NWBRCalls, ir.SymAndPos{Sym: to, Pos: pos}) +} + +func (c *nowritebarrierrecChecker) check() { + // We walk the call graph as late as possible so we can + // capture all calls created by lowering, but this means we + // only get to see the obj.LSyms of calls. symToFunc lets us + // get back to the ODCLFUNCs. + symToFunc := make(map[*obj.LSym]*ir.Func) + // funcs records the back-edges of the BFS call graph walk. It + // maps from the ODCLFUNC of each function that must not have + // write barriers to the call that inhibits them. Functions + // that are directly marked go:nowritebarrierrec are in this + // map with a zero-valued nowritebarrierrecCall. This also + // acts as the set of marks for the BFS of the call graph. + funcs := make(map[*ir.Func]nowritebarrierrecCall) + // q is the queue of ODCLFUNC Nodes to visit in BFS order. + var q ir.NameQueue + + for _, n := range typecheck.Target.Decls { + if n.Op() != ir.ODCLFUNC { + continue + } + fn := n.(*ir.Func) + + symToFunc[fn.LSym] = fn + + // Make nowritebarrierrec functions BFS roots. + if fn.Pragma&ir.Nowritebarrierrec != 0 { + funcs[fn] = nowritebarrierrecCall{} + q.PushRight(fn.Nname) + } + // Check go:nowritebarrier functions. + if fn.Pragma&ir.Nowritebarrier != 0 && fn.WBPos.IsKnown() { + base.ErrorfAt(fn.WBPos, "write barrier prohibited") + } + } + + // Perform a BFS of the call graph from all + // go:nowritebarrierrec functions. + enqueue := func(src, target *ir.Func, pos src.XPos) { + if target.Pragma&ir.Yeswritebarrierrec != 0 { + // Don't flow into this function. + return + } + if _, ok := funcs[target]; ok { + // Already found a path to target. + return + } + + // Record the path. + funcs[target] = nowritebarrierrecCall{target: src, lineno: pos} + q.PushRight(target.Nname) + } + for !q.Empty() { + fn := q.PopLeft().Func + + // Check fn. + if fn.WBPos.IsKnown() { + var err bytes.Buffer + call := funcs[fn] + for call.target != nil { + fmt.Fprintf(&err, "\n\t%v: called by %v", base.FmtPos(call.lineno), call.target.Nname) + call = funcs[call.target] + } + base.ErrorfAt(fn.WBPos, "write barrier prohibited by caller; %v%s", fn.Nname, err.String()) + continue + } + + // Enqueue fn's calls. + for _, callee := range c.extraCalls[fn] { + enqueue(fn, callee.target, callee.lineno) + } + if fn.NWBRCalls == nil { + continue + } + for _, callee := range *fn.NWBRCalls { + target := symToFunc[callee.Sym] + if target != nil { + enqueue(fn, target, callee.Pos) + } + } + } +} diff --git a/src/cmd/compile/internal/ssagen/pgen.go b/src/cmd/compile/internal/ssagen/pgen.go new file mode 100644 index 0000000000000000000000000000000000000000..62567535d76caccdd37e1fdea3996b51a0fac13c --- /dev/null +++ b/src/cmd/compile/internal/ssagen/pgen.go @@ -0,0 +1,273 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssagen + +import ( + "internal/buildcfg" + "internal/race" + "math/rand" + "sort" + "sync" + "time" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/ssa" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/src" +) + +// cmpstackvarlt reports whether the stack variable a sorts before b. +// +// Sort the list of stack variables. Autos after anything else, +// within autos, unused after used, within used, things with +// pointers first, zeroed things first, and then decreasing size. +// Because autos are laid out in decreasing addresses +// on the stack, pointers first, zeroed things first and decreasing size +// really means, in memory, things with pointers needing zeroing at +// the top of the stack and increasing in size. +// Non-autos sort on offset. +func cmpstackvarlt(a, b *ir.Name) bool { + if needAlloc(a) != needAlloc(b) { + return needAlloc(b) + } + + if !needAlloc(a) { + return a.FrameOffset() < b.FrameOffset() + } + + if a.Used() != b.Used() { + return a.Used() + } + + ap := a.Type().HasPointers() + bp := b.Type().HasPointers() + if ap != bp { + return ap + } + + ap = a.Needzero() + bp = b.Needzero() + if ap != bp { + return ap + } + + if a.Type().Width != b.Type().Width { + return a.Type().Width > b.Type().Width + } + + return a.Sym().Name < b.Sym().Name +} + +// byStackvar implements sort.Interface for []*Node using cmpstackvarlt. +type byStackVar []*ir.Name + +func (s byStackVar) Len() int { return len(s) } +func (s byStackVar) Less(i, j int) bool { return cmpstackvarlt(s[i], s[j]) } +func (s byStackVar) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// needAlloc reports whether n is within the current frame, for which we need to +// allocate space. In particular, it excludes arguments and results, which are in +// the callers frame. +func needAlloc(n *ir.Name) bool { + return n.Class == ir.PAUTO || n.Class == ir.PPARAMOUT && n.IsOutputParamInRegisters() +} + +func (s *ssafn) AllocFrame(f *ssa.Func) { + s.stksize = 0 + s.stkptrsize = 0 + fn := s.curfn + + // Mark the PAUTO's unused. + for _, ln := range fn.Dcl { + if needAlloc(ln) { + ln.SetUsed(false) + } + } + + for _, l := range f.RegAlloc { + if ls, ok := l.(ssa.LocalSlot); ok { + ls.N.SetUsed(true) + } + } + + for _, b := range f.Blocks { + for _, v := range b.Values { + if n, ok := v.Aux.(*ir.Name); ok { + switch n.Class { + case ir.PPARAMOUT: + if n.IsOutputParamInRegisters() && v.Op == ssa.OpVarDef { + // ignore VarDef, look for "real" uses. + // TODO: maybe do this for PAUTO as well? + continue + } + fallthrough + case ir.PPARAM, ir.PAUTO: + n.SetUsed(true) + } + } + } + } + + sort.Sort(byStackVar(fn.Dcl)) + + // Reassign stack offsets of the locals that are used. + lastHasPtr := false + for i, n := range fn.Dcl { + if n.Op() != ir.ONAME || n.Class != ir.PAUTO && !(n.Class == ir.PPARAMOUT && n.IsOutputParamInRegisters()) { + // i.e., stack assign if AUTO, or if PARAMOUT in registers (which has no predefined spill locations) + continue + } + if !n.Used() { + fn.Dcl = fn.Dcl[:i] + break + } + + types.CalcSize(n.Type()) + w := n.Type().Width + if w >= types.MaxWidth || w < 0 { + base.Fatalf("bad width") + } + if w == 0 && lastHasPtr { + // Pad between a pointer-containing object and a zero-sized object. + // This prevents a pointer to the zero-sized object from being interpreted + // as a pointer to the pointer-containing object (and causing it + // to be scanned when it shouldn't be). See issue 24993. + w = 1 + } + s.stksize += w + s.stksize = types.Rnd(s.stksize, int64(n.Type().Align)) + if n.Type().HasPointers() { + s.stkptrsize = s.stksize + lastHasPtr = true + } else { + lastHasPtr = false + } + n.SetFrameOffset(-s.stksize) + } + + s.stksize = types.Rnd(s.stksize, int64(types.RegSize)) + s.stkptrsize = types.Rnd(s.stkptrsize, int64(types.RegSize)) +} + +const maxStackSize = 1 << 30 + +// Compile builds an SSA backend function, +// uses it to generate a plist, +// and flushes that plist to machine code. +// worker indicates which of the backend workers is doing the processing. +func Compile(fn *ir.Func, worker int) { + f := buildssa(fn, worker) + // Note: check arg size to fix issue 25507. + if f.Frontend().(*ssafn).stksize >= maxStackSize || f.OwnAux.ArgWidth() >= maxStackSize { + largeStackFramesMu.Lock() + largeStackFrames = append(largeStackFrames, largeStack{locals: f.Frontend().(*ssafn).stksize, args: f.OwnAux.ArgWidth(), pos: fn.Pos()}) + largeStackFramesMu.Unlock() + return + } + pp := objw.NewProgs(fn, worker) + defer pp.Free() + genssa(f, pp) + // Check frame size again. + // The check above included only the space needed for local variables. + // After genssa, the space needed includes local variables and the callee arg region. + // We must do this check prior to calling pp.Flush. + // If there are any oversized stack frames, + // the assembler may emit inscrutable complaints about invalid instructions. + if pp.Text.To.Offset >= maxStackSize { + largeStackFramesMu.Lock() + locals := f.Frontend().(*ssafn).stksize + largeStackFrames = append(largeStackFrames, largeStack{locals: locals, args: f.OwnAux.ArgWidth(), callee: pp.Text.To.Offset - locals, pos: fn.Pos()}) + largeStackFramesMu.Unlock() + return + } + + pp.Flush() // assemble, fill in boilerplate, etc. + // fieldtrack must be called after pp.Flush. See issue 20014. + fieldtrack(pp.Text.From.Sym, fn.FieldTrack) +} + +func init() { + if race.Enabled { + rand.Seed(time.Now().UnixNano()) + } +} + +// StackOffset returns the stack location of a LocalSlot relative to the +// stack pointer, suitable for use in a DWARF location entry. This has nothing +// to do with its offset in the user variable. +func StackOffset(slot ssa.LocalSlot) int32 { + n := slot.N + var off int64 + switch n.Class { + case ir.PPARAM, ir.PPARAMOUT: + if !n.IsOutputParamInRegisters() { + off = n.FrameOffset() + base.Ctxt.FixedFrameSize() + break + } + fallthrough // PPARAMOUT in registers allocates like an AUTO + case ir.PAUTO: + off = n.FrameOffset() + if base.Ctxt.FixedFrameSize() == 0 { + off -= int64(types.PtrSize) + } + if buildcfg.FramePointerEnabled { + off -= int64(types.PtrSize) + } + } + return int32(off + slot.Off) +} + +// fieldtrack adds R_USEFIELD relocations to fnsym to record any +// struct fields that it used. +func fieldtrack(fnsym *obj.LSym, tracked map[*obj.LSym]struct{}) { + if fnsym == nil { + return + } + if !buildcfg.Experiment.FieldTrack || len(tracked) == 0 { + return + } + + trackSyms := make([]*obj.LSym, 0, len(tracked)) + for sym := range tracked { + trackSyms = append(trackSyms, sym) + } + sort.Slice(trackSyms, func(i, j int) bool { return trackSyms[i].Name < trackSyms[j].Name }) + for _, sym := range trackSyms { + r := obj.Addrel(fnsym) + r.Sym = sym + r.Type = objabi.R_USEFIELD + } +} + +// largeStack is info about a function whose stack frame is too large (rare). +type largeStack struct { + locals int64 + args int64 + callee int64 + pos src.XPos +} + +var ( + largeStackFramesMu sync.Mutex // protects largeStackFrames + largeStackFrames []largeStack +) + +func CheckLargeStacks() { + // Check whether any of the functions we have compiled have gigantic stack frames. + sort.Slice(largeStackFrames, func(i, j int) bool { + return largeStackFrames[i].pos.Before(largeStackFrames[j].pos) + }) + for _, large := range largeStackFrames { + if large.callee != 0 { + base.ErrorfAt(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args + %d MB callee", large.locals>>20, large.args>>20, large.callee>>20) + } else { + base.ErrorfAt(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args", large.locals>>20, large.args>>20) + } + } +} diff --git a/src/cmd/compile/internal/ssagen/pgen_test.go b/src/cmd/compile/internal/ssagen/pgen_test.go new file mode 100644 index 0000000000000000000000000000000000000000..69ed8ad74e974490714d93321c62239cd0b1bb89 --- /dev/null +++ b/src/cmd/compile/internal/ssagen/pgen_test.go @@ -0,0 +1,209 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssagen + +import ( + "reflect" + "sort" + "testing" + + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +func typeWithoutPointers() *types.Type { + return types.NewStruct(types.NoPkg, []*types.Field{ + types.NewField(src.NoXPos, nil, types.New(types.TINT)), + }) +} + +func typeWithPointers() *types.Type { + return types.NewStruct(types.NoPkg, []*types.Field{ + types.NewField(src.NoXPos, nil, types.NewPtr(types.New(types.TINT))), + }) +} + +func markUsed(n *ir.Name) *ir.Name { + n.SetUsed(true) + return n +} + +func markNeedZero(n *ir.Name) *ir.Name { + n.SetNeedzero(true) + return n +} + +// Test all code paths for cmpstackvarlt. +func TestCmpstackvar(t *testing.T) { + nod := func(xoffset int64, t *types.Type, s *types.Sym, cl ir.Class) *ir.Name { + if s == nil { + s = &types.Sym{Name: "."} + } + n := typecheck.NewName(s) + n.SetType(t) + n.SetFrameOffset(xoffset) + n.Class = cl + return n + } + testdata := []struct { + a, b *ir.Name + lt bool + }{ + { + nod(0, nil, nil, ir.PAUTO), + nod(0, nil, nil, ir.PFUNC), + false, + }, + { + nod(0, nil, nil, ir.PFUNC), + nod(0, nil, nil, ir.PAUTO), + true, + }, + { + nod(0, nil, nil, ir.PFUNC), + nod(10, nil, nil, ir.PFUNC), + true, + }, + { + nod(20, nil, nil, ir.PFUNC), + nod(10, nil, nil, ir.PFUNC), + false, + }, + { + nod(10, nil, nil, ir.PFUNC), + nod(10, nil, nil, ir.PFUNC), + false, + }, + { + nod(10, nil, nil, ir.PPARAM), + nod(20, nil, nil, ir.PPARAMOUT), + true, + }, + { + nod(10, nil, nil, ir.PPARAMOUT), + nod(20, nil, nil, ir.PPARAM), + true, + }, + { + markUsed(nod(0, nil, nil, ir.PAUTO)), + nod(0, nil, nil, ir.PAUTO), + true, + }, + { + nod(0, nil, nil, ir.PAUTO), + markUsed(nod(0, nil, nil, ir.PAUTO)), + false, + }, + { + nod(0, typeWithoutPointers(), nil, ir.PAUTO), + nod(0, typeWithPointers(), nil, ir.PAUTO), + false, + }, + { + nod(0, typeWithPointers(), nil, ir.PAUTO), + nod(0, typeWithoutPointers(), nil, ir.PAUTO), + true, + }, + { + markNeedZero(nod(0, &types.Type{}, nil, ir.PAUTO)), + nod(0, &types.Type{}, nil, ir.PAUTO), + true, + }, + { + nod(0, &types.Type{}, nil, ir.PAUTO), + markNeedZero(nod(0, &types.Type{}, nil, ir.PAUTO)), + false, + }, + { + nod(0, &types.Type{Width: 1}, nil, ir.PAUTO), + nod(0, &types.Type{Width: 2}, nil, ir.PAUTO), + false, + }, + { + nod(0, &types.Type{Width: 2}, nil, ir.PAUTO), + nod(0, &types.Type{Width: 1}, nil, ir.PAUTO), + true, + }, + { + nod(0, &types.Type{}, &types.Sym{Name: "abc"}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{Name: "xyz"}, ir.PAUTO), + true, + }, + { + nod(0, &types.Type{}, &types.Sym{Name: "abc"}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{Name: "abc"}, ir.PAUTO), + false, + }, + { + nod(0, &types.Type{}, &types.Sym{Name: "xyz"}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{Name: "abc"}, ir.PAUTO), + false, + }, + } + for _, d := range testdata { + got := cmpstackvarlt(d.a, d.b) + if got != d.lt { + t.Errorf("want %v < %v", d.a, d.b) + } + // If we expect a < b to be true, check that b < a is false. + if d.lt && cmpstackvarlt(d.b, d.a) { + t.Errorf("unexpected %v < %v", d.b, d.a) + } + } +} + +func TestStackvarSort(t *testing.T) { + nod := func(xoffset int64, t *types.Type, s *types.Sym, cl ir.Class) *ir.Name { + n := typecheck.NewName(s) + n.SetType(t) + n.SetFrameOffset(xoffset) + n.Class = cl + return n + } + inp := []*ir.Name{ + nod(0, &types.Type{}, &types.Sym{}, ir.PFUNC), + nod(0, &types.Type{}, &types.Sym{}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{}, ir.PFUNC), + nod(10, &types.Type{}, &types.Sym{}, ir.PFUNC), + nod(20, &types.Type{}, &types.Sym{}, ir.PFUNC), + markUsed(nod(0, &types.Type{}, &types.Sym{}, ir.PAUTO)), + nod(0, typeWithoutPointers(), &types.Sym{}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{}, ir.PAUTO), + markNeedZero(nod(0, &types.Type{}, &types.Sym{}, ir.PAUTO)), + nod(0, &types.Type{Width: 1}, &types.Sym{}, ir.PAUTO), + nod(0, &types.Type{Width: 2}, &types.Sym{}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{Name: "abc"}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{Name: "xyz"}, ir.PAUTO), + } + want := []*ir.Name{ + nod(0, &types.Type{}, &types.Sym{}, ir.PFUNC), + nod(0, &types.Type{}, &types.Sym{}, ir.PFUNC), + nod(10, &types.Type{}, &types.Sym{}, ir.PFUNC), + nod(20, &types.Type{}, &types.Sym{}, ir.PFUNC), + markUsed(nod(0, &types.Type{}, &types.Sym{}, ir.PAUTO)), + markNeedZero(nod(0, &types.Type{}, &types.Sym{}, ir.PAUTO)), + nod(0, &types.Type{Width: 2}, &types.Sym{}, ir.PAUTO), + nod(0, &types.Type{Width: 1}, &types.Sym{}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{Name: "abc"}, ir.PAUTO), + nod(0, &types.Type{}, &types.Sym{Name: "xyz"}, ir.PAUTO), + nod(0, typeWithoutPointers(), &types.Sym{}, ir.PAUTO), + } + sort.Sort(byStackVar(inp)) + if !reflect.DeepEqual(want, inp) { + t.Error("sort failed") + for i := range inp { + g := inp[i] + w := want[i] + eq := reflect.DeepEqual(w, g) + if !eq { + t.Log(i, w, g) + } + } + } +} diff --git a/src/cmd/compile/internal/gc/phi.go b/src/cmd/compile/internal/ssagen/phi.go similarity index 90% rename from src/cmd/compile/internal/gc/phi.go rename to src/cmd/compile/internal/ssagen/phi.go index 5218cd0ef3d1443ffa44219426a7670c5711c134..01ad211282cf3436310e49a325ec8586f8147351 100644 --- a/src/cmd/compile/internal/gc/phi.go +++ b/src/cmd/compile/internal/ssagen/phi.go @@ -2,14 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package ssagen import ( + "container/heap" + "fmt" + + "cmd/compile/internal/ir" "cmd/compile/internal/ssa" "cmd/compile/internal/types" "cmd/internal/src" - "container/heap" - "fmt" ) // This file contains the algorithm to place phi nodes in a function. @@ -22,6 +24,14 @@ const smallBlocks = 500 const debugPhi = false +// fwdRefAux wraps an arbitrary ir.Node as an ssa.Aux for use with OpFwdref. +type fwdRefAux struct { + _ [0]func() // ensure ir.Node isn't compared for equality + N ir.Node +} + +func (fwdRefAux) CanBeAnSSAAux() {} + // insertPhis finds all the places in the function where a phi is // necessary and inserts them. // Uses FwdRef ops to find all uses of variables, and s.defvars to find @@ -40,11 +50,11 @@ func (s *state) insertPhis() { } type phiState struct { - s *state // SSA state - f *ssa.Func // function to work on - defvars []map[*Node]*ssa.Value // defined variables at end of each block + s *state // SSA state + f *ssa.Func // function to work on + defvars []map[ir.Node]*ssa.Value // defined variables at end of each block - varnum map[*Node]int32 // variable numbering + varnum map[ir.Node]int32 // variable numbering // properties of the dominator tree idom []*ssa.Block // dominator parents @@ -59,7 +69,7 @@ type phiState struct { hasDef *sparseSet // has a write of the variable we're processing // miscellaneous - placeholder *ssa.Value // dummy value to use as a "not set yet" placeholder. + placeholder *ssa.Value // value to use as a "not set yet" placeholder. } func (s *phiState) insertPhis() { @@ -70,15 +80,15 @@ func (s *phiState) insertPhis() { // Find all the variables for which we need to match up reads & writes. // This step prunes any basic-block-only variables from consideration. // Generate a numbering for these variables. - s.varnum = map[*Node]int32{} - var vars []*Node + s.varnum = map[ir.Node]int32{} + var vars []ir.Node var vartypes []*types.Type for _, b := range s.f.Blocks { for _, v := range b.Values { if v.Op != ssa.OpFwdRef { continue } - var_ := v.Aux.(*Node) + var_ := v.Aux.(fwdRefAux).N // Optimization: look back 1 block for the definition. if len(b.Preds) == 1 { @@ -179,11 +189,16 @@ levels: if v.Op == ssa.OpPhi { v.AuxInt = 0 } + // Any remaining FwdRefs are dead code. + if v.Op == ssa.OpFwdRef { + v.Op = ssa.OpUnknown + v.Aux = nil + } } } } -func (s *phiState) insertVarPhis(n int, var_ *Node, defs []*ssa.Block, typ *types.Type) { +func (s *phiState) insertVarPhis(n int, var_ ir.Node, defs []*ssa.Block, typ *types.Type) { priq := &s.priq q := s.q queued := s.queued @@ -240,7 +255,9 @@ func (s *phiState) insertVarPhis(n int, var_ *Node, defs []*ssa.Block, typ *type hasPhi.add(c.ID) v := c.NewValue0I(currentRoot.Pos, ssa.OpPhi, typ, int64(n)) // TODO: line number right? // Note: we store the variable number in the phi's AuxInt field. Used temporarily by phi building. - s.s.addNamedValue(var_, v) + if var_.Op() == ir.ONAME { + s.s.addNamedValue(var_.(*ir.Name), v) + } for range c.Preds { v.AddArg(s.placeholder) // Actual args will be filled in by resolveFwdRefs. } @@ -318,7 +335,7 @@ func (s *phiState) resolveFwdRefs() { if v.Op != ssa.OpFwdRef { continue } - n := s.varnum[v.Aux.(*Node)] + n := s.varnum[v.Aux.(fwdRefAux).N] v.Op = ssa.OpCopy v.Aux = nil v.AddArg(values[n]) @@ -432,11 +449,11 @@ func (s *sparseSet) clear() { // Variant to use for small functions. type simplePhiState struct { - s *state // SSA state - f *ssa.Func // function to work on - fwdrefs []*ssa.Value // list of FwdRefs to be processed - defvars []map[*Node]*ssa.Value // defined variables at end of each block - reachable []bool // which blocks are reachable + s *state // SSA state + f *ssa.Func // function to work on + fwdrefs []*ssa.Value // list of FwdRefs to be processed + defvars []map[ir.Node]*ssa.Value // defined variables at end of each block + reachable []bool // which blocks are reachable } func (s *simplePhiState) insertPhis() { @@ -449,7 +466,7 @@ func (s *simplePhiState) insertPhis() { continue } s.fwdrefs = append(s.fwdrefs, v) - var_ := v.Aux.(*Node) + var_ := v.Aux.(fwdRefAux).N if _, ok := s.defvars[b.ID][var_]; !ok { s.defvars[b.ID][var_] = v // treat FwdDefs as definitions. } @@ -463,7 +480,7 @@ loop: v := s.fwdrefs[len(s.fwdrefs)-1] s.fwdrefs = s.fwdrefs[:len(s.fwdrefs)-1] b := v.Block - var_ := v.Aux.(*Node) + var_ := v.Aux.(fwdRefAux).N if b == s.f.Entry { // No variable should be live at entry. s.s.Fatalf("Value live at entry. It shouldn't be. func %s, node %v, value %v", s.f.Name, var_, v) @@ -511,7 +528,7 @@ loop: } // lookupVarOutgoing finds the variable's value at the end of block b. -func (s *simplePhiState) lookupVarOutgoing(b *ssa.Block, t *types.Type, var_ *Node, line src.XPos) *ssa.Value { +func (s *simplePhiState) lookupVarOutgoing(b *ssa.Block, t *types.Type, var_ ir.Node, line src.XPos) *ssa.Value { for { if v := s.defvars[b.ID][var_]; v != nil { return v @@ -530,9 +547,11 @@ func (s *simplePhiState) lookupVarOutgoing(b *ssa.Block, t *types.Type, var_ *No } } // Generate a FwdRef for the variable and return that. - v := b.NewValue0A(line, ssa.OpFwdRef, t, var_) + v := b.NewValue0A(line, ssa.OpFwdRef, t, fwdRefAux{N: var_}) s.defvars[b.ID][var_] = v - s.s.addNamedValue(var_, v) + if var_.Op() == ir.ONAME { + s.s.addNamedValue(var_.(*ir.Name), v) + } s.fwdrefs = append(s.fwdrefs, v) return v } diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go similarity index 51% rename from src/cmd/compile/internal/gc/ssa.go rename to src/cmd/compile/internal/ssagen/ssa.go index 5b74754b53291211f486cca9d28017ed1ce60f23..dfa76006de790110b05def67fe3d8a4451584245 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -2,19 +2,29 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package ssagen import ( - "encoding/binary" + "bufio" + "bytes" + "cmd/compile/internal/abi" "fmt" + "go/constant" "html" + "internal/buildcfg" "os" "path/filepath" "sort" + "strings" - "bufio" - "bytes" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/liveness" + "cmd/compile/internal/objw" + "cmd/compile/internal/reflectdata" "cmd/compile/internal/ssa" + "cmd/compile/internal/staticdata" + "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/x86" @@ -32,165 +42,251 @@ var ssaDumpStdout bool // whether to dump to stdout var ssaDumpCFG string // generate CFGs for these phases const ssaDumpFile = "ssa.html" -// The max number of defers in a function using open-coded defers. We enforce this -// limit because the deferBits bitmask is currently a single byte (to minimize code size) -const maxOpenDefers = 8 - // ssaDumpInlined holds all inlined functions when ssaDump contains a function name. -var ssaDumpInlined []*Node +var ssaDumpInlined []*ir.Func + +func DumpInline(fn *ir.Func) { + if ssaDump != "" && ssaDump == ir.FuncName(fn) { + ssaDumpInlined = append(ssaDumpInlined, fn) + } +} -func initssaconfig() { +func InitEnv() { + ssaDump = os.Getenv("GOSSAFUNC") + ssaDir = os.Getenv("GOSSADIR") + if ssaDump != "" { + if strings.HasSuffix(ssaDump, "+") { + ssaDump = ssaDump[:len(ssaDump)-1] + ssaDumpStdout = true + } + spl := strings.Split(ssaDump, ":") + if len(spl) > 1 { + ssaDump = spl[0] + ssaDumpCFG = spl[1] + } + } +} + +func InitConfig() { types_ := ssa.NewTypes() - if thearch.SoftFloat { + if Arch.SoftFloat { softfloatInit() } // Generate a few pointer types that are uncommon in the frontend but common in the backend. // Caching is disabled in the backend, so generating these here avoids allocations. - _ = types.NewPtr(types.Types[TINTER]) // *interface{} - _ = types.NewPtr(types.NewPtr(types.Types[TSTRING])) // **string - _ = types.NewPtr(types.NewSlice(types.Types[TINTER])) // *[]interface{} - _ = types.NewPtr(types.NewPtr(types.Bytetype)) // **byte - _ = types.NewPtr(types.NewSlice(types.Bytetype)) // *[]byte - _ = types.NewPtr(types.NewSlice(types.Types[TSTRING])) // *[]string - _ = types.NewPtr(types.NewPtr(types.NewPtr(types.Types[TUINT8]))) // ***uint8 - _ = types.NewPtr(types.Types[TINT16]) // *int16 - _ = types.NewPtr(types.Types[TINT64]) // *int64 - _ = types.NewPtr(types.Errortype) // *error + _ = types.NewPtr(types.Types[types.TINTER]) // *interface{} + _ = types.NewPtr(types.NewPtr(types.Types[types.TSTRING])) // **string + _ = types.NewPtr(types.NewSlice(types.Types[types.TINTER])) // *[]interface{} + _ = types.NewPtr(types.NewPtr(types.ByteType)) // **byte + _ = types.NewPtr(types.NewSlice(types.ByteType)) // *[]byte + _ = types.NewPtr(types.NewSlice(types.Types[types.TSTRING])) // *[]string + _ = types.NewPtr(types.NewPtr(types.NewPtr(types.Types[types.TUINT8]))) // ***uint8 + _ = types.NewPtr(types.Types[types.TINT16]) // *int16 + _ = types.NewPtr(types.Types[types.TINT64]) // *int64 + _ = types.NewPtr(types.ErrorType) // *error types.NewPtrCacheEnabled = false - ssaConfig = ssa.NewConfig(thearch.LinkArch.Name, *types_, Ctxt, Debug.N == 0) - ssaConfig.SoftFloat = thearch.SoftFloat - ssaConfig.Race = flag_race - ssaCaches = make([]ssa.Cache, nBackendWorkers) + ssaConfig = ssa.NewConfig(base.Ctxt.Arch.Name, *types_, base.Ctxt, base.Flag.N == 0) + ssaConfig.SoftFloat = Arch.SoftFloat + ssaConfig.Race = base.Flag.Race + ssaCaches = make([]ssa.Cache, base.Flag.LowerC) // Set up some runtime functions we'll need to call. - assertE2I = sysfunc("assertE2I") - assertE2I2 = sysfunc("assertE2I2") - assertI2I = sysfunc("assertI2I") - assertI2I2 = sysfunc("assertI2I2") - deferproc = sysfunc("deferproc") - deferprocStack = sysfunc("deferprocStack") - Deferreturn = sysfunc("deferreturn") - Duffcopy = sysfunc("duffcopy") - Duffzero = sysfunc("duffzero") - gcWriteBarrier = sysfunc("gcWriteBarrier") - goschedguarded = sysfunc("goschedguarded") - growslice = sysfunc("growslice") - msanread = sysfunc("msanread") - msanwrite = sysfunc("msanwrite") - msanmove = sysfunc("msanmove") - newobject = sysfunc("newobject") - newproc = sysfunc("newproc") - panicdivide = sysfunc("panicdivide") - panicdottypeE = sysfunc("panicdottypeE") - panicdottypeI = sysfunc("panicdottypeI") - panicnildottype = sysfunc("panicnildottype") - panicoverflow = sysfunc("panicoverflow") - panicshift = sysfunc("panicshift") - raceread = sysfunc("raceread") - racereadrange = sysfunc("racereadrange") - racewrite = sysfunc("racewrite") - racewriterange = sysfunc("racewriterange") - x86HasPOPCNT = sysvar("x86HasPOPCNT") // bool - x86HasSSE41 = sysvar("x86HasSSE41") // bool - x86HasFMA = sysvar("x86HasFMA") // bool - armHasVFPv4 = sysvar("armHasVFPv4") // bool - arm64HasATOMICS = sysvar("arm64HasATOMICS") // bool - typedmemclr = sysfunc("typedmemclr") - typedmemmove = sysfunc("typedmemmove") - Udiv = sysvar("udiv") // asm func with special ABI - writeBarrier = sysvar("writeBarrier") // struct { bool; ... } - zerobaseSym = sysvar("zerobase") + ir.Syms.AssertE2I = typecheck.LookupRuntimeFunc("assertE2I") + ir.Syms.AssertE2I2 = typecheck.LookupRuntimeFunc("assertE2I2") + ir.Syms.AssertI2I = typecheck.LookupRuntimeFunc("assertI2I") + ir.Syms.AssertI2I2 = typecheck.LookupRuntimeFunc("assertI2I2") + ir.Syms.Deferproc = typecheck.LookupRuntimeFunc("deferproc") + ir.Syms.DeferprocStack = typecheck.LookupRuntimeFunc("deferprocStack") + ir.Syms.Deferreturn = typecheck.LookupRuntimeFunc("deferreturn") + ir.Syms.Duffcopy = typecheck.LookupRuntimeFunc("duffcopy") + ir.Syms.Duffzero = typecheck.LookupRuntimeFunc("duffzero") + ir.Syms.GCWriteBarrier = typecheck.LookupRuntimeFunc("gcWriteBarrier") + ir.Syms.Goschedguarded = typecheck.LookupRuntimeFunc("goschedguarded") + ir.Syms.Growslice = typecheck.LookupRuntimeFunc("growslice") + ir.Syms.Msanread = typecheck.LookupRuntimeFunc("msanread") + ir.Syms.Msanwrite = typecheck.LookupRuntimeFunc("msanwrite") + ir.Syms.Msanmove = typecheck.LookupRuntimeFunc("msanmove") + ir.Syms.Newobject = typecheck.LookupRuntimeFunc("newobject") + ir.Syms.Newproc = typecheck.LookupRuntimeFunc("newproc") + ir.Syms.Panicdivide = typecheck.LookupRuntimeFunc("panicdivide") + ir.Syms.PanicdottypeE = typecheck.LookupRuntimeFunc("panicdottypeE") + ir.Syms.PanicdottypeI = typecheck.LookupRuntimeFunc("panicdottypeI") + ir.Syms.Panicnildottype = typecheck.LookupRuntimeFunc("panicnildottype") + ir.Syms.Panicoverflow = typecheck.LookupRuntimeFunc("panicoverflow") + ir.Syms.Panicshift = typecheck.LookupRuntimeFunc("panicshift") + ir.Syms.Raceread = typecheck.LookupRuntimeFunc("raceread") + ir.Syms.Racereadrange = typecheck.LookupRuntimeFunc("racereadrange") + ir.Syms.Racewrite = typecheck.LookupRuntimeFunc("racewrite") + ir.Syms.Racewriterange = typecheck.LookupRuntimeFunc("racewriterange") + ir.Syms.X86HasPOPCNT = typecheck.LookupRuntimeVar("x86HasPOPCNT") // bool + ir.Syms.X86HasSSE41 = typecheck.LookupRuntimeVar("x86HasSSE41") // bool + ir.Syms.X86HasFMA = typecheck.LookupRuntimeVar("x86HasFMA") // bool + ir.Syms.ARMHasVFPv4 = typecheck.LookupRuntimeVar("armHasVFPv4") // bool + ir.Syms.ARM64HasATOMICS = typecheck.LookupRuntimeVar("arm64HasATOMICS") // bool + ir.Syms.Staticuint64s = typecheck.LookupRuntimeVar("staticuint64s") + ir.Syms.Typedmemclr = typecheck.LookupRuntimeFunc("typedmemclr") + ir.Syms.Typedmemmove = typecheck.LookupRuntimeFunc("typedmemmove") + ir.Syms.Udiv = typecheck.LookupRuntimeVar("udiv") // asm func with special ABI + ir.Syms.WriteBarrier = typecheck.LookupRuntimeVar("writeBarrier") // struct { bool; ... } + ir.Syms.Zerobase = typecheck.LookupRuntimeVar("zerobase") // asm funcs with special ABI - if thearch.LinkArch.Name == "amd64" { + if base.Ctxt.Arch.Name == "amd64" { GCWriteBarrierReg = map[int16]*obj.LSym{ - x86.REG_AX: sysfunc("gcWriteBarrier"), - x86.REG_CX: sysfunc("gcWriteBarrierCX"), - x86.REG_DX: sysfunc("gcWriteBarrierDX"), - x86.REG_BX: sysfunc("gcWriteBarrierBX"), - x86.REG_BP: sysfunc("gcWriteBarrierBP"), - x86.REG_SI: sysfunc("gcWriteBarrierSI"), - x86.REG_R8: sysfunc("gcWriteBarrierR8"), - x86.REG_R9: sysfunc("gcWriteBarrierR9"), - } - } - - if thearch.LinkArch.Family == sys.Wasm { - BoundsCheckFunc[ssa.BoundsIndex] = sysfunc("goPanicIndex") - BoundsCheckFunc[ssa.BoundsIndexU] = sysfunc("goPanicIndexU") - BoundsCheckFunc[ssa.BoundsSliceAlen] = sysfunc("goPanicSliceAlen") - BoundsCheckFunc[ssa.BoundsSliceAlenU] = sysfunc("goPanicSliceAlenU") - BoundsCheckFunc[ssa.BoundsSliceAcap] = sysfunc("goPanicSliceAcap") - BoundsCheckFunc[ssa.BoundsSliceAcapU] = sysfunc("goPanicSliceAcapU") - BoundsCheckFunc[ssa.BoundsSliceB] = sysfunc("goPanicSliceB") - BoundsCheckFunc[ssa.BoundsSliceBU] = sysfunc("goPanicSliceBU") - BoundsCheckFunc[ssa.BoundsSlice3Alen] = sysfunc("goPanicSlice3Alen") - BoundsCheckFunc[ssa.BoundsSlice3AlenU] = sysfunc("goPanicSlice3AlenU") - BoundsCheckFunc[ssa.BoundsSlice3Acap] = sysfunc("goPanicSlice3Acap") - BoundsCheckFunc[ssa.BoundsSlice3AcapU] = sysfunc("goPanicSlice3AcapU") - BoundsCheckFunc[ssa.BoundsSlice3B] = sysfunc("goPanicSlice3B") - BoundsCheckFunc[ssa.BoundsSlice3BU] = sysfunc("goPanicSlice3BU") - BoundsCheckFunc[ssa.BoundsSlice3C] = sysfunc("goPanicSlice3C") - BoundsCheckFunc[ssa.BoundsSlice3CU] = sysfunc("goPanicSlice3CU") + x86.REG_AX: typecheck.LookupRuntimeFunc("gcWriteBarrier"), + x86.REG_CX: typecheck.LookupRuntimeFunc("gcWriteBarrierCX"), + x86.REG_DX: typecheck.LookupRuntimeFunc("gcWriteBarrierDX"), + x86.REG_BX: typecheck.LookupRuntimeFunc("gcWriteBarrierBX"), + x86.REG_BP: typecheck.LookupRuntimeFunc("gcWriteBarrierBP"), + x86.REG_SI: typecheck.LookupRuntimeFunc("gcWriteBarrierSI"), + x86.REG_R8: typecheck.LookupRuntimeFunc("gcWriteBarrierR8"), + x86.REG_R9: typecheck.LookupRuntimeFunc("gcWriteBarrierR9"), + } + } + + if Arch.LinkArch.Family == sys.Wasm { + BoundsCheckFunc[ssa.BoundsIndex] = typecheck.LookupRuntimeFunc("goPanicIndex") + BoundsCheckFunc[ssa.BoundsIndexU] = typecheck.LookupRuntimeFunc("goPanicIndexU") + BoundsCheckFunc[ssa.BoundsSliceAlen] = typecheck.LookupRuntimeFunc("goPanicSliceAlen") + BoundsCheckFunc[ssa.BoundsSliceAlenU] = typecheck.LookupRuntimeFunc("goPanicSliceAlenU") + BoundsCheckFunc[ssa.BoundsSliceAcap] = typecheck.LookupRuntimeFunc("goPanicSliceAcap") + BoundsCheckFunc[ssa.BoundsSliceAcapU] = typecheck.LookupRuntimeFunc("goPanicSliceAcapU") + BoundsCheckFunc[ssa.BoundsSliceB] = typecheck.LookupRuntimeFunc("goPanicSliceB") + BoundsCheckFunc[ssa.BoundsSliceBU] = typecheck.LookupRuntimeFunc("goPanicSliceBU") + BoundsCheckFunc[ssa.BoundsSlice3Alen] = typecheck.LookupRuntimeFunc("goPanicSlice3Alen") + BoundsCheckFunc[ssa.BoundsSlice3AlenU] = typecheck.LookupRuntimeFunc("goPanicSlice3AlenU") + BoundsCheckFunc[ssa.BoundsSlice3Acap] = typecheck.LookupRuntimeFunc("goPanicSlice3Acap") + BoundsCheckFunc[ssa.BoundsSlice3AcapU] = typecheck.LookupRuntimeFunc("goPanicSlice3AcapU") + BoundsCheckFunc[ssa.BoundsSlice3B] = typecheck.LookupRuntimeFunc("goPanicSlice3B") + BoundsCheckFunc[ssa.BoundsSlice3BU] = typecheck.LookupRuntimeFunc("goPanicSlice3BU") + BoundsCheckFunc[ssa.BoundsSlice3C] = typecheck.LookupRuntimeFunc("goPanicSlice3C") + BoundsCheckFunc[ssa.BoundsSlice3CU] = typecheck.LookupRuntimeFunc("goPanicSlice3CU") + BoundsCheckFunc[ssa.BoundsConvert] = typecheck.LookupRuntimeFunc("goPanicSliceConvert") } else { - BoundsCheckFunc[ssa.BoundsIndex] = sysfunc("panicIndex") - BoundsCheckFunc[ssa.BoundsIndexU] = sysfunc("panicIndexU") - BoundsCheckFunc[ssa.BoundsSliceAlen] = sysfunc("panicSliceAlen") - BoundsCheckFunc[ssa.BoundsSliceAlenU] = sysfunc("panicSliceAlenU") - BoundsCheckFunc[ssa.BoundsSliceAcap] = sysfunc("panicSliceAcap") - BoundsCheckFunc[ssa.BoundsSliceAcapU] = sysfunc("panicSliceAcapU") - BoundsCheckFunc[ssa.BoundsSliceB] = sysfunc("panicSliceB") - BoundsCheckFunc[ssa.BoundsSliceBU] = sysfunc("panicSliceBU") - BoundsCheckFunc[ssa.BoundsSlice3Alen] = sysfunc("panicSlice3Alen") - BoundsCheckFunc[ssa.BoundsSlice3AlenU] = sysfunc("panicSlice3AlenU") - BoundsCheckFunc[ssa.BoundsSlice3Acap] = sysfunc("panicSlice3Acap") - BoundsCheckFunc[ssa.BoundsSlice3AcapU] = sysfunc("panicSlice3AcapU") - BoundsCheckFunc[ssa.BoundsSlice3B] = sysfunc("panicSlice3B") - BoundsCheckFunc[ssa.BoundsSlice3BU] = sysfunc("panicSlice3BU") - BoundsCheckFunc[ssa.BoundsSlice3C] = sysfunc("panicSlice3C") - BoundsCheckFunc[ssa.BoundsSlice3CU] = sysfunc("panicSlice3CU") - } - if thearch.LinkArch.PtrSize == 4 { - ExtendCheckFunc[ssa.BoundsIndex] = sysvar("panicExtendIndex") - ExtendCheckFunc[ssa.BoundsIndexU] = sysvar("panicExtendIndexU") - ExtendCheckFunc[ssa.BoundsSliceAlen] = sysvar("panicExtendSliceAlen") - ExtendCheckFunc[ssa.BoundsSliceAlenU] = sysvar("panicExtendSliceAlenU") - ExtendCheckFunc[ssa.BoundsSliceAcap] = sysvar("panicExtendSliceAcap") - ExtendCheckFunc[ssa.BoundsSliceAcapU] = sysvar("panicExtendSliceAcapU") - ExtendCheckFunc[ssa.BoundsSliceB] = sysvar("panicExtendSliceB") - ExtendCheckFunc[ssa.BoundsSliceBU] = sysvar("panicExtendSliceBU") - ExtendCheckFunc[ssa.BoundsSlice3Alen] = sysvar("panicExtendSlice3Alen") - ExtendCheckFunc[ssa.BoundsSlice3AlenU] = sysvar("panicExtendSlice3AlenU") - ExtendCheckFunc[ssa.BoundsSlice3Acap] = sysvar("panicExtendSlice3Acap") - ExtendCheckFunc[ssa.BoundsSlice3AcapU] = sysvar("panicExtendSlice3AcapU") - ExtendCheckFunc[ssa.BoundsSlice3B] = sysvar("panicExtendSlice3B") - ExtendCheckFunc[ssa.BoundsSlice3BU] = sysvar("panicExtendSlice3BU") - ExtendCheckFunc[ssa.BoundsSlice3C] = sysvar("panicExtendSlice3C") - ExtendCheckFunc[ssa.BoundsSlice3CU] = sysvar("panicExtendSlice3CU") + BoundsCheckFunc[ssa.BoundsIndex] = typecheck.LookupRuntimeFunc("panicIndex") + BoundsCheckFunc[ssa.BoundsIndexU] = typecheck.LookupRuntimeFunc("panicIndexU") + BoundsCheckFunc[ssa.BoundsSliceAlen] = typecheck.LookupRuntimeFunc("panicSliceAlen") + BoundsCheckFunc[ssa.BoundsSliceAlenU] = typecheck.LookupRuntimeFunc("panicSliceAlenU") + BoundsCheckFunc[ssa.BoundsSliceAcap] = typecheck.LookupRuntimeFunc("panicSliceAcap") + BoundsCheckFunc[ssa.BoundsSliceAcapU] = typecheck.LookupRuntimeFunc("panicSliceAcapU") + BoundsCheckFunc[ssa.BoundsSliceB] = typecheck.LookupRuntimeFunc("panicSliceB") + BoundsCheckFunc[ssa.BoundsSliceBU] = typecheck.LookupRuntimeFunc("panicSliceBU") + BoundsCheckFunc[ssa.BoundsSlice3Alen] = typecheck.LookupRuntimeFunc("panicSlice3Alen") + BoundsCheckFunc[ssa.BoundsSlice3AlenU] = typecheck.LookupRuntimeFunc("panicSlice3AlenU") + BoundsCheckFunc[ssa.BoundsSlice3Acap] = typecheck.LookupRuntimeFunc("panicSlice3Acap") + BoundsCheckFunc[ssa.BoundsSlice3AcapU] = typecheck.LookupRuntimeFunc("panicSlice3AcapU") + BoundsCheckFunc[ssa.BoundsSlice3B] = typecheck.LookupRuntimeFunc("panicSlice3B") + BoundsCheckFunc[ssa.BoundsSlice3BU] = typecheck.LookupRuntimeFunc("panicSlice3BU") + BoundsCheckFunc[ssa.BoundsSlice3C] = typecheck.LookupRuntimeFunc("panicSlice3C") + BoundsCheckFunc[ssa.BoundsSlice3CU] = typecheck.LookupRuntimeFunc("panicSlice3CU") + BoundsCheckFunc[ssa.BoundsConvert] = typecheck.LookupRuntimeFunc("panicSliceConvert") + } + if Arch.LinkArch.PtrSize == 4 { + ExtendCheckFunc[ssa.BoundsIndex] = typecheck.LookupRuntimeVar("panicExtendIndex") + ExtendCheckFunc[ssa.BoundsIndexU] = typecheck.LookupRuntimeVar("panicExtendIndexU") + ExtendCheckFunc[ssa.BoundsSliceAlen] = typecheck.LookupRuntimeVar("panicExtendSliceAlen") + ExtendCheckFunc[ssa.BoundsSliceAlenU] = typecheck.LookupRuntimeVar("panicExtendSliceAlenU") + ExtendCheckFunc[ssa.BoundsSliceAcap] = typecheck.LookupRuntimeVar("panicExtendSliceAcap") + ExtendCheckFunc[ssa.BoundsSliceAcapU] = typecheck.LookupRuntimeVar("panicExtendSliceAcapU") + ExtendCheckFunc[ssa.BoundsSliceB] = typecheck.LookupRuntimeVar("panicExtendSliceB") + ExtendCheckFunc[ssa.BoundsSliceBU] = typecheck.LookupRuntimeVar("panicExtendSliceBU") + ExtendCheckFunc[ssa.BoundsSlice3Alen] = typecheck.LookupRuntimeVar("panicExtendSlice3Alen") + ExtendCheckFunc[ssa.BoundsSlice3AlenU] = typecheck.LookupRuntimeVar("panicExtendSlice3AlenU") + ExtendCheckFunc[ssa.BoundsSlice3Acap] = typecheck.LookupRuntimeVar("panicExtendSlice3Acap") + ExtendCheckFunc[ssa.BoundsSlice3AcapU] = typecheck.LookupRuntimeVar("panicExtendSlice3AcapU") + ExtendCheckFunc[ssa.BoundsSlice3B] = typecheck.LookupRuntimeVar("panicExtendSlice3B") + ExtendCheckFunc[ssa.BoundsSlice3BU] = typecheck.LookupRuntimeVar("panicExtendSlice3BU") + ExtendCheckFunc[ssa.BoundsSlice3C] = typecheck.LookupRuntimeVar("panicExtendSlice3C") + ExtendCheckFunc[ssa.BoundsSlice3CU] = typecheck.LookupRuntimeVar("panicExtendSlice3CU") } // Wasm (all asm funcs with special ABIs) - WasmMove = sysvar("wasmMove") - WasmZero = sysvar("wasmZero") - WasmDiv = sysvar("wasmDiv") - WasmTruncS = sysvar("wasmTruncS") - WasmTruncU = sysvar("wasmTruncU") - SigPanic = sysfunc("sigpanic") + ir.Syms.WasmMove = typecheck.LookupRuntimeVar("wasmMove") + ir.Syms.WasmZero = typecheck.LookupRuntimeVar("wasmZero") + ir.Syms.WasmDiv = typecheck.LookupRuntimeVar("wasmDiv") + ir.Syms.WasmTruncS = typecheck.LookupRuntimeVar("wasmTruncS") + ir.Syms.WasmTruncU = typecheck.LookupRuntimeVar("wasmTruncU") + ir.Syms.SigPanic = typecheck.LookupRuntimeFunc("sigpanic") +} + +// AbiForBodylessFuncStackMap returns the ABI for a bodyless function's stack map. +// This is not necessarily the ABI used to call it. +// Currently (1.17 dev) such a stack map is always ABI0; +// any ABI wrapper that is present is nosplit, hence a precise +// stack map is not needed there (the parameters survive only long +// enough to call the wrapped assembly function). +// This always returns a freshly copied ABI. +func AbiForBodylessFuncStackMap(fn *ir.Func) *abi.ABIConfig { + return ssaConfig.ABI0.Copy() // No idea what races will result, be safe +} + +// These are disabled but remain ready for use in case they are needed for the next regabi port. +// TODO if they are not needed for 1.18 / next register abi port, delete them. +const magicNameDotSuffix = ".*disabled*MagicMethodNameForTestingRegisterABI" +const magicLastTypeName = "*disabled*MagicLastTypeNameForTestingRegisterABI" + +// abiForFunc implements ABI policy for a function, but does not return a copy of the ABI. +// Passing a nil function returns the default ABI based on experiment configuration. +func abiForFunc(fn *ir.Func, abi0, abi1 *abi.ABIConfig) *abi.ABIConfig { + if buildcfg.Experiment.RegabiArgs { + // Select the ABI based on the function's defining ABI. + if fn == nil { + return abi1 + } + switch fn.ABI { + case obj.ABI0: + return abi0 + case obj.ABIInternal: + // TODO(austin): Clean up the nomenclature here. + // It's not clear that "abi1" is ABIInternal. + return abi1 + } + base.Fatalf("function %v has unknown ABI %v", fn, fn.ABI) + panic("not reachable") + } + + a := abi0 + if fn != nil { + name := ir.FuncName(fn) + magicName := strings.HasSuffix(name, magicNameDotSuffix) + if fn.Pragma&ir.RegisterParams != 0 { // TODO(register args) remove after register abi is working + if strings.Contains(name, ".") { + if !magicName { + base.ErrorfAt(fn.Pos(), "Calls to //go:registerparams method %s won't work, remove the pragma from the declaration.", name) + } + } + a = abi1 + } else if magicName { + if base.FmtPos(fn.Pos()) == ":1" { + // no way to put a pragma here, and it will error out in the real source code if they did not do it there. + a = abi1 + } else { + base.ErrorfAt(fn.Pos(), "Methods with magic name %s (method %s) must also specify //go:registerparams", magicNameDotSuffix[1:], name) + } + } + if regAbiForFuncType(fn.Type().FuncType()) { + // fmt.Printf("Saw magic last type name for function %s\n", name) + a = abi1 + } + } + return a +} + +func regAbiForFuncType(ft *types.Func) bool { + np := ft.Params.NumFields() + return np > 0 && strings.Contains(ft.Params.FieldType(np-1).String(), magicLastTypeName) } // getParam returns the Field of ith param of node n (which is a // function/method/interface call), where the receiver of a method call is // considered as the 0th parameter. This does not include the receiver of an // interface call. -func getParam(n *Node, i int) *types.Field { - t := n.Left.Type - if n.Op == OCALLMETH { - if i == 0 { - return t.Recv() - } - return t.Params().Field(i - 1) +func getParam(n *ir.CallExpr, i int) *types.Field { + t := n.X.Type() + if n.Op() == ir.OCALLMETH { + base.Fatalf("OCALLMETH missed by walkCall") } return t.Params().Field(i) } @@ -201,22 +297,22 @@ func dvarint(x *obj.LSym, off int, v int64) int { panic(fmt.Sprintf("dvarint: bad offset for funcdata - %v", v)) } if v < 1<<7 { - return duint8(x, off, uint8(v)) + return objw.Uint8(x, off, uint8(v)) } - off = duint8(x, off, uint8((v&127)|128)) + off = objw.Uint8(x, off, uint8((v&127)|128)) if v < 1<<14 { - return duint8(x, off, uint8(v>>7)) + return objw.Uint8(x, off, uint8(v>>7)) } - off = duint8(x, off, uint8(((v>>7)&127)|128)) + off = objw.Uint8(x, off, uint8(((v>>7)&127)|128)) if v < 1<<21 { - return duint8(x, off, uint8(v>>14)) + return objw.Uint8(x, off, uint8(v>>14)) } - off = duint8(x, off, uint8(((v>>14)&127)|128)) + off = objw.Uint8(x, off, uint8(((v>>14)&127)|128)) if v < 1<<28 { - return duint8(x, off, uint8(v>>21)) + return objw.Uint8(x, off, uint8(v>>21)) } - off = duint8(x, off, uint8(((v>>21)&127)|128)) - return duint8(x, off, uint8(v>>28)) + off = objw.Uint8(x, off, uint8(((v>>21)&127)|128)) + return objw.Uint8(x, off, uint8(v>>28)) } // emitOpenDeferInfo emits FUNCDATA information about the defers in a function @@ -240,8 +336,8 @@ func dvarint(x *obj.LSym, off int, v int64) int { // - Size of the argument // - Offset of where argument should be placed in the args frame when making call func (s *state) emitOpenDeferInfo() { - x := Ctxt.Lookup(s.curfn.Func.lsym.Name + ".opendefer") - s.curfn.Func.lsym.Func().OpenCodedDeferInfo = x + x := base.Ctxt.Lookup(s.curfn.LSym.Name + ".opendefer") + s.curfn.LSym.Func().OpenCodedDeferInfo = x off := 0 // Compute maxargsize (max size of arguments for all defers) @@ -249,20 +345,20 @@ func (s *state) emitOpenDeferInfo() { var maxargsize int64 for i := len(s.openDefers) - 1; i >= 0; i-- { r := s.openDefers[i] - argsize := r.n.Left.Type.ArgWidth() + argsize := r.n.X.Type().ArgWidth() // TODO register args: but maybe use of abi0 will make this easy if argsize > maxargsize { maxargsize = argsize } } off = dvarint(x, off, maxargsize) - off = dvarint(x, off, -s.deferBitsTemp.Xoffset) + off = dvarint(x, off, -s.deferBitsTemp.FrameOffset()) off = dvarint(x, off, int64(len(s.openDefers))) // Write in reverse-order, for ease of running in that order at runtime for i := len(s.openDefers) - 1; i >= 0; i-- { r := s.openDefers[i] - off = dvarint(x, off, r.n.Left.Type.ArgWidth()) - off = dvarint(x, off, -r.closureNode.Xoffset) + off = dvarint(x, off, r.n.X.Type().ArgWidth()) + off = dvarint(x, off, -r.closureNode.FrameOffset()) numArgs := len(r.argNodes) if r.rcvrNode != nil { // If there's an interface receiver, treat/place it as the first @@ -271,34 +367,49 @@ func (s *state) emitOpenDeferInfo() { numArgs++ } off = dvarint(x, off, int64(numArgs)) + argAdjust := 0 // presence of receiver offsets the parameter count. if r.rcvrNode != nil { - off = dvarint(x, off, -r.rcvrNode.Xoffset) + off = dvarint(x, off, -okOffset(r.rcvrNode.FrameOffset())) off = dvarint(x, off, s.config.PtrSize) - off = dvarint(x, off, 0) + off = dvarint(x, off, 0) // This is okay because defer records use ABI0 (for now) + argAdjust++ } + + // TODO(register args) assume abi0 for this? + ab := s.f.ABI0 + pri := ab.ABIAnalyzeFuncType(r.n.X.Type().FuncType()) for j, arg := range r.argNodes { f := getParam(r.n, j) - off = dvarint(x, off, -arg.Xoffset) + off = dvarint(x, off, -okOffset(arg.FrameOffset())) off = dvarint(x, off, f.Type.Size()) - off = dvarint(x, off, f.Offset) + off = dvarint(x, off, okOffset(pri.InParam(j+argAdjust).FrameOffset(pri))) } } } +func okOffset(offset int64) int64 { + if offset == types.BOGUS_FUNARG_OFFSET { + panic(fmt.Errorf("Bogus offset %d", offset)) + } + return offset +} + // buildssa builds an SSA function for fn. // worker indicates which of the backend workers is doing the processing. -func buildssa(fn *Node, worker int) *ssa.Func { - name := fn.funcname() +func buildssa(fn *ir.Func, worker int) *ssa.Func { + name := ir.FuncName(fn) printssa := false - if ssaDump != "" { // match either a simple name e.g. "(*Reader).Reset", or a package.name e.g. "compress/gzip.(*Reader).Reset" - printssa = name == ssaDump || myimportpath+"."+name == ssaDump + if ssaDump != "" { // match either a simple name e.g. "(*Reader).Reset", package.name e.g. "compress/gzip.(*Reader).Reset", or subpackage name "gzip.(*Reader).Reset" + pkgDotName := base.Ctxt.Pkgpath + "." + name + printssa = name == ssaDump || + strings.HasSuffix(pkgDotName, ssaDump) && (pkgDotName == ssaDump || strings.HasSuffix(pkgDotName, "/"+ssaDump)) } var astBuf *bytes.Buffer if printssa { astBuf = &bytes.Buffer{} - fdumplist(astBuf, "buildssa-enter", fn.Func.Enter) - fdumplist(astBuf, "buildssa-body", fn.Nbody) - fdumplist(astBuf, "buildssa-exit", fn.Func.Exit) + ir.FDumpList(astBuf, "buildssa-enter", fn.Enter) + ir.FDumpList(astBuf, "buildssa-body", fn.Body) + ir.FDumpList(astBuf, "buildssa-exit", fn.Exit) if ssaDumpStdout { fmt.Println("generating SSA for", name) fmt.Print(astBuf.String()) @@ -306,11 +417,11 @@ func buildssa(fn *Node, worker int) *ssa.Func { } var s state - s.pushLine(fn.Pos) + s.pushLine(fn.Pos()) defer s.popLine() - s.hasdefer = fn.Func.HasDefer() - if fn.Func.Pragma&CgoUnsafeArgs != 0 { + s.hasdefer = fn.HasDefer() + if fn.Pragma&ir.CgoUnsafeArgs != 0 { s.cgoUnsafeArgs = true } @@ -322,27 +433,32 @@ func buildssa(fn *Node, worker int) *ssa.Func { s.f = ssa.NewFunc(&fe) s.config = ssaConfig - s.f.Type = fn.Type + s.f.Type = fn.Type() s.f.Config = ssaConfig s.f.Cache = &ssaCaches[worker] s.f.Cache.Reset() s.f.Name = name s.f.DebugTest = s.f.DebugHashMatch("GOSSAHASH") s.f.PrintOrHtmlSSA = printssa - if fn.Func.Pragma&Nosplit != 0 { + if fn.Pragma&ir.Nosplit != 0 { s.f.NoSplit = true } + s.f.ABI0 = ssaConfig.ABI0.Copy() // Make a copy to avoid racy map operations in type-register-width cache. + s.f.ABI1 = ssaConfig.ABI1.Copy() + s.f.ABIDefault = abiForFunc(nil, s.f.ABI0, s.f.ABI1) + s.f.ABISelf = abiForFunc(fn, s.f.ABI0, s.f.ABI1) + s.panics = map[funcLine]*ssa.Block{} s.softFloat = s.config.SoftFloat // Allocate starting block s.f.Entry = s.f.NewBlock(ssa.BlockPlain) - s.f.Entry.Pos = fn.Pos + s.f.Entry.Pos = fn.Pos() if printssa { ssaDF := ssaDumpFile if ssaDir != "" { - ssaDF = filepath.Join(ssaDir, myimportpath+"."+name+".html") + ssaDF = filepath.Join(ssaDir, base.Ctxt.Pkgpath+"."+name+".html") ssaD := filepath.Dir(ssaDF) os.MkdirAll(ssaD, 0755) } @@ -354,28 +470,37 @@ func buildssa(fn *Node, worker int) *ssa.Func { // Allocate starting values s.labels = map[string]*ssaLabel{} - s.labeledNodes = map[*Node]*ssaLabel{} - s.fwdVars = map[*Node]*ssa.Value{} + s.fwdVars = map[ir.Node]*ssa.Value{} s.startmem = s.entryNewValue0(ssa.OpInitMem, types.TypeMem) - s.hasOpenDefers = Debug.N == 0 && s.hasdefer && !s.curfn.Func.OpenCodedDeferDisallowed() + s.hasOpenDefers = base.Flag.N == 0 && s.hasdefer && !s.curfn.OpenCodedDeferDisallowed() switch { - case s.hasOpenDefers && (Ctxt.Flag_shared || Ctxt.Flag_dynlink) && thearch.LinkArch.Name == "386": + case base.Debug.NoOpenDefer != 0: + s.hasOpenDefers = false + case s.hasOpenDefers && (base.Ctxt.Flag_shared || base.Ctxt.Flag_dynlink) && base.Ctxt.Arch.Name == "386": // Don't support open-coded defers for 386 ONLY when using shared // libraries, because there is extra code (added by rewriteToUseGot()) - // preceding the deferreturn/ret code that is generated by gencallret() - // that we don't track correctly. + // preceding the deferreturn/ret code that we don't track correctly. s.hasOpenDefers = false } - if s.hasOpenDefers && s.curfn.Func.Exit.Len() > 0 { + if s.hasOpenDefers && len(s.curfn.Exit) > 0 { // Skip doing open defers if there is any extra exit code (likely - // copying heap-allocated return values or race detection), since - // we will not generate that code in the case of the extra - // deferreturn/ret segment. + // race detection), since we will not generate that code in the + // case of the extra deferreturn/ret segment. s.hasOpenDefers = false } + if s.hasOpenDefers { + // Similarly, skip if there are any heap-allocated result + // parameters that need to be copied back to their stack slots. + for _, f := range s.curfn.Type().Results().FieldSlice() { + if !f.Nname.(*ir.Name).OnStack() { + s.hasOpenDefers = false + break + } + } + } if s.hasOpenDefers && - s.curfn.Func.numReturns*s.curfn.Func.numDefers > 15 { + s.curfn.NumReturns*s.curfn.NumDefers > 15 { // Since we are generating defer calls at every exit for // open-coded defers, skip doing open-coded defers if there are // too many returns (especially if there are multiple defers). @@ -384,76 +509,121 @@ func buildssa(fn *Node, worker int) *ssa.Func { s.hasOpenDefers = false } - s.sp = s.entryNewValue0(ssa.OpSP, types.Types[TUINTPTR]) // TODO: use generic pointer type (unsafe.Pointer?) instead - s.sb = s.entryNewValue0(ssa.OpSB, types.Types[TUINTPTR]) + s.sp = s.entryNewValue0(ssa.OpSP, types.Types[types.TUINTPTR]) // TODO: use generic pointer type (unsafe.Pointer?) instead + s.sb = s.entryNewValue0(ssa.OpSB, types.Types[types.TUINTPTR]) s.startBlock(s.f.Entry) - s.vars[&memVar] = s.startmem + s.vars[memVar] = s.startmem if s.hasOpenDefers { // Create the deferBits variable and stack slot. deferBits is a // bitmask showing which of the open-coded defers in this function // have been activated. - deferBitsTemp := tempAt(src.NoXPos, s.curfn, types.Types[TUINT8]) + deferBitsTemp := typecheck.TempAt(src.NoXPos, s.curfn, types.Types[types.TUINT8]) + deferBitsTemp.SetAddrtaken(true) s.deferBitsTemp = deferBitsTemp // For this value, AuxInt is initialized to zero by default - startDeferBits := s.entryNewValue0(ssa.OpConst8, types.Types[TUINT8]) - s.vars[&deferBitsVar] = startDeferBits + startDeferBits := s.entryNewValue0(ssa.OpConst8, types.Types[types.TUINT8]) + s.vars[deferBitsVar] = startDeferBits s.deferBitsAddr = s.addr(deferBitsTemp) - s.store(types.Types[TUINT8], s.deferBitsAddr, startDeferBits) + s.store(types.Types[types.TUINT8], s.deferBitsAddr, startDeferBits) // Make sure that the deferBits stack slot is kept alive (for use // by panics) and stores to deferBits are not eliminated, even if // all checking code on deferBits in the function exit can be // eliminated, because the defer statements were all // unconditional. - s.vars[&memVar] = s.newValue1Apos(ssa.OpVarLive, types.TypeMem, deferBitsTemp, s.mem(), false) + s.vars[memVar] = s.newValue1Apos(ssa.OpVarLive, types.TypeMem, deferBitsTemp, s.mem(), false) } + var params *abi.ABIParamResultInfo + params = s.f.ABISelf.ABIAnalyze(fn.Type(), true) + // Generate addresses of local declarations - s.decladdrs = map[*Node]*ssa.Value{} - var args []ssa.Param - var results []ssa.Param - for _, n := range fn.Func.Dcl { - switch n.Class() { - case PPARAM: - s.decladdrs[n] = s.entryNewValue2A(ssa.OpLocalAddr, types.NewPtr(n.Type), n, s.sp, s.startmem) - args = append(args, ssa.Param{Type: n.Type, Offset: int32(n.Xoffset)}) - case PPARAMOUT: - s.decladdrs[n] = s.entryNewValue2A(ssa.OpLocalAddr, types.NewPtr(n.Type), n, s.sp, s.startmem) - results = append(results, ssa.Param{Type: n.Type, Offset: int32(n.Xoffset)}) - if s.canSSA(n) { - // Save ssa-able PPARAMOUT variables so we can - // store them back to the stack at the end of - // the function. - s.returns = append(s.returns, n) - } - case PAUTO: + s.decladdrs = map[*ir.Name]*ssa.Value{} + for _, n := range fn.Dcl { + switch n.Class { + case ir.PPARAM: + // Be aware that blank and unnamed input parameters will not appear here, but do appear in the type + s.decladdrs[n] = s.entryNewValue2A(ssa.OpLocalAddr, types.NewPtr(n.Type()), n, s.sp, s.startmem) + case ir.PPARAMOUT: + s.decladdrs[n] = s.entryNewValue2A(ssa.OpLocalAddr, types.NewPtr(n.Type()), n, s.sp, s.startmem) + case ir.PAUTO: // processed at each use, to prevent Addr coming // before the decl. - case PAUTOHEAP: - // moved to heap - already handled by frontend - case PFUNC: - // local function - already handled by frontend default: - s.Fatalf("local variable with class %v unimplemented", n.Class()) + s.Fatalf("local variable with class %v unimplemented", n.Class) } } + s.f.OwnAux = ssa.OwnAuxCall(fn.LSym, params) + // Populate SSAable arguments. - for _, n := range fn.Func.Dcl { - if n.Class() == PPARAM && s.canSSA(n) { - v := s.newValue0A(ssa.OpArg, n.Type, n) - s.vars[n] = v - s.addNamedValue(n, v) // This helps with debugging information, not needed for compilation itself. + for _, n := range fn.Dcl { + if n.Class == ir.PPARAM { + if s.canSSA(n) { + v := s.newValue0A(ssa.OpArg, n.Type(), n) + s.vars[n] = v + s.addNamedValue(n, v) // This helps with debugging information, not needed for compilation itself. + } else { // address was taken AND/OR too large for SSA + paramAssignment := ssa.ParamAssignmentForArgName(s.f, n) + if len(paramAssignment.Registers) > 0 { + if TypeOK(n.Type()) { // SSA-able type, so address was taken -- receive value in OpArg, DO NOT bind to var, store immediately to memory. + v := s.newValue0A(ssa.OpArg, n.Type(), n) + s.store(n.Type(), s.decladdrs[n], v) + } else { // Too big for SSA. + // Brute force, and early, do a bunch of stores from registers + // TODO fix the nasty storeArgOrLoad recursion in ssa/expand_calls.go so this Just Works with store of a big Arg. + s.storeParameterRegsToStack(s.f.ABISelf, paramAssignment, n, s.decladdrs[n], false) + } + } + } + } + } + + // Populate closure variables. + if !fn.ClosureCalled() { + clo := s.entryNewValue0(ssa.OpGetClosurePtr, s.f.Config.Types.BytePtr) + offset := int64(types.PtrSize) // PtrSize to skip past function entry PC field + for _, n := range fn.ClosureVars { + typ := n.Type() + if !n.Byval() { + typ = types.NewPtr(typ) + } + + offset = types.Rnd(offset, typ.Alignment()) + ptr := s.newValue1I(ssa.OpOffPtr, types.NewPtr(typ), offset, clo) + offset += typ.Size() + + // If n is a small variable captured by value, promote + // it to PAUTO so it can be converted to SSA. + // + // Note: While we never capture a variable by value if + // the user took its address, we may have generated + // runtime calls that did (#43701). Since we don't + // convert Addrtaken variables to SSA anyway, no point + // in promoting them either. + if n.Byval() && !n.Addrtaken() && TypeOK(n.Type()) { + n.Class = ir.PAUTO + fn.Dcl = append(fn.Dcl, n) + s.assign(n, s.load(n.Type(), ptr), false, 0) + continue + } + + if !n.Byval() { + ptr = s.load(typ, ptr) + } + s.setHeapaddr(fn.Pos(), n, ptr) } } // Convert the AST-based IR to the SSA-based IR - s.stmtList(fn.Func.Enter) - s.stmtList(fn.Nbody) + s.stmtList(fn.Enter) + s.zeroResults() + s.paramsToHeap() + s.stmtList(fn.Body) // fallthrough to exit if s.curBlock != nil { - s.pushLine(fn.Func.Endlineno) + s.pushLine(fn.Endlineno) s.exit() s.popLine() } @@ -464,6 +634,8 @@ func buildssa(fn *Node, worker int) *ssa.Func { } } + s.f.HTMLWriter.WritePhase("before insert phis", "before insert phis") + s.insertPhis() // Main call to ssa package to compile function @@ -473,13 +645,140 @@ func buildssa(fn *Node, worker int) *ssa.Func { s.emitOpenDeferInfo() } + // Record incoming parameter spill information for morestack calls emitted in the assembler. + // This is done here, using all the parameters (used, partially used, and unused) because + // it mimics the behavior of the former ABI (everything stored) and because it's not 100% + // clear if naming conventions are respected in autogenerated code. + // TODO figure out exactly what's unused, don't spill it. Make liveness fine-grained, also. + // TODO non-amd64 architectures have link registers etc that may require adjustment here. + for _, p := range params.InParams() { + typs, offs := p.RegisterTypesAndOffsets() + for i, t := range typs { + o := offs[i] // offset within parameter + fo := p.FrameOffset(params) // offset of parameter in frame + reg := ssa.ObjRegForAbiReg(p.Registers[i], s.f.Config) + s.f.RegArgs = append(s.f.RegArgs, ssa.Spill{Reg: reg, Offset: fo + o, Type: t}) + } + } + return s.f } -func dumpSourcesColumn(writer *ssa.HTMLWriter, fn *Node) { +func (s *state) storeParameterRegsToStack(abi *abi.ABIConfig, paramAssignment *abi.ABIParamAssignment, n *ir.Name, addr *ssa.Value, pointersOnly bool) { + typs, offs := paramAssignment.RegisterTypesAndOffsets() + for i, t := range typs { + if pointersOnly && !t.IsPtrShaped() { + continue + } + r := paramAssignment.Registers[i] + o := offs[i] + op, reg := ssa.ArgOpAndRegisterFor(r, abi) + aux := &ssa.AuxNameOffset{Name: n, Offset: o} + v := s.newValue0I(op, t, reg) + v.Aux = aux + p := s.newValue1I(ssa.OpOffPtr, types.NewPtr(t), o, addr) + s.store(t, p, v) + } +} + +// zeroResults zeros the return values at the start of the function. +// We need to do this very early in the function. Defer might stop a +// panic and show the return values as they exist at the time of +// panic. For precise stacks, the garbage collector assumes results +// are always live, so we need to zero them before any allocations, +// even allocations to move params/results to the heap. +func (s *state) zeroResults() { + for _, f := range s.curfn.Type().Results().FieldSlice() { + n := f.Nname.(*ir.Name) + if !n.OnStack() { + // The local which points to the return value is the + // thing that needs zeroing. This is already handled + // by a Needzero annotation in plive.go:(*liveness).epilogue. + continue + } + // Zero the stack location containing f. + if typ := n.Type(); TypeOK(typ) { + s.assign(n, s.zeroVal(typ), false, 0) + } else { + s.vars[memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, n, s.mem()) + s.zero(n.Type(), s.decladdrs[n]) + } + } +} + +// paramsToHeap produces code to allocate memory for heap-escaped parameters +// and to copy non-result parameters' values from the stack. +func (s *state) paramsToHeap() { + do := func(params *types.Type) { + for _, f := range params.FieldSlice() { + if f.Nname == nil { + continue // anonymous or blank parameter + } + n := f.Nname.(*ir.Name) + if ir.IsBlank(n) || n.OnStack() { + continue + } + s.newHeapaddr(n) + if n.Class == ir.PPARAM { + s.move(n.Type(), s.expr(n.Heapaddr), s.decladdrs[n]) + } + } + } + + typ := s.curfn.Type() + do(typ.Recvs()) + do(typ.Params()) + do(typ.Results()) +} + +// newHeapaddr allocates heap memory for n and sets its heap address. +func (s *state) newHeapaddr(n *ir.Name) { + s.setHeapaddr(n.Pos(), n, s.newObject(n.Type())) +} + +// setHeapaddr allocates a new PAUTO variable to store ptr (which must be non-nil) +// and then sets it as n's heap address. +func (s *state) setHeapaddr(pos src.XPos, n *ir.Name, ptr *ssa.Value) { + if !ptr.Type.IsPtr() || !types.Identical(n.Type(), ptr.Type.Elem()) { + base.FatalfAt(n.Pos(), "setHeapaddr %L with type %v", n, ptr.Type) + } + + // Declare variable to hold address. + addr := ir.NewNameAt(pos, &types.Sym{Name: "&" + n.Sym().Name, Pkg: types.LocalPkg}) + addr.SetType(types.NewPtr(n.Type())) + addr.Class = ir.PAUTO + addr.SetUsed(true) + addr.Curfn = s.curfn + s.curfn.Dcl = append(s.curfn.Dcl, addr) + types.CalcSize(addr.Type()) + + if n.Class == ir.PPARAMOUT { + addr.SetIsOutputParamHeapAddr(true) + } + + n.Heapaddr = addr + s.assign(addr, ptr, false, 0) +} + +// newObject returns an SSA value denoting new(typ). +func (s *state) newObject(typ *types.Type) *ssa.Value { + if typ.Size() == 0 { + return s.newValue1A(ssa.OpAddr, types.NewPtr(typ), ir.Syms.Zerobase, s.sb) + } + return s.rtcall(ir.Syms.Newobject, true, []*types.Type{types.NewPtr(typ)}, s.reflectType(typ))[0] +} + +// reflectType returns an SSA value representing a pointer to typ's +// reflection type descriptor. +func (s *state) reflectType(typ *types.Type) *ssa.Value { + lsym := reflectdata.TypeLinksym(typ) + return s.entryNewValue1A(ssa.OpAddr, types.NewPtr(types.Types[types.TUINT8]), lsym, s.sb) +} + +func dumpSourcesColumn(writer *ssa.HTMLWriter, fn *ir.Func) { // Read sources of target function fn. - fname := Ctxt.PosTable.Pos(fn.Pos).Filename() - targetFn, err := readFuncLines(fname, fn.Pos.Line(), fn.Func.Endlineno.Line()) + fname := base.Ctxt.PosTable.Pos(fn.Pos()).Filename() + targetFn, err := readFuncLines(fname, fn.Pos().Line(), fn.Endlineno.Line()) if err != nil { writer.Logf("cannot read sources for function %v: %v", fn, err) } @@ -487,15 +786,9 @@ func dumpSourcesColumn(writer *ssa.HTMLWriter, fn *Node) { // Read sources of inlined functions. var inlFns []*ssa.FuncLines for _, fi := range ssaDumpInlined { - var elno src.XPos - if fi.Name.Defn == nil { - // Endlineno is filled from exported data. - elno = fi.Func.Endlineno - } else { - elno = fi.Name.Defn.Func.Endlineno - } - fname := Ctxt.PosTable.Pos(fi.Pos).Filename() - fnLines, err := readFuncLines(fname, fi.Pos.Line(), elno.Line()) + elno := fi.Endlineno + fname := base.Ctxt.PosTable.Pos(fi.Pos()).Filename() + fnLines, err := readFuncLines(fname, fi.Pos().Line(), elno.Line()) if err != nil { writer.Logf("cannot read sources for inlined function %v: %v", fi, err) continue @@ -563,25 +856,25 @@ func (s *state) updateUnsetPredPos(b *ssa.Block) { // Information about each open-coded defer. type openDeferInfo struct { - // The ODEFER node representing the function call of the defer - n *Node + // The node representing the call of the defer + n *ir.CallExpr // If defer call is closure call, the address of the argtmp where the // closure is stored. closure *ssa.Value // The node representing the argtmp where the closure is stored - used for // function, method, or interface call, to store a closure that panic // processing can use for this defer. - closureNode *Node + closureNode *ir.Name // If defer call is interface call, the address of the argtmp where the // receiver is stored rcvr *ssa.Value // The node representing the argtmp where the receiver is stored - rcvrNode *Node + rcvrNode *ir.Name // The addresses of the argtmps where the evaluated arguments of the defer // function call are stored. argVals []*ssa.Value // The nodes representing the argtmps where the args of the defer are stored - argNodes []*Node + argNodes []*ir.Name } type state struct { @@ -592,11 +885,10 @@ type state struct { f *ssa.Func // Node for function - curfn *Node + curfn *ir.Func - // labels and labeled control flow nodes (OFOR, OFORUNTIL, OSWITCH, OSELECT) in f - labels map[string]*ssaLabel - labeledNodes map[*Node]*ssaLabel + // labels in f + labels map[string]*ssaLabel // unlabeled break and continue statement tracking breakTo *ssa.Block // current target for plain break statement @@ -608,18 +900,18 @@ type state struct { // variable assignments in the current block (map from variable symbol to ssa value) // *Node is the unique identifier (an ONAME Node) for the variable. // TODO: keep a single varnum map, then make all of these maps slices instead? - vars map[*Node]*ssa.Value + vars map[ir.Node]*ssa.Value // fwdVars are variables that are used before they are defined in the current block. // This map exists just to coalesce multiple references into a single FwdRef op. // *Node is the unique identifier (an ONAME Node) for the variable. - fwdVars map[*Node]*ssa.Value + fwdVars map[ir.Node]*ssa.Value // all defined variables at the end of each block. Indexed by block ID. - defvars []map[*Node]*ssa.Value + defvars []map[ir.Node]*ssa.Value - // addresses of PPARAM and PPARAMOUT variables. - decladdrs map[*Node]*ssa.Value + // addresses of PPARAM and PPARAMOUT variables on the stack. + decladdrs map[*ir.Name]*ssa.Value // starting values. Memory, stack pointer, and globals pointer startmem *ssa.Value @@ -627,7 +919,7 @@ type state struct { sb *ssa.Value // value representing address of where deferBits autotmp is stored deferBitsAddr *ssa.Value - deferBitsTemp *Node + deferBitsTemp *ir.Name // line number stack. The current line number is top of stack line []src.XPos @@ -638,9 +930,6 @@ type state struct { // Used to deduplicate panic calls. panics map[funcLine]*ssa.Block - // list of PPARAMOUT (return) variables. - returns []*Node - cgoUnsafeArgs bool hasdefer bool // whether the function contains a defer statement softFloat bool @@ -691,18 +980,22 @@ func (s *state) Fatalf(msg string, args ...interface{}) { func (s *state) Warnl(pos src.XPos, msg string, args ...interface{}) { s.f.Warnl(pos, msg, args...) } func (s *state) Debug_checknil() bool { return s.f.Frontend().Debug_checknil() } +func ssaMarker(name string) *ir.Name { + return typecheck.NewName(&types.Sym{Name: name}) +} + var ( - // dummy node for the memory variable - memVar = Node{Op: ONAME, Sym: &types.Sym{Name: "mem"}} - - // dummy nodes for temporary variables - ptrVar = Node{Op: ONAME, Sym: &types.Sym{Name: "ptr"}} - lenVar = Node{Op: ONAME, Sym: &types.Sym{Name: "len"}} - newlenVar = Node{Op: ONAME, Sym: &types.Sym{Name: "newlen"}} - capVar = Node{Op: ONAME, Sym: &types.Sym{Name: "cap"}} - typVar = Node{Op: ONAME, Sym: &types.Sym{Name: "typ"}} - okVar = Node{Op: ONAME, Sym: &types.Sym{Name: "ok"}} - deferBitsVar = Node{Op: ONAME, Sym: &types.Sym{Name: "deferBits"}} + // marker node for the memory variable + memVar = ssaMarker("mem") + + // marker nodes for temporary variables + ptrVar = ssaMarker("ptr") + lenVar = ssaMarker("len") + newlenVar = ssaMarker("newlen") + capVar = ssaMarker("cap") + typVar = ssaMarker("typ") + okVar = ssaMarker("ok") + deferBitsVar = ssaMarker("deferBits") ) // startBlock sets the current block we're generating code in to b. @@ -711,7 +1004,7 @@ func (s *state) startBlock(b *ssa.Block) { s.Fatalf("starting block %v when block %v has not ended", b, s.curBlock) } s.curBlock = b - s.vars = map[*Node]*ssa.Value{} + s.vars = map[ir.Node]*ssa.Value{} for n := range s.fwdVars { delete(s.fwdVars, n) } @@ -748,8 +1041,8 @@ func (s *state) pushLine(line src.XPos) { // the frontend may emit node with line number missing, // use the parent line number in this case. line = s.peekPos() - if Debug.K != 0 { - Warn("buildssa: unknown position (line 0)") + if base.Flag.K != 0 { + base.Warn("buildssa: unknown position (line 0)") } } else { s.lastPos = line @@ -774,7 +1067,7 @@ func (s *state) newValue0(op ssa.Op, t *types.Type) *ssa.Value { } // newValue0A adds a new value with no arguments and an aux value to the current block. -func (s *state) newValue0A(op ssa.Op, t *types.Type, aux interface{}) *ssa.Value { +func (s *state) newValue0A(op ssa.Op, t *types.Type, aux ssa.Aux) *ssa.Value { return s.curBlock.NewValue0A(s.peekPos(), op, t, aux) } @@ -789,14 +1082,14 @@ func (s *state) newValue1(op ssa.Op, t *types.Type, arg *ssa.Value) *ssa.Value { } // newValue1A adds a new value with one argument and an aux value to the current block. -func (s *state) newValue1A(op ssa.Op, t *types.Type, aux interface{}, arg *ssa.Value) *ssa.Value { +func (s *state) newValue1A(op ssa.Op, t *types.Type, aux ssa.Aux, arg *ssa.Value) *ssa.Value { return s.curBlock.NewValue1A(s.peekPos(), op, t, aux, arg) } // newValue1Apos adds a new value with one argument and an aux value to the current block. // isStmt determines whether the created values may be a statement or not // (i.e., false means never, yes means maybe). -func (s *state) newValue1Apos(op ssa.Op, t *types.Type, aux interface{}, arg *ssa.Value, isStmt bool) *ssa.Value { +func (s *state) newValue1Apos(op ssa.Op, t *types.Type, aux ssa.Aux, arg *ssa.Value, isStmt bool) *ssa.Value { if isStmt { return s.curBlock.NewValue1A(s.peekPos(), op, t, aux, arg) } @@ -814,14 +1107,14 @@ func (s *state) newValue2(op ssa.Op, t *types.Type, arg0, arg1 *ssa.Value) *ssa. } // newValue2A adds a new value with two arguments and an aux value to the current block. -func (s *state) newValue2A(op ssa.Op, t *types.Type, aux interface{}, arg0, arg1 *ssa.Value) *ssa.Value { +func (s *state) newValue2A(op ssa.Op, t *types.Type, aux ssa.Aux, arg0, arg1 *ssa.Value) *ssa.Value { return s.curBlock.NewValue2A(s.peekPos(), op, t, aux, arg0, arg1) } // newValue2Apos adds a new value with two arguments and an aux value to the current block. // isStmt determines whether the created values may be a statement or not // (i.e., false means never, yes means maybe). -func (s *state) newValue2Apos(op ssa.Op, t *types.Type, aux interface{}, arg0, arg1 *ssa.Value, isStmt bool) *ssa.Value { +func (s *state) newValue2Apos(op ssa.Op, t *types.Type, aux ssa.Aux, arg0, arg1 *ssa.Value, isStmt bool) *ssa.Value { if isStmt { return s.curBlock.NewValue2A(s.peekPos(), op, t, aux, arg0, arg1) } @@ -844,14 +1137,14 @@ func (s *state) newValue3I(op ssa.Op, t *types.Type, aux int64, arg0, arg1, arg2 } // newValue3A adds a new value with three arguments and an aux value to the current block. -func (s *state) newValue3A(op ssa.Op, t *types.Type, aux interface{}, arg0, arg1, arg2 *ssa.Value) *ssa.Value { +func (s *state) newValue3A(op ssa.Op, t *types.Type, aux ssa.Aux, arg0, arg1, arg2 *ssa.Value) *ssa.Value { return s.curBlock.NewValue3A(s.peekPos(), op, t, aux, arg0, arg1, arg2) } // newValue3Apos adds a new value with three arguments and an aux value to the current block. // isStmt determines whether the created values may be a statement or not // (i.e., false means never, yes means maybe). -func (s *state) newValue3Apos(op ssa.Op, t *types.Type, aux interface{}, arg0, arg1, arg2 *ssa.Value, isStmt bool) *ssa.Value { +func (s *state) newValue3Apos(op ssa.Op, t *types.Type, aux ssa.Aux, arg0, arg1, arg2 *ssa.Value, isStmt bool) *ssa.Value { if isStmt { return s.curBlock.NewValue3A(s.peekPos(), op, t, aux, arg0, arg1, arg2) } @@ -868,39 +1161,51 @@ func (s *state) newValue4I(op ssa.Op, t *types.Type, aux int64, arg0, arg1, arg2 return s.curBlock.NewValue4I(s.peekPos(), op, t, aux, arg0, arg1, arg2, arg3) } +func (s *state) entryBlock() *ssa.Block { + b := s.f.Entry + if base.Flag.N > 0 && s.curBlock != nil { + // If optimizations are off, allocate in current block instead. Since with -N + // we're not doing the CSE or tighten passes, putting lots of stuff in the + // entry block leads to O(n^2) entries in the live value map during regalloc. + // See issue 45897. + b = s.curBlock + } + return b +} + // entryNewValue0 adds a new value with no arguments to the entry block. func (s *state) entryNewValue0(op ssa.Op, t *types.Type) *ssa.Value { - return s.f.Entry.NewValue0(src.NoXPos, op, t) + return s.entryBlock().NewValue0(src.NoXPos, op, t) } // entryNewValue0A adds a new value with no arguments and an aux value to the entry block. -func (s *state) entryNewValue0A(op ssa.Op, t *types.Type, aux interface{}) *ssa.Value { - return s.f.Entry.NewValue0A(src.NoXPos, op, t, aux) +func (s *state) entryNewValue0A(op ssa.Op, t *types.Type, aux ssa.Aux) *ssa.Value { + return s.entryBlock().NewValue0A(src.NoXPos, op, t, aux) } // entryNewValue1 adds a new value with one argument to the entry block. func (s *state) entryNewValue1(op ssa.Op, t *types.Type, arg *ssa.Value) *ssa.Value { - return s.f.Entry.NewValue1(src.NoXPos, op, t, arg) + return s.entryBlock().NewValue1(src.NoXPos, op, t, arg) } // entryNewValue1 adds a new value with one argument and an auxint value to the entry block. func (s *state) entryNewValue1I(op ssa.Op, t *types.Type, auxint int64, arg *ssa.Value) *ssa.Value { - return s.f.Entry.NewValue1I(src.NoXPos, op, t, auxint, arg) + return s.entryBlock().NewValue1I(src.NoXPos, op, t, auxint, arg) } // entryNewValue1A adds a new value with one argument and an aux value to the entry block. -func (s *state) entryNewValue1A(op ssa.Op, t *types.Type, aux interface{}, arg *ssa.Value) *ssa.Value { - return s.f.Entry.NewValue1A(src.NoXPos, op, t, aux, arg) +func (s *state) entryNewValue1A(op ssa.Op, t *types.Type, aux ssa.Aux, arg *ssa.Value) *ssa.Value { + return s.entryBlock().NewValue1A(src.NoXPos, op, t, aux, arg) } // entryNewValue2 adds a new value with two arguments to the entry block. func (s *state) entryNewValue2(op ssa.Op, t *types.Type, arg0, arg1 *ssa.Value) *ssa.Value { - return s.f.Entry.NewValue2(src.NoXPos, op, t, arg0, arg1) + return s.entryBlock().NewValue2(src.NoXPos, op, t, arg0, arg1) } // entryNewValue2A adds a new value with two arguments and an aux value to the entry block. -func (s *state) entryNewValue2A(op ssa.Op, t *types.Type, aux interface{}, arg0, arg1 *ssa.Value) *ssa.Value { - return s.f.Entry.NewValue2A(src.NoXPos, op, t, aux, arg0, arg1) +func (s *state) entryNewValue2A(op ssa.Op, t *types.Type, aux ssa.Aux, arg0, arg1 *ssa.Value) *ssa.Value { + return s.entryBlock().NewValue2A(src.NoXPos, op, t, aux, arg0, arg1) } // const* routines add a new const value to the entry block. @@ -915,7 +1220,7 @@ func (s *state) constEmptyString(t *types.Type) *ssa.Value { return s.f.ConstEmptyString(t) } func (s *state) constBool(c bool) *ssa.Value { - return s.f.ConstBool(types.Types[TBOOL], c) + return s.f.ConstBool(types.Types[types.TBOOL], c) } func (s *state) constInt8(t *types.Type, c int8) *ssa.Value { return s.f.ConstInt8(t, c) @@ -983,7 +1288,7 @@ func (s *state) instrument(t *types.Type, addr *ssa.Value, kind instrumentKind) // If it is instrumenting for MSAN and t is a struct type, it instruments // operation for each field, instead of for the whole struct. func (s *state) instrumentFields(t *types.Type, addr *ssa.Value, kind instrumentKind) { - if !flag_msan || !t.IsStruct() { + if !base.Flag.MSan || !t.IsStruct() { s.instrument(t, addr, kind) return } @@ -997,7 +1302,7 @@ func (s *state) instrumentFields(t *types.Type, addr *ssa.Value, kind instrument } func (s *state) instrumentMove(t *types.Type, dst, src *ssa.Value) { - if flag_msan { + if base.Flag.MSan { s.instrument2(t, dst, src, instrumentMove) } else { s.instrument(t, src, instrumentRead) @@ -1006,7 +1311,7 @@ func (s *state) instrumentMove(t *types.Type, dst, src *ssa.Value) { } func (s *state) instrument2(t *types.Type, addr, addr2 *ssa.Value, kind instrumentKind) { - if !s.curfn.Func.InstrumentBody() { + if !s.curfn.InstrumentBody() { return } @@ -1026,39 +1331,39 @@ func (s *state) instrument2(t *types.Type, addr, addr2 *ssa.Value, kind instrume panic("instrument2: non-nil addr2 for non-move instrumentation") } - if flag_msan { + if base.Flag.MSan { switch kind { case instrumentRead: - fn = msanread + fn = ir.Syms.Msanread case instrumentWrite: - fn = msanwrite + fn = ir.Syms.Msanwrite case instrumentMove: - fn = msanmove + fn = ir.Syms.Msanmove default: panic("unreachable") } needWidth = true - } else if flag_race && t.NumComponents(types.CountBlankFields) > 1 { + } else if base.Flag.Race && t.NumComponents(types.CountBlankFields) > 1 { // for composite objects we have to write every address // because a write might happen to any subobject. // composites with only one element don't have subobjects, though. switch kind { case instrumentRead: - fn = racereadrange + fn = ir.Syms.Racereadrange case instrumentWrite: - fn = racewriterange + fn = ir.Syms.Racewriterange default: panic("unreachable") } needWidth = true - } else if flag_race { + } else if base.Flag.Race { // for non-composite objects we can write just the start // address, as any write must write the first byte. switch kind { case instrumentRead: - fn = raceread + fn = ir.Syms.Raceread case instrumentWrite: - fn = racewrite + fn = ir.Syms.Racewrite default: panic("unreachable") } @@ -1071,7 +1376,7 @@ func (s *state) instrument2(t *types.Type, addr, addr2 *ssa.Value, kind instrume args = append(args, addr2) } if needWidth { - args = append(args, s.constInt(types.Types[TUINTPTR], w)) + args = append(args, s.constInt(types.Types[types.TUINTPTR], w)) } s.rtcall(fn, true, nil, args...) } @@ -1086,66 +1391,69 @@ func (s *state) rawLoad(t *types.Type, src *ssa.Value) *ssa.Value { } func (s *state) store(t *types.Type, dst, val *ssa.Value) { - s.vars[&memVar] = s.newValue3A(ssa.OpStore, types.TypeMem, t, dst, val, s.mem()) + s.vars[memVar] = s.newValue3A(ssa.OpStore, types.TypeMem, t, dst, val, s.mem()) } func (s *state) zero(t *types.Type, dst *ssa.Value) { s.instrument(t, dst, instrumentWrite) store := s.newValue2I(ssa.OpZero, types.TypeMem, t.Size(), dst, s.mem()) store.Aux = t - s.vars[&memVar] = store + s.vars[memVar] = store } func (s *state) move(t *types.Type, dst, src *ssa.Value) { s.instrumentMove(t, dst, src) store := s.newValue3I(ssa.OpMove, types.TypeMem, t.Size(), dst, src, s.mem()) store.Aux = t - s.vars[&memVar] = store + s.vars[memVar] = store } // stmtList converts the statement list n to SSA and adds it to s. -func (s *state) stmtList(l Nodes) { - for _, n := range l.Slice() { +func (s *state) stmtList(l ir.Nodes) { + for _, n := range l { s.stmt(n) } } // stmt converts the statement n to SSA and adds it to s. -func (s *state) stmt(n *Node) { - if !(n.Op == OVARKILL || n.Op == OVARLIVE || n.Op == OVARDEF) { +func (s *state) stmt(n ir.Node) { + if !(n.Op() == ir.OVARKILL || n.Op() == ir.OVARLIVE || n.Op() == ir.OVARDEF) { // OVARKILL, OVARLIVE, and OVARDEF are invisible to the programmer, so we don't use their line numbers to avoid confusion in debugging. - s.pushLine(n.Pos) + s.pushLine(n.Pos()) defer s.popLine() } // If s.curBlock is nil, and n isn't a label (which might have an associated goto somewhere), // then this code is dead. Stop here. - if s.curBlock == nil && n.Op != OLABEL { + if s.curBlock == nil && n.Op() != ir.OLABEL { return } - s.stmtList(n.Ninit) - switch n.Op { + s.stmtList(n.Init()) + switch n.Op() { - case OBLOCK: + case ir.OBLOCK: + n := n.(*ir.BlockStmt) s.stmtList(n.List) // No-ops - case OEMPTY, ODCLCONST, ODCLTYPE, OFALL: + case ir.ODCLCONST, ir.ODCLTYPE, ir.OFALL: // Expression statements - case OCALLFUNC: - if isIntrinsicCall(n) { + case ir.OCALLFUNC: + n := n.(*ir.CallExpr) + if ir.IsIntrinsicCall(n) { s.intrinsicCall(n) return } fallthrough - case OCALLMETH, OCALLINTER: + case ir.OCALLINTER: + n := n.(*ir.CallExpr) s.callResult(n, callNormal) - if n.Op == OCALLFUNC && n.Left.Op == ONAME && n.Left.Class() == PFUNC { - if fn := n.Left.Sym.Name; compiling_runtime && fn == "throw" || - n.Left.Sym.Pkg == Runtimepkg && (fn == "throwinit" || fn == "gopanic" || fn == "panicwrap" || fn == "block" || fn == "panicmakeslicelen" || fn == "panicmakeslicecap") { + if n.Op() == ir.OCALLFUNC && n.X.Op() == ir.ONAME && n.X.(*ir.Name).Class == ir.PFUNC { + if fn := n.X.Sym().Name; base.Flag.CompilingRuntime && fn == "throw" || + n.X.Sym().Pkg == ir.Pkgs.Runtime && (fn == "throwinit" || fn == "gopanic" || fn == "panicwrap" || fn == "block" || fn == "panicmakeslicelen" || fn == "panicmakeslicecap") { m := s.mem() b := s.endBlock() b.Kind = ssa.BlockExit @@ -1155,34 +1463,37 @@ func (s *state) stmt(n *Node) { // go through SSA. } } - case ODEFER: - if Debug_defer > 0 { + case ir.ODEFER: + n := n.(*ir.GoDeferStmt) + if base.Debug.Defer > 0 { var defertype string if s.hasOpenDefers { defertype = "open-coded" - } else if n.Esc == EscNever { + } else if n.Esc() == ir.EscNever { defertype = "stack-allocated" } else { defertype = "heap-allocated" } - Warnl(n.Pos, "%s defer", defertype) + base.WarnfAt(n.Pos(), "%s defer", defertype) } if s.hasOpenDefers { - s.openDeferRecord(n.Left) + s.openDeferRecord(n.Call.(*ir.CallExpr)) } else { d := callDefer - if n.Esc == EscNever { + if n.Esc() == ir.EscNever { d = callDeferStack } - s.callResult(n.Left, d) + s.callResult(n.Call.(*ir.CallExpr), d) } - case OGO: - s.callResult(n.Left, callGo) + case ir.OGO: + n := n.(*ir.GoDeferStmt) + s.callResult(n.Call.(*ir.CallExpr), callGo) - case OAS2DOTTYPE: - res, resok := s.dottype(n.Right, true) + case ir.OAS2DOTTYPE: + n := n.(*ir.AssignListStmt) + res, resok := s.dottype(n.Rhs[0].(*ir.TypeAssertExpr), true) deref := false - if !canSSAType(n.Right.Type) { + if !TypeOK(n.Rhs[0].Type()) { if res.Op != ssa.OpLoad { s.Fatalf("dottype of non-load") } @@ -1196,36 +1507,35 @@ func (s *state) stmt(n *Node) { deref = true res = res.Args[0] } - s.assign(n.List.First(), res, deref, 0) - s.assign(n.List.Second(), resok, false, 0) + s.assign(n.Lhs[0], res, deref, 0) + s.assign(n.Lhs[1], resok, false, 0) return - case OAS2FUNC: + case ir.OAS2FUNC: // We come here only when it is an intrinsic call returning two values. - if !isIntrinsicCall(n.Right) { - s.Fatalf("non-intrinsic AS2FUNC not expanded %v", n.Right) - } - v := s.intrinsicCall(n.Right) - v1 := s.newValue1(ssa.OpSelect0, n.List.First().Type, v) - v2 := s.newValue1(ssa.OpSelect1, n.List.Second().Type, v) - s.assign(n.List.First(), v1, false, 0) - s.assign(n.List.Second(), v2, false, 0) + n := n.(*ir.AssignListStmt) + call := n.Rhs[0].(*ir.CallExpr) + if !ir.IsIntrinsicCall(call) { + s.Fatalf("non-intrinsic AS2FUNC not expanded %v", call) + } + v := s.intrinsicCall(call) + v1 := s.newValue1(ssa.OpSelect0, n.Lhs[0].Type(), v) + v2 := s.newValue1(ssa.OpSelect1, n.Lhs[1].Type(), v) + s.assign(n.Lhs[0], v1, false, 0) + s.assign(n.Lhs[1], v2, false, 0) return - case ODCL: - if n.Left.Class() == PAUTOHEAP { - s.Fatalf("DCL %v", n) + case ir.ODCL: + n := n.(*ir.Decl) + if v := n.X; v.Esc() == ir.EscHeap { + s.newHeapaddr(v) } - case OLABEL: - sym := n.Sym + case ir.OLABEL: + n := n.(*ir.LabelStmt) + sym := n.Label lab := s.label(sym) - // Associate label with its control flow node, if any - if ctl := n.labeledControl(); ctl != nil { - s.labeledNodes[ctl] = lab - } - // The label might already have a target block via a goto. if lab.target == nil { lab.target = s.f.NewBlock(ssa.BlockPlain) @@ -1239,8 +1549,9 @@ func (s *state) stmt(n *Node) { } s.startBlock(lab.target) - case OGOTO: - sym := n.Sym + case ir.OGOTO: + n := n.(*ir.BranchStmt) + sym := n.Label lab := s.label(sym) if lab.target == nil { @@ -1251,8 +1562,9 @@ func (s *state) stmt(n *Node) { b.Pos = s.lastPos.WithIsStmt() // Do this even if b is an empty block. b.AddEdgeTo(lab.target) - case OAS: - if n.Left == n.Right && n.Left.Op == ONAME { + case ir.OAS: + n := n.(*ir.AssignStmt) + if n.X == n.Y && n.X.Op() == ir.ONAME { // An x=x assignment. No point in doing anything // here. In addition, skipping this assignment // prevents generating: @@ -1264,42 +1576,43 @@ func (s *state) stmt(n *Node) { } // Evaluate RHS. - rhs := n.Right + rhs := n.Y if rhs != nil { - switch rhs.Op { - case OSTRUCTLIT, OARRAYLIT, OSLICELIT: + switch rhs.Op() { + case ir.OSTRUCTLIT, ir.OARRAYLIT, ir.OSLICELIT: // All literals with nonzero fields have already been // rewritten during walk. Any that remain are just T{} // or equivalents. Use the zero value. - if !isZero(rhs) { + if !ir.IsZero(rhs) { s.Fatalf("literal with nonzero value in SSA: %v", rhs) } rhs = nil - case OAPPEND: + case ir.OAPPEND: + rhs := rhs.(*ir.CallExpr) // Check whether we're writing the result of an append back to the same slice. // If so, we handle it specially to avoid write barriers on the fast // (non-growth) path. - if !samesafeexpr(n.Left, rhs.List.First()) || Debug.N != 0 { + if !ir.SameSafeExpr(n.X, rhs.Args[0]) || base.Flag.N != 0 { break } // If the slice can be SSA'd, it'll be on the stack, // so there will be no write barriers, // so there's no need to attempt to prevent them. - if s.canSSA(n.Left) { - if Debug_append > 0 { // replicating old diagnostic message - Warnl(n.Pos, "append: len-only update (in local slice)") + if s.canSSA(n.X) { + if base.Debug.Append > 0 { // replicating old diagnostic message + base.WarnfAt(n.Pos(), "append: len-only update (in local slice)") } break } - if Debug_append > 0 { - Warnl(n.Pos, "append: len-only update") + if base.Debug.Append > 0 { + base.WarnfAt(n.Pos(), "append: len-only update") } s.append(rhs, true) return } } - if n.Left.isBlank() { + if ir.IsBlank(n.X) { // _ = rhs // Just evaluate rhs for side-effects. if rhs != nil { @@ -1309,14 +1622,14 @@ func (s *state) stmt(n *Node) { } var t *types.Type - if n.Right != nil { - t = n.Right.Type + if n.Y != nil { + t = n.Y.Type() } else { - t = n.Left.Type + t = n.X.Type() } var r *ssa.Value - deref := !canSSAType(t) + deref := !TypeOK(t) if deref { if rhs == nil { r = nil // Signal assign to use OpZero. @@ -1332,11 +1645,12 @@ func (s *state) stmt(n *Node) { } var skip skipMask - if rhs != nil && (rhs.Op == OSLICE || rhs.Op == OSLICE3 || rhs.Op == OSLICESTR) && samesafeexpr(rhs.Left, n.Left) { + if rhs != nil && (rhs.Op() == ir.OSLICE || rhs.Op() == ir.OSLICE3 || rhs.Op() == ir.OSLICESTR) && ir.SameSafeExpr(rhs.(*ir.SliceExpr).X, n.X) { // We're assigning a slicing operation back to its source. // Don't write back fields we aren't changing. See issue #14855. - i, j, k := rhs.SliceBounds() - if i != nil && (i.Op == OLITERAL && i.Val().Ctype() == CTINT && i.Int64Val() == 0) { + rhs := rhs.(*ir.SliceExpr) + i, j, k := rhs.Low, rhs.High, rhs.Max + if i != nil && (i.Op() == ir.OLITERAL && i.Val().Kind() == constant.Int && ir.Int64Val(i) == 0) { // [0:...] is the same as [:...] i = nil } @@ -1344,10 +1658,10 @@ func (s *state) stmt(n *Node) { // Currently doesn't really work because (*p)[:len(*p)] appears here as: // tmp = len(*p) // (*p)[:tmp] - //if j != nil && (j.Op == OLEN && samesafeexpr(j.Left, n.Left)) { + //if j != nil && (j.Op == OLEN && SameSafeExpr(j.Left, n.Left)) { // j = nil //} - //if k != nil && (k.Op == OCAP && samesafeexpr(k.Left, n.Left)) { + //if k != nil && (k.Op == OCAP && SameSafeExpr(k.Left, n.Left)) { // k = nil //} if i == nil { @@ -1361,83 +1675,86 @@ func (s *state) stmt(n *Node) { } } - s.assign(n.Left, r, deref, skip) + s.assign(n.X, r, deref, skip) - case OIF: - if Isconst(n.Left, CTBOOL) { - s.stmtList(n.Left.Ninit) - if n.Left.BoolVal() { - s.stmtList(n.Nbody) + case ir.OIF: + n := n.(*ir.IfStmt) + if ir.IsConst(n.Cond, constant.Bool) { + s.stmtList(n.Cond.Init()) + if ir.BoolVal(n.Cond) { + s.stmtList(n.Body) } else { - s.stmtList(n.Rlist) + s.stmtList(n.Else) } break } bEnd := s.f.NewBlock(ssa.BlockPlain) var likely int8 - if n.Likely() { + if n.Likely { likely = 1 } var bThen *ssa.Block - if n.Nbody.Len() != 0 { + if len(n.Body) != 0 { bThen = s.f.NewBlock(ssa.BlockPlain) } else { bThen = bEnd } var bElse *ssa.Block - if n.Rlist.Len() != 0 { + if len(n.Else) != 0 { bElse = s.f.NewBlock(ssa.BlockPlain) } else { bElse = bEnd } - s.condBranch(n.Left, bThen, bElse, likely) + s.condBranch(n.Cond, bThen, bElse, likely) - if n.Nbody.Len() != 0 { + if len(n.Body) != 0 { s.startBlock(bThen) - s.stmtList(n.Nbody) + s.stmtList(n.Body) if b := s.endBlock(); b != nil { b.AddEdgeTo(bEnd) } } - if n.Rlist.Len() != 0 { + if len(n.Else) != 0 { s.startBlock(bElse) - s.stmtList(n.Rlist) + s.stmtList(n.Else) if b := s.endBlock(); b != nil { b.AddEdgeTo(bEnd) } } s.startBlock(bEnd) - case ORETURN: - s.stmtList(n.List) + case ir.ORETURN: + n := n.(*ir.ReturnStmt) + s.stmtList(n.Results) b := s.exit() b.Pos = s.lastPos.WithIsStmt() - case ORETJMP: - s.stmtList(n.List) + case ir.OTAILCALL: + n := n.(*ir.TailCallStmt) b := s.exit() b.Kind = ssa.BlockRetJmp // override BlockRet - b.Aux = n.Sym.Linksym() + b.Aux = callTargetLSym(n.Target) - case OCONTINUE, OBREAK: + case ir.OCONTINUE, ir.OBREAK: + n := n.(*ir.BranchStmt) var to *ssa.Block - if n.Sym == nil { + if n.Label == nil { // plain break/continue - switch n.Op { - case OCONTINUE: + switch n.Op() { + case ir.OCONTINUE: to = s.continueTo - case OBREAK: + case ir.OBREAK: to = s.breakTo } } else { // labeled break/continue; look up the target - sym := n.Sym + sym := n.Label lab := s.label(sym) - switch n.Op { - case OCONTINUE: + switch n.Op() { + case ir.OCONTINUE: to = lab.continueTarget - case OBREAK: + case ir.OBREAK: to = lab.breakTarget } } @@ -1446,28 +1763,29 @@ func (s *state) stmt(n *Node) { b.Pos = s.lastPos.WithIsStmt() // Do this even if b is an empty block. b.AddEdgeTo(to) - case OFOR, OFORUNTIL: + case ir.OFOR, ir.OFORUNTIL: // OFOR: for Ninit; Left; Right { Nbody } // cond (Left); body (Nbody); incr (Right) // // OFORUNTIL: for Ninit; Left; Right; List { Nbody } // => body: { Nbody }; incr: Right; if Left { lateincr: List; goto body }; end: + n := n.(*ir.ForStmt) bCond := s.f.NewBlock(ssa.BlockPlain) bBody := s.f.NewBlock(ssa.BlockPlain) bIncr := s.f.NewBlock(ssa.BlockPlain) bEnd := s.f.NewBlock(ssa.BlockPlain) // ensure empty for loops have correct position; issue #30167 - bBody.Pos = n.Pos + bBody.Pos = n.Pos() // first, jump to condition test (OFOR) or body (OFORUNTIL) b := s.endBlock() - if n.Op == OFOR { + if n.Op() == ir.OFOR { b.AddEdgeTo(bCond) // generate code to test condition s.startBlock(bCond) - if n.Left != nil { - s.condBranch(n.Left, bBody, bEnd, 1) + if n.Cond != nil { + s.condBranch(n.Cond, bBody, bEnd, 1) } else { b := s.endBlock() b.Kind = ssa.BlockPlain @@ -1483,16 +1801,17 @@ func (s *state) stmt(n *Node) { prevBreak := s.breakTo s.continueTo = bIncr s.breakTo = bEnd - lab := s.labeledNodes[n] - if lab != nil { + var lab *ssaLabel + if sym := n.Label; sym != nil { // labeled for loop + lab = s.label(sym) lab.continueTarget = bIncr lab.breakTarget = bEnd } // generate body s.startBlock(bBody) - s.stmtList(n.Nbody) + s.stmtList(n.Body) // tear down continue/break s.continueTo = prevContinue @@ -1509,15 +1828,15 @@ func (s *state) stmt(n *Node) { // generate incr (and, for OFORUNTIL, condition) s.startBlock(bIncr) - if n.Right != nil { - s.stmt(n.Right) + if n.Post != nil { + s.stmt(n.Post) } - if n.Op == OFOR { + if n.Op() == ir.OFOR { if b := s.endBlock(); b != nil { b.AddEdgeTo(bCond) // It can happen that bIncr ends in a block containing only VARKILL, // and that muddles the debugging experience. - if n.Op != OFORUNTIL && b.Pos == src.NoXPos { + if b.Pos == src.NoXPos { b.Pos = bCond.Pos } } @@ -1525,30 +1844,43 @@ func (s *state) stmt(n *Node) { // bCond is unused in OFORUNTIL, so repurpose it. bLateIncr := bCond // test condition - s.condBranch(n.Left, bLateIncr, bEnd, 1) + s.condBranch(n.Cond, bLateIncr, bEnd, 1) // generate late increment s.startBlock(bLateIncr) - s.stmtList(n.List) + s.stmtList(n.Late) s.endBlock().AddEdgeTo(bBody) } s.startBlock(bEnd) - case OSWITCH, OSELECT: + case ir.OSWITCH, ir.OSELECT: // These have been mostly rewritten by the front end into their Nbody fields. // Our main task is to correctly hook up any break statements. bEnd := s.f.NewBlock(ssa.BlockPlain) prevBreak := s.breakTo s.breakTo = bEnd - lab := s.labeledNodes[n] - if lab != nil { + var sym *types.Sym + var body ir.Nodes + if n.Op() == ir.OSWITCH { + n := n.(*ir.SwitchStmt) + sym = n.Label + body = n.Compiled + } else { + n := n.(*ir.SelectStmt) + sym = n.Label + body = n.Compiled + } + + var lab *ssaLabel + if sym != nil { // labeled + lab = s.label(sym) lab.breakTarget = bEnd } // generate body code - s.stmtList(n.Nbody) + s.stmtList(body) s.breakTo = prevBreak if lab != nil { @@ -1565,40 +1897,46 @@ func (s *state) stmt(n *Node) { } s.startBlock(bEnd) - case OVARDEF: - if !s.canSSA(n.Left) { - s.vars[&memVar] = s.newValue1Apos(ssa.OpVarDef, types.TypeMem, n.Left, s.mem(), false) + case ir.OVARDEF: + n := n.(*ir.UnaryExpr) + if !s.canSSA(n.X) { + s.vars[memVar] = s.newValue1Apos(ssa.OpVarDef, types.TypeMem, n.X.(*ir.Name), s.mem(), false) } - case OVARKILL: + case ir.OVARKILL: // Insert a varkill op to record that a variable is no longer live. // We only care about liveness info at call sites, so putting the // varkill in the store chain is enough to keep it correctly ordered // with respect to call ops. - if !s.canSSA(n.Left) { - s.vars[&memVar] = s.newValue1Apos(ssa.OpVarKill, types.TypeMem, n.Left, s.mem(), false) + n := n.(*ir.UnaryExpr) + if !s.canSSA(n.X) { + s.vars[memVar] = s.newValue1Apos(ssa.OpVarKill, types.TypeMem, n.X.(*ir.Name), s.mem(), false) } - case OVARLIVE: + case ir.OVARLIVE: // Insert a varlive op to record that a variable is still live. - if !n.Left.Name.Addrtaken() { - s.Fatalf("VARLIVE variable %v must have Addrtaken set", n.Left) + n := n.(*ir.UnaryExpr) + v := n.X.(*ir.Name) + if !v.Addrtaken() { + s.Fatalf("VARLIVE variable %v must have Addrtaken set", v) } - switch n.Left.Class() { - case PAUTO, PPARAM, PPARAMOUT: + switch v.Class { + case ir.PAUTO, ir.PPARAM, ir.PPARAMOUT: default: - s.Fatalf("VARLIVE variable %v must be Auto or Arg", n.Left) + s.Fatalf("VARLIVE variable %v must be Auto or Arg", v) } - s.vars[&memVar] = s.newValue1A(ssa.OpVarLive, types.TypeMem, n.Left, s.mem()) + s.vars[memVar] = s.newValue1A(ssa.OpVarLive, types.TypeMem, v, s.mem()) - case OCHECKNIL: - p := s.expr(n.Left) + case ir.OCHECKNIL: + n := n.(*ir.UnaryExpr) + p := s.expr(n.X) s.nilCheck(p) - case OINLMARK: - s.newValue1I(ssa.OpInlMark, types.TypeVoid, n.Xoffset, s.mem()) + case ir.OINLMARK: + n := n.(*ir.InlineMarkStmt) + s.newValue1I(ssa.OpInlMark, types.TypeVoid, n.Index, s.mem()) default: - s.Fatalf("unhandled stmt %v", n.Op) + s.Fatalf("unhandled stmt %v", n.Op()) } } @@ -1622,28 +1960,49 @@ func (s *state) exit() *ssa.Block { } s.openDeferExit() } else { - s.rtcall(Deferreturn, true, nil) + s.rtcall(ir.Syms.Deferreturn, true, nil) } } - // Run exit code. Typically, this code copies heap-allocated PPARAMOUT - // variables back to the stack. - s.stmtList(s.curfn.Func.Exit) - - // Store SSAable PPARAMOUT variables back to stack locations. - for _, n := range s.returns { - addr := s.decladdrs[n] - val := s.variable(n, n.Type) - s.vars[&memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, n, s.mem()) - s.store(n.Type, addr, val) - // TODO: if val is ever spilled, we'd like to use the - // PPARAMOUT slot for spilling it. That won't happen - // currently. + var b *ssa.Block + var m *ssa.Value + // Do actual return. + // These currently turn into self-copies (in many cases). + resultFields := s.curfn.Type().Results().FieldSlice() + results := make([]*ssa.Value, len(resultFields)+1, len(resultFields)+1) + m = s.newValue0(ssa.OpMakeResult, s.f.OwnAux.LateExpansionResultType()) + // Store SSAable and heap-escaped PPARAMOUT variables back to stack locations. + for i, f := range resultFields { + n := f.Nname.(*ir.Name) + if s.canSSA(n) { // result is in some SSA variable + if !n.IsOutputParamInRegisters() { + // We are about to store to the result slot. + s.vars[memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, n, s.mem()) + } + results[i] = s.variable(n, n.Type()) + } else if !n.OnStack() { // result is actually heap allocated + // We are about to copy the in-heap result to the result slot. + s.vars[memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, n, s.mem()) + ha := s.expr(n.Heapaddr) + s.instrumentFields(n.Type(), ha, instrumentRead) + results[i] = s.newValue2(ssa.OpDereference, n.Type(), ha, s.mem()) + } else { // result is not SSA-able; not escaped, so not on heap, but too large for SSA. + // Before register ABI this ought to be a self-move, home=dest, + // With register ABI, it's still a self-move if parameter is on stack (i.e., too big or overflowed) + // No VarDef, as the result slot is already holding live value. + results[i] = s.newValue2(ssa.OpDereference, n.Type(), s.addr(n), s.mem()) + } } - // Do actual return. - m := s.mem() - b := s.endBlock() + // Run exit code. Today, this is just racefuncexit, in -race mode. + // TODO(register args) this seems risky here with a register-ABI, but not clear it is right to do it earlier either. + // Spills in register allocation might just fix it. + s.stmtList(s.curfn.Exit) + + results[len(results)-1] = s.mem() + m.AddArgs(results...) + + b = s.endBlock() b.Kind = ssa.BlockRet b.SetControl(m) if s.hasdefer && s.hasOpenDefers { @@ -1653,206 +2012,206 @@ func (s *state) exit() *ssa.Block { } type opAndType struct { - op Op - etype types.EType + op ir.Op + etype types.Kind } var opToSSA = map[opAndType]ssa.Op{ - opAndType{OADD, TINT8}: ssa.OpAdd8, - opAndType{OADD, TUINT8}: ssa.OpAdd8, - opAndType{OADD, TINT16}: ssa.OpAdd16, - opAndType{OADD, TUINT16}: ssa.OpAdd16, - opAndType{OADD, TINT32}: ssa.OpAdd32, - opAndType{OADD, TUINT32}: ssa.OpAdd32, - opAndType{OADD, TINT64}: ssa.OpAdd64, - opAndType{OADD, TUINT64}: ssa.OpAdd64, - opAndType{OADD, TFLOAT32}: ssa.OpAdd32F, - opAndType{OADD, TFLOAT64}: ssa.OpAdd64F, - - opAndType{OSUB, TINT8}: ssa.OpSub8, - opAndType{OSUB, TUINT8}: ssa.OpSub8, - opAndType{OSUB, TINT16}: ssa.OpSub16, - opAndType{OSUB, TUINT16}: ssa.OpSub16, - opAndType{OSUB, TINT32}: ssa.OpSub32, - opAndType{OSUB, TUINT32}: ssa.OpSub32, - opAndType{OSUB, TINT64}: ssa.OpSub64, - opAndType{OSUB, TUINT64}: ssa.OpSub64, - opAndType{OSUB, TFLOAT32}: ssa.OpSub32F, - opAndType{OSUB, TFLOAT64}: ssa.OpSub64F, - - opAndType{ONOT, TBOOL}: ssa.OpNot, - - opAndType{ONEG, TINT8}: ssa.OpNeg8, - opAndType{ONEG, TUINT8}: ssa.OpNeg8, - opAndType{ONEG, TINT16}: ssa.OpNeg16, - opAndType{ONEG, TUINT16}: ssa.OpNeg16, - opAndType{ONEG, TINT32}: ssa.OpNeg32, - opAndType{ONEG, TUINT32}: ssa.OpNeg32, - opAndType{ONEG, TINT64}: ssa.OpNeg64, - opAndType{ONEG, TUINT64}: ssa.OpNeg64, - opAndType{ONEG, TFLOAT32}: ssa.OpNeg32F, - opAndType{ONEG, TFLOAT64}: ssa.OpNeg64F, - - opAndType{OBITNOT, TINT8}: ssa.OpCom8, - opAndType{OBITNOT, TUINT8}: ssa.OpCom8, - opAndType{OBITNOT, TINT16}: ssa.OpCom16, - opAndType{OBITNOT, TUINT16}: ssa.OpCom16, - opAndType{OBITNOT, TINT32}: ssa.OpCom32, - opAndType{OBITNOT, TUINT32}: ssa.OpCom32, - opAndType{OBITNOT, TINT64}: ssa.OpCom64, - opAndType{OBITNOT, TUINT64}: ssa.OpCom64, - - opAndType{OIMAG, TCOMPLEX64}: ssa.OpComplexImag, - opAndType{OIMAG, TCOMPLEX128}: ssa.OpComplexImag, - opAndType{OREAL, TCOMPLEX64}: ssa.OpComplexReal, - opAndType{OREAL, TCOMPLEX128}: ssa.OpComplexReal, - - opAndType{OMUL, TINT8}: ssa.OpMul8, - opAndType{OMUL, TUINT8}: ssa.OpMul8, - opAndType{OMUL, TINT16}: ssa.OpMul16, - opAndType{OMUL, TUINT16}: ssa.OpMul16, - opAndType{OMUL, TINT32}: ssa.OpMul32, - opAndType{OMUL, TUINT32}: ssa.OpMul32, - opAndType{OMUL, TINT64}: ssa.OpMul64, - opAndType{OMUL, TUINT64}: ssa.OpMul64, - opAndType{OMUL, TFLOAT32}: ssa.OpMul32F, - opAndType{OMUL, TFLOAT64}: ssa.OpMul64F, - - opAndType{ODIV, TFLOAT32}: ssa.OpDiv32F, - opAndType{ODIV, TFLOAT64}: ssa.OpDiv64F, - - opAndType{ODIV, TINT8}: ssa.OpDiv8, - opAndType{ODIV, TUINT8}: ssa.OpDiv8u, - opAndType{ODIV, TINT16}: ssa.OpDiv16, - opAndType{ODIV, TUINT16}: ssa.OpDiv16u, - opAndType{ODIV, TINT32}: ssa.OpDiv32, - opAndType{ODIV, TUINT32}: ssa.OpDiv32u, - opAndType{ODIV, TINT64}: ssa.OpDiv64, - opAndType{ODIV, TUINT64}: ssa.OpDiv64u, - - opAndType{OMOD, TINT8}: ssa.OpMod8, - opAndType{OMOD, TUINT8}: ssa.OpMod8u, - opAndType{OMOD, TINT16}: ssa.OpMod16, - opAndType{OMOD, TUINT16}: ssa.OpMod16u, - opAndType{OMOD, TINT32}: ssa.OpMod32, - opAndType{OMOD, TUINT32}: ssa.OpMod32u, - opAndType{OMOD, TINT64}: ssa.OpMod64, - opAndType{OMOD, TUINT64}: ssa.OpMod64u, - - opAndType{OAND, TINT8}: ssa.OpAnd8, - opAndType{OAND, TUINT8}: ssa.OpAnd8, - opAndType{OAND, TINT16}: ssa.OpAnd16, - opAndType{OAND, TUINT16}: ssa.OpAnd16, - opAndType{OAND, TINT32}: ssa.OpAnd32, - opAndType{OAND, TUINT32}: ssa.OpAnd32, - opAndType{OAND, TINT64}: ssa.OpAnd64, - opAndType{OAND, TUINT64}: ssa.OpAnd64, - - opAndType{OOR, TINT8}: ssa.OpOr8, - opAndType{OOR, TUINT8}: ssa.OpOr8, - opAndType{OOR, TINT16}: ssa.OpOr16, - opAndType{OOR, TUINT16}: ssa.OpOr16, - opAndType{OOR, TINT32}: ssa.OpOr32, - opAndType{OOR, TUINT32}: ssa.OpOr32, - opAndType{OOR, TINT64}: ssa.OpOr64, - opAndType{OOR, TUINT64}: ssa.OpOr64, - - opAndType{OXOR, TINT8}: ssa.OpXor8, - opAndType{OXOR, TUINT8}: ssa.OpXor8, - opAndType{OXOR, TINT16}: ssa.OpXor16, - opAndType{OXOR, TUINT16}: ssa.OpXor16, - opAndType{OXOR, TINT32}: ssa.OpXor32, - opAndType{OXOR, TUINT32}: ssa.OpXor32, - opAndType{OXOR, TINT64}: ssa.OpXor64, - opAndType{OXOR, TUINT64}: ssa.OpXor64, - - opAndType{OEQ, TBOOL}: ssa.OpEqB, - opAndType{OEQ, TINT8}: ssa.OpEq8, - opAndType{OEQ, TUINT8}: ssa.OpEq8, - opAndType{OEQ, TINT16}: ssa.OpEq16, - opAndType{OEQ, TUINT16}: ssa.OpEq16, - opAndType{OEQ, TINT32}: ssa.OpEq32, - opAndType{OEQ, TUINT32}: ssa.OpEq32, - opAndType{OEQ, TINT64}: ssa.OpEq64, - opAndType{OEQ, TUINT64}: ssa.OpEq64, - opAndType{OEQ, TINTER}: ssa.OpEqInter, - opAndType{OEQ, TSLICE}: ssa.OpEqSlice, - opAndType{OEQ, TFUNC}: ssa.OpEqPtr, - opAndType{OEQ, TMAP}: ssa.OpEqPtr, - opAndType{OEQ, TCHAN}: ssa.OpEqPtr, - opAndType{OEQ, TPTR}: ssa.OpEqPtr, - opAndType{OEQ, TUINTPTR}: ssa.OpEqPtr, - opAndType{OEQ, TUNSAFEPTR}: ssa.OpEqPtr, - opAndType{OEQ, TFLOAT64}: ssa.OpEq64F, - opAndType{OEQ, TFLOAT32}: ssa.OpEq32F, - - opAndType{ONE, TBOOL}: ssa.OpNeqB, - opAndType{ONE, TINT8}: ssa.OpNeq8, - opAndType{ONE, TUINT8}: ssa.OpNeq8, - opAndType{ONE, TINT16}: ssa.OpNeq16, - opAndType{ONE, TUINT16}: ssa.OpNeq16, - opAndType{ONE, TINT32}: ssa.OpNeq32, - opAndType{ONE, TUINT32}: ssa.OpNeq32, - opAndType{ONE, TINT64}: ssa.OpNeq64, - opAndType{ONE, TUINT64}: ssa.OpNeq64, - opAndType{ONE, TINTER}: ssa.OpNeqInter, - opAndType{ONE, TSLICE}: ssa.OpNeqSlice, - opAndType{ONE, TFUNC}: ssa.OpNeqPtr, - opAndType{ONE, TMAP}: ssa.OpNeqPtr, - opAndType{ONE, TCHAN}: ssa.OpNeqPtr, - opAndType{ONE, TPTR}: ssa.OpNeqPtr, - opAndType{ONE, TUINTPTR}: ssa.OpNeqPtr, - opAndType{ONE, TUNSAFEPTR}: ssa.OpNeqPtr, - opAndType{ONE, TFLOAT64}: ssa.OpNeq64F, - opAndType{ONE, TFLOAT32}: ssa.OpNeq32F, - - opAndType{OLT, TINT8}: ssa.OpLess8, - opAndType{OLT, TUINT8}: ssa.OpLess8U, - opAndType{OLT, TINT16}: ssa.OpLess16, - opAndType{OLT, TUINT16}: ssa.OpLess16U, - opAndType{OLT, TINT32}: ssa.OpLess32, - opAndType{OLT, TUINT32}: ssa.OpLess32U, - opAndType{OLT, TINT64}: ssa.OpLess64, - opAndType{OLT, TUINT64}: ssa.OpLess64U, - opAndType{OLT, TFLOAT64}: ssa.OpLess64F, - opAndType{OLT, TFLOAT32}: ssa.OpLess32F, - - opAndType{OLE, TINT8}: ssa.OpLeq8, - opAndType{OLE, TUINT8}: ssa.OpLeq8U, - opAndType{OLE, TINT16}: ssa.OpLeq16, - opAndType{OLE, TUINT16}: ssa.OpLeq16U, - opAndType{OLE, TINT32}: ssa.OpLeq32, - opAndType{OLE, TUINT32}: ssa.OpLeq32U, - opAndType{OLE, TINT64}: ssa.OpLeq64, - opAndType{OLE, TUINT64}: ssa.OpLeq64U, - opAndType{OLE, TFLOAT64}: ssa.OpLeq64F, - opAndType{OLE, TFLOAT32}: ssa.OpLeq32F, -} - -func (s *state) concreteEtype(t *types.Type) types.EType { - e := t.Etype + opAndType{ir.OADD, types.TINT8}: ssa.OpAdd8, + opAndType{ir.OADD, types.TUINT8}: ssa.OpAdd8, + opAndType{ir.OADD, types.TINT16}: ssa.OpAdd16, + opAndType{ir.OADD, types.TUINT16}: ssa.OpAdd16, + opAndType{ir.OADD, types.TINT32}: ssa.OpAdd32, + opAndType{ir.OADD, types.TUINT32}: ssa.OpAdd32, + opAndType{ir.OADD, types.TINT64}: ssa.OpAdd64, + opAndType{ir.OADD, types.TUINT64}: ssa.OpAdd64, + opAndType{ir.OADD, types.TFLOAT32}: ssa.OpAdd32F, + opAndType{ir.OADD, types.TFLOAT64}: ssa.OpAdd64F, + + opAndType{ir.OSUB, types.TINT8}: ssa.OpSub8, + opAndType{ir.OSUB, types.TUINT8}: ssa.OpSub8, + opAndType{ir.OSUB, types.TINT16}: ssa.OpSub16, + opAndType{ir.OSUB, types.TUINT16}: ssa.OpSub16, + opAndType{ir.OSUB, types.TINT32}: ssa.OpSub32, + opAndType{ir.OSUB, types.TUINT32}: ssa.OpSub32, + opAndType{ir.OSUB, types.TINT64}: ssa.OpSub64, + opAndType{ir.OSUB, types.TUINT64}: ssa.OpSub64, + opAndType{ir.OSUB, types.TFLOAT32}: ssa.OpSub32F, + opAndType{ir.OSUB, types.TFLOAT64}: ssa.OpSub64F, + + opAndType{ir.ONOT, types.TBOOL}: ssa.OpNot, + + opAndType{ir.ONEG, types.TINT8}: ssa.OpNeg8, + opAndType{ir.ONEG, types.TUINT8}: ssa.OpNeg8, + opAndType{ir.ONEG, types.TINT16}: ssa.OpNeg16, + opAndType{ir.ONEG, types.TUINT16}: ssa.OpNeg16, + opAndType{ir.ONEG, types.TINT32}: ssa.OpNeg32, + opAndType{ir.ONEG, types.TUINT32}: ssa.OpNeg32, + opAndType{ir.ONEG, types.TINT64}: ssa.OpNeg64, + opAndType{ir.ONEG, types.TUINT64}: ssa.OpNeg64, + opAndType{ir.ONEG, types.TFLOAT32}: ssa.OpNeg32F, + opAndType{ir.ONEG, types.TFLOAT64}: ssa.OpNeg64F, + + opAndType{ir.OBITNOT, types.TINT8}: ssa.OpCom8, + opAndType{ir.OBITNOT, types.TUINT8}: ssa.OpCom8, + opAndType{ir.OBITNOT, types.TINT16}: ssa.OpCom16, + opAndType{ir.OBITNOT, types.TUINT16}: ssa.OpCom16, + opAndType{ir.OBITNOT, types.TINT32}: ssa.OpCom32, + opAndType{ir.OBITNOT, types.TUINT32}: ssa.OpCom32, + opAndType{ir.OBITNOT, types.TINT64}: ssa.OpCom64, + opAndType{ir.OBITNOT, types.TUINT64}: ssa.OpCom64, + + opAndType{ir.OIMAG, types.TCOMPLEX64}: ssa.OpComplexImag, + opAndType{ir.OIMAG, types.TCOMPLEX128}: ssa.OpComplexImag, + opAndType{ir.OREAL, types.TCOMPLEX64}: ssa.OpComplexReal, + opAndType{ir.OREAL, types.TCOMPLEX128}: ssa.OpComplexReal, + + opAndType{ir.OMUL, types.TINT8}: ssa.OpMul8, + opAndType{ir.OMUL, types.TUINT8}: ssa.OpMul8, + opAndType{ir.OMUL, types.TINT16}: ssa.OpMul16, + opAndType{ir.OMUL, types.TUINT16}: ssa.OpMul16, + opAndType{ir.OMUL, types.TINT32}: ssa.OpMul32, + opAndType{ir.OMUL, types.TUINT32}: ssa.OpMul32, + opAndType{ir.OMUL, types.TINT64}: ssa.OpMul64, + opAndType{ir.OMUL, types.TUINT64}: ssa.OpMul64, + opAndType{ir.OMUL, types.TFLOAT32}: ssa.OpMul32F, + opAndType{ir.OMUL, types.TFLOAT64}: ssa.OpMul64F, + + opAndType{ir.ODIV, types.TFLOAT32}: ssa.OpDiv32F, + opAndType{ir.ODIV, types.TFLOAT64}: ssa.OpDiv64F, + + opAndType{ir.ODIV, types.TINT8}: ssa.OpDiv8, + opAndType{ir.ODIV, types.TUINT8}: ssa.OpDiv8u, + opAndType{ir.ODIV, types.TINT16}: ssa.OpDiv16, + opAndType{ir.ODIV, types.TUINT16}: ssa.OpDiv16u, + opAndType{ir.ODIV, types.TINT32}: ssa.OpDiv32, + opAndType{ir.ODIV, types.TUINT32}: ssa.OpDiv32u, + opAndType{ir.ODIV, types.TINT64}: ssa.OpDiv64, + opAndType{ir.ODIV, types.TUINT64}: ssa.OpDiv64u, + + opAndType{ir.OMOD, types.TINT8}: ssa.OpMod8, + opAndType{ir.OMOD, types.TUINT8}: ssa.OpMod8u, + opAndType{ir.OMOD, types.TINT16}: ssa.OpMod16, + opAndType{ir.OMOD, types.TUINT16}: ssa.OpMod16u, + opAndType{ir.OMOD, types.TINT32}: ssa.OpMod32, + opAndType{ir.OMOD, types.TUINT32}: ssa.OpMod32u, + opAndType{ir.OMOD, types.TINT64}: ssa.OpMod64, + opAndType{ir.OMOD, types.TUINT64}: ssa.OpMod64u, + + opAndType{ir.OAND, types.TINT8}: ssa.OpAnd8, + opAndType{ir.OAND, types.TUINT8}: ssa.OpAnd8, + opAndType{ir.OAND, types.TINT16}: ssa.OpAnd16, + opAndType{ir.OAND, types.TUINT16}: ssa.OpAnd16, + opAndType{ir.OAND, types.TINT32}: ssa.OpAnd32, + opAndType{ir.OAND, types.TUINT32}: ssa.OpAnd32, + opAndType{ir.OAND, types.TINT64}: ssa.OpAnd64, + opAndType{ir.OAND, types.TUINT64}: ssa.OpAnd64, + + opAndType{ir.OOR, types.TINT8}: ssa.OpOr8, + opAndType{ir.OOR, types.TUINT8}: ssa.OpOr8, + opAndType{ir.OOR, types.TINT16}: ssa.OpOr16, + opAndType{ir.OOR, types.TUINT16}: ssa.OpOr16, + opAndType{ir.OOR, types.TINT32}: ssa.OpOr32, + opAndType{ir.OOR, types.TUINT32}: ssa.OpOr32, + opAndType{ir.OOR, types.TINT64}: ssa.OpOr64, + opAndType{ir.OOR, types.TUINT64}: ssa.OpOr64, + + opAndType{ir.OXOR, types.TINT8}: ssa.OpXor8, + opAndType{ir.OXOR, types.TUINT8}: ssa.OpXor8, + opAndType{ir.OXOR, types.TINT16}: ssa.OpXor16, + opAndType{ir.OXOR, types.TUINT16}: ssa.OpXor16, + opAndType{ir.OXOR, types.TINT32}: ssa.OpXor32, + opAndType{ir.OXOR, types.TUINT32}: ssa.OpXor32, + opAndType{ir.OXOR, types.TINT64}: ssa.OpXor64, + opAndType{ir.OXOR, types.TUINT64}: ssa.OpXor64, + + opAndType{ir.OEQ, types.TBOOL}: ssa.OpEqB, + opAndType{ir.OEQ, types.TINT8}: ssa.OpEq8, + opAndType{ir.OEQ, types.TUINT8}: ssa.OpEq8, + opAndType{ir.OEQ, types.TINT16}: ssa.OpEq16, + opAndType{ir.OEQ, types.TUINT16}: ssa.OpEq16, + opAndType{ir.OEQ, types.TINT32}: ssa.OpEq32, + opAndType{ir.OEQ, types.TUINT32}: ssa.OpEq32, + opAndType{ir.OEQ, types.TINT64}: ssa.OpEq64, + opAndType{ir.OEQ, types.TUINT64}: ssa.OpEq64, + opAndType{ir.OEQ, types.TINTER}: ssa.OpEqInter, + opAndType{ir.OEQ, types.TSLICE}: ssa.OpEqSlice, + opAndType{ir.OEQ, types.TFUNC}: ssa.OpEqPtr, + opAndType{ir.OEQ, types.TMAP}: ssa.OpEqPtr, + opAndType{ir.OEQ, types.TCHAN}: ssa.OpEqPtr, + opAndType{ir.OEQ, types.TPTR}: ssa.OpEqPtr, + opAndType{ir.OEQ, types.TUINTPTR}: ssa.OpEqPtr, + opAndType{ir.OEQ, types.TUNSAFEPTR}: ssa.OpEqPtr, + opAndType{ir.OEQ, types.TFLOAT64}: ssa.OpEq64F, + opAndType{ir.OEQ, types.TFLOAT32}: ssa.OpEq32F, + + opAndType{ir.ONE, types.TBOOL}: ssa.OpNeqB, + opAndType{ir.ONE, types.TINT8}: ssa.OpNeq8, + opAndType{ir.ONE, types.TUINT8}: ssa.OpNeq8, + opAndType{ir.ONE, types.TINT16}: ssa.OpNeq16, + opAndType{ir.ONE, types.TUINT16}: ssa.OpNeq16, + opAndType{ir.ONE, types.TINT32}: ssa.OpNeq32, + opAndType{ir.ONE, types.TUINT32}: ssa.OpNeq32, + opAndType{ir.ONE, types.TINT64}: ssa.OpNeq64, + opAndType{ir.ONE, types.TUINT64}: ssa.OpNeq64, + opAndType{ir.ONE, types.TINTER}: ssa.OpNeqInter, + opAndType{ir.ONE, types.TSLICE}: ssa.OpNeqSlice, + opAndType{ir.ONE, types.TFUNC}: ssa.OpNeqPtr, + opAndType{ir.ONE, types.TMAP}: ssa.OpNeqPtr, + opAndType{ir.ONE, types.TCHAN}: ssa.OpNeqPtr, + opAndType{ir.ONE, types.TPTR}: ssa.OpNeqPtr, + opAndType{ir.ONE, types.TUINTPTR}: ssa.OpNeqPtr, + opAndType{ir.ONE, types.TUNSAFEPTR}: ssa.OpNeqPtr, + opAndType{ir.ONE, types.TFLOAT64}: ssa.OpNeq64F, + opAndType{ir.ONE, types.TFLOAT32}: ssa.OpNeq32F, + + opAndType{ir.OLT, types.TINT8}: ssa.OpLess8, + opAndType{ir.OLT, types.TUINT8}: ssa.OpLess8U, + opAndType{ir.OLT, types.TINT16}: ssa.OpLess16, + opAndType{ir.OLT, types.TUINT16}: ssa.OpLess16U, + opAndType{ir.OLT, types.TINT32}: ssa.OpLess32, + opAndType{ir.OLT, types.TUINT32}: ssa.OpLess32U, + opAndType{ir.OLT, types.TINT64}: ssa.OpLess64, + opAndType{ir.OLT, types.TUINT64}: ssa.OpLess64U, + opAndType{ir.OLT, types.TFLOAT64}: ssa.OpLess64F, + opAndType{ir.OLT, types.TFLOAT32}: ssa.OpLess32F, + + opAndType{ir.OLE, types.TINT8}: ssa.OpLeq8, + opAndType{ir.OLE, types.TUINT8}: ssa.OpLeq8U, + opAndType{ir.OLE, types.TINT16}: ssa.OpLeq16, + opAndType{ir.OLE, types.TUINT16}: ssa.OpLeq16U, + opAndType{ir.OLE, types.TINT32}: ssa.OpLeq32, + opAndType{ir.OLE, types.TUINT32}: ssa.OpLeq32U, + opAndType{ir.OLE, types.TINT64}: ssa.OpLeq64, + opAndType{ir.OLE, types.TUINT64}: ssa.OpLeq64U, + opAndType{ir.OLE, types.TFLOAT64}: ssa.OpLeq64F, + opAndType{ir.OLE, types.TFLOAT32}: ssa.OpLeq32F, +} + +func (s *state) concreteEtype(t *types.Type) types.Kind { + e := t.Kind() switch e { default: return e - case TINT: + case types.TINT: if s.config.PtrSize == 8 { - return TINT64 + return types.TINT64 } - return TINT32 - case TUINT: + return types.TINT32 + case types.TUINT: if s.config.PtrSize == 8 { - return TUINT64 + return types.TUINT64 } - return TUINT32 - case TUINTPTR: + return types.TUINT32 + case types.TUINTPTR: if s.config.PtrSize == 8 { - return TUINT64 + return types.TUINT64 } - return TUINT32 + return types.TUINT32 } } -func (s *state) ssaOp(op Op, t *types.Type) ssa.Op { +func (s *state) ssaOp(op ir.Op, t *types.Type) ssa.Op { etype := s.concreteEtype(t) x, ok := opToSSA[opAndType{op, etype}] if !ok { @@ -1861,186 +2220,164 @@ func (s *state) ssaOp(op Op, t *types.Type) ssa.Op { return x } -func floatForComplex(t *types.Type) *types.Type { - switch t.Etype { - case TCOMPLEX64: - return types.Types[TFLOAT32] - case TCOMPLEX128: - return types.Types[TFLOAT64] - } - Fatalf("unexpected type: %v", t) - return nil -} - -func complexForFloat(t *types.Type) *types.Type { - switch t.Etype { - case TFLOAT32: - return types.Types[TCOMPLEX64] - case TFLOAT64: - return types.Types[TCOMPLEX128] - } - Fatalf("unexpected type: %v", t) - return nil -} - type opAndTwoTypes struct { - op Op - etype1 types.EType - etype2 types.EType + op ir.Op + etype1 types.Kind + etype2 types.Kind } type twoTypes struct { - etype1 types.EType - etype2 types.EType + etype1 types.Kind + etype2 types.Kind } type twoOpsAndType struct { op1 ssa.Op op2 ssa.Op - intermediateType types.EType + intermediateType types.Kind } var fpConvOpToSSA = map[twoTypes]twoOpsAndType{ - twoTypes{TINT8, TFLOAT32}: twoOpsAndType{ssa.OpSignExt8to32, ssa.OpCvt32to32F, TINT32}, - twoTypes{TINT16, TFLOAT32}: twoOpsAndType{ssa.OpSignExt16to32, ssa.OpCvt32to32F, TINT32}, - twoTypes{TINT32, TFLOAT32}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt32to32F, TINT32}, - twoTypes{TINT64, TFLOAT32}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt64to32F, TINT64}, - - twoTypes{TINT8, TFLOAT64}: twoOpsAndType{ssa.OpSignExt8to32, ssa.OpCvt32to64F, TINT32}, - twoTypes{TINT16, TFLOAT64}: twoOpsAndType{ssa.OpSignExt16to32, ssa.OpCvt32to64F, TINT32}, - twoTypes{TINT32, TFLOAT64}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt32to64F, TINT32}, - twoTypes{TINT64, TFLOAT64}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt64to64F, TINT64}, - - twoTypes{TFLOAT32, TINT8}: twoOpsAndType{ssa.OpCvt32Fto32, ssa.OpTrunc32to8, TINT32}, - twoTypes{TFLOAT32, TINT16}: twoOpsAndType{ssa.OpCvt32Fto32, ssa.OpTrunc32to16, TINT32}, - twoTypes{TFLOAT32, TINT32}: twoOpsAndType{ssa.OpCvt32Fto32, ssa.OpCopy, TINT32}, - twoTypes{TFLOAT32, TINT64}: twoOpsAndType{ssa.OpCvt32Fto64, ssa.OpCopy, TINT64}, - - twoTypes{TFLOAT64, TINT8}: twoOpsAndType{ssa.OpCvt64Fto32, ssa.OpTrunc32to8, TINT32}, - twoTypes{TFLOAT64, TINT16}: twoOpsAndType{ssa.OpCvt64Fto32, ssa.OpTrunc32to16, TINT32}, - twoTypes{TFLOAT64, TINT32}: twoOpsAndType{ssa.OpCvt64Fto32, ssa.OpCopy, TINT32}, - twoTypes{TFLOAT64, TINT64}: twoOpsAndType{ssa.OpCvt64Fto64, ssa.OpCopy, TINT64}, + twoTypes{types.TINT8, types.TFLOAT32}: twoOpsAndType{ssa.OpSignExt8to32, ssa.OpCvt32to32F, types.TINT32}, + twoTypes{types.TINT16, types.TFLOAT32}: twoOpsAndType{ssa.OpSignExt16to32, ssa.OpCvt32to32F, types.TINT32}, + twoTypes{types.TINT32, types.TFLOAT32}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt32to32F, types.TINT32}, + twoTypes{types.TINT64, types.TFLOAT32}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt64to32F, types.TINT64}, + + twoTypes{types.TINT8, types.TFLOAT64}: twoOpsAndType{ssa.OpSignExt8to32, ssa.OpCvt32to64F, types.TINT32}, + twoTypes{types.TINT16, types.TFLOAT64}: twoOpsAndType{ssa.OpSignExt16to32, ssa.OpCvt32to64F, types.TINT32}, + twoTypes{types.TINT32, types.TFLOAT64}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt32to64F, types.TINT32}, + twoTypes{types.TINT64, types.TFLOAT64}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt64to64F, types.TINT64}, + + twoTypes{types.TFLOAT32, types.TINT8}: twoOpsAndType{ssa.OpCvt32Fto32, ssa.OpTrunc32to8, types.TINT32}, + twoTypes{types.TFLOAT32, types.TINT16}: twoOpsAndType{ssa.OpCvt32Fto32, ssa.OpTrunc32to16, types.TINT32}, + twoTypes{types.TFLOAT32, types.TINT32}: twoOpsAndType{ssa.OpCvt32Fto32, ssa.OpCopy, types.TINT32}, + twoTypes{types.TFLOAT32, types.TINT64}: twoOpsAndType{ssa.OpCvt32Fto64, ssa.OpCopy, types.TINT64}, + + twoTypes{types.TFLOAT64, types.TINT8}: twoOpsAndType{ssa.OpCvt64Fto32, ssa.OpTrunc32to8, types.TINT32}, + twoTypes{types.TFLOAT64, types.TINT16}: twoOpsAndType{ssa.OpCvt64Fto32, ssa.OpTrunc32to16, types.TINT32}, + twoTypes{types.TFLOAT64, types.TINT32}: twoOpsAndType{ssa.OpCvt64Fto32, ssa.OpCopy, types.TINT32}, + twoTypes{types.TFLOAT64, types.TINT64}: twoOpsAndType{ssa.OpCvt64Fto64, ssa.OpCopy, types.TINT64}, // unsigned - twoTypes{TUINT8, TFLOAT32}: twoOpsAndType{ssa.OpZeroExt8to32, ssa.OpCvt32to32F, TINT32}, - twoTypes{TUINT16, TFLOAT32}: twoOpsAndType{ssa.OpZeroExt16to32, ssa.OpCvt32to32F, TINT32}, - twoTypes{TUINT32, TFLOAT32}: twoOpsAndType{ssa.OpZeroExt32to64, ssa.OpCvt64to32F, TINT64}, // go wide to dodge unsigned - twoTypes{TUINT64, TFLOAT32}: twoOpsAndType{ssa.OpCopy, ssa.OpInvalid, TUINT64}, // Cvt64Uto32F, branchy code expansion instead - - twoTypes{TUINT8, TFLOAT64}: twoOpsAndType{ssa.OpZeroExt8to32, ssa.OpCvt32to64F, TINT32}, - twoTypes{TUINT16, TFLOAT64}: twoOpsAndType{ssa.OpZeroExt16to32, ssa.OpCvt32to64F, TINT32}, - twoTypes{TUINT32, TFLOAT64}: twoOpsAndType{ssa.OpZeroExt32to64, ssa.OpCvt64to64F, TINT64}, // go wide to dodge unsigned - twoTypes{TUINT64, TFLOAT64}: twoOpsAndType{ssa.OpCopy, ssa.OpInvalid, TUINT64}, // Cvt64Uto64F, branchy code expansion instead - - twoTypes{TFLOAT32, TUINT8}: twoOpsAndType{ssa.OpCvt32Fto32, ssa.OpTrunc32to8, TINT32}, - twoTypes{TFLOAT32, TUINT16}: twoOpsAndType{ssa.OpCvt32Fto32, ssa.OpTrunc32to16, TINT32}, - twoTypes{TFLOAT32, TUINT32}: twoOpsAndType{ssa.OpCvt32Fto64, ssa.OpTrunc64to32, TINT64}, // go wide to dodge unsigned - twoTypes{TFLOAT32, TUINT64}: twoOpsAndType{ssa.OpInvalid, ssa.OpCopy, TUINT64}, // Cvt32Fto64U, branchy code expansion instead - - twoTypes{TFLOAT64, TUINT8}: twoOpsAndType{ssa.OpCvt64Fto32, ssa.OpTrunc32to8, TINT32}, - twoTypes{TFLOAT64, TUINT16}: twoOpsAndType{ssa.OpCvt64Fto32, ssa.OpTrunc32to16, TINT32}, - twoTypes{TFLOAT64, TUINT32}: twoOpsAndType{ssa.OpCvt64Fto64, ssa.OpTrunc64to32, TINT64}, // go wide to dodge unsigned - twoTypes{TFLOAT64, TUINT64}: twoOpsAndType{ssa.OpInvalid, ssa.OpCopy, TUINT64}, // Cvt64Fto64U, branchy code expansion instead + twoTypes{types.TUINT8, types.TFLOAT32}: twoOpsAndType{ssa.OpZeroExt8to32, ssa.OpCvt32to32F, types.TINT32}, + twoTypes{types.TUINT16, types.TFLOAT32}: twoOpsAndType{ssa.OpZeroExt16to32, ssa.OpCvt32to32F, types.TINT32}, + twoTypes{types.TUINT32, types.TFLOAT32}: twoOpsAndType{ssa.OpZeroExt32to64, ssa.OpCvt64to32F, types.TINT64}, // go wide to dodge unsigned + twoTypes{types.TUINT64, types.TFLOAT32}: twoOpsAndType{ssa.OpCopy, ssa.OpInvalid, types.TUINT64}, // Cvt64Uto32F, branchy code expansion instead + + twoTypes{types.TUINT8, types.TFLOAT64}: twoOpsAndType{ssa.OpZeroExt8to32, ssa.OpCvt32to64F, types.TINT32}, + twoTypes{types.TUINT16, types.TFLOAT64}: twoOpsAndType{ssa.OpZeroExt16to32, ssa.OpCvt32to64F, types.TINT32}, + twoTypes{types.TUINT32, types.TFLOAT64}: twoOpsAndType{ssa.OpZeroExt32to64, ssa.OpCvt64to64F, types.TINT64}, // go wide to dodge unsigned + twoTypes{types.TUINT64, types.TFLOAT64}: twoOpsAndType{ssa.OpCopy, ssa.OpInvalid, types.TUINT64}, // Cvt64Uto64F, branchy code expansion instead + + twoTypes{types.TFLOAT32, types.TUINT8}: twoOpsAndType{ssa.OpCvt32Fto32, ssa.OpTrunc32to8, types.TINT32}, + twoTypes{types.TFLOAT32, types.TUINT16}: twoOpsAndType{ssa.OpCvt32Fto32, ssa.OpTrunc32to16, types.TINT32}, + twoTypes{types.TFLOAT32, types.TUINT32}: twoOpsAndType{ssa.OpCvt32Fto64, ssa.OpTrunc64to32, types.TINT64}, // go wide to dodge unsigned + twoTypes{types.TFLOAT32, types.TUINT64}: twoOpsAndType{ssa.OpInvalid, ssa.OpCopy, types.TUINT64}, // Cvt32Fto64U, branchy code expansion instead + + twoTypes{types.TFLOAT64, types.TUINT8}: twoOpsAndType{ssa.OpCvt64Fto32, ssa.OpTrunc32to8, types.TINT32}, + twoTypes{types.TFLOAT64, types.TUINT16}: twoOpsAndType{ssa.OpCvt64Fto32, ssa.OpTrunc32to16, types.TINT32}, + twoTypes{types.TFLOAT64, types.TUINT32}: twoOpsAndType{ssa.OpCvt64Fto64, ssa.OpTrunc64to32, types.TINT64}, // go wide to dodge unsigned + twoTypes{types.TFLOAT64, types.TUINT64}: twoOpsAndType{ssa.OpInvalid, ssa.OpCopy, types.TUINT64}, // Cvt64Fto64U, branchy code expansion instead // float - twoTypes{TFLOAT64, TFLOAT32}: twoOpsAndType{ssa.OpCvt64Fto32F, ssa.OpCopy, TFLOAT32}, - twoTypes{TFLOAT64, TFLOAT64}: twoOpsAndType{ssa.OpRound64F, ssa.OpCopy, TFLOAT64}, - twoTypes{TFLOAT32, TFLOAT32}: twoOpsAndType{ssa.OpRound32F, ssa.OpCopy, TFLOAT32}, - twoTypes{TFLOAT32, TFLOAT64}: twoOpsAndType{ssa.OpCvt32Fto64F, ssa.OpCopy, TFLOAT64}, + twoTypes{types.TFLOAT64, types.TFLOAT32}: twoOpsAndType{ssa.OpCvt64Fto32F, ssa.OpCopy, types.TFLOAT32}, + twoTypes{types.TFLOAT64, types.TFLOAT64}: twoOpsAndType{ssa.OpRound64F, ssa.OpCopy, types.TFLOAT64}, + twoTypes{types.TFLOAT32, types.TFLOAT32}: twoOpsAndType{ssa.OpRound32F, ssa.OpCopy, types.TFLOAT32}, + twoTypes{types.TFLOAT32, types.TFLOAT64}: twoOpsAndType{ssa.OpCvt32Fto64F, ssa.OpCopy, types.TFLOAT64}, } // this map is used only for 32-bit arch, and only includes the difference // on 32-bit arch, don't use int64<->float conversion for uint32 var fpConvOpToSSA32 = map[twoTypes]twoOpsAndType{ - twoTypes{TUINT32, TFLOAT32}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt32Uto32F, TUINT32}, - twoTypes{TUINT32, TFLOAT64}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt32Uto64F, TUINT32}, - twoTypes{TFLOAT32, TUINT32}: twoOpsAndType{ssa.OpCvt32Fto32U, ssa.OpCopy, TUINT32}, - twoTypes{TFLOAT64, TUINT32}: twoOpsAndType{ssa.OpCvt64Fto32U, ssa.OpCopy, TUINT32}, + twoTypes{types.TUINT32, types.TFLOAT32}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt32Uto32F, types.TUINT32}, + twoTypes{types.TUINT32, types.TFLOAT64}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt32Uto64F, types.TUINT32}, + twoTypes{types.TFLOAT32, types.TUINT32}: twoOpsAndType{ssa.OpCvt32Fto32U, ssa.OpCopy, types.TUINT32}, + twoTypes{types.TFLOAT64, types.TUINT32}: twoOpsAndType{ssa.OpCvt64Fto32U, ssa.OpCopy, types.TUINT32}, } // uint64<->float conversions, only on machines that have instructions for that var uint64fpConvOpToSSA = map[twoTypes]twoOpsAndType{ - twoTypes{TUINT64, TFLOAT32}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt64Uto32F, TUINT64}, - twoTypes{TUINT64, TFLOAT64}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt64Uto64F, TUINT64}, - twoTypes{TFLOAT32, TUINT64}: twoOpsAndType{ssa.OpCvt32Fto64U, ssa.OpCopy, TUINT64}, - twoTypes{TFLOAT64, TUINT64}: twoOpsAndType{ssa.OpCvt64Fto64U, ssa.OpCopy, TUINT64}, + twoTypes{types.TUINT64, types.TFLOAT32}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt64Uto32F, types.TUINT64}, + twoTypes{types.TUINT64, types.TFLOAT64}: twoOpsAndType{ssa.OpCopy, ssa.OpCvt64Uto64F, types.TUINT64}, + twoTypes{types.TFLOAT32, types.TUINT64}: twoOpsAndType{ssa.OpCvt32Fto64U, ssa.OpCopy, types.TUINT64}, + twoTypes{types.TFLOAT64, types.TUINT64}: twoOpsAndType{ssa.OpCvt64Fto64U, ssa.OpCopy, types.TUINT64}, } var shiftOpToSSA = map[opAndTwoTypes]ssa.Op{ - opAndTwoTypes{OLSH, TINT8, TUINT8}: ssa.OpLsh8x8, - opAndTwoTypes{OLSH, TUINT8, TUINT8}: ssa.OpLsh8x8, - opAndTwoTypes{OLSH, TINT8, TUINT16}: ssa.OpLsh8x16, - opAndTwoTypes{OLSH, TUINT8, TUINT16}: ssa.OpLsh8x16, - opAndTwoTypes{OLSH, TINT8, TUINT32}: ssa.OpLsh8x32, - opAndTwoTypes{OLSH, TUINT8, TUINT32}: ssa.OpLsh8x32, - opAndTwoTypes{OLSH, TINT8, TUINT64}: ssa.OpLsh8x64, - opAndTwoTypes{OLSH, TUINT8, TUINT64}: ssa.OpLsh8x64, - - opAndTwoTypes{OLSH, TINT16, TUINT8}: ssa.OpLsh16x8, - opAndTwoTypes{OLSH, TUINT16, TUINT8}: ssa.OpLsh16x8, - opAndTwoTypes{OLSH, TINT16, TUINT16}: ssa.OpLsh16x16, - opAndTwoTypes{OLSH, TUINT16, TUINT16}: ssa.OpLsh16x16, - opAndTwoTypes{OLSH, TINT16, TUINT32}: ssa.OpLsh16x32, - opAndTwoTypes{OLSH, TUINT16, TUINT32}: ssa.OpLsh16x32, - opAndTwoTypes{OLSH, TINT16, TUINT64}: ssa.OpLsh16x64, - opAndTwoTypes{OLSH, TUINT16, TUINT64}: ssa.OpLsh16x64, - - opAndTwoTypes{OLSH, TINT32, TUINT8}: ssa.OpLsh32x8, - opAndTwoTypes{OLSH, TUINT32, TUINT8}: ssa.OpLsh32x8, - opAndTwoTypes{OLSH, TINT32, TUINT16}: ssa.OpLsh32x16, - opAndTwoTypes{OLSH, TUINT32, TUINT16}: ssa.OpLsh32x16, - opAndTwoTypes{OLSH, TINT32, TUINT32}: ssa.OpLsh32x32, - opAndTwoTypes{OLSH, TUINT32, TUINT32}: ssa.OpLsh32x32, - opAndTwoTypes{OLSH, TINT32, TUINT64}: ssa.OpLsh32x64, - opAndTwoTypes{OLSH, TUINT32, TUINT64}: ssa.OpLsh32x64, - - opAndTwoTypes{OLSH, TINT64, TUINT8}: ssa.OpLsh64x8, - opAndTwoTypes{OLSH, TUINT64, TUINT8}: ssa.OpLsh64x8, - opAndTwoTypes{OLSH, TINT64, TUINT16}: ssa.OpLsh64x16, - opAndTwoTypes{OLSH, TUINT64, TUINT16}: ssa.OpLsh64x16, - opAndTwoTypes{OLSH, TINT64, TUINT32}: ssa.OpLsh64x32, - opAndTwoTypes{OLSH, TUINT64, TUINT32}: ssa.OpLsh64x32, - opAndTwoTypes{OLSH, TINT64, TUINT64}: ssa.OpLsh64x64, - opAndTwoTypes{OLSH, TUINT64, TUINT64}: ssa.OpLsh64x64, - - opAndTwoTypes{ORSH, TINT8, TUINT8}: ssa.OpRsh8x8, - opAndTwoTypes{ORSH, TUINT8, TUINT8}: ssa.OpRsh8Ux8, - opAndTwoTypes{ORSH, TINT8, TUINT16}: ssa.OpRsh8x16, - opAndTwoTypes{ORSH, TUINT8, TUINT16}: ssa.OpRsh8Ux16, - opAndTwoTypes{ORSH, TINT8, TUINT32}: ssa.OpRsh8x32, - opAndTwoTypes{ORSH, TUINT8, TUINT32}: ssa.OpRsh8Ux32, - opAndTwoTypes{ORSH, TINT8, TUINT64}: ssa.OpRsh8x64, - opAndTwoTypes{ORSH, TUINT8, TUINT64}: ssa.OpRsh8Ux64, - - opAndTwoTypes{ORSH, TINT16, TUINT8}: ssa.OpRsh16x8, - opAndTwoTypes{ORSH, TUINT16, TUINT8}: ssa.OpRsh16Ux8, - opAndTwoTypes{ORSH, TINT16, TUINT16}: ssa.OpRsh16x16, - opAndTwoTypes{ORSH, TUINT16, TUINT16}: ssa.OpRsh16Ux16, - opAndTwoTypes{ORSH, TINT16, TUINT32}: ssa.OpRsh16x32, - opAndTwoTypes{ORSH, TUINT16, TUINT32}: ssa.OpRsh16Ux32, - opAndTwoTypes{ORSH, TINT16, TUINT64}: ssa.OpRsh16x64, - opAndTwoTypes{ORSH, TUINT16, TUINT64}: ssa.OpRsh16Ux64, - - opAndTwoTypes{ORSH, TINT32, TUINT8}: ssa.OpRsh32x8, - opAndTwoTypes{ORSH, TUINT32, TUINT8}: ssa.OpRsh32Ux8, - opAndTwoTypes{ORSH, TINT32, TUINT16}: ssa.OpRsh32x16, - opAndTwoTypes{ORSH, TUINT32, TUINT16}: ssa.OpRsh32Ux16, - opAndTwoTypes{ORSH, TINT32, TUINT32}: ssa.OpRsh32x32, - opAndTwoTypes{ORSH, TUINT32, TUINT32}: ssa.OpRsh32Ux32, - opAndTwoTypes{ORSH, TINT32, TUINT64}: ssa.OpRsh32x64, - opAndTwoTypes{ORSH, TUINT32, TUINT64}: ssa.OpRsh32Ux64, - - opAndTwoTypes{ORSH, TINT64, TUINT8}: ssa.OpRsh64x8, - opAndTwoTypes{ORSH, TUINT64, TUINT8}: ssa.OpRsh64Ux8, - opAndTwoTypes{ORSH, TINT64, TUINT16}: ssa.OpRsh64x16, - opAndTwoTypes{ORSH, TUINT64, TUINT16}: ssa.OpRsh64Ux16, - opAndTwoTypes{ORSH, TINT64, TUINT32}: ssa.OpRsh64x32, - opAndTwoTypes{ORSH, TUINT64, TUINT32}: ssa.OpRsh64Ux32, - opAndTwoTypes{ORSH, TINT64, TUINT64}: ssa.OpRsh64x64, - opAndTwoTypes{ORSH, TUINT64, TUINT64}: ssa.OpRsh64Ux64, -} - -func (s *state) ssaShiftOp(op Op, t *types.Type, u *types.Type) ssa.Op { + opAndTwoTypes{ir.OLSH, types.TINT8, types.TUINT8}: ssa.OpLsh8x8, + opAndTwoTypes{ir.OLSH, types.TUINT8, types.TUINT8}: ssa.OpLsh8x8, + opAndTwoTypes{ir.OLSH, types.TINT8, types.TUINT16}: ssa.OpLsh8x16, + opAndTwoTypes{ir.OLSH, types.TUINT8, types.TUINT16}: ssa.OpLsh8x16, + opAndTwoTypes{ir.OLSH, types.TINT8, types.TUINT32}: ssa.OpLsh8x32, + opAndTwoTypes{ir.OLSH, types.TUINT8, types.TUINT32}: ssa.OpLsh8x32, + opAndTwoTypes{ir.OLSH, types.TINT8, types.TUINT64}: ssa.OpLsh8x64, + opAndTwoTypes{ir.OLSH, types.TUINT8, types.TUINT64}: ssa.OpLsh8x64, + + opAndTwoTypes{ir.OLSH, types.TINT16, types.TUINT8}: ssa.OpLsh16x8, + opAndTwoTypes{ir.OLSH, types.TUINT16, types.TUINT8}: ssa.OpLsh16x8, + opAndTwoTypes{ir.OLSH, types.TINT16, types.TUINT16}: ssa.OpLsh16x16, + opAndTwoTypes{ir.OLSH, types.TUINT16, types.TUINT16}: ssa.OpLsh16x16, + opAndTwoTypes{ir.OLSH, types.TINT16, types.TUINT32}: ssa.OpLsh16x32, + opAndTwoTypes{ir.OLSH, types.TUINT16, types.TUINT32}: ssa.OpLsh16x32, + opAndTwoTypes{ir.OLSH, types.TINT16, types.TUINT64}: ssa.OpLsh16x64, + opAndTwoTypes{ir.OLSH, types.TUINT16, types.TUINT64}: ssa.OpLsh16x64, + + opAndTwoTypes{ir.OLSH, types.TINT32, types.TUINT8}: ssa.OpLsh32x8, + opAndTwoTypes{ir.OLSH, types.TUINT32, types.TUINT8}: ssa.OpLsh32x8, + opAndTwoTypes{ir.OLSH, types.TINT32, types.TUINT16}: ssa.OpLsh32x16, + opAndTwoTypes{ir.OLSH, types.TUINT32, types.TUINT16}: ssa.OpLsh32x16, + opAndTwoTypes{ir.OLSH, types.TINT32, types.TUINT32}: ssa.OpLsh32x32, + opAndTwoTypes{ir.OLSH, types.TUINT32, types.TUINT32}: ssa.OpLsh32x32, + opAndTwoTypes{ir.OLSH, types.TINT32, types.TUINT64}: ssa.OpLsh32x64, + opAndTwoTypes{ir.OLSH, types.TUINT32, types.TUINT64}: ssa.OpLsh32x64, + + opAndTwoTypes{ir.OLSH, types.TINT64, types.TUINT8}: ssa.OpLsh64x8, + opAndTwoTypes{ir.OLSH, types.TUINT64, types.TUINT8}: ssa.OpLsh64x8, + opAndTwoTypes{ir.OLSH, types.TINT64, types.TUINT16}: ssa.OpLsh64x16, + opAndTwoTypes{ir.OLSH, types.TUINT64, types.TUINT16}: ssa.OpLsh64x16, + opAndTwoTypes{ir.OLSH, types.TINT64, types.TUINT32}: ssa.OpLsh64x32, + opAndTwoTypes{ir.OLSH, types.TUINT64, types.TUINT32}: ssa.OpLsh64x32, + opAndTwoTypes{ir.OLSH, types.TINT64, types.TUINT64}: ssa.OpLsh64x64, + opAndTwoTypes{ir.OLSH, types.TUINT64, types.TUINT64}: ssa.OpLsh64x64, + + opAndTwoTypes{ir.ORSH, types.TINT8, types.TUINT8}: ssa.OpRsh8x8, + opAndTwoTypes{ir.ORSH, types.TUINT8, types.TUINT8}: ssa.OpRsh8Ux8, + opAndTwoTypes{ir.ORSH, types.TINT8, types.TUINT16}: ssa.OpRsh8x16, + opAndTwoTypes{ir.ORSH, types.TUINT8, types.TUINT16}: ssa.OpRsh8Ux16, + opAndTwoTypes{ir.ORSH, types.TINT8, types.TUINT32}: ssa.OpRsh8x32, + opAndTwoTypes{ir.ORSH, types.TUINT8, types.TUINT32}: ssa.OpRsh8Ux32, + opAndTwoTypes{ir.ORSH, types.TINT8, types.TUINT64}: ssa.OpRsh8x64, + opAndTwoTypes{ir.ORSH, types.TUINT8, types.TUINT64}: ssa.OpRsh8Ux64, + + opAndTwoTypes{ir.ORSH, types.TINT16, types.TUINT8}: ssa.OpRsh16x8, + opAndTwoTypes{ir.ORSH, types.TUINT16, types.TUINT8}: ssa.OpRsh16Ux8, + opAndTwoTypes{ir.ORSH, types.TINT16, types.TUINT16}: ssa.OpRsh16x16, + opAndTwoTypes{ir.ORSH, types.TUINT16, types.TUINT16}: ssa.OpRsh16Ux16, + opAndTwoTypes{ir.ORSH, types.TINT16, types.TUINT32}: ssa.OpRsh16x32, + opAndTwoTypes{ir.ORSH, types.TUINT16, types.TUINT32}: ssa.OpRsh16Ux32, + opAndTwoTypes{ir.ORSH, types.TINT16, types.TUINT64}: ssa.OpRsh16x64, + opAndTwoTypes{ir.ORSH, types.TUINT16, types.TUINT64}: ssa.OpRsh16Ux64, + + opAndTwoTypes{ir.ORSH, types.TINT32, types.TUINT8}: ssa.OpRsh32x8, + opAndTwoTypes{ir.ORSH, types.TUINT32, types.TUINT8}: ssa.OpRsh32Ux8, + opAndTwoTypes{ir.ORSH, types.TINT32, types.TUINT16}: ssa.OpRsh32x16, + opAndTwoTypes{ir.ORSH, types.TUINT32, types.TUINT16}: ssa.OpRsh32Ux16, + opAndTwoTypes{ir.ORSH, types.TINT32, types.TUINT32}: ssa.OpRsh32x32, + opAndTwoTypes{ir.ORSH, types.TUINT32, types.TUINT32}: ssa.OpRsh32Ux32, + opAndTwoTypes{ir.ORSH, types.TINT32, types.TUINT64}: ssa.OpRsh32x64, + opAndTwoTypes{ir.ORSH, types.TUINT32, types.TUINT64}: ssa.OpRsh32Ux64, + + opAndTwoTypes{ir.ORSH, types.TINT64, types.TUINT8}: ssa.OpRsh64x8, + opAndTwoTypes{ir.ORSH, types.TUINT64, types.TUINT8}: ssa.OpRsh64Ux8, + opAndTwoTypes{ir.ORSH, types.TINT64, types.TUINT16}: ssa.OpRsh64x16, + opAndTwoTypes{ir.ORSH, types.TUINT64, types.TUINT16}: ssa.OpRsh64Ux16, + opAndTwoTypes{ir.ORSH, types.TINT64, types.TUINT32}: ssa.OpRsh64x32, + opAndTwoTypes{ir.ORSH, types.TUINT64, types.TUINT32}: ssa.OpRsh64Ux32, + opAndTwoTypes{ir.ORSH, types.TINT64, types.TUINT64}: ssa.OpRsh64x64, + opAndTwoTypes{ir.ORSH, types.TUINT64, types.TUINT64}: ssa.OpRsh64Ux64, +} + +func (s *state) ssaShiftOp(op ir.Op, t *types.Type, u *types.Type) ssa.Op { etype1 := s.concreteEtype(t) etype2 := s.concreteEtype(u) x, ok := shiftOpToSSA[opAndTwoTypes{op, etype1, etype2}] @@ -2051,117 +2388,131 @@ func (s *state) ssaShiftOp(op Op, t *types.Type, u *types.Type) ssa.Op { } // expr converts the expression n to ssa, adds it to s and returns the ssa result. -func (s *state) expr(n *Node) *ssa.Value { - if !(n.Op == ONAME || n.Op == OLITERAL && n.Sym != nil) { +func (s *state) expr(n ir.Node) *ssa.Value { + if ir.HasUniquePos(n) { // ONAMEs and named OLITERALs have the line number // of the decl, not the use. See issue 14742. - s.pushLine(n.Pos) + s.pushLine(n.Pos()) defer s.popLine() } - s.stmtList(n.Ninit) - switch n.Op { - case OBYTES2STRTMP: - slice := s.expr(n.Left) + s.stmtList(n.Init()) + switch n.Op() { + case ir.OBYTES2STRTMP: + n := n.(*ir.ConvExpr) + slice := s.expr(n.X) ptr := s.newValue1(ssa.OpSlicePtr, s.f.Config.Types.BytePtr, slice) - len := s.newValue1(ssa.OpSliceLen, types.Types[TINT], slice) - return s.newValue2(ssa.OpStringMake, n.Type, ptr, len) - case OSTR2BYTESTMP: - str := s.expr(n.Left) + len := s.newValue1(ssa.OpSliceLen, types.Types[types.TINT], slice) + return s.newValue2(ssa.OpStringMake, n.Type(), ptr, len) + case ir.OSTR2BYTESTMP: + n := n.(*ir.ConvExpr) + str := s.expr(n.X) ptr := s.newValue1(ssa.OpStringPtr, s.f.Config.Types.BytePtr, str) - len := s.newValue1(ssa.OpStringLen, types.Types[TINT], str) - return s.newValue3(ssa.OpSliceMake, n.Type, ptr, len, len) - case OCFUNC: - aux := n.Left.Sym.Linksym() - return s.entryNewValue1A(ssa.OpAddr, n.Type, aux, s.sb) - case ONAME: - if n.Class() == PFUNC { + len := s.newValue1(ssa.OpStringLen, types.Types[types.TINT], str) + return s.newValue3(ssa.OpSliceMake, n.Type(), ptr, len, len) + case ir.OCFUNC: + n := n.(*ir.UnaryExpr) + aux := n.X.(*ir.Name).Linksym() + // OCFUNC is used to build function values, which must + // always reference ABIInternal entry points. + if aux.ABI() != obj.ABIInternal { + s.Fatalf("expected ABIInternal: %v", aux.ABI()) + } + return s.entryNewValue1A(ssa.OpAddr, n.Type(), aux, s.sb) + case ir.ONAME: + n := n.(*ir.Name) + if n.Class == ir.PFUNC { // "value" of a function is the address of the function's closure - sym := funcsym(n.Sym).Linksym() - return s.entryNewValue1A(ssa.OpAddr, types.NewPtr(n.Type), sym, s.sb) + sym := staticdata.FuncLinksym(n) + return s.entryNewValue1A(ssa.OpAddr, types.NewPtr(n.Type()), sym, s.sb) } if s.canSSA(n) { - return s.variable(n, n.Type) - } - addr := s.addr(n) - return s.load(n.Type, addr) - case OCLOSUREVAR: - addr := s.addr(n) - return s.load(n.Type, addr) - case OLITERAL: - switch u := n.Val().U.(type) { - case *Mpint: - i := u.Int64() - switch n.Type.Size() { + return s.variable(n, n.Type()) + } + return s.load(n.Type(), s.addr(n)) + case ir.OLINKSYMOFFSET: + n := n.(*ir.LinksymOffsetExpr) + return s.load(n.Type(), s.addr(n)) + case ir.ONIL: + n := n.(*ir.NilExpr) + t := n.Type() + switch { + case t.IsSlice(): + return s.constSlice(t) + case t.IsInterface(): + return s.constInterface(t) + default: + return s.constNil(t) + } + case ir.OLITERAL: + switch u := n.Val(); u.Kind() { + case constant.Int: + i := ir.IntVal(n.Type(), u) + switch n.Type().Size() { case 1: - return s.constInt8(n.Type, int8(i)) + return s.constInt8(n.Type(), int8(i)) case 2: - return s.constInt16(n.Type, int16(i)) + return s.constInt16(n.Type(), int16(i)) case 4: - return s.constInt32(n.Type, int32(i)) + return s.constInt32(n.Type(), int32(i)) case 8: - return s.constInt64(n.Type, i) + return s.constInt64(n.Type(), i) default: - s.Fatalf("bad integer size %d", n.Type.Size()) + s.Fatalf("bad integer size %d", n.Type().Size()) return nil } - case string: - if u == "" { - return s.constEmptyString(n.Type) + case constant.String: + i := constant.StringVal(u) + if i == "" { + return s.constEmptyString(n.Type()) } - return s.entryNewValue0A(ssa.OpConstString, n.Type, u) - case bool: - return s.constBool(u) - case *NilVal: - t := n.Type - switch { - case t.IsSlice(): - return s.constSlice(t) - case t.IsInterface(): - return s.constInterface(t) - default: - return s.constNil(t) - } - case *Mpflt: - switch n.Type.Size() { + return s.entryNewValue0A(ssa.OpConstString, n.Type(), ssa.StringToAux(i)) + case constant.Bool: + return s.constBool(constant.BoolVal(u)) + case constant.Float: + f, _ := constant.Float64Val(u) + switch n.Type().Size() { case 4: - return s.constFloat32(n.Type, u.Float32()) + return s.constFloat32(n.Type(), f) case 8: - return s.constFloat64(n.Type, u.Float64()) + return s.constFloat64(n.Type(), f) default: - s.Fatalf("bad float size %d", n.Type.Size()) + s.Fatalf("bad float size %d", n.Type().Size()) return nil } - case *Mpcplx: - r := &u.Real - i := &u.Imag - switch n.Type.Size() { + case constant.Complex: + re, _ := constant.Float64Val(constant.Real(u)) + im, _ := constant.Float64Val(constant.Imag(u)) + switch n.Type().Size() { case 8: - pt := types.Types[TFLOAT32] - return s.newValue2(ssa.OpComplexMake, n.Type, - s.constFloat32(pt, r.Float32()), - s.constFloat32(pt, i.Float32())) + pt := types.Types[types.TFLOAT32] + return s.newValue2(ssa.OpComplexMake, n.Type(), + s.constFloat32(pt, re), + s.constFloat32(pt, im)) case 16: - pt := types.Types[TFLOAT64] - return s.newValue2(ssa.OpComplexMake, n.Type, - s.constFloat64(pt, r.Float64()), - s.constFloat64(pt, i.Float64())) + pt := types.Types[types.TFLOAT64] + return s.newValue2(ssa.OpComplexMake, n.Type(), + s.constFloat64(pt, re), + s.constFloat64(pt, im)) default: - s.Fatalf("bad float size %d", n.Type.Size()) + s.Fatalf("bad complex size %d", n.Type().Size()) return nil } - default: - s.Fatalf("unhandled OLITERAL %v", n.Val().Ctype()) + s.Fatalf("unhandled OLITERAL %v", u.Kind()) return nil } - case OCONVNOP: - to := n.Type - from := n.Left.Type + case ir.OCONVNOP: + n := n.(*ir.ConvExpr) + to := n.Type() + from := n.X.Type() // Assume everything will work out, so set up our return value. // Anything interesting that happens from here is a fatal. - x := s.expr(n.Left) + x := s.expr(n.X) + if to == from { + return x + } // Special case for not confusing GC and liveness. // We don't want pointers accidentally classified @@ -2174,12 +2525,12 @@ func (s *state) expr(n *Node) *ssa.Value { v := s.newValue1(ssa.OpCopy, to, x) // ensure that v has the right type // CONVNOP closure - if to.Etype == TFUNC && from.IsPtrShaped() { + if to.Kind() == types.TFUNC && from.IsPtrShaped() { return v } // named <--> unnamed type or typed <--> untyped const - if from.Etype == to.Etype { + if from.Kind() == to.Kind() { return v } @@ -2189,30 +2540,30 @@ func (s *state) expr(n *Node) *ssa.Value { } // map <--> *hmap - if to.Etype == TMAP && from.IsPtr() && + if to.Kind() == types.TMAP && from.IsPtr() && to.MapType().Hmap == from.Elem() { return v } - dowidth(from) - dowidth(to) + types.CalcSize(from) + types.CalcSize(to) if from.Width != to.Width { s.Fatalf("CONVNOP width mismatch %v (%d) -> %v (%d)\n", from, from.Width, to, to.Width) return nil } - if etypesign(from.Etype) != etypesign(to.Etype) { - s.Fatalf("CONVNOP sign mismatch %v (%s) -> %v (%s)\n", from, from.Etype, to, to.Etype) + if etypesign(from.Kind()) != etypesign(to.Kind()) { + s.Fatalf("CONVNOP sign mismatch %v (%s) -> %v (%s)\n", from, from.Kind(), to, to.Kind()) return nil } - if instrumenting { + if base.Flag.Cfg.Instrumenting { // These appear to be fine, but they fail the // integer constraint below, so okay them here. // Sample non-integer conversion: map[string]string -> *uint8 return v } - if etypesign(from.Etype) == 0 { + if etypesign(from.Kind()) == 0 { s.Fatalf("CONVNOP unrecognized non-integer %v -> %v\n", from, to) return nil } @@ -2220,13 +2571,14 @@ func (s *state) expr(n *Node) *ssa.Value { // integer, same width, same sign return v - case OCONV: - x := s.expr(n.Left) - ft := n.Left.Type // from type - tt := n.Type // to type - if ft.IsBoolean() && tt.IsKind(TUINT8) { + case ir.OCONV: + n := n.(*ir.ConvExpr) + x := s.expr(n.X) + ft := n.X.Type() // from type + tt := n.Type() // to type + if ft.IsBoolean() && tt.IsKind(types.TUINT8) { // Bool -> uint8 is generated internally when indexing into runtime.staticbyte. - return s.newValue1(ssa.OpCopy, n.Type, x) + return s.newValue1(ssa.OpCopy, n.Type(), x) } if ft.IsInteger() && tt.IsInteger() { var op ssa.Op @@ -2287,23 +2639,23 @@ func (s *state) expr(n *Node) *ssa.Value { s.Fatalf("weird integer sign extension %v -> %v", ft, tt) } } - return s.newValue1(op, n.Type, x) + return s.newValue1(op, n.Type(), x) } if ft.IsFloat() || tt.IsFloat() { conv, ok := fpConvOpToSSA[twoTypes{s.concreteEtype(ft), s.concreteEtype(tt)}] - if s.config.RegSize == 4 && thearch.LinkArch.Family != sys.MIPS && !s.softFloat { + if s.config.RegSize == 4 && Arch.LinkArch.Family != sys.MIPS && !s.softFloat { if conv1, ok1 := fpConvOpToSSA32[twoTypes{s.concreteEtype(ft), s.concreteEtype(tt)}]; ok1 { conv = conv1 } } - if thearch.LinkArch.Family == sys.ARM64 || thearch.LinkArch.Family == sys.Wasm || thearch.LinkArch.Family == sys.S390X || s.softFloat { + if Arch.LinkArch.Family == sys.ARM64 || Arch.LinkArch.Family == sys.Wasm || Arch.LinkArch.Family == sys.S390X || s.softFloat { if conv1, ok1 := uint64fpConvOpToSSA[twoTypes{s.concreteEtype(ft), s.concreteEtype(tt)}]; ok1 { conv = conv1 } } - if thearch.LinkArch.Family == sys.MIPS && !s.softFloat { + if Arch.LinkArch.Family == sys.MIPS && !s.softFloat { if ft.Size() == 4 && ft.IsInteger() && !ft.IsSigned() { // tt is float32 or float64, and ft is also unsigned if tt.Size() == 4 { @@ -2334,12 +2686,12 @@ func (s *state) expr(n *Node) *ssa.Value { if op2 == ssa.OpCopy { return x } - return s.newValueOrSfCall1(op2, n.Type, x) + return s.newValueOrSfCall1(op2, n.Type(), x) } if op2 == ssa.OpCopy { - return s.newValueOrSfCall1(op1, n.Type, x) + return s.newValueOrSfCall1(op1, n.Type(), x) } - return s.newValueOrSfCall1(op2, n.Type, s.newValueOrSfCall1(op1, types.Types[it], x)) + return s.newValueOrSfCall1(op2, n.Type(), s.newValueOrSfCall1(op1, types.Types[it], x)) } // Tricky 64-bit unsigned cases. if ft.IsInteger() { @@ -2381,63 +2733,66 @@ func (s *state) expr(n *Node) *ssa.Value { } else { s.Fatalf("weird complex conversion %v -> %v", ft, tt) } - ftp := floatForComplex(ft) - ttp := floatForComplex(tt) + ftp := types.FloatForComplex(ft) + ttp := types.FloatForComplex(tt) return s.newValue2(ssa.OpComplexMake, tt, s.newValueOrSfCall1(op, ttp, s.newValue1(ssa.OpComplexReal, ftp, x)), s.newValueOrSfCall1(op, ttp, s.newValue1(ssa.OpComplexImag, ftp, x))) } - s.Fatalf("unhandled OCONV %s -> %s", n.Left.Type.Etype, n.Type.Etype) + s.Fatalf("unhandled OCONV %s -> %s", n.X.Type().Kind(), n.Type().Kind()) return nil - case ODOTTYPE: + case ir.ODOTTYPE: + n := n.(*ir.TypeAssertExpr) res, _ := s.dottype(n, false) return res // binary ops - case OLT, OEQ, ONE, OLE, OGE, OGT: - a := s.expr(n.Left) - b := s.expr(n.Right) - if n.Left.Type.IsComplex() { - pt := floatForComplex(n.Left.Type) - op := s.ssaOp(OEQ, pt) - r := s.newValueOrSfCall2(op, types.Types[TBOOL], s.newValue1(ssa.OpComplexReal, pt, a), s.newValue1(ssa.OpComplexReal, pt, b)) - i := s.newValueOrSfCall2(op, types.Types[TBOOL], s.newValue1(ssa.OpComplexImag, pt, a), s.newValue1(ssa.OpComplexImag, pt, b)) - c := s.newValue2(ssa.OpAndB, types.Types[TBOOL], r, i) - switch n.Op { - case OEQ: + case ir.OLT, ir.OEQ, ir.ONE, ir.OLE, ir.OGE, ir.OGT: + n := n.(*ir.BinaryExpr) + a := s.expr(n.X) + b := s.expr(n.Y) + if n.X.Type().IsComplex() { + pt := types.FloatForComplex(n.X.Type()) + op := s.ssaOp(ir.OEQ, pt) + r := s.newValueOrSfCall2(op, types.Types[types.TBOOL], s.newValue1(ssa.OpComplexReal, pt, a), s.newValue1(ssa.OpComplexReal, pt, b)) + i := s.newValueOrSfCall2(op, types.Types[types.TBOOL], s.newValue1(ssa.OpComplexImag, pt, a), s.newValue1(ssa.OpComplexImag, pt, b)) + c := s.newValue2(ssa.OpAndB, types.Types[types.TBOOL], r, i) + switch n.Op() { + case ir.OEQ: return c - case ONE: - return s.newValue1(ssa.OpNot, types.Types[TBOOL], c) + case ir.ONE: + return s.newValue1(ssa.OpNot, types.Types[types.TBOOL], c) default: - s.Fatalf("ordered complex compare %v", n.Op) + s.Fatalf("ordered complex compare %v", n.Op()) } } // Convert OGE and OGT into OLE and OLT. - op := n.Op + op := n.Op() switch op { - case OGE: - op, a, b = OLE, b, a - case OGT: - op, a, b = OLT, b, a + case ir.OGE: + op, a, b = ir.OLE, b, a + case ir.OGT: + op, a, b = ir.OLT, b, a } - if n.Left.Type.IsFloat() { + if n.X.Type().IsFloat() { // float comparison - return s.newValueOrSfCall2(s.ssaOp(op, n.Left.Type), types.Types[TBOOL], a, b) + return s.newValueOrSfCall2(s.ssaOp(op, n.X.Type()), types.Types[types.TBOOL], a, b) } // integer comparison - return s.newValue2(s.ssaOp(op, n.Left.Type), types.Types[TBOOL], a, b) - case OMUL: - a := s.expr(n.Left) - b := s.expr(n.Right) - if n.Type.IsComplex() { + return s.newValue2(s.ssaOp(op, n.X.Type()), types.Types[types.TBOOL], a, b) + case ir.OMUL: + n := n.(*ir.BinaryExpr) + a := s.expr(n.X) + b := s.expr(n.Y) + if n.Type().IsComplex() { mulop := ssa.OpMul64F addop := ssa.OpAdd64F subop := ssa.OpSub64F - pt := floatForComplex(n.Type) // Could be Float32 or Float64 - wt := types.Types[TFLOAT64] // Compute in Float64 to minimize cancellation error + pt := types.FloatForComplex(n.Type()) // Could be Float32 or Float64 + wt := types.Types[types.TFLOAT64] // Compute in Float64 to minimize cancellation error areal := s.newValue1(ssa.OpComplexReal, pt, a) breal := s.newValue1(ssa.OpComplexReal, pt, b) @@ -2459,19 +2814,20 @@ func (s *state) expr(n *Node) *ssa.Value { ximag = s.newValueOrSfCall1(ssa.OpCvt64Fto32F, pt, ximag) } - return s.newValue2(ssa.OpComplexMake, n.Type, xreal, ximag) + return s.newValue2(ssa.OpComplexMake, n.Type(), xreal, ximag) } - if n.Type.IsFloat() { - return s.newValueOrSfCall2(s.ssaOp(n.Op, n.Type), a.Type, a, b) + if n.Type().IsFloat() { + return s.newValueOrSfCall2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b) } - return s.newValue2(s.ssaOp(n.Op, n.Type), a.Type, a, b) + return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b) - case ODIV: - a := s.expr(n.Left) - b := s.expr(n.Right) - if n.Type.IsComplex() { + case ir.ODIV: + n := n.(*ir.BinaryExpr) + a := s.expr(n.X) + b := s.expr(n.Y) + if n.Type().IsComplex() { // TODO this is not executed because the front-end substitutes a runtime call. // That probably ought to change; with modest optimization the widen/narrow // conversions could all be elided in larger expression trees. @@ -2479,8 +2835,8 @@ func (s *state) expr(n *Node) *ssa.Value { addop := ssa.OpAdd64F subop := ssa.OpSub64F divop := ssa.OpDiv64F - pt := floatForComplex(n.Type) // Could be Float32 or Float64 - wt := types.Types[TFLOAT64] // Compute in Float64 to minimize cancellation error + pt := types.FloatForComplex(n.Type()) // Could be Float32 or Float64 + wt := types.Types[types.TFLOAT64] // Compute in Float64 to minimize cancellation error areal := s.newValue1(ssa.OpComplexReal, pt, a) breal := s.newValue1(ssa.OpComplexReal, pt, b) @@ -2509,50 +2865,55 @@ func (s *state) expr(n *Node) *ssa.Value { xreal = s.newValueOrSfCall1(ssa.OpCvt64Fto32F, pt, xreal) ximag = s.newValueOrSfCall1(ssa.OpCvt64Fto32F, pt, ximag) } - return s.newValue2(ssa.OpComplexMake, n.Type, xreal, ximag) + return s.newValue2(ssa.OpComplexMake, n.Type(), xreal, ximag) } - if n.Type.IsFloat() { - return s.newValueOrSfCall2(s.ssaOp(n.Op, n.Type), a.Type, a, b) + if n.Type().IsFloat() { + return s.newValueOrSfCall2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b) } return s.intDivide(n, a, b) - case OMOD: - a := s.expr(n.Left) - b := s.expr(n.Right) + case ir.OMOD: + n := n.(*ir.BinaryExpr) + a := s.expr(n.X) + b := s.expr(n.Y) return s.intDivide(n, a, b) - case OADD, OSUB: - a := s.expr(n.Left) - b := s.expr(n.Right) - if n.Type.IsComplex() { - pt := floatForComplex(n.Type) - op := s.ssaOp(n.Op, pt) - return s.newValue2(ssa.OpComplexMake, n.Type, + case ir.OADD, ir.OSUB: + n := n.(*ir.BinaryExpr) + a := s.expr(n.X) + b := s.expr(n.Y) + if n.Type().IsComplex() { + pt := types.FloatForComplex(n.Type()) + op := s.ssaOp(n.Op(), pt) + return s.newValue2(ssa.OpComplexMake, n.Type(), s.newValueOrSfCall2(op, pt, s.newValue1(ssa.OpComplexReal, pt, a), s.newValue1(ssa.OpComplexReal, pt, b)), s.newValueOrSfCall2(op, pt, s.newValue1(ssa.OpComplexImag, pt, a), s.newValue1(ssa.OpComplexImag, pt, b))) } - if n.Type.IsFloat() { - return s.newValueOrSfCall2(s.ssaOp(n.Op, n.Type), a.Type, a, b) - } - return s.newValue2(s.ssaOp(n.Op, n.Type), a.Type, a, b) - case OAND, OOR, OXOR: - a := s.expr(n.Left) - b := s.expr(n.Right) - return s.newValue2(s.ssaOp(n.Op, n.Type), a.Type, a, b) - case OANDNOT: - a := s.expr(n.Left) - b := s.expr(n.Right) - b = s.newValue1(s.ssaOp(OBITNOT, b.Type), b.Type, b) - return s.newValue2(s.ssaOp(OAND, n.Type), a.Type, a, b) - case OLSH, ORSH: - a := s.expr(n.Left) - b := s.expr(n.Right) + if n.Type().IsFloat() { + return s.newValueOrSfCall2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b) + } + return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b) + case ir.OAND, ir.OOR, ir.OXOR: + n := n.(*ir.BinaryExpr) + a := s.expr(n.X) + b := s.expr(n.Y) + return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b) + case ir.OANDNOT: + n := n.(*ir.BinaryExpr) + a := s.expr(n.X) + b := s.expr(n.Y) + b = s.newValue1(s.ssaOp(ir.OBITNOT, b.Type), b.Type, b) + return s.newValue2(s.ssaOp(ir.OAND, n.Type()), a.Type, a, b) + case ir.OLSH, ir.ORSH: + n := n.(*ir.BinaryExpr) + a := s.expr(n.X) + b := s.expr(n.Y) bt := b.Type if bt.IsSigned() { - cmp := s.newValue2(s.ssaOp(OLE, bt), types.Types[TBOOL], s.zeroVal(bt), b) - s.check(cmp, panicshift) + cmp := s.newValue2(s.ssaOp(ir.OLE, bt), types.Types[types.TBOOL], s.zeroVal(bt), b) + s.check(cmp, ir.Syms.Panicshift) bt = bt.ToUnsigned() } - return s.newValue2(s.ssaShiftOp(n.Op, n.Type, bt), a.Type, a, b) - case OANDAND, OOROR: + return s.newValue2(s.ssaShiftOp(n.Op(), n.Type(), bt), a.Type, a, b) + case ir.OANDAND, ir.OOROR: // To implement OANDAND (and OOROR), we introduce a // new temporary variable to hold the result. The // variable is associated with the OANDAND node in the @@ -2566,7 +2927,8 @@ func (s *state) expr(n *Node) *ssa.Value { // } // Using var in the subsequent block introduces the // necessary phi variable. - el := s.expr(n.Left) + n := n.(*ir.LogicalExpr) + el := s.expr(n.X) s.vars[n] = el b := s.endBlock() @@ -2579,266 +2941,316 @@ func (s *state) expr(n *Node) *ssa.Value { bRight := s.f.NewBlock(ssa.BlockPlain) bResult := s.f.NewBlock(ssa.BlockPlain) - if n.Op == OANDAND { + if n.Op() == ir.OANDAND { b.AddEdgeTo(bRight) b.AddEdgeTo(bResult) - } else if n.Op == OOROR { + } else if n.Op() == ir.OOROR { b.AddEdgeTo(bResult) b.AddEdgeTo(bRight) } s.startBlock(bRight) - er := s.expr(n.Right) + er := s.expr(n.Y) s.vars[n] = er b = s.endBlock() b.AddEdgeTo(bResult) s.startBlock(bResult) - return s.variable(n, types.Types[TBOOL]) - case OCOMPLEX: - r := s.expr(n.Left) - i := s.expr(n.Right) - return s.newValue2(ssa.OpComplexMake, n.Type, r, i) + return s.variable(n, types.Types[types.TBOOL]) + case ir.OCOMPLEX: + n := n.(*ir.BinaryExpr) + r := s.expr(n.X) + i := s.expr(n.Y) + return s.newValue2(ssa.OpComplexMake, n.Type(), r, i) // unary ops - case ONEG: - a := s.expr(n.Left) - if n.Type.IsComplex() { - tp := floatForComplex(n.Type) - negop := s.ssaOp(n.Op, tp) - return s.newValue2(ssa.OpComplexMake, n.Type, + case ir.ONEG: + n := n.(*ir.UnaryExpr) + a := s.expr(n.X) + if n.Type().IsComplex() { + tp := types.FloatForComplex(n.Type()) + negop := s.ssaOp(n.Op(), tp) + return s.newValue2(ssa.OpComplexMake, n.Type(), s.newValue1(negop, tp, s.newValue1(ssa.OpComplexReal, tp, a)), s.newValue1(negop, tp, s.newValue1(ssa.OpComplexImag, tp, a))) } - return s.newValue1(s.ssaOp(n.Op, n.Type), a.Type, a) - case ONOT, OBITNOT: - a := s.expr(n.Left) - return s.newValue1(s.ssaOp(n.Op, n.Type), a.Type, a) - case OIMAG, OREAL: - a := s.expr(n.Left) - return s.newValue1(s.ssaOp(n.Op, n.Left.Type), n.Type, a) - case OPLUS: - return s.expr(n.Left) - - case OADDR: - return s.addr(n.Left) - - case ORESULT: + return s.newValue1(s.ssaOp(n.Op(), n.Type()), a.Type, a) + case ir.ONOT, ir.OBITNOT: + n := n.(*ir.UnaryExpr) + a := s.expr(n.X) + return s.newValue1(s.ssaOp(n.Op(), n.Type()), a.Type, a) + case ir.OIMAG, ir.OREAL: + n := n.(*ir.UnaryExpr) + a := s.expr(n.X) + return s.newValue1(s.ssaOp(n.Op(), n.X.Type()), n.Type(), a) + case ir.OPLUS: + n := n.(*ir.UnaryExpr) + return s.expr(n.X) + + case ir.OADDR: + n := n.(*ir.AddrExpr) + return s.addr(n.X) + + case ir.ORESULT: + n := n.(*ir.ResultExpr) if s.prevCall == nil || s.prevCall.Op != ssa.OpStaticLECall && s.prevCall.Op != ssa.OpInterLECall && s.prevCall.Op != ssa.OpClosureLECall { - // Do the old thing - addr := s.constOffPtrSP(types.NewPtr(n.Type), n.Xoffset) - return s.rawLoad(n.Type, addr) + panic("Expected to see a previous call") } - which := s.prevCall.Aux.(*ssa.AuxCall).ResultForOffset(n.Xoffset) + which := n.Index if which == -1 { - // Do the old thing // TODO: Panic instead. - addr := s.constOffPtrSP(types.NewPtr(n.Type), n.Xoffset) - return s.rawLoad(n.Type, addr) - } - if canSSAType(n.Type) { - return s.newValue1I(ssa.OpSelectN, n.Type, which, s.prevCall) - } else { - addr := s.newValue1I(ssa.OpSelectNAddr, types.NewPtr(n.Type), which, s.prevCall) - return s.rawLoad(n.Type, addr) + panic(fmt.Errorf("ORESULT %v does not match call %s", n, s.prevCall)) } + return s.resultOfCall(s.prevCall, which, n.Type()) - case ODEREF: - p := s.exprPtr(n.Left, n.Bounded(), n.Pos) - return s.load(n.Type, p) + case ir.ODEREF: + n := n.(*ir.StarExpr) + p := s.exprPtr(n.X, n.Bounded(), n.Pos()) + return s.load(n.Type(), p) - case ODOT: - if n.Left.Op == OSTRUCTLIT { + case ir.ODOT: + n := n.(*ir.SelectorExpr) + if n.X.Op() == ir.OSTRUCTLIT { // All literals with nonzero fields have already been // rewritten during walk. Any that remain are just T{} // or equivalents. Use the zero value. - if !isZero(n.Left) { - s.Fatalf("literal with nonzero value in SSA: %v", n.Left) + if !ir.IsZero(n.X) { + s.Fatalf("literal with nonzero value in SSA: %v", n.X) } - return s.zeroVal(n.Type) + return s.zeroVal(n.Type()) } // If n is addressable and can't be represented in // SSA, then load just the selected field. This // prevents false memory dependencies in race/msan // instrumentation. - if islvalue(n) && !s.canSSA(n) { + if ir.IsAddressable(n) && !s.canSSA(n) { p := s.addr(n) - return s.load(n.Type, p) + return s.load(n.Type(), p) } - v := s.expr(n.Left) - return s.newValue1I(ssa.OpStructSelect, n.Type, int64(fieldIdx(n)), v) + v := s.expr(n.X) + return s.newValue1I(ssa.OpStructSelect, n.Type(), int64(fieldIdx(n)), v) - case ODOTPTR: - p := s.exprPtr(n.Left, n.Bounded(), n.Pos) - p = s.newValue1I(ssa.OpOffPtr, types.NewPtr(n.Type), n.Xoffset, p) - return s.load(n.Type, p) + case ir.ODOTPTR: + n := n.(*ir.SelectorExpr) + p := s.exprPtr(n.X, n.Bounded(), n.Pos()) + p = s.newValue1I(ssa.OpOffPtr, types.NewPtr(n.Type()), n.Offset(), p) + return s.load(n.Type(), p) - case OINDEX: + case ir.OINDEX: + n := n.(*ir.IndexExpr) switch { - case n.Left.Type.IsString(): - if n.Bounded() && Isconst(n.Left, CTSTR) && Isconst(n.Right, CTINT) { + case n.X.Type().IsString(): + if n.Bounded() && ir.IsConst(n.X, constant.String) && ir.IsConst(n.Index, constant.Int) { // Replace "abc"[1] with 'b'. // Delayed until now because "abc"[1] is not an ideal constant. // See test/fixedbugs/issue11370.go. - return s.newValue0I(ssa.OpConst8, types.Types[TUINT8], int64(int8(n.Left.StringVal()[n.Right.Int64Val()]))) + return s.newValue0I(ssa.OpConst8, types.Types[types.TUINT8], int64(int8(ir.StringVal(n.X)[ir.Int64Val(n.Index)]))) } - a := s.expr(n.Left) - i := s.expr(n.Right) - len := s.newValue1(ssa.OpStringLen, types.Types[TINT], a) + a := s.expr(n.X) + i := s.expr(n.Index) + len := s.newValue1(ssa.OpStringLen, types.Types[types.TINT], a) i = s.boundsCheck(i, len, ssa.BoundsIndex, n.Bounded()) ptrtyp := s.f.Config.Types.BytePtr ptr := s.newValue1(ssa.OpStringPtr, ptrtyp, a) - if Isconst(n.Right, CTINT) { - ptr = s.newValue1I(ssa.OpOffPtr, ptrtyp, n.Right.Int64Val(), ptr) + if ir.IsConst(n.Index, constant.Int) { + ptr = s.newValue1I(ssa.OpOffPtr, ptrtyp, ir.Int64Val(n.Index), ptr) } else { ptr = s.newValue2(ssa.OpAddPtr, ptrtyp, ptr, i) } - return s.load(types.Types[TUINT8], ptr) - case n.Left.Type.IsSlice(): + return s.load(types.Types[types.TUINT8], ptr) + case n.X.Type().IsSlice(): p := s.addr(n) - return s.load(n.Left.Type.Elem(), p) - case n.Left.Type.IsArray(): - if canSSAType(n.Left.Type) { + return s.load(n.X.Type().Elem(), p) + case n.X.Type().IsArray(): + if TypeOK(n.X.Type()) { // SSA can handle arrays of length at most 1. - bound := n.Left.Type.NumElem() - a := s.expr(n.Left) - i := s.expr(n.Right) + bound := n.X.Type().NumElem() + a := s.expr(n.X) + i := s.expr(n.Index) if bound == 0 { // Bounds check will never succeed. Might as well // use constants for the bounds check. - z := s.constInt(types.Types[TINT], 0) + z := s.constInt(types.Types[types.TINT], 0) s.boundsCheck(z, z, ssa.BoundsIndex, false) // The return value won't be live, return junk. - return s.newValue0(ssa.OpUnknown, n.Type) + return s.newValue0(ssa.OpUnknown, n.Type()) } - len := s.constInt(types.Types[TINT], bound) + len := s.constInt(types.Types[types.TINT], bound) s.boundsCheck(i, len, ssa.BoundsIndex, n.Bounded()) // checks i == 0 - return s.newValue1I(ssa.OpArraySelect, n.Type, 0, a) + return s.newValue1I(ssa.OpArraySelect, n.Type(), 0, a) } p := s.addr(n) - return s.load(n.Left.Type.Elem(), p) + return s.load(n.X.Type().Elem(), p) default: - s.Fatalf("bad type for index %v", n.Left.Type) + s.Fatalf("bad type for index %v", n.X.Type()) return nil } - case OLEN, OCAP: + case ir.OLEN, ir.OCAP: + n := n.(*ir.UnaryExpr) switch { - case n.Left.Type.IsSlice(): + case n.X.Type().IsSlice(): op := ssa.OpSliceLen - if n.Op == OCAP { + if n.Op() == ir.OCAP { op = ssa.OpSliceCap } - return s.newValue1(op, types.Types[TINT], s.expr(n.Left)) - case n.Left.Type.IsString(): // string; not reachable for OCAP - return s.newValue1(ssa.OpStringLen, types.Types[TINT], s.expr(n.Left)) - case n.Left.Type.IsMap(), n.Left.Type.IsChan(): - return s.referenceTypeBuiltin(n, s.expr(n.Left)) + return s.newValue1(op, types.Types[types.TINT], s.expr(n.X)) + case n.X.Type().IsString(): // string; not reachable for OCAP + return s.newValue1(ssa.OpStringLen, types.Types[types.TINT], s.expr(n.X)) + case n.X.Type().IsMap(), n.X.Type().IsChan(): + return s.referenceTypeBuiltin(n, s.expr(n.X)) default: // array - return s.constInt(types.Types[TINT], n.Left.Type.NumElem()) + return s.constInt(types.Types[types.TINT], n.X.Type().NumElem()) } - case OSPTR: - a := s.expr(n.Left) - if n.Left.Type.IsSlice() { - return s.newValue1(ssa.OpSlicePtr, n.Type, a) + case ir.OSPTR: + n := n.(*ir.UnaryExpr) + a := s.expr(n.X) + if n.X.Type().IsSlice() { + return s.newValue1(ssa.OpSlicePtr, n.Type(), a) } else { - return s.newValue1(ssa.OpStringPtr, n.Type, a) - } - - case OITAB: - a := s.expr(n.Left) - return s.newValue1(ssa.OpITab, n.Type, a) - - case OIDATA: - a := s.expr(n.Left) - return s.newValue1(ssa.OpIData, n.Type, a) - - case OEFACE: - tab := s.expr(n.Left) - data := s.expr(n.Right) - return s.newValue2(ssa.OpIMake, n.Type, tab, data) - - case OSLICEHEADER: - p := s.expr(n.Left) - l := s.expr(n.List.First()) - c := s.expr(n.List.Second()) - return s.newValue3(ssa.OpSliceMake, n.Type, p, l, c) - - case OSLICE, OSLICEARR, OSLICE3, OSLICE3ARR: - v := s.expr(n.Left) + return s.newValue1(ssa.OpStringPtr, n.Type(), a) + } + + case ir.OITAB: + n := n.(*ir.UnaryExpr) + a := s.expr(n.X) + return s.newValue1(ssa.OpITab, n.Type(), a) + + case ir.OIDATA: + n := n.(*ir.UnaryExpr) + a := s.expr(n.X) + return s.newValue1(ssa.OpIData, n.Type(), a) + + case ir.OEFACE: + n := n.(*ir.BinaryExpr) + tab := s.expr(n.X) + data := s.expr(n.Y) + return s.newValue2(ssa.OpIMake, n.Type(), tab, data) + + case ir.OSLICEHEADER: + n := n.(*ir.SliceHeaderExpr) + p := s.expr(n.Ptr) + l := s.expr(n.Len) + c := s.expr(n.Cap) + return s.newValue3(ssa.OpSliceMake, n.Type(), p, l, c) + + case ir.OSLICE, ir.OSLICEARR, ir.OSLICE3, ir.OSLICE3ARR: + n := n.(*ir.SliceExpr) + v := s.expr(n.X) var i, j, k *ssa.Value - low, high, max := n.SliceBounds() - if low != nil { - i = s.expr(low) + if n.Low != nil { + i = s.expr(n.Low) } - if high != nil { - j = s.expr(high) + if n.High != nil { + j = s.expr(n.High) } - if max != nil { - k = s.expr(max) + if n.Max != nil { + k = s.expr(n.Max) } p, l, c := s.slice(v, i, j, k, n.Bounded()) - return s.newValue3(ssa.OpSliceMake, n.Type, p, l, c) + return s.newValue3(ssa.OpSliceMake, n.Type(), p, l, c) - case OSLICESTR: - v := s.expr(n.Left) + case ir.OSLICESTR: + n := n.(*ir.SliceExpr) + v := s.expr(n.X) var i, j *ssa.Value - low, high, _ := n.SliceBounds() - if low != nil { - i = s.expr(low) + if n.Low != nil { + i = s.expr(n.Low) } - if high != nil { - j = s.expr(high) + if n.High != nil { + j = s.expr(n.High) } p, l, _ := s.slice(v, i, j, nil, n.Bounded()) - return s.newValue2(ssa.OpStringMake, n.Type, p, l) - - case OCALLFUNC: - if isIntrinsicCall(n) { + return s.newValue2(ssa.OpStringMake, n.Type(), p, l) + + case ir.OSLICE2ARRPTR: + // if arrlen > slice.len { + // panic(...) + // } + // slice.ptr + n := n.(*ir.ConvExpr) + v := s.expr(n.X) + arrlen := s.constInt(types.Types[types.TINT], n.Type().Elem().NumElem()) + cap := s.newValue1(ssa.OpSliceLen, types.Types[types.TINT], v) + s.boundsCheck(arrlen, cap, ssa.BoundsConvert, false) + return s.newValue1(ssa.OpSlicePtrUnchecked, n.Type(), v) + + case ir.OCALLFUNC: + n := n.(*ir.CallExpr) + if ir.IsIntrinsicCall(n) { return s.intrinsicCall(n) } fallthrough - case OCALLINTER, OCALLMETH: + case ir.OCALLINTER, ir.OCALLMETH: + n := n.(*ir.CallExpr) return s.callResult(n, callNormal) - case OGETG: - return s.newValue1(ssa.OpGetG, n.Type, s.mem()) + case ir.OGETG: + n := n.(*ir.CallExpr) + return s.newValue1(ssa.OpGetG, n.Type(), s.mem()) - case OAPPEND: - return s.append(n, false) + case ir.OAPPEND: + return s.append(n.(*ir.CallExpr), false) - case OSTRUCTLIT, OARRAYLIT: + case ir.OSTRUCTLIT, ir.OARRAYLIT: // All literals with nonzero fields have already been // rewritten during walk. Any that remain are just T{} // or equivalents. Use the zero value. - if !isZero(n) { + n := n.(*ir.CompLitExpr) + if !ir.IsZero(n) { s.Fatalf("literal with nonzero value in SSA: %v", n) } - return s.zeroVal(n.Type) + return s.zeroVal(n.Type()) - case ONEWOBJ: - if n.Type.Elem().Size() == 0 { - return s.newValue1A(ssa.OpAddr, n.Type, zerobaseSym, s.sb) - } - typ := s.expr(n.Left) - vv := s.rtcall(newobject, true, []*types.Type{n.Type}, typ) - return vv[0] + case ir.ONEW: + n := n.(*ir.UnaryExpr) + return s.newObject(n.Type().Elem()) + + case ir.OUNSAFEADD: + n := n.(*ir.BinaryExpr) + ptr := s.expr(n.X) + len := s.expr(n.Y) + return s.newValue2(ssa.OpAddPtr, n.Type(), ptr, len) default: - s.Fatalf("unhandled expr %v", n.Op) + s.Fatalf("unhandled expr %v", n.Op()) return nil } } +func (s *state) resultOfCall(c *ssa.Value, which int64, t *types.Type) *ssa.Value { + aux := c.Aux.(*ssa.AuxCall) + pa := aux.ParamAssignmentForResult(which) + // TODO(register args) determine if in-memory TypeOK is better loaded early from SelectNAddr or later when SelectN is expanded. + // SelectN is better for pattern-matching and possible call-aware analysis we might want to do in the future. + if len(pa.Registers) == 0 && !TypeOK(t) { + addr := s.newValue1I(ssa.OpSelectNAddr, types.NewPtr(t), which, c) + return s.rawLoad(t, addr) + } + return s.newValue1I(ssa.OpSelectN, t, which, c) +} + +func (s *state) resultAddrOfCall(c *ssa.Value, which int64, t *types.Type) *ssa.Value { + aux := c.Aux.(*ssa.AuxCall) + pa := aux.ParamAssignmentForResult(which) + if len(pa.Registers) == 0 { + return s.newValue1I(ssa.OpSelectNAddr, types.NewPtr(t), which, c) + } + _, addr := s.temp(c.Pos, t) + rval := s.newValue1I(ssa.OpSelectN, t, which, c) + s.vars[memVar] = s.newValue3Apos(ssa.OpStore, types.TypeMem, t, addr, rval, s.mem(), false) + return addr +} + // append converts an OAPPEND node to SSA. // If inplace is false, it converts the OAPPEND expression n to an ssa.Value, // adds it to s, and returns the Value. // If inplace is true, it writes the result of the OAPPEND expression n // back to the slice being appended to, and returns nil. // inplace MUST be set to false if the slice can be SSA'd. -func (s *state) append(n *Node, inplace bool) *ssa.Value { +func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value { // If inplace is false, process as expression "append(s, e1, e2, e3)": // // ptr, len, cap := s @@ -2872,16 +3284,16 @@ func (s *state) append(n *Node, inplace bool) *ssa.Value { // *(ptr+len+1) = e2 // *(ptr+len+2) = e3 - et := n.Type.Elem() + et := n.Type().Elem() pt := types.NewPtr(et) // Evaluate slice - sn := n.List.First() // the slice node is the first in the list + sn := n.Args[0] // the slice node is the first in the list var slice, addr *ssa.Value if inplace { addr = s.addr(sn) - slice = s.load(n.Type, addr) + slice = s.load(n.Type(), addr) } else { slice = s.expr(sn) } @@ -2891,20 +3303,20 @@ func (s *state) append(n *Node, inplace bool) *ssa.Value { assign := s.f.NewBlock(ssa.BlockPlain) // Decide if we need to grow - nargs := int64(n.List.Len() - 1) + nargs := int64(len(n.Args) - 1) p := s.newValue1(ssa.OpSlicePtr, pt, slice) - l := s.newValue1(ssa.OpSliceLen, types.Types[TINT], slice) - c := s.newValue1(ssa.OpSliceCap, types.Types[TINT], slice) - nl := s.newValue2(s.ssaOp(OADD, types.Types[TINT]), types.Types[TINT], l, s.constInt(types.Types[TINT], nargs)) + l := s.newValue1(ssa.OpSliceLen, types.Types[types.TINT], slice) + c := s.newValue1(ssa.OpSliceCap, types.Types[types.TINT], slice) + nl := s.newValue2(s.ssaOp(ir.OADD, types.Types[types.TINT]), types.Types[types.TINT], l, s.constInt(types.Types[types.TINT], nargs)) - cmp := s.newValue2(s.ssaOp(OLT, types.Types[TUINT]), types.Types[TBOOL], c, nl) - s.vars[&ptrVar] = p + cmp := s.newValue2(s.ssaOp(ir.OLT, types.Types[types.TUINT]), types.Types[types.TBOOL], c, nl) + s.vars[ptrVar] = p if !inplace { - s.vars[&newlenVar] = nl - s.vars[&capVar] = c + s.vars[newlenVar] = nl + s.vars[capVar] = c } else { - s.vars[&lenVar] = l + s.vars[lenVar] = l } b := s.endBlock() @@ -2916,24 +3328,27 @@ func (s *state) append(n *Node, inplace bool) *ssa.Value { // Call growslice s.startBlock(grow) - taddr := s.expr(n.Left) - r := s.rtcall(growslice, true, []*types.Type{pt, types.Types[TINT], types.Types[TINT]}, taddr, p, l, c, nl) + taddr := s.expr(n.X) + r := s.rtcall(ir.Syms.Growslice, true, []*types.Type{pt, types.Types[types.TINT], types.Types[types.TINT]}, taddr, p, l, c, nl) if inplace { - if sn.Op == ONAME && sn.Class() != PEXTERN { - // Tell liveness we're about to build a new slice - s.vars[&memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, sn, s.mem()) + if sn.Op() == ir.ONAME { + sn := sn.(*ir.Name) + if sn.Class != ir.PEXTERN { + // Tell liveness we're about to build a new slice + s.vars[memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, sn, s.mem()) + } } - capaddr := s.newValue1I(ssa.OpOffPtr, s.f.Config.Types.IntPtr, sliceCapOffset, addr) - s.store(types.Types[TINT], capaddr, r[2]) + capaddr := s.newValue1I(ssa.OpOffPtr, s.f.Config.Types.IntPtr, types.SliceCapOffset, addr) + s.store(types.Types[types.TINT], capaddr, r[2]) s.store(pt, addr, r[0]) // load the value we just stored to avoid having to spill it - s.vars[&ptrVar] = s.load(pt, addr) - s.vars[&lenVar] = r[1] // avoid a spill in the fast path + s.vars[ptrVar] = s.load(pt, addr) + s.vars[lenVar] = r[1] // avoid a spill in the fast path } else { - s.vars[&ptrVar] = r[0] - s.vars[&newlenVar] = s.newValue2(s.ssaOp(OADD, types.Types[TINT]), types.Types[TINT], r[1], s.constInt(types.Types[TINT], nargs)) - s.vars[&capVar] = r[2] + s.vars[ptrVar] = r[0] + s.vars[newlenVar] = s.newValue2(s.ssaOp(ir.OADD, types.Types[types.TINT]), types.Types[types.TINT], r[1], s.constInt(types.Types[types.TINT], nargs)) + s.vars[capVar] = r[2] } b = s.endBlock() @@ -2943,10 +3358,10 @@ func (s *state) append(n *Node, inplace bool) *ssa.Value { s.startBlock(assign) if inplace { - l = s.variable(&lenVar, types.Types[TINT]) // generates phi for len - nl = s.newValue2(s.ssaOp(OADD, types.Types[TINT]), types.Types[TINT], l, s.constInt(types.Types[TINT], nargs)) - lenaddr := s.newValue1I(ssa.OpOffPtr, s.f.Config.Types.IntPtr, sliceLenOffset, addr) - s.store(types.Types[TINT], lenaddr, nl) + l = s.variable(lenVar, types.Types[types.TINT]) // generates phi for len + nl = s.newValue2(s.ssaOp(ir.OADD, types.Types[types.TINT]), types.Types[types.TINT], l, s.constInt(types.Types[types.TINT], nargs)) + lenaddr := s.newValue1I(ssa.OpOffPtr, s.f.Config.Types.IntPtr, types.SliceLenOffset, addr) + s.store(types.Types[types.TINT], lenaddr, nl) } // Evaluate args @@ -2957,8 +3372,8 @@ func (s *state) append(n *Node, inplace bool) *ssa.Value { store bool } args := make([]argRec, 0, nargs) - for _, n := range n.List.Slice()[1:] { - if canSSAType(n.Type) { + for _, n := range n.Args[1:] { + if TypeOK(n.Type()) { args = append(args, argRec{v: s.expr(n), store: true}) } else { v := s.addr(n) @@ -2966,14 +3381,14 @@ func (s *state) append(n *Node, inplace bool) *ssa.Value { } } - p = s.variable(&ptrVar, pt) // generates phi for ptr + p = s.variable(ptrVar, pt) // generates phi for ptr if !inplace { - nl = s.variable(&newlenVar, types.Types[TINT]) // generates phi for nl - c = s.variable(&capVar, types.Types[TINT]) // generates phi for cap + nl = s.variable(newlenVar, types.Types[types.TINT]) // generates phi for nl + c = s.variable(capVar, types.Types[types.TINT]) // generates phi for cap } p2 := s.newValue2(ssa.OpPtrIndex, pt, p, l) for i, arg := range args { - addr := s.newValue2(ssa.OpPtrIndex, pt, p2, s.constInt(types.Types[TINT], int64(i))) + addr := s.newValue2(ssa.OpPtrIndex, pt, p2, s.constInt(types.Types[types.TINT], int64(i))) if arg.store { s.storeType(et, addr, arg.v, 0, true) } else { @@ -2981,29 +3396,30 @@ func (s *state) append(n *Node, inplace bool) *ssa.Value { } } - delete(s.vars, &ptrVar) + delete(s.vars, ptrVar) if inplace { - delete(s.vars, &lenVar) + delete(s.vars, lenVar) return nil } - delete(s.vars, &newlenVar) - delete(s.vars, &capVar) + delete(s.vars, newlenVar) + delete(s.vars, capVar) // make result - return s.newValue3(ssa.OpSliceMake, n.Type, p, nl, c) + return s.newValue3(ssa.OpSliceMake, n.Type(), p, nl, c) } // condBranch evaluates the boolean expression cond and branches to yes // if cond is true and no if cond is false. // This function is intended to handle && and || better than just calling // s.expr(cond) and branching on the result. -func (s *state) condBranch(cond *Node, yes, no *ssa.Block, likely int8) { - switch cond.Op { - case OANDAND: +func (s *state) condBranch(cond ir.Node, yes, no *ssa.Block, likely int8) { + switch cond.Op() { + case ir.OANDAND: + cond := cond.(*ir.LogicalExpr) mid := s.f.NewBlock(ssa.BlockPlain) - s.stmtList(cond.Ninit) - s.condBranch(cond.Left, mid, no, max8(likely, 0)) + s.stmtList(cond.Init()) + s.condBranch(cond.X, mid, no, max8(likely, 0)) s.startBlock(mid) - s.condBranch(cond.Right, yes, no, likely) + s.condBranch(cond.Y, yes, no, likely) return // Note: if likely==1, then both recursive calls pass 1. // If likely==-1, then we don't have enough information to decide @@ -3011,19 +3427,26 @@ func (s *state) condBranch(cond *Node, yes, no *ssa.Block, likely int8) { // the likeliness of the first branch. // TODO: have the frontend give us branch prediction hints for // OANDAND and OOROR nodes (if it ever has such info). - case OOROR: + case ir.OOROR: + cond := cond.(*ir.LogicalExpr) mid := s.f.NewBlock(ssa.BlockPlain) - s.stmtList(cond.Ninit) - s.condBranch(cond.Left, yes, mid, min8(likely, 0)) + s.stmtList(cond.Init()) + s.condBranch(cond.X, yes, mid, min8(likely, 0)) s.startBlock(mid) - s.condBranch(cond.Right, yes, no, likely) + s.condBranch(cond.Y, yes, no, likely) return // Note: if likely==-1, then both recursive calls pass -1. // If likely==1, then we don't have enough info to decide // the likelihood of the first branch. - case ONOT: - s.stmtList(cond.Ninit) - s.condBranch(cond.Left, no, yes, -likely) + case ir.ONOT: + cond := cond.(*ir.UnaryExpr) + s.stmtList(cond.Init()) + s.condBranch(cond.X, no, yes, -likely) + return + case ir.OCONVNOP: + cond := cond.(*ir.ConvExpr) + s.stmtList(cond.Init()) + s.condBranch(cond.X, yes, no, likely) return } c := s.expr(cond) @@ -3048,17 +3471,17 @@ const ( // If deref is true, then we do left = *right instead (and right has already been nil-checked). // If deref is true and right == nil, just do left = 0. // skip indicates assignments (at the top level) that can be avoided. -func (s *state) assign(left *Node, right *ssa.Value, deref bool, skip skipMask) { - if left.Op == ONAME && left.isBlank() { +func (s *state) assign(left ir.Node, right *ssa.Value, deref bool, skip skipMask) { + if left.Op() == ir.ONAME && ir.IsBlank(left) { return } - t := left.Type - dowidth(t) + t := left.Type() + types.CalcSize(t) if s.canSSA(left) { if deref { s.Fatalf("can SSA LHS %v but not RHS %s", left, right) } - if left.Op == ODOT { + if left.Op() == ir.ODOT { // We're assigning to a field of an ssa-able value. // We need to build a new structure with the new value for the // field we're assigning and the old values for the other fields. @@ -3069,12 +3492,13 @@ func (s *state) assign(left *Node, right *ssa.Value, deref bool, skip skipMask) // For the x.b = 5 assignment we want to generate x = T{x.a, 5, x.c} // Grab information about the structure type. - t := left.Left.Type + left := left.(*ir.SelectorExpr) + t := left.X.Type() nf := t.NumFields() idx := fieldIdx(left) // Grab old value of structure. - old := s.expr(left.Left) + old := s.expr(left.X) // Make new structure. new := s.newValue0(ssa.StructMakeOp(t.NumFields()), t) @@ -3089,23 +3513,24 @@ func (s *state) assign(left *Node, right *ssa.Value, deref bool, skip skipMask) } // Recursively assign the new value we've made to the base of the dot op. - s.assign(left.Left, new, false, 0) + s.assign(left.X, new, false, 0) // TODO: do we need to update named values here? return } - if left.Op == OINDEX && left.Left.Type.IsArray() { - s.pushLine(left.Pos) + if left.Op() == ir.OINDEX && left.(*ir.IndexExpr).X.Type().IsArray() { + left := left.(*ir.IndexExpr) + s.pushLine(left.Pos()) defer s.popLine() // We're assigning to an element of an ssa-able array. // a[i] = v - t := left.Left.Type + t := left.X.Type() n := t.NumElem() - i := s.expr(left.Right) // index + i := s.expr(left.Index) // index if n == 0 { // The bounds check must fail. Might as well // ignore the actual index and just use zeros. - z := s.constInt(types.Types[TINT], 0) + z := s.constInt(types.Types[types.TINT], 0) s.boundsCheck(z, z, ssa.BoundsIndex, false) return } @@ -3113,12 +3538,13 @@ func (s *state) assign(left *Node, right *ssa.Value, deref bool, skip skipMask) s.Fatalf("assigning to non-1-length array") } // Rewrite to a = [1]{v} - len := s.constInt(types.Types[TINT], 1) + len := s.constInt(types.Types[types.TINT], 1) s.boundsCheck(i, len, ssa.BoundsIndex, false) // checks i == 0 v := s.newValue1(ssa.OpArrayMake1, t, right) - s.assign(left.Left, v, false, 0) + s.assign(left.X, v, false, 0) return } + left := left.(*ir.Name) // Update variable assignment. s.vars[left] = right s.addNamedValue(left, right) @@ -3127,19 +3553,19 @@ func (s *state) assign(left *Node, right *ssa.Value, deref bool, skip skipMask) // If this assignment clobbers an entire local variable, then emit // OpVarDef so liveness analysis knows the variable is redefined. - if base := clobberBase(left); base.Op == ONAME && base.Class() != PEXTERN && skip == 0 { - s.vars[&memVar] = s.newValue1Apos(ssa.OpVarDef, types.TypeMem, base, s.mem(), !base.IsAutoTmp()) + if base, ok := clobberBase(left).(*ir.Name); ok && base.OnStack() && skip == 0 { + s.vars[memVar] = s.newValue1Apos(ssa.OpVarDef, types.TypeMem, base, s.mem(), !ir.IsAutoTmp(base)) } // Left is not ssa-able. Compute its address. addr := s.addr(left) - if isReflectHeaderDataField(left) { + if ir.IsReflectHeaderDataField(left) { // Package unsafe's documentation says storing pointers into // reflect.SliceHeader and reflect.StringHeader's Data fields // is valid, even though they have type uintptr (#19168). // Mark it pointer type to signal the writebarrier pass to // insert a write barrier. - t = types.Types[TUNSAFEPTR] + t = types.Types[types.TUNSAFEPTR] } if deref { // Treat as a mem->mem move. @@ -3151,7 +3577,7 @@ func (s *state) assign(left *Node, right *ssa.Value, deref bool, skip skipMask) return } // Treat as a store. - s.storeType(t, addr, right, skip, !left.IsAutoTmp()) + s.storeType(t, addr, right, skip, !ir.IsAutoTmp(left)) } // zeroVal returns the zero value for type t. @@ -3182,10 +3608,10 @@ func (s *state) zeroVal(t *types.Type) *ssa.Value { case t.IsComplex(): switch t.Size() { case 8: - z := s.constFloat32(types.Types[TFLOAT32], 0) + z := s.constFloat32(types.Types[types.TFLOAT32], 0) return s.entryNewValue2(ssa.OpComplexMake, t, z, z) case 16: - z := s.constFloat64(types.Types[TFLOAT64], 0) + z := s.constFloat64(types.Types[types.TFLOAT64], 0) return s.entryNewValue2(ssa.OpComplexMake, t, z, z) default: s.Fatalf("bad sized complex type %v", t) @@ -3231,7 +3657,7 @@ const ( type sfRtCallDef struct { rtfn *obj.LSym - rtype types.EType + rtype types.Kind } var softFloatOps map[ssa.Op]sfRtCallDef @@ -3239,38 +3665,38 @@ var softFloatOps map[ssa.Op]sfRtCallDef func softfloatInit() { // Some of these operations get transformed by sfcall. softFloatOps = map[ssa.Op]sfRtCallDef{ - ssa.OpAdd32F: sfRtCallDef{sysfunc("fadd32"), TFLOAT32}, - ssa.OpAdd64F: sfRtCallDef{sysfunc("fadd64"), TFLOAT64}, - ssa.OpSub32F: sfRtCallDef{sysfunc("fadd32"), TFLOAT32}, - ssa.OpSub64F: sfRtCallDef{sysfunc("fadd64"), TFLOAT64}, - ssa.OpMul32F: sfRtCallDef{sysfunc("fmul32"), TFLOAT32}, - ssa.OpMul64F: sfRtCallDef{sysfunc("fmul64"), TFLOAT64}, - ssa.OpDiv32F: sfRtCallDef{sysfunc("fdiv32"), TFLOAT32}, - ssa.OpDiv64F: sfRtCallDef{sysfunc("fdiv64"), TFLOAT64}, - - ssa.OpEq64F: sfRtCallDef{sysfunc("feq64"), TBOOL}, - ssa.OpEq32F: sfRtCallDef{sysfunc("feq32"), TBOOL}, - ssa.OpNeq64F: sfRtCallDef{sysfunc("feq64"), TBOOL}, - ssa.OpNeq32F: sfRtCallDef{sysfunc("feq32"), TBOOL}, - ssa.OpLess64F: sfRtCallDef{sysfunc("fgt64"), TBOOL}, - ssa.OpLess32F: sfRtCallDef{sysfunc("fgt32"), TBOOL}, - ssa.OpLeq64F: sfRtCallDef{sysfunc("fge64"), TBOOL}, - ssa.OpLeq32F: sfRtCallDef{sysfunc("fge32"), TBOOL}, - - ssa.OpCvt32to32F: sfRtCallDef{sysfunc("fint32to32"), TFLOAT32}, - ssa.OpCvt32Fto32: sfRtCallDef{sysfunc("f32toint32"), TINT32}, - ssa.OpCvt64to32F: sfRtCallDef{sysfunc("fint64to32"), TFLOAT32}, - ssa.OpCvt32Fto64: sfRtCallDef{sysfunc("f32toint64"), TINT64}, - ssa.OpCvt64Uto32F: sfRtCallDef{sysfunc("fuint64to32"), TFLOAT32}, - ssa.OpCvt32Fto64U: sfRtCallDef{sysfunc("f32touint64"), TUINT64}, - ssa.OpCvt32to64F: sfRtCallDef{sysfunc("fint32to64"), TFLOAT64}, - ssa.OpCvt64Fto32: sfRtCallDef{sysfunc("f64toint32"), TINT32}, - ssa.OpCvt64to64F: sfRtCallDef{sysfunc("fint64to64"), TFLOAT64}, - ssa.OpCvt64Fto64: sfRtCallDef{sysfunc("f64toint64"), TINT64}, - ssa.OpCvt64Uto64F: sfRtCallDef{sysfunc("fuint64to64"), TFLOAT64}, - ssa.OpCvt64Fto64U: sfRtCallDef{sysfunc("f64touint64"), TUINT64}, - ssa.OpCvt32Fto64F: sfRtCallDef{sysfunc("f32to64"), TFLOAT64}, - ssa.OpCvt64Fto32F: sfRtCallDef{sysfunc("f64to32"), TFLOAT32}, + ssa.OpAdd32F: sfRtCallDef{typecheck.LookupRuntimeFunc("fadd32"), types.TFLOAT32}, + ssa.OpAdd64F: sfRtCallDef{typecheck.LookupRuntimeFunc("fadd64"), types.TFLOAT64}, + ssa.OpSub32F: sfRtCallDef{typecheck.LookupRuntimeFunc("fadd32"), types.TFLOAT32}, + ssa.OpSub64F: sfRtCallDef{typecheck.LookupRuntimeFunc("fadd64"), types.TFLOAT64}, + ssa.OpMul32F: sfRtCallDef{typecheck.LookupRuntimeFunc("fmul32"), types.TFLOAT32}, + ssa.OpMul64F: sfRtCallDef{typecheck.LookupRuntimeFunc("fmul64"), types.TFLOAT64}, + ssa.OpDiv32F: sfRtCallDef{typecheck.LookupRuntimeFunc("fdiv32"), types.TFLOAT32}, + ssa.OpDiv64F: sfRtCallDef{typecheck.LookupRuntimeFunc("fdiv64"), types.TFLOAT64}, + + ssa.OpEq64F: sfRtCallDef{typecheck.LookupRuntimeFunc("feq64"), types.TBOOL}, + ssa.OpEq32F: sfRtCallDef{typecheck.LookupRuntimeFunc("feq32"), types.TBOOL}, + ssa.OpNeq64F: sfRtCallDef{typecheck.LookupRuntimeFunc("feq64"), types.TBOOL}, + ssa.OpNeq32F: sfRtCallDef{typecheck.LookupRuntimeFunc("feq32"), types.TBOOL}, + ssa.OpLess64F: sfRtCallDef{typecheck.LookupRuntimeFunc("fgt64"), types.TBOOL}, + ssa.OpLess32F: sfRtCallDef{typecheck.LookupRuntimeFunc("fgt32"), types.TBOOL}, + ssa.OpLeq64F: sfRtCallDef{typecheck.LookupRuntimeFunc("fge64"), types.TBOOL}, + ssa.OpLeq32F: sfRtCallDef{typecheck.LookupRuntimeFunc("fge32"), types.TBOOL}, + + ssa.OpCvt32to32F: sfRtCallDef{typecheck.LookupRuntimeFunc("fint32to32"), types.TFLOAT32}, + ssa.OpCvt32Fto32: sfRtCallDef{typecheck.LookupRuntimeFunc("f32toint32"), types.TINT32}, + ssa.OpCvt64to32F: sfRtCallDef{typecheck.LookupRuntimeFunc("fint64to32"), types.TFLOAT32}, + ssa.OpCvt32Fto64: sfRtCallDef{typecheck.LookupRuntimeFunc("f32toint64"), types.TINT64}, + ssa.OpCvt64Uto32F: sfRtCallDef{typecheck.LookupRuntimeFunc("fuint64to32"), types.TFLOAT32}, + ssa.OpCvt32Fto64U: sfRtCallDef{typecheck.LookupRuntimeFunc("f32touint64"), types.TUINT64}, + ssa.OpCvt32to64F: sfRtCallDef{typecheck.LookupRuntimeFunc("fint32to64"), types.TFLOAT64}, + ssa.OpCvt64Fto32: sfRtCallDef{typecheck.LookupRuntimeFunc("f64toint32"), types.TINT32}, + ssa.OpCvt64to64F: sfRtCallDef{typecheck.LookupRuntimeFunc("fint64to64"), types.TFLOAT64}, + ssa.OpCvt64Fto64: sfRtCallDef{typecheck.LookupRuntimeFunc("f64toint64"), types.TINT64}, + ssa.OpCvt64Uto64F: sfRtCallDef{typecheck.LookupRuntimeFunc("fuint64to64"), types.TFLOAT64}, + ssa.OpCvt64Fto64U: sfRtCallDef{typecheck.LookupRuntimeFunc("f64touint64"), types.TUINT64}, + ssa.OpCvt32Fto64F: sfRtCallDef{typecheck.LookupRuntimeFunc("f32to64"), types.TFLOAT64}, + ssa.OpCvt64Fto32F: sfRtCallDef{typecheck.LookupRuntimeFunc("f64to32"), types.TFLOAT32}, } } @@ -3286,7 +3712,7 @@ func (s *state) sfcall(op ssa.Op, args ...*ssa.Value) (*ssa.Value, bool) { args[0], args[1] = args[1], args[0] case ssa.OpSub32F, ssa.OpSub64F: - args[1] = s.newValue1(s.ssaOp(ONEG, types.Types[callDef.rtype]), args[1].Type, args[1]) + args[1] = s.newValue1(s.ssaOp(ir.ONEG, types.Types[callDef.rtype]), args[1].Type, args[1]) } result := s.rtcall(callDef.rtfn, true, []*types.Type{types.Types[callDef.rtype]}, args...)[0] @@ -3302,7 +3728,7 @@ var intrinsics map[intrinsicKey]intrinsicBuilder // An intrinsicBuilder converts a call node n into an ssa value that // implements that call as an intrinsic. args is a list of arguments to the func. -type intrinsicBuilder func(s *state, n *Node, args []*ssa.Value) *ssa.Value +type intrinsicBuilder func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value type intrinsicKey struct { arch *sys.Arch @@ -3310,7 +3736,7 @@ type intrinsicKey struct { fn string } -func init() { +func InitTables() { intrinsics = map[intrinsicKey]intrinsicBuilder{} var all []*sys.Arch @@ -3365,175 +3791,175 @@ func init() { } /******** runtime ********/ - if !instrumenting { + if !base.Flag.Cfg.Instrumenting { add("runtime", "slicebytetostringtmp", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { // Compiler frontend optimizations emit OBYTES2STRTMP nodes // for the backend instead of slicebytetostringtmp calls // when not instrumenting. - return s.newValue2(ssa.OpStringMake, n.Type, args[0], args[1]) + return s.newValue2(ssa.OpStringMake, n.Type(), args[0], args[1]) }, all...) } addF("runtime/internal/math", "MulUintptr", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { if s.config.PtrSize == 4 { - return s.newValue2(ssa.OpMul32uover, types.NewTuple(types.Types[TUINT], types.Types[TUINT]), args[0], args[1]) + return s.newValue2(ssa.OpMul32uover, types.NewTuple(types.Types[types.TUINT], types.Types[types.TUINT]), args[0], args[1]) } - return s.newValue2(ssa.OpMul64uover, types.NewTuple(types.Types[TUINT], types.Types[TUINT]), args[0], args[1]) + return s.newValue2(ssa.OpMul64uover, types.NewTuple(types.Types[types.TUINT], types.Types[types.TUINT]), args[0], args[1]) }, sys.AMD64, sys.I386, sys.MIPS64) add("runtime", "KeepAlive", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { data := s.newValue1(ssa.OpIData, s.f.Config.Types.BytePtr, args[0]) - s.vars[&memVar] = s.newValue2(ssa.OpKeepAlive, types.TypeMem, data, s.mem()) + s.vars[memVar] = s.newValue2(ssa.OpKeepAlive, types.TypeMem, data, s.mem()) return nil }, all...) add("runtime", "getclosureptr", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { return s.newValue0(ssa.OpGetClosurePtr, s.f.Config.Types.Uintptr) }, all...) add("runtime", "getcallerpc", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { return s.newValue0(ssa.OpGetCallerPC, s.f.Config.Types.Uintptr) }, all...) add("runtime", "getcallersp", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { return s.newValue0(ssa.OpGetCallerSP, s.f.Config.Types.Uintptr) }, all...) /******** runtime/internal/sys ********/ addF("runtime/internal/sys", "Ctz32", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpCtz32, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpCtz32, types.Types[types.TINT], args[0]) }, sys.AMD64, sys.ARM64, sys.ARM, sys.S390X, sys.MIPS, sys.PPC64) addF("runtime/internal/sys", "Ctz64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpCtz64, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpCtz64, types.Types[types.TINT], args[0]) }, sys.AMD64, sys.ARM64, sys.ARM, sys.S390X, sys.MIPS, sys.PPC64) addF("runtime/internal/sys", "Bswap32", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpBswap32, types.Types[TUINT32], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpBswap32, types.Types[types.TUINT32], args[0]) }, sys.AMD64, sys.ARM64, sys.ARM, sys.S390X) addF("runtime/internal/sys", "Bswap64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpBswap64, types.Types[TUINT64], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpBswap64, types.Types[types.TUINT64], args[0]) }, sys.AMD64, sys.ARM64, sys.ARM, sys.S390X) /******** runtime/internal/atomic ********/ addF("runtime/internal/atomic", "Load", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue2(ssa.OpAtomicLoad32, types.NewTuple(types.Types[TUINT32], types.TypeMem), args[0], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TUINT32], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue2(ssa.OpAtomicLoad32, types.NewTuple(types.Types[types.TUINT32], types.TypeMem), args[0], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TUINT32], v) }, sys.AMD64, sys.ARM64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Load8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue2(ssa.OpAtomicLoad8, types.NewTuple(types.Types[TUINT8], types.TypeMem), args[0], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TUINT8], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue2(ssa.OpAtomicLoad8, types.NewTuple(types.Types[types.TUINT8], types.TypeMem), args[0], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TUINT8], v) }, sys.AMD64, sys.ARM64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Load64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue2(ssa.OpAtomicLoad64, types.NewTuple(types.Types[TUINT64], types.TypeMem), args[0], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TUINT64], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue2(ssa.OpAtomicLoad64, types.NewTuple(types.Types[types.TUINT64], types.TypeMem), args[0], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TUINT64], v) }, sys.AMD64, sys.ARM64, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "LoadAcq", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue2(ssa.OpAtomicLoadAcq32, types.NewTuple(types.Types[TUINT32], types.TypeMem), args[0], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TUINT32], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue2(ssa.OpAtomicLoadAcq32, types.NewTuple(types.Types[types.TUINT32], types.TypeMem), args[0], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TUINT32], v) }, sys.PPC64, sys.S390X) addF("runtime/internal/atomic", "LoadAcq64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue2(ssa.OpAtomicLoadAcq64, types.NewTuple(types.Types[TUINT64], types.TypeMem), args[0], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TUINT64], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue2(ssa.OpAtomicLoadAcq64, types.NewTuple(types.Types[types.TUINT64], types.TypeMem), args[0], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TUINT64], v) }, sys.PPC64) addF("runtime/internal/atomic", "Loadp", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { v := s.newValue2(ssa.OpAtomicLoadPtr, types.NewTuple(s.f.Config.Types.BytePtr, types.TypeMem), args[0], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) return s.newValue1(ssa.OpSelect0, s.f.Config.Types.BytePtr, v) }, sys.AMD64, sys.ARM64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Store", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - s.vars[&memVar] = s.newValue3(ssa.OpAtomicStore32, types.TypeMem, args[0], args[1], s.mem()) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + s.vars[memVar] = s.newValue3(ssa.OpAtomicStore32, types.TypeMem, args[0], args[1], s.mem()) return nil }, sys.AMD64, sys.ARM64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Store8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - s.vars[&memVar] = s.newValue3(ssa.OpAtomicStore8, types.TypeMem, args[0], args[1], s.mem()) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + s.vars[memVar] = s.newValue3(ssa.OpAtomicStore8, types.TypeMem, args[0], args[1], s.mem()) return nil }, sys.AMD64, sys.ARM64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Store64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - s.vars[&memVar] = s.newValue3(ssa.OpAtomicStore64, types.TypeMem, args[0], args[1], s.mem()) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + s.vars[memVar] = s.newValue3(ssa.OpAtomicStore64, types.TypeMem, args[0], args[1], s.mem()) return nil }, sys.AMD64, sys.ARM64, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "StorepNoWB", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - s.vars[&memVar] = s.newValue3(ssa.OpAtomicStorePtrNoWB, types.TypeMem, args[0], args[1], s.mem()) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + s.vars[memVar] = s.newValue3(ssa.OpAtomicStorePtrNoWB, types.TypeMem, args[0], args[1], s.mem()) return nil }, sys.AMD64, sys.ARM64, sys.MIPS, sys.MIPS64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "StoreRel", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - s.vars[&memVar] = s.newValue3(ssa.OpAtomicStoreRel32, types.TypeMem, args[0], args[1], s.mem()) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + s.vars[memVar] = s.newValue3(ssa.OpAtomicStoreRel32, types.TypeMem, args[0], args[1], s.mem()) return nil }, sys.PPC64, sys.S390X) addF("runtime/internal/atomic", "StoreRel64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - s.vars[&memVar] = s.newValue3(ssa.OpAtomicStoreRel64, types.TypeMem, args[0], args[1], s.mem()) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + s.vars[memVar] = s.newValue3(ssa.OpAtomicStoreRel64, types.TypeMem, args[0], args[1], s.mem()) return nil }, sys.PPC64) addF("runtime/internal/atomic", "Xchg", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue3(ssa.OpAtomicExchange32, types.NewTuple(types.Types[TUINT32], types.TypeMem), args[0], args[1], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TUINT32], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue3(ssa.OpAtomicExchange32, types.NewTuple(types.Types[types.TUINT32], types.TypeMem), args[0], args[1], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TUINT32], v) }, sys.AMD64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Xchg64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue3(ssa.OpAtomicExchange64, types.NewTuple(types.Types[TUINT64], types.TypeMem), args[0], args[1], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TUINT64], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue3(ssa.OpAtomicExchange64, types.NewTuple(types.Types[types.TUINT64], types.TypeMem), args[0], args[1], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TUINT64], v) }, sys.AMD64, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) - type atomicOpEmitter func(s *state, n *Node, args []*ssa.Value, op ssa.Op, typ types.EType) + type atomicOpEmitter func(s *state, n *ir.CallExpr, args []*ssa.Value, op ssa.Op, typ types.Kind) - makeAtomicGuardedIntrinsicARM64 := func(op0, op1 ssa.Op, typ, rtyp types.EType, emit atomicOpEmitter) intrinsicBuilder { + makeAtomicGuardedIntrinsicARM64 := func(op0, op1 ssa.Op, typ, rtyp types.Kind, emit atomicOpEmitter) intrinsicBuilder { - return func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + return func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { // Target Atomic feature is identified by dynamic detection - addr := s.entryNewValue1A(ssa.OpAddr, types.Types[TBOOL].PtrTo(), arm64HasATOMICS, s.sb) - v := s.load(types.Types[TBOOL], addr) + addr := s.entryNewValue1A(ssa.OpAddr, types.Types[types.TBOOL].PtrTo(), ir.Syms.ARM64HasATOMICS, s.sb) + v := s.load(types.Types[types.TBOOL], addr) b := s.endBlock() b.Kind = ssa.BlockIf b.SetControl(v) @@ -3556,7 +3982,7 @@ func init() { // Merge results. s.startBlock(bEnd) - if rtyp == TNIL { + if rtyp == types.TNIL { return nil } else { return s.variable(n, types.Types[rtyp]) @@ -3564,129 +3990,134 @@ func init() { } } - atomicXchgXaddEmitterARM64 := func(s *state, n *Node, args []*ssa.Value, op ssa.Op, typ types.EType) { + atomicXchgXaddEmitterARM64 := func(s *state, n *ir.CallExpr, args []*ssa.Value, op ssa.Op, typ types.Kind) { v := s.newValue3(op, types.NewTuple(types.Types[typ], types.TypeMem), args[0], args[1], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) s.vars[n] = s.newValue1(ssa.OpSelect0, types.Types[typ], v) } addF("runtime/internal/atomic", "Xchg", - makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicExchange32, ssa.OpAtomicExchange32Variant, TUINT32, TUINT32, atomicXchgXaddEmitterARM64), + makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicExchange32, ssa.OpAtomicExchange32Variant, types.TUINT32, types.TUINT32, atomicXchgXaddEmitterARM64), sys.ARM64) addF("runtime/internal/atomic", "Xchg64", - makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicExchange64, ssa.OpAtomicExchange64Variant, TUINT64, TUINT64, atomicXchgXaddEmitterARM64), + makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicExchange64, ssa.OpAtomicExchange64Variant, types.TUINT64, types.TUINT64, atomicXchgXaddEmitterARM64), sys.ARM64) addF("runtime/internal/atomic", "Xadd", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue3(ssa.OpAtomicAdd32, types.NewTuple(types.Types[TUINT32], types.TypeMem), args[0], args[1], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TUINT32], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue3(ssa.OpAtomicAdd32, types.NewTuple(types.Types[types.TUINT32], types.TypeMem), args[0], args[1], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TUINT32], v) }, sys.AMD64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Xadd64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue3(ssa.OpAtomicAdd64, types.NewTuple(types.Types[TUINT64], types.TypeMem), args[0], args[1], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TUINT64], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue3(ssa.OpAtomicAdd64, types.NewTuple(types.Types[types.TUINT64], types.TypeMem), args[0], args[1], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TUINT64], v) }, sys.AMD64, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Xadd", - makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicAdd32, ssa.OpAtomicAdd32Variant, TUINT32, TUINT32, atomicXchgXaddEmitterARM64), + makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicAdd32, ssa.OpAtomicAdd32Variant, types.TUINT32, types.TUINT32, atomicXchgXaddEmitterARM64), sys.ARM64) addF("runtime/internal/atomic", "Xadd64", - makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicAdd64, ssa.OpAtomicAdd64Variant, TUINT64, TUINT64, atomicXchgXaddEmitterARM64), + makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicAdd64, ssa.OpAtomicAdd64Variant, types.TUINT64, types.TUINT64, atomicXchgXaddEmitterARM64), sys.ARM64) addF("runtime/internal/atomic", "Cas", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue4(ssa.OpAtomicCompareAndSwap32, types.NewTuple(types.Types[TBOOL], types.TypeMem), args[0], args[1], args[2], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TBOOL], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue4(ssa.OpAtomicCompareAndSwap32, types.NewTuple(types.Types[types.TBOOL], types.TypeMem), args[0], args[1], args[2], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TBOOL], v) }, sys.AMD64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Cas64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue4(ssa.OpAtomicCompareAndSwap64, types.NewTuple(types.Types[TBOOL], types.TypeMem), args[0], args[1], args[2], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TBOOL], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue4(ssa.OpAtomicCompareAndSwap64, types.NewTuple(types.Types[types.TBOOL], types.TypeMem), args[0], args[1], args[2], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TBOOL], v) }, sys.AMD64, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "CasRel", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.newValue4(ssa.OpAtomicCompareAndSwap32, types.NewTuple(types.Types[TBOOL], types.TypeMem), args[0], args[1], args[2], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) - return s.newValue1(ssa.OpSelect0, types.Types[TBOOL], v) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.newValue4(ssa.OpAtomicCompareAndSwap32, types.NewTuple(types.Types[types.TBOOL], types.TypeMem), args[0], args[1], args[2], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + return s.newValue1(ssa.OpSelect0, types.Types[types.TBOOL], v) }, sys.PPC64) - atomicCasEmitterARM64 := func(s *state, n *Node, args []*ssa.Value, op ssa.Op, typ types.EType) { - v := s.newValue4(op, types.NewTuple(types.Types[TBOOL], types.TypeMem), args[0], args[1], args[2], s.mem()) - s.vars[&memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) + atomicCasEmitterARM64 := func(s *state, n *ir.CallExpr, args []*ssa.Value, op ssa.Op, typ types.Kind) { + v := s.newValue4(op, types.NewTuple(types.Types[types.TBOOL], types.TypeMem), args[0], args[1], args[2], s.mem()) + s.vars[memVar] = s.newValue1(ssa.OpSelect1, types.TypeMem, v) s.vars[n] = s.newValue1(ssa.OpSelect0, types.Types[typ], v) } addF("runtime/internal/atomic", "Cas", - makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicCompareAndSwap32, ssa.OpAtomicCompareAndSwap32Variant, TUINT32, TBOOL, atomicCasEmitterARM64), + makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicCompareAndSwap32, ssa.OpAtomicCompareAndSwap32Variant, types.TUINT32, types.TBOOL, atomicCasEmitterARM64), sys.ARM64) addF("runtime/internal/atomic", "Cas64", - makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicCompareAndSwap64, ssa.OpAtomicCompareAndSwap64Variant, TUINT64, TBOOL, atomicCasEmitterARM64), + makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicCompareAndSwap64, ssa.OpAtomicCompareAndSwap64Variant, types.TUINT64, types.TBOOL, atomicCasEmitterARM64), sys.ARM64) addF("runtime/internal/atomic", "And8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - s.vars[&memVar] = s.newValue3(ssa.OpAtomicAnd8, types.TypeMem, args[0], args[1], s.mem()) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + s.vars[memVar] = s.newValue3(ssa.OpAtomicAnd8, types.TypeMem, args[0], args[1], s.mem()) return nil }, - sys.AMD64, sys.MIPS, sys.PPC64, sys.S390X) + sys.AMD64, sys.MIPS, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "And", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - s.vars[&memVar] = s.newValue3(ssa.OpAtomicAnd32, types.TypeMem, args[0], args[1], s.mem()) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + s.vars[memVar] = s.newValue3(ssa.OpAtomicAnd32, types.TypeMem, args[0], args[1], s.mem()) return nil }, - sys.AMD64, sys.MIPS, sys.PPC64, sys.S390X) + sys.AMD64, sys.MIPS, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Or8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - s.vars[&memVar] = s.newValue3(ssa.OpAtomicOr8, types.TypeMem, args[0], args[1], s.mem()) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + s.vars[memVar] = s.newValue3(ssa.OpAtomicOr8, types.TypeMem, args[0], args[1], s.mem()) return nil }, - sys.AMD64, sys.ARM64, sys.MIPS, sys.PPC64, sys.S390X) + sys.AMD64, sys.ARM64, sys.MIPS, sys.PPC64, sys.RISCV64, sys.S390X) addF("runtime/internal/atomic", "Or", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - s.vars[&memVar] = s.newValue3(ssa.OpAtomicOr32, types.TypeMem, args[0], args[1], s.mem()) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + s.vars[memVar] = s.newValue3(ssa.OpAtomicOr32, types.TypeMem, args[0], args[1], s.mem()) return nil }, - sys.AMD64, sys.MIPS, sys.PPC64, sys.S390X) + sys.AMD64, sys.MIPS, sys.PPC64, sys.RISCV64, sys.S390X) - atomicAndOrEmitterARM64 := func(s *state, n *Node, args []*ssa.Value, op ssa.Op, typ types.EType) { - s.vars[&memVar] = s.newValue3(op, types.TypeMem, args[0], args[1], s.mem()) + atomicAndOrEmitterARM64 := func(s *state, n *ir.CallExpr, args []*ssa.Value, op ssa.Op, typ types.Kind) { + s.vars[memVar] = s.newValue3(op, types.TypeMem, args[0], args[1], s.mem()) } addF("runtime/internal/atomic", "And8", - makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicAnd8, ssa.OpAtomicAnd8Variant, TNIL, TNIL, atomicAndOrEmitterARM64), + makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicAnd8, ssa.OpAtomicAnd8Variant, types.TNIL, types.TNIL, atomicAndOrEmitterARM64), sys.ARM64) addF("runtime/internal/atomic", "And", - makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicAnd32, ssa.OpAtomicAnd32Variant, TNIL, TNIL, atomicAndOrEmitterARM64), + makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicAnd32, ssa.OpAtomicAnd32Variant, types.TNIL, types.TNIL, atomicAndOrEmitterARM64), sys.ARM64) addF("runtime/internal/atomic", "Or8", - makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicOr8, ssa.OpAtomicOr8Variant, TNIL, TNIL, atomicAndOrEmitterARM64), + makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicOr8, ssa.OpAtomicOr8Variant, types.TNIL, types.TNIL, atomicAndOrEmitterARM64), sys.ARM64) addF("runtime/internal/atomic", "Or", - makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicOr32, ssa.OpAtomicOr32Variant, TNIL, TNIL, atomicAndOrEmitterARM64), + makeAtomicGuardedIntrinsicARM64(ssa.OpAtomicOr32, ssa.OpAtomicOr32Variant, types.TNIL, types.TNIL, atomicAndOrEmitterARM64), sys.ARM64) + // Aliases for atomic load operations + alias("runtime/internal/atomic", "Loadint32", "runtime/internal/atomic", "Load", all...) alias("runtime/internal/atomic", "Loadint64", "runtime/internal/atomic", "Load64", all...) - alias("runtime/internal/atomic", "Xaddint64", "runtime/internal/atomic", "Xadd64", all...) - alias("runtime/internal/atomic", "Loaduint", "runtime/internal/atomic", "Load", p4...) - alias("runtime/internal/atomic", "Loaduint", "runtime/internal/atomic", "Load64", p8...) alias("runtime/internal/atomic", "Loaduintptr", "runtime/internal/atomic", "Load", p4...) alias("runtime/internal/atomic", "Loaduintptr", "runtime/internal/atomic", "Load64", p8...) + alias("runtime/internal/atomic", "Loaduint", "runtime/internal/atomic", "Load", p4...) + alias("runtime/internal/atomic", "Loaduint", "runtime/internal/atomic", "Load64", p8...) alias("runtime/internal/atomic", "LoadAcq", "runtime/internal/atomic", "Load", lwatomics...) alias("runtime/internal/atomic", "LoadAcq64", "runtime/internal/atomic", "Load64", lwatomics...) alias("runtime/internal/atomic", "LoadAcquintptr", "runtime/internal/atomic", "LoadAcq", p4...) alias("sync", "runtime_LoadAcquintptr", "runtime/internal/atomic", "LoadAcq", p4...) // linknamed alias("runtime/internal/atomic", "LoadAcquintptr", "runtime/internal/atomic", "LoadAcq64", p8...) alias("sync", "runtime_LoadAcquintptr", "runtime/internal/atomic", "LoadAcq64", p8...) // linknamed + + // Aliases for atomic store operations + alias("runtime/internal/atomic", "Storeint32", "runtime/internal/atomic", "Store", all...) + alias("runtime/internal/atomic", "Storeint64", "runtime/internal/atomic", "Store64", all...) alias("runtime/internal/atomic", "Storeuintptr", "runtime/internal/atomic", "Store", p4...) alias("runtime/internal/atomic", "Storeuintptr", "runtime/internal/atomic", "Store64", p8...) alias("runtime/internal/atomic", "StoreRel", "runtime/internal/atomic", "Store", lwatomics...) @@ -3695,10 +4126,22 @@ func init() { alias("sync", "runtime_StoreReluintptr", "runtime/internal/atomic", "StoreRel", p4...) // linknamed alias("runtime/internal/atomic", "StoreReluintptr", "runtime/internal/atomic", "StoreRel64", p8...) alias("sync", "runtime_StoreReluintptr", "runtime/internal/atomic", "StoreRel64", p8...) // linknamed + + // Aliases for atomic swap operations + alias("runtime/internal/atomic", "Xchgint32", "runtime/internal/atomic", "Xchg", all...) + alias("runtime/internal/atomic", "Xchgint64", "runtime/internal/atomic", "Xchg64", all...) alias("runtime/internal/atomic", "Xchguintptr", "runtime/internal/atomic", "Xchg", p4...) alias("runtime/internal/atomic", "Xchguintptr", "runtime/internal/atomic", "Xchg64", p8...) + + // Aliases for atomic add operations + alias("runtime/internal/atomic", "Xaddint32", "runtime/internal/atomic", "Xadd", all...) + alias("runtime/internal/atomic", "Xaddint64", "runtime/internal/atomic", "Xadd64", all...) alias("runtime/internal/atomic", "Xadduintptr", "runtime/internal/atomic", "Xadd", p4...) alias("runtime/internal/atomic", "Xadduintptr", "runtime/internal/atomic", "Xadd64", p8...) + + // Aliases for atomic CAS operations + alias("runtime/internal/atomic", "Casint32", "runtime/internal/atomic", "Cas", all...) + alias("runtime/internal/atomic", "Casint64", "runtime/internal/atomic", "Cas64", all...) alias("runtime/internal/atomic", "Casuintptr", "runtime/internal/atomic", "Cas", p4...) alias("runtime/internal/atomic", "Casuintptr", "runtime/internal/atomic", "Cas64", p8...) alias("runtime/internal/atomic", "Casp1", "runtime/internal/atomic", "Cas", p4...) @@ -3707,57 +4150,57 @@ func init() { /******** math ********/ addF("math", "Sqrt", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpSqrt, types.Types[TFLOAT64], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpSqrt, types.Types[types.TFLOAT64], args[0]) }, sys.I386, sys.AMD64, sys.ARM, sys.ARM64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64, sys.S390X, sys.Wasm) addF("math", "Trunc", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpTrunc, types.Types[TFLOAT64], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpTrunc, types.Types[types.TFLOAT64], args[0]) }, sys.ARM64, sys.PPC64, sys.S390X, sys.Wasm) addF("math", "Ceil", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpCeil, types.Types[TFLOAT64], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpCeil, types.Types[types.TFLOAT64], args[0]) }, sys.ARM64, sys.PPC64, sys.S390X, sys.Wasm) addF("math", "Floor", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpFloor, types.Types[TFLOAT64], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpFloor, types.Types[types.TFLOAT64], args[0]) }, sys.ARM64, sys.PPC64, sys.S390X, sys.Wasm) addF("math", "Round", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpRound, types.Types[TFLOAT64], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpRound, types.Types[types.TFLOAT64], args[0]) }, sys.ARM64, sys.PPC64, sys.S390X) addF("math", "RoundToEven", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpRoundToEven, types.Types[TFLOAT64], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpRoundToEven, types.Types[types.TFLOAT64], args[0]) }, sys.ARM64, sys.S390X, sys.Wasm) addF("math", "Abs", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpAbs, types.Types[TFLOAT64], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpAbs, types.Types[types.TFLOAT64], args[0]) }, sys.ARM64, sys.ARM, sys.PPC64, sys.Wasm) addF("math", "Copysign", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue2(ssa.OpCopysign, types.Types[TFLOAT64], args[0], args[1]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue2(ssa.OpCopysign, types.Types[types.TFLOAT64], args[0], args[1]) }, sys.PPC64, sys.Wasm) addF("math", "FMA", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue3(ssa.OpFMA, types.Types[TFLOAT64], args[0], args[1], args[2]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue3(ssa.OpFMA, types.Types[types.TFLOAT64], args[0], args[1], args[2]) }, sys.ARM64, sys.PPC64, sys.S390X) addF("math", "FMA", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { if !s.config.UseFMA { s.vars[n] = s.callResult(n, callNormal) // types.Types[TFLOAT64] - return s.variable(n, types.Types[TFLOAT64]) + return s.variable(n, types.Types[types.TFLOAT64]) } - v := s.entryNewValue0A(ssa.OpHasCPUFeature, types.Types[TBOOL], x86HasFMA) + v := s.entryNewValue0A(ssa.OpHasCPUFeature, types.Types[types.TBOOL], ir.Syms.X86HasFMA) b := s.endBlock() b.Kind = ssa.BlockIf b.SetControl(v) @@ -3770,7 +4213,7 @@ func init() { // We have the intrinsic - use it directly. s.startBlock(bTrue) - s.vars[n] = s.newValue3(ssa.OpFMA, types.Types[TFLOAT64], args[0], args[1], args[2]) + s.vars[n] = s.newValue3(ssa.OpFMA, types.Types[types.TFLOAT64], args[0], args[1], args[2]) s.endBlock().AddEdgeTo(bEnd) // Call the pure Go version. @@ -3780,17 +4223,17 @@ func init() { // Merge results. s.startBlock(bEnd) - return s.variable(n, types.Types[TFLOAT64]) + return s.variable(n, types.Types[types.TFLOAT64]) }, sys.AMD64) addF("math", "FMA", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { if !s.config.UseFMA { s.vars[n] = s.callResult(n, callNormal) // types.Types[TFLOAT64] - return s.variable(n, types.Types[TFLOAT64]) + return s.variable(n, types.Types[types.TFLOAT64]) } - addr := s.entryNewValue1A(ssa.OpAddr, types.Types[TBOOL].PtrTo(), armHasVFPv4, s.sb) - v := s.load(types.Types[TBOOL], addr) + addr := s.entryNewValue1A(ssa.OpAddr, types.Types[types.TBOOL].PtrTo(), ir.Syms.ARMHasVFPv4, s.sb) + v := s.load(types.Types[types.TBOOL], addr) b := s.endBlock() b.Kind = ssa.BlockIf b.SetControl(v) @@ -3803,7 +4246,7 @@ func init() { // We have the intrinsic - use it directly. s.startBlock(bTrue) - s.vars[n] = s.newValue3(ssa.OpFMA, types.Types[TFLOAT64], args[0], args[1], args[2]) + s.vars[n] = s.newValue3(ssa.OpFMA, types.Types[types.TFLOAT64], args[0], args[1], args[2]) s.endBlock().AddEdgeTo(bEnd) // Call the pure Go version. @@ -3813,13 +4256,13 @@ func init() { // Merge results. s.startBlock(bEnd) - return s.variable(n, types.Types[TFLOAT64]) + return s.variable(n, types.Types[types.TFLOAT64]) }, sys.ARM) - makeRoundAMD64 := func(op ssa.Op) func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.entryNewValue0A(ssa.OpHasCPUFeature, types.Types[TBOOL], x86HasSSE41) + makeRoundAMD64 := func(op ssa.Op) func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.entryNewValue0A(ssa.OpHasCPUFeature, types.Types[types.TBOOL], ir.Syms.X86HasSSE41) b := s.endBlock() b.Kind = ssa.BlockIf b.SetControl(v) @@ -3832,7 +4275,7 @@ func init() { // We have the intrinsic - use it directly. s.startBlock(bTrue) - s.vars[n] = s.newValue1(op, types.Types[TFLOAT64], args[0]) + s.vars[n] = s.newValue1(op, types.Types[types.TFLOAT64], args[0]) s.endBlock().AddEdgeTo(bEnd) // Call the pure Go version. @@ -3842,7 +4285,7 @@ func init() { // Merge results. s.startBlock(bEnd) - return s.variable(n, types.Types[TFLOAT64]) + return s.variable(n, types.Types[types.TFLOAT64]) } } addF("math", "RoundToEven", @@ -3860,55 +4303,55 @@ func init() { /******** math/bits ********/ addF("math/bits", "TrailingZeros64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpCtz64, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpCtz64, types.Types[types.TINT], args[0]) }, sys.AMD64, sys.ARM64, sys.ARM, sys.S390X, sys.MIPS, sys.PPC64, sys.Wasm) addF("math/bits", "TrailingZeros32", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpCtz32, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpCtz32, types.Types[types.TINT], args[0]) }, sys.AMD64, sys.ARM64, sys.ARM, sys.S390X, sys.MIPS, sys.PPC64, sys.Wasm) addF("math/bits", "TrailingZeros16", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - x := s.newValue1(ssa.OpZeroExt16to32, types.Types[TUINT32], args[0]) - c := s.constInt32(types.Types[TUINT32], 1<<16) - y := s.newValue2(ssa.OpOr32, types.Types[TUINT32], x, c) - return s.newValue1(ssa.OpCtz32, types.Types[TINT], y) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + x := s.newValue1(ssa.OpZeroExt16to32, types.Types[types.TUINT32], args[0]) + c := s.constInt32(types.Types[types.TUINT32], 1<<16) + y := s.newValue2(ssa.OpOr32, types.Types[types.TUINT32], x, c) + return s.newValue1(ssa.OpCtz32, types.Types[types.TINT], y) }, sys.MIPS) addF("math/bits", "TrailingZeros16", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpCtz16, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpCtz16, types.Types[types.TINT], args[0]) }, sys.AMD64, sys.I386, sys.ARM, sys.ARM64, sys.Wasm) addF("math/bits", "TrailingZeros16", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - x := s.newValue1(ssa.OpZeroExt16to64, types.Types[TUINT64], args[0]) - c := s.constInt64(types.Types[TUINT64], 1<<16) - y := s.newValue2(ssa.OpOr64, types.Types[TUINT64], x, c) - return s.newValue1(ssa.OpCtz64, types.Types[TINT], y) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + x := s.newValue1(ssa.OpZeroExt16to64, types.Types[types.TUINT64], args[0]) + c := s.constInt64(types.Types[types.TUINT64], 1<<16) + y := s.newValue2(ssa.OpOr64, types.Types[types.TUINT64], x, c) + return s.newValue1(ssa.OpCtz64, types.Types[types.TINT], y) }, sys.S390X, sys.PPC64) addF("math/bits", "TrailingZeros8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - x := s.newValue1(ssa.OpZeroExt8to32, types.Types[TUINT32], args[0]) - c := s.constInt32(types.Types[TUINT32], 1<<8) - y := s.newValue2(ssa.OpOr32, types.Types[TUINT32], x, c) - return s.newValue1(ssa.OpCtz32, types.Types[TINT], y) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + x := s.newValue1(ssa.OpZeroExt8to32, types.Types[types.TUINT32], args[0]) + c := s.constInt32(types.Types[types.TUINT32], 1<<8) + y := s.newValue2(ssa.OpOr32, types.Types[types.TUINT32], x, c) + return s.newValue1(ssa.OpCtz32, types.Types[types.TINT], y) }, sys.MIPS) addF("math/bits", "TrailingZeros8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpCtz8, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpCtz8, types.Types[types.TINT], args[0]) }, sys.AMD64, sys.ARM, sys.ARM64, sys.Wasm) addF("math/bits", "TrailingZeros8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - x := s.newValue1(ssa.OpZeroExt8to64, types.Types[TUINT64], args[0]) - c := s.constInt64(types.Types[TUINT64], 1<<8) - y := s.newValue2(ssa.OpOr64, types.Types[TUINT64], x, c) - return s.newValue1(ssa.OpCtz64, types.Types[TINT], y) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + x := s.newValue1(ssa.OpZeroExt8to64, types.Types[types.TUINT64], args[0]) + c := s.constInt64(types.Types[types.TUINT64], 1<<8) + y := s.newValue2(ssa.OpOr64, types.Types[types.TUINT64], x, c) + return s.newValue1(ssa.OpCtz64, types.Types[types.TINT], y) }, sys.S390X) alias("math/bits", "ReverseBytes64", "runtime/internal/sys", "Bswap64", all...) @@ -3916,116 +4359,116 @@ func init() { // ReverseBytes inlines correctly, no need to intrinsify it. // ReverseBytes16 lowers to a rotate, no need for anything special here. addF("math/bits", "Len64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpBitLen64, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpBitLen64, types.Types[types.TINT], args[0]) }, sys.AMD64, sys.ARM64, sys.ARM, sys.S390X, sys.MIPS, sys.PPC64, sys.Wasm) addF("math/bits", "Len32", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpBitLen32, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpBitLen32, types.Types[types.TINT], args[0]) }, sys.AMD64, sys.ARM64) addF("math/bits", "Len32", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { if s.config.PtrSize == 4 { - return s.newValue1(ssa.OpBitLen32, types.Types[TINT], args[0]) + return s.newValue1(ssa.OpBitLen32, types.Types[types.TINT], args[0]) } - x := s.newValue1(ssa.OpZeroExt32to64, types.Types[TUINT64], args[0]) - return s.newValue1(ssa.OpBitLen64, types.Types[TINT], x) + x := s.newValue1(ssa.OpZeroExt32to64, types.Types[types.TUINT64], args[0]) + return s.newValue1(ssa.OpBitLen64, types.Types[types.TINT], x) }, sys.ARM, sys.S390X, sys.MIPS, sys.PPC64, sys.Wasm) addF("math/bits", "Len16", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { if s.config.PtrSize == 4 { - x := s.newValue1(ssa.OpZeroExt16to32, types.Types[TUINT32], args[0]) - return s.newValue1(ssa.OpBitLen32, types.Types[TINT], x) + x := s.newValue1(ssa.OpZeroExt16to32, types.Types[types.TUINT32], args[0]) + return s.newValue1(ssa.OpBitLen32, types.Types[types.TINT], x) } - x := s.newValue1(ssa.OpZeroExt16to64, types.Types[TUINT64], args[0]) - return s.newValue1(ssa.OpBitLen64, types.Types[TINT], x) + x := s.newValue1(ssa.OpZeroExt16to64, types.Types[types.TUINT64], args[0]) + return s.newValue1(ssa.OpBitLen64, types.Types[types.TINT], x) }, sys.ARM64, sys.ARM, sys.S390X, sys.MIPS, sys.PPC64, sys.Wasm) addF("math/bits", "Len16", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpBitLen16, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpBitLen16, types.Types[types.TINT], args[0]) }, sys.AMD64) addF("math/bits", "Len8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { if s.config.PtrSize == 4 { - x := s.newValue1(ssa.OpZeroExt8to32, types.Types[TUINT32], args[0]) - return s.newValue1(ssa.OpBitLen32, types.Types[TINT], x) + x := s.newValue1(ssa.OpZeroExt8to32, types.Types[types.TUINT32], args[0]) + return s.newValue1(ssa.OpBitLen32, types.Types[types.TINT], x) } - x := s.newValue1(ssa.OpZeroExt8to64, types.Types[TUINT64], args[0]) - return s.newValue1(ssa.OpBitLen64, types.Types[TINT], x) + x := s.newValue1(ssa.OpZeroExt8to64, types.Types[types.TUINT64], args[0]) + return s.newValue1(ssa.OpBitLen64, types.Types[types.TINT], x) }, sys.ARM64, sys.ARM, sys.S390X, sys.MIPS, sys.PPC64, sys.Wasm) addF("math/bits", "Len8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpBitLen8, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpBitLen8, types.Types[types.TINT], args[0]) }, sys.AMD64) addF("math/bits", "Len", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { if s.config.PtrSize == 4 { - return s.newValue1(ssa.OpBitLen32, types.Types[TINT], args[0]) + return s.newValue1(ssa.OpBitLen32, types.Types[types.TINT], args[0]) } - return s.newValue1(ssa.OpBitLen64, types.Types[TINT], args[0]) + return s.newValue1(ssa.OpBitLen64, types.Types[types.TINT], args[0]) }, sys.AMD64, sys.ARM64, sys.ARM, sys.S390X, sys.MIPS, sys.PPC64, sys.Wasm) // LeadingZeros is handled because it trivially calls Len. addF("math/bits", "Reverse64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpBitRev64, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpBitRev64, types.Types[types.TINT], args[0]) }, sys.ARM64) addF("math/bits", "Reverse32", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpBitRev32, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpBitRev32, types.Types[types.TINT], args[0]) }, sys.ARM64) addF("math/bits", "Reverse16", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpBitRev16, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpBitRev16, types.Types[types.TINT], args[0]) }, sys.ARM64) addF("math/bits", "Reverse8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpBitRev8, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpBitRev8, types.Types[types.TINT], args[0]) }, sys.ARM64) addF("math/bits", "Reverse", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { if s.config.PtrSize == 4 { - return s.newValue1(ssa.OpBitRev32, types.Types[TINT], args[0]) + return s.newValue1(ssa.OpBitRev32, types.Types[types.TINT], args[0]) } - return s.newValue1(ssa.OpBitRev64, types.Types[TINT], args[0]) + return s.newValue1(ssa.OpBitRev64, types.Types[types.TINT], args[0]) }, sys.ARM64) addF("math/bits", "RotateLeft8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue2(ssa.OpRotateLeft8, types.Types[TUINT8], args[0], args[1]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue2(ssa.OpRotateLeft8, types.Types[types.TUINT8], args[0], args[1]) }, sys.AMD64) addF("math/bits", "RotateLeft16", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue2(ssa.OpRotateLeft16, types.Types[TUINT16], args[0], args[1]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue2(ssa.OpRotateLeft16, types.Types[types.TUINT16], args[0], args[1]) }, sys.AMD64) addF("math/bits", "RotateLeft32", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue2(ssa.OpRotateLeft32, types.Types[TUINT32], args[0], args[1]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue2(ssa.OpRotateLeft32, types.Types[types.TUINT32], args[0], args[1]) }, sys.AMD64, sys.ARM, sys.ARM64, sys.S390X, sys.PPC64, sys.Wasm) addF("math/bits", "RotateLeft64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue2(ssa.OpRotateLeft64, types.Types[TUINT64], args[0], args[1]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue2(ssa.OpRotateLeft64, types.Types[types.TUINT64], args[0], args[1]) }, sys.AMD64, sys.ARM64, sys.S390X, sys.PPC64, sys.Wasm) alias("math/bits", "RotateLeft", "math/bits", "RotateLeft64", p8...) - makeOnesCountAMD64 := func(op64 ssa.Op, op32 ssa.Op) func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - v := s.entryNewValue0A(ssa.OpHasCPUFeature, types.Types[TBOOL], x86HasPOPCNT) + makeOnesCountAMD64 := func(op64 ssa.Op, op32 ssa.Op) func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + v := s.entryNewValue0A(ssa.OpHasCPUFeature, types.Types[types.TBOOL], ir.Syms.X86HasPOPCNT) b := s.endBlock() b.Kind = ssa.BlockIf b.SetControl(v) @@ -4042,7 +4485,7 @@ func init() { if s.config.PtrSize == 4 { op = op32 } - s.vars[n] = s.newValue1(op, types.Types[TINT], args[0]) + s.vars[n] = s.newValue1(op, types.Types[types.TINT], args[0]) s.endBlock().AddEdgeTo(bEnd) // Call the pure Go version. @@ -4052,67 +4495,68 @@ func init() { // Merge results. s.startBlock(bEnd) - return s.variable(n, types.Types[TINT]) + return s.variable(n, types.Types[types.TINT]) } } addF("math/bits", "OnesCount64", makeOnesCountAMD64(ssa.OpPopCount64, ssa.OpPopCount64), sys.AMD64) addF("math/bits", "OnesCount64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpPopCount64, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpPopCount64, types.Types[types.TINT], args[0]) }, sys.PPC64, sys.ARM64, sys.S390X, sys.Wasm) addF("math/bits", "OnesCount32", makeOnesCountAMD64(ssa.OpPopCount32, ssa.OpPopCount32), sys.AMD64) addF("math/bits", "OnesCount32", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpPopCount32, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpPopCount32, types.Types[types.TINT], args[0]) }, sys.PPC64, sys.ARM64, sys.S390X, sys.Wasm) addF("math/bits", "OnesCount16", makeOnesCountAMD64(ssa.OpPopCount16, ssa.OpPopCount16), sys.AMD64) addF("math/bits", "OnesCount16", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpPopCount16, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpPopCount16, types.Types[types.TINT], args[0]) }, sys.ARM64, sys.S390X, sys.PPC64, sys.Wasm) addF("math/bits", "OnesCount8", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue1(ssa.OpPopCount8, types.Types[TINT], args[0]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue1(ssa.OpPopCount8, types.Types[types.TINT], args[0]) }, sys.S390X, sys.PPC64, sys.Wasm) addF("math/bits", "OnesCount", makeOnesCountAMD64(ssa.OpPopCount64, ssa.OpPopCount32), sys.AMD64) addF("math/bits", "Mul64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue2(ssa.OpMul64uhilo, types.NewTuple(types.Types[TUINT64], types.Types[TUINT64]), args[0], args[1]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue2(ssa.OpMul64uhilo, types.NewTuple(types.Types[types.TUINT64], types.Types[types.TUINT64]), args[0], args[1]) }, sys.AMD64, sys.ARM64, sys.PPC64, sys.S390X, sys.MIPS64) - alias("math/bits", "Mul", "math/bits", "Mul64", sys.ArchAMD64, sys.ArchARM64, sys.ArchPPC64, sys.ArchS390X, sys.ArchMIPS64, sys.ArchMIPS64LE) + alias("math/bits", "Mul", "math/bits", "Mul64", sys.ArchAMD64, sys.ArchARM64, sys.ArchPPC64, sys.ArchPPC64LE, sys.ArchS390X, sys.ArchMIPS64, sys.ArchMIPS64LE) + alias("runtime/internal/math", "Mul64", "math/bits", "Mul64", sys.ArchAMD64, sys.ArchARM64, sys.ArchPPC64, sys.ArchPPC64LE, sys.ArchS390X, sys.ArchMIPS64, sys.ArchMIPS64LE) addF("math/bits", "Add64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue3(ssa.OpAdd64carry, types.NewTuple(types.Types[TUINT64], types.Types[TUINT64]), args[0], args[1], args[2]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue3(ssa.OpAdd64carry, types.NewTuple(types.Types[types.TUINT64], types.Types[types.TUINT64]), args[0], args[1], args[2]) }, sys.AMD64, sys.ARM64, sys.PPC64, sys.S390X) - alias("math/bits", "Add", "math/bits", "Add64", sys.ArchAMD64, sys.ArchARM64, sys.ArchPPC64, sys.ArchS390X) + alias("math/bits", "Add", "math/bits", "Add64", sys.ArchAMD64, sys.ArchARM64, sys.ArchPPC64, sys.ArchPPC64LE, sys.ArchS390X) addF("math/bits", "Sub64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue3(ssa.OpSub64borrow, types.NewTuple(types.Types[TUINT64], types.Types[TUINT64]), args[0], args[1], args[2]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue3(ssa.OpSub64borrow, types.NewTuple(types.Types[types.TUINT64], types.Types[types.TUINT64]), args[0], args[1], args[2]) }, sys.AMD64, sys.ARM64, sys.S390X) alias("math/bits", "Sub", "math/bits", "Sub64", sys.ArchAMD64, sys.ArchARM64, sys.ArchS390X) addF("math/bits", "Div64", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { // check for divide-by-zero/overflow and panic with appropriate message - cmpZero := s.newValue2(s.ssaOp(ONE, types.Types[TUINT64]), types.Types[TBOOL], args[2], s.zeroVal(types.Types[TUINT64])) - s.check(cmpZero, panicdivide) - cmpOverflow := s.newValue2(s.ssaOp(OLT, types.Types[TUINT64]), types.Types[TBOOL], args[0], args[2]) - s.check(cmpOverflow, panicoverflow) - return s.newValue3(ssa.OpDiv128u, types.NewTuple(types.Types[TUINT64], types.Types[TUINT64]), args[0], args[1], args[2]) + cmpZero := s.newValue2(s.ssaOp(ir.ONE, types.Types[types.TUINT64]), types.Types[types.TBOOL], args[2], s.zeroVal(types.Types[types.TUINT64])) + s.check(cmpZero, ir.Syms.Panicdivide) + cmpOverflow := s.newValue2(s.ssaOp(ir.OLT, types.Types[types.TUINT64]), types.Types[types.TBOOL], args[0], args[2]) + s.check(cmpOverflow, ir.Syms.Panicoverflow) + return s.newValue3(ssa.OpDiv128u, types.NewTuple(types.Types[types.TUINT64], types.Types[types.TUINT64]), args[0], args[1], args[2]) }, sys.AMD64) alias("math/bits", "Div", "math/bits", "Div64", sys.ArchAMD64) @@ -4166,8 +4610,8 @@ func init() { /******** math/big ********/ add("math/big", "mulWW", - func(s *state, n *Node, args []*ssa.Value) *ssa.Value { - return s.newValue2(ssa.OpMul64uhilo, types.NewTuple(types.Types[TUINT64], types.Types[TUINT64]), args[0], args[1]) + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue2(ssa.OpMul64uhilo, types.NewTuple(types.Types[types.TUINT64], types.Types[types.TUINT64]), args[0], args[1]) }, sys.ArchAMD64, sys.ArchARM64, sys.ArchPPC64LE, sys.ArchPPC64, sys.ArchS390X) } @@ -4179,17 +4623,20 @@ func findIntrinsic(sym *types.Sym) intrinsicBuilder { return nil } pkg := sym.Pkg.Path - if sym.Pkg == localpkg { - pkg = myimportpath + if sym.Pkg == types.LocalPkg { + pkg = base.Ctxt.Pkgpath + } + if sym.Pkg == ir.Pkgs.Runtime { + pkg = "runtime" } - if flag_race && pkg == "sync/atomic" { + if base.Flag.Race && pkg == "sync/atomic" { // The race detector needs to be able to intercept these calls. // We can't intrinsify them. return nil } // Skip intrinsifying math functions (which may contain hard-float // instructions) when soft-float - if thearch.SoftFloat && pkg == "math" { + if Arch.SoftFloat && pkg == "math" { return nil } @@ -4201,19 +4648,23 @@ func findIntrinsic(sym *types.Sym) intrinsicBuilder { return nil } } - return intrinsics[intrinsicKey{thearch.LinkArch.Arch, pkg, fn}] + return intrinsics[intrinsicKey{Arch.LinkArch.Arch, pkg, fn}] } -func isIntrinsicCall(n *Node) bool { - if n == nil || n.Left == nil { +func IsIntrinsicCall(n *ir.CallExpr) bool { + if n == nil { return false } - return findIntrinsic(n.Left.Sym) != nil + name, ok := n.X.(*ir.Name) + if !ok { + return false + } + return findIntrinsic(name.Sym()) != nil } // intrinsicCall converts a call to a recognized intrinsic function into the intrinsic SSA operation. -func (s *state) intrinsicCall(n *Node) *ssa.Value { - v := findIntrinsic(n.Left.Sym)(s, n, s.intrinsicArgs(n)) +func (s *state) intrinsicCall(n *ir.CallExpr) *ssa.Value { + v := findIntrinsic(n.X.Sym())(s, n, s.intrinsicArgs(n)) if ssa.IntrinsicsDebug > 0 { x := v if x == nil { @@ -4222,36 +4673,15 @@ func (s *state) intrinsicCall(n *Node) *ssa.Value { if x.Op == ssa.OpSelect0 || x.Op == ssa.OpSelect1 { x = x.Args[0] } - Warnl(n.Pos, "intrinsic substitution for %v with %s", n.Left.Sym.Name, x.LongString()) + base.WarnfAt(n.Pos(), "intrinsic substitution for %v with %s", n.X.Sym().Name, x.LongString()) } return v } // intrinsicArgs extracts args from n, evaluates them to SSA values, and returns them. -func (s *state) intrinsicArgs(n *Node) []*ssa.Value { - // Construct map of temps; see comments in s.call about the structure of n. - temps := map[*Node]*ssa.Value{} - for _, a := range n.List.Slice() { - if a.Op != OAS { - s.Fatalf("non-assignment as a temp function argument %v", a.Op) - } - l, r := a.Left, a.Right - if l.Op != ONAME { - s.Fatalf("non-ONAME temp function argument %v", a.Op) - } - // Evaluate and store to "temporary". - // Walk ensures these temporaries are dead outside of n. - temps[l] = s.expr(r) - } - args := make([]*ssa.Value, n.Rlist.Len()) - for i, n := range n.Rlist.Slice() { - // Store a value to an argument slot. - if x, ok := temps[n]; ok { - // This is a previously computed temporary. - args[i] = x - continue - } - // This is an explicit value; evaluate it. +func (s *state) intrinsicArgs(n *ir.CallExpr) []*ssa.Value { + args := make([]*ssa.Value, len(n.Args)) + for i, n := range n.Args { args[i] = s.expr(n) } return args @@ -4263,62 +4693,52 @@ func (s *state) intrinsicArgs(n *Node) []*ssa.Value { // call. We will also record funcdata information on where the args are stored // (as well as the deferBits variable), and this will enable us to run the proper // defer calls during panics. -func (s *state) openDeferRecord(n *Node) { - // Do any needed expression evaluation for the args (including the - // receiver, if any). This may be evaluating something like 'autotmp_3 = - // once.mutex'. Such a statement will create a mapping in s.vars[] from - // the autotmp name to the evaluated SSA arg value, but won't do any - // stores to the stack. - s.stmtList(n.List) - +func (s *state) openDeferRecord(n *ir.CallExpr) { var args []*ssa.Value - var argNodes []*Node + var argNodes []*ir.Name + + if buildcfg.Experiment.RegabiDefer && (len(n.Args) != 0 || n.Op() == ir.OCALLINTER || n.X.Type().NumResults() != 0) { + s.Fatalf("defer call with arguments or results: %v", n) + } opendefer := &openDeferInfo{ n: n, } - fn := n.Left - if n.Op == OCALLFUNC { + fn := n.X + if n.Op() == ir.OCALLFUNC { // We must always store the function value in a stack slot for the // runtime panic code to use. But in the defer exit code, we will // call the function directly if it is a static function. closureVal := s.expr(fn) - closure := s.openDeferSave(nil, fn.Type, closureVal) - opendefer.closureNode = closure.Aux.(*Node) - if !(fn.Op == ONAME && fn.Class() == PFUNC) { + closure := s.openDeferSave(nil, fn.Type(), closureVal) + opendefer.closureNode = closure.Aux.(*ir.Name) + if !(fn.Op() == ir.ONAME && fn.(*ir.Name).Class == ir.PFUNC) { opendefer.closure = closure } - } else if n.Op == OCALLMETH { - if fn.Op != ODOTMETH { - Fatalf("OCALLMETH: n.Left not an ODOTMETH: %v", fn) - } - closureVal := s.getMethodClosure(fn) - // We must always store the function value in a stack slot for the - // runtime panic code to use. But in the defer exit code, we will - // call the method directly. - closure := s.openDeferSave(nil, fn.Type, closureVal) - opendefer.closureNode = closure.Aux.(*Node) + } else if n.Op() == ir.OCALLMETH { + base.Fatalf("OCALLMETH missed by walkCall") } else { - if fn.Op != ODOTINTER { - Fatalf("OCALLINTER: n.Left not an ODOTINTER: %v", fn.Op) + if fn.Op() != ir.ODOTINTER { + base.Fatalf("OCALLINTER: n.Left not an ODOTINTER: %v", fn.Op()) } + fn := fn.(*ir.SelectorExpr) closure, rcvr := s.getClosureAndRcvr(fn) opendefer.closure = s.openDeferSave(nil, closure.Type, closure) // Important to get the receiver type correct, so it is recognized // as a pointer for GC purposes. - opendefer.rcvr = s.openDeferSave(nil, fn.Type.Recv().Type, rcvr) - opendefer.closureNode = opendefer.closure.Aux.(*Node) - opendefer.rcvrNode = opendefer.rcvr.Aux.(*Node) + opendefer.rcvr = s.openDeferSave(nil, fn.Type().Recv().Type, rcvr) + opendefer.closureNode = opendefer.closure.Aux.(*ir.Name) + opendefer.rcvrNode = opendefer.rcvr.Aux.(*ir.Name) } - for _, argn := range n.Rlist.Slice() { + for _, argn := range n.Args { var v *ssa.Value - if canSSAType(argn.Type) { - v = s.openDeferSave(nil, argn.Type, s.expr(argn)) + if TypeOK(argn.Type()) { + v = s.openDeferSave(nil, argn.Type(), s.expr(argn)) } else { - v = s.openDeferSave(argn, argn.Type, nil) + v = s.openDeferSave(argn, argn.Type(), nil) } args = append(args, v) - argNodes = append(argNodes, v.Aux.(*Node)) + argNodes = append(argNodes, v.Aux.(*ir.Name)) } opendefer.argVals = args opendefer.argNodes = argNodes @@ -4327,10 +4747,10 @@ func (s *state) openDeferRecord(n *Node) { // Update deferBits only after evaluation and storage to stack of // args/receiver/interface is successful. - bitvalue := s.constInt8(types.Types[TUINT8], 1<= 0; i-- { r := s.openDefers[i] bCond := s.f.NewBlock(ssa.BlockPlain) bEnd := s.f.NewBlock(ssa.BlockPlain) - deferBits := s.variable(&deferBitsVar, types.Types[TUINT8]) + deferBits := s.variable(deferBitsVar, types.Types[types.TUINT8]) // Generate code to check if the bit associated with the current // defer is set. - bitval := s.constInt8(types.Types[TUINT8], 1< int64(4*Widthptr) { +// TypeOK reports whether variables of type t are SSA-able. +func TypeOK(t *types.Type) bool { + types.CalcSize(t) + if t.Width > int64(4*types.PtrSize) { // 4*Widthptr is an arbitrary constant. We want it // to be at least 3*Widthptr so slices can be registerized. // Too big and we'll introduce too much register pressure. return false } - switch t.Etype { - case TARRAY: + switch t.Kind() { + case types.TARRAY: // We can't do larger arrays because dynamic indexing is // not supported on SSA variables. // TODO: allow if all indexes are constant. if t.NumElem() <= 1 { - return canSSAType(t.Elem()) + return TypeOK(t.Elem()) } return false - case TSTRUCT: + case types.TSTRUCT: if t.NumFields() > ssa.MaxStruct { return false } for _, t1 := range t.Fields().Slice() { - if !canSSAType(t1.Type) { + if !TypeOK(t1.Type) { return false } } @@ -5060,7 +5422,7 @@ func canSSAType(t *types.Type) bool { } // exprPtr evaluates n to a pointer and nil-checks it. -func (s *state) exprPtr(n *Node, bounded bool, lineno src.XPos) *ssa.Value { +func (s *state) exprPtr(n ir.Node, bounded bool, lineno src.XPos) *ssa.Value { p := s.expr(n) if bounded || n.NonNil() { if s.f.Frontend().Debug_checknil() && lineno.Line() > 1 { @@ -5076,7 +5438,7 @@ func (s *state) exprPtr(n *Node, bounded bool, lineno src.XPos) *ssa.Value { // Used only for automatically inserted nil checks, // not for user code like 'x != nil'. func (s *state) nilCheck(ptr *ssa.Value) { - if disable_checknil != 0 || s.curfn.Func.NilCheckDisabled() { + if base.Debug.DisableNil != 0 || s.curfn.NilCheckDisabled() { return } s.newValue2(ssa.OpNilCheck, types.TypeVoid, ptr, s.mem()) @@ -5091,7 +5453,7 @@ func (s *state) nilCheck(ptr *ssa.Value) { func (s *state) boundsCheck(idx, len *ssa.Value, kind ssa.BoundsKind, bounded bool) *ssa.Value { idx = s.extendIndex(idx, len, kind, bounded) - if bounded || Debug.B != 0 { + if bounded || base.Flag.B != 0 { // If bounded or bounds checking is flag-disabled, then no check necessary, // just return the extended index. // @@ -5141,9 +5503,9 @@ func (s *state) boundsCheck(idx, len *ssa.Value, kind ssa.BoundsKind, bounded bo var cmp *ssa.Value if kind == ssa.BoundsIndex || kind == ssa.BoundsIndexU { - cmp = s.newValue2(ssa.OpIsInBounds, types.Types[TBOOL], idx, len) + cmp = s.newValue2(ssa.OpIsInBounds, types.Types[types.TBOOL], idx, len) } else { - cmp = s.newValue2(ssa.OpIsSliceInBounds, types.Types[TBOOL], idx, len) + cmp = s.newValue2(ssa.OpIsSliceInBounds, types.Types[types.TBOOL], idx, len) } b := s.endBlock() b.Kind = ssa.BlockIf @@ -5153,7 +5515,7 @@ func (s *state) boundsCheck(idx, len *ssa.Value, kind ssa.BoundsKind, bounded bo b.AddEdgeTo(bPanic) s.startBlock(bPanic) - if thearch.LinkArch.Family == sys.Wasm { + if Arch.LinkArch.Family == sys.Wasm { // TODO(khr): figure out how to do "register" based calling convention for bounds checks. // Should be similar to gcWriteBarrier, but I can't make it work. s.rtcall(BoundsCheckFunc[kind], false, nil, idx, len) @@ -5164,12 +5526,12 @@ func (s *state) boundsCheck(idx, len *ssa.Value, kind ssa.BoundsKind, bounded bo s.startBlock(bNext) // In Spectre index mode, apply an appropriate mask to avoid speculative out-of-bounds accesses. - if spectreIndex { + if base.Flag.Cfg.SpectreIndex { op := ssa.OpSpectreIndex if kind != ssa.BoundsIndex && kind != ssa.BoundsIndexU { op = ssa.OpSpectreSliceIndex } - idx = s.newValue2(op, types.Types[TINT], idx, len) + idx = s.newValue2(op, types.Types[types.TINT], idx, len) } return idx @@ -5183,7 +5545,7 @@ func (s *state) check(cmp *ssa.Value, fn *obj.LSym) { b.Likely = ssa.BranchLikely bNext := s.f.NewBlock(ssa.BlockPlain) line := s.peekPos() - pos := Ctxt.PosTable.Pos(line) + pos := base.Ctxt.PosTable.Pos(line) fl := funcLine{f: fn, base: pos.Base(), line: pos.Line()} bPanic := s.panics[fl] if bPanic == nil { @@ -5199,7 +5561,7 @@ func (s *state) check(cmp *ssa.Value, fn *obj.LSym) { s.startBlock(bNext) } -func (s *state) intDivide(n *Node, a, b *ssa.Value) *ssa.Value { +func (s *state) intDivide(n ir.Node, a, b *ssa.Value) *ssa.Value { needcheck := true switch b.Op { case ssa.OpConst8, ssa.OpConst16, ssa.OpConst32, ssa.OpConst64: @@ -5209,10 +5571,10 @@ func (s *state) intDivide(n *Node, a, b *ssa.Value) *ssa.Value { } if needcheck { // do a size-appropriate check for zero - cmp := s.newValue2(s.ssaOp(ONE, n.Type), types.Types[TBOOL], b, s.zeroVal(n.Type)) - s.check(cmp, panicdivide) + cmp := s.newValue2(s.ssaOp(ir.ONE, n.Type()), types.Types[types.TBOOL], b, s.zeroVal(n.Type())) + s.check(cmp, ir.Syms.Panicdivide) } - return s.newValue2(s.ssaOp(n.Op, n.Type), a.Type, a, b) + return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b) } // rtcall issues a call to the given runtime function fn with the listed args. @@ -5222,54 +5584,41 @@ func (s *state) intDivide(n *Node, a, b *ssa.Value) *ssa.Value { func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args ...*ssa.Value) []*ssa.Value { s.prevCall = nil // Write args to the stack - off := Ctxt.FixedFrameSize() - testLateExpansion := ssa.LateCallExpansionEnabledWithin(s.f) - var ACArgs []ssa.Param - var ACResults []ssa.Param + off := base.Ctxt.FixedFrameSize() var callArgs []*ssa.Value + var callArgTypes []*types.Type for _, arg := range args { t := arg.Type - off = Rnd(off, t.Alignment()) + off = types.Rnd(off, t.Alignment()) size := t.Size() - ACArgs = append(ACArgs, ssa.Param{Type: t, Offset: int32(off)}) - if testLateExpansion { - callArgs = append(callArgs, arg) - } else { - ptr := s.constOffPtrSP(t.PtrTo(), off) - s.store(t, ptr, arg) - } + callArgs = append(callArgs, arg) + callArgTypes = append(callArgTypes, t) off += size } - off = Rnd(off, int64(Widthreg)) + off = types.Rnd(off, int64(types.RegSize)) // Accumulate results types and offsets offR := off for _, t := range results { - offR = Rnd(offR, t.Alignment()) - ACResults = append(ACResults, ssa.Param{Type: t, Offset: int32(offR)}) + offR = types.Rnd(offR, t.Alignment()) offR += t.Size() } // Issue call var call *ssa.Value - aux := ssa.StaticAuxCall(fn, ACArgs, ACResults) - if testLateExpansion { - callArgs = append(callArgs, s.mem()) - call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux) - call.AddArgs(callArgs...) - s.vars[&memVar] = s.newValue1I(ssa.OpSelectN, types.TypeMem, int64(len(ACResults)), call) - } else { - call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, aux, s.mem()) - s.vars[&memVar] = call - } + aux := ssa.StaticAuxCall(fn, s.f.ABIDefault.ABIAnalyzeTypes(nil, callArgTypes, results)) + callArgs = append(callArgs, s.mem()) + call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux) + call.AddArgs(callArgs...) + s.vars[memVar] = s.newValue1I(ssa.OpSelectN, types.TypeMem, int64(len(results)), call) if !returns { // Finish block b := s.endBlock() b.Kind = ssa.BlockExit b.SetControl(call) - call.AuxInt = off - Ctxt.FixedFrameSize() + call.AuxInt = off - base.Ctxt.FixedFrameSize() if len(results) > 0 { s.Fatalf("panic call can't have results") } @@ -5278,26 +5627,12 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args . // Load results res := make([]*ssa.Value, len(results)) - if testLateExpansion { - for i, t := range results { - off = Rnd(off, t.Alignment()) - if canSSAType(t) { - res[i] = s.newValue1I(ssa.OpSelectN, t, int64(i), call) - } else { - addr := s.newValue1I(ssa.OpSelectNAddr, types.NewPtr(t), int64(i), call) - res[i] = s.rawLoad(t, addr) - } - off += t.Size() - } - } else { - for i, t := range results { - off = Rnd(off, t.Alignment()) - ptr := s.constOffPtrSP(types.NewPtr(t), off) - res[i] = s.load(t, ptr) - off += t.Size() - } + for i, t := range results { + off = types.Rnd(off, t.Alignment()) + res[i] = s.resultOfCall(call, int64(i), t) + off += t.Size() } - off = Rnd(off, int64(Widthptr)) + off = types.Rnd(off, int64(types.PtrSize)) // Remember how much callee stack space we needed. call.AuxInt = off @@ -5311,7 +5646,7 @@ func (s *state) storeType(t *types.Type, left, right *ssa.Value, skip skipMask, if skip == 0 && (!t.HasPointers() || ssa.IsStackAddr(left)) { // Known to not have write barrier. Store the whole type. - s.vars[&memVar] = s.newValue3Apos(ssa.OpStore, types.TypeMem, t, left, right, s.mem(), leftIsStmt) + s.vars[memVar] = s.newValue3Apos(ssa.OpStore, types.TypeMem, t, left, right, s.mem(), leftIsStmt) return } @@ -5340,24 +5675,24 @@ func (s *state) storeTypeScalars(t *types.Type, left, right *ssa.Value, skip ski if skip&skipLen != 0 { return } - len := s.newValue1(ssa.OpStringLen, types.Types[TINT], right) + len := s.newValue1(ssa.OpStringLen, types.Types[types.TINT], right) lenAddr := s.newValue1I(ssa.OpOffPtr, s.f.Config.Types.IntPtr, s.config.PtrSize, left) - s.store(types.Types[TINT], lenAddr, len) + s.store(types.Types[types.TINT], lenAddr, len) case t.IsSlice(): if skip&skipLen == 0 { - len := s.newValue1(ssa.OpSliceLen, types.Types[TINT], right) + len := s.newValue1(ssa.OpSliceLen, types.Types[types.TINT], right) lenAddr := s.newValue1I(ssa.OpOffPtr, s.f.Config.Types.IntPtr, s.config.PtrSize, left) - s.store(types.Types[TINT], lenAddr, len) + s.store(types.Types[types.TINT], lenAddr, len) } if skip&skipCap == 0 { - cap := s.newValue1(ssa.OpSliceCap, types.Types[TINT], right) + cap := s.newValue1(ssa.OpSliceCap, types.Types[types.TINT], right) capAddr := s.newValue1I(ssa.OpOffPtr, s.f.Config.Types.IntPtr, 2*s.config.PtrSize, left) - s.store(types.Types[TINT], capAddr, cap) + s.store(types.Types[types.TINT], capAddr, cap) } case t.IsInterface(): // itab field doesn't need a write barrier (even though it is a pointer). itab := s.newValue1(ssa.OpITab, s.f.Config.Types.BytePtr, right) - s.store(types.Types[TUINTPTR], left, itab) + s.store(types.Types[types.TUINTPTR], left, itab) case t.IsStruct(): n := t.NumFields() for i := 0; i < n; i++ { @@ -5415,24 +5750,18 @@ func (s *state) storeTypePtrs(t *types.Type, left, right *ssa.Value) { } } -// putArg evaluates n for the purpose of passing it as an argument to a function and returns the corresponding Param for the call. -// If forLateExpandedCall is true, it returns the argument value to pass to the call operation. -// If forLateExpandedCall is false, then the value is stored at the specified stack offset, and the returned value is nil. -func (s *state) putArg(n *Node, t *types.Type, off int64, forLateExpandedCall bool) (ssa.Param, *ssa.Value) { +// putArg evaluates n for the purpose of passing it as an argument to a function and returns the value for the call. +func (s *state) putArg(n ir.Node, t *types.Type) *ssa.Value { var a *ssa.Value - if forLateExpandedCall { - if !canSSAType(t) { - a = s.newValue2(ssa.OpDereference, t, s.addr(n), s.mem()) - } else { - a = s.expr(n) - } + if !TypeOK(t) { + a = s.newValue2(ssa.OpDereference, t, s.addr(n), s.mem()) } else { - s.storeArgWithBase(n, t, s.sp, off) + a = s.expr(n) } - return ssa.Param{Type: t, Offset: int32(off)}, a + return a } -func (s *state) storeArgWithBase(n *Node, t *types.Type, base *ssa.Value, off int64) { +func (s *state) storeArgWithBase(n ir.Node, t *types.Type, base *ssa.Value, off int64) { pt := types.NewPtr(t) var addr *ssa.Value if base == s.sp { @@ -5442,7 +5771,7 @@ func (s *state) storeArgWithBase(n *Node, t *types.Type, base *ssa.Value, off in addr = s.newValue1I(ssa.OpOffPtr, pt, off, base) } - if !canSSAType(t) { + if !TypeOK(t) { a := s.addr(n) s.move(t, addr, a) return @@ -5461,11 +5790,11 @@ func (s *state) slice(v, i, j, k *ssa.Value, bounded bool) (p, l, c *ssa.Value) switch { case t.IsSlice(): ptr = s.newValue1(ssa.OpSlicePtr, types.NewPtr(t.Elem()), v) - len = s.newValue1(ssa.OpSliceLen, types.Types[TINT], v) - cap = s.newValue1(ssa.OpSliceCap, types.Types[TINT], v) + len = s.newValue1(ssa.OpSliceLen, types.Types[types.TINT], v) + cap = s.newValue1(ssa.OpSliceCap, types.Types[types.TINT], v) case t.IsString(): - ptr = s.newValue1(ssa.OpStringPtr, types.NewPtr(types.Types[TUINT8]), v) - len = s.newValue1(ssa.OpStringLen, types.Types[TINT], v) + ptr = s.newValue1(ssa.OpStringPtr, types.NewPtr(types.Types[types.TUINT8]), v) + len = s.newValue1(ssa.OpStringLen, types.Types[types.TINT], v) cap = len case t.IsPtr(): if !t.Elem().IsArray() { @@ -5473,7 +5802,7 @@ func (s *state) slice(v, i, j, k *ssa.Value, bounded bool) (p, l, c *ssa.Value) } s.nilCheck(v) ptr = s.newValue1(ssa.OpCopy, types.NewPtr(t.Elem().Elem()), v) - len = s.constInt(types.Types[TINT], t.Elem().NumElem()) + len = s.constInt(types.Types[types.TINT], t.Elem().NumElem()) cap = len default: s.Fatalf("bad type in slice %v\n", t) @@ -5481,7 +5810,7 @@ func (s *state) slice(v, i, j, k *ssa.Value, bounded bool) (p, l, c *ssa.Value) // Set default values if i == nil { - i = s.constInt(types.Types[TINT], 0) + i = s.constInt(types.Types[types.TINT], 0) } if j == nil { j = len @@ -5519,18 +5848,18 @@ func (s *state) slice(v, i, j, k *ssa.Value, bounded bool) (p, l, c *ssa.Value) } // Word-sized integer operations. - subOp := s.ssaOp(OSUB, types.Types[TINT]) - mulOp := s.ssaOp(OMUL, types.Types[TINT]) - andOp := s.ssaOp(OAND, types.Types[TINT]) + subOp := s.ssaOp(ir.OSUB, types.Types[types.TINT]) + mulOp := s.ssaOp(ir.OMUL, types.Types[types.TINT]) + andOp := s.ssaOp(ir.OAND, types.Types[types.TINT]) // Calculate the length (rlen) and capacity (rcap) of the new slice. // For strings the capacity of the result is unimportant. However, // we use rcap to test if we've generated a zero-length slice. // Use length of strings for that. - rlen := s.newValue2(subOp, types.Types[TINT], j, i) + rlen := s.newValue2(subOp, types.Types[types.TINT], j, i) rcap := rlen if j != k && !t.IsString() { - rcap = s.newValue2(subOp, types.Types[TINT], k, i) + rcap = s.newValue2(subOp, types.Types[types.TINT], k, i) } if (i.Op == ssa.OpConst64 || i.Op == ssa.OpConst32) && i.AuxInt == 0 { @@ -5552,15 +5881,15 @@ func (s *state) slice(v, i, j, k *ssa.Value, bounded bool) (p, l, c *ssa.Value) // // Where mask(x) is 0 if x==0 and -1 if x>0 and stride is the width // of the element type. - stride := s.constInt(types.Types[TINT], ptr.Type.Elem().Width) + stride := s.constInt(types.Types[types.TINT], ptr.Type.Elem().Width) // The delta is the number of bytes to offset ptr by. - delta := s.newValue2(mulOp, types.Types[TINT], i, stride) + delta := s.newValue2(mulOp, types.Types[types.TINT], i, stride) // If we're slicing to the point where the capacity is zero, // zero out the delta. - mask := s.newValue1(ssa.OpSlicemask, types.Types[TINT], rcap) - delta = s.newValue2(andOp, types.Types[TINT], delta, mask) + mask := s.newValue1(ssa.OpSlicemask, types.Types[types.TINT], rcap) + delta = s.newValue2(andOp, types.Types[types.TINT], delta, mask) // Compute rptr = ptr + delta. rptr := s.newValue2(ssa.OpAddPtr, ptr.Type, ptr, delta) @@ -5593,15 +5922,15 @@ var u64_f32 = u642fcvtTab{ one: (*state).constInt64, } -func (s *state) uint64Tofloat64(n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) uint64Tofloat64(n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { return s.uint64Tofloat(&u64_f64, n, x, ft, tt) } -func (s *state) uint64Tofloat32(n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) uint64Tofloat32(n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { return s.uint64Tofloat(&u64_f32, n, x, ft, tt) } -func (s *state) uint64Tofloat(cvttab *u642fcvtTab, n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) uint64Tofloat(cvttab *u642fcvtTab, n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { // if x >= 0 { // result = (floatY) x // } else { @@ -5627,7 +5956,7 @@ func (s *state) uint64Tofloat(cvttab *u642fcvtTab, n *Node, x *ssa.Value, ft, tt // equal to 10000000001; that rounds up, and the 1 cannot // be lost else it would round down if the LSB of the // candidate mantissa is 0. - cmp := s.newValue2(cvttab.leq, types.Types[TBOOL], s.zeroVal(ft), x) + cmp := s.newValue2(cvttab.leq, types.Types[types.TBOOL], s.zeroVal(ft), x) b := s.endBlock() b.Kind = ssa.BlockIf b.SetControl(cmp) @@ -5657,7 +5986,7 @@ func (s *state) uint64Tofloat(cvttab *u642fcvtTab, n *Node, x *ssa.Value, ft, tt bElse.AddEdgeTo(bAfter) s.startBlock(bAfter) - return s.variable(n, n.Type) + return s.variable(n, n.Type()) } type u322fcvtTab struct { @@ -5674,21 +6003,21 @@ var u32_f32 = u322fcvtTab{ cvtF2F: ssa.OpCvt64Fto32F, } -func (s *state) uint32Tofloat64(n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) uint32Tofloat64(n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { return s.uint32Tofloat(&u32_f64, n, x, ft, tt) } -func (s *state) uint32Tofloat32(n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) uint32Tofloat32(n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { return s.uint32Tofloat(&u32_f32, n, x, ft, tt) } -func (s *state) uint32Tofloat(cvttab *u322fcvtTab, n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) uint32Tofloat(cvttab *u322fcvtTab, n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { // if x >= 0 { // result = floatY(x) // } else { // result = floatY(float64(x) + (1<<32)) // } - cmp := s.newValue2(ssa.OpLeq32, types.Types[TBOOL], s.zeroVal(ft), x) + cmp := s.newValue2(ssa.OpLeq32, types.Types[types.TBOOL], s.zeroVal(ft), x) b := s.endBlock() b.Kind = ssa.BlockIf b.SetControl(cmp) @@ -5707,9 +6036,9 @@ func (s *state) uint32Tofloat(cvttab *u322fcvtTab, n *Node, x *ssa.Value, ft, tt b.AddEdgeTo(bElse) s.startBlock(bElse) - a1 := s.newValue1(ssa.OpCvt32to64F, types.Types[TFLOAT64], x) - twoToThe32 := s.constFloat64(types.Types[TFLOAT64], float64(1<<32)) - a2 := s.newValue2(ssa.OpAdd64F, types.Types[TFLOAT64], a1, twoToThe32) + a1 := s.newValue1(ssa.OpCvt32to64F, types.Types[types.TFLOAT64], x) + twoToThe32 := s.constFloat64(types.Types[types.TFLOAT64], float64(1<<32)) + a2 := s.newValue2(ssa.OpAdd64F, types.Types[types.TFLOAT64], a1, twoToThe32) a3 := s.newValue1(cvttab.cvtF2F, tt, a2) s.vars[n] = a3 @@ -5717,12 +6046,12 @@ func (s *state) uint32Tofloat(cvttab *u322fcvtTab, n *Node, x *ssa.Value, ft, tt bElse.AddEdgeTo(bAfter) s.startBlock(bAfter) - return s.variable(n, n.Type) + return s.variable(n, n.Type()) } // referenceTypeBuiltin generates code for the len/cap builtins for maps and channels. -func (s *state) referenceTypeBuiltin(n *Node, x *ssa.Value) *ssa.Value { - if !n.Left.Type.IsMap() && !n.Left.Type.IsChan() { +func (s *state) referenceTypeBuiltin(n *ir.UnaryExpr, x *ssa.Value) *ssa.Value { + if !n.X.Type().IsMap() && !n.X.Type().IsChan() { s.Fatalf("node must be a map or a channel") } // if n == nil { @@ -5733,9 +6062,9 @@ func (s *state) referenceTypeBuiltin(n *Node, x *ssa.Value) *ssa.Value { // // cap // return *(((*int)n)+1) // } - lenType := n.Type - nilValue := s.constNil(types.Types[TUINTPTR]) - cmp := s.newValue2(ssa.OpEqPtr, types.Types[TBOOL], x, nilValue) + lenType := n.Type() + nilValue := s.constNil(types.Types[types.TUINTPTR]) + cmp := s.newValue2(ssa.OpEqPtr, types.Types[types.TBOOL], x, nilValue) b := s.endBlock() b.Kind = ssa.BlockIf b.SetControl(cmp) @@ -5754,11 +6083,11 @@ func (s *state) referenceTypeBuiltin(n *Node, x *ssa.Value) *ssa.Value { b.AddEdgeTo(bElse) s.startBlock(bElse) - switch n.Op { - case OLEN: + switch n.Op() { + case ir.OLEN: // length is stored in the first word for map/chan s.vars[n] = s.load(lenType, x) - case OCAP: + case ir.OCAP: // capacity is stored in the second word for chan sw := s.newValue1I(ssa.OpOffPtr, lenType.PtrTo(), lenType.Width, x) s.vars[n] = s.load(lenType, sw) @@ -5819,22 +6148,22 @@ var f64_u32 = f2uCvtTab{ cutoff: 1 << 31, } -func (s *state) float32ToUint64(n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) float32ToUint64(n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { return s.floatToUint(&f32_u64, n, x, ft, tt) } -func (s *state) float64ToUint64(n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) float64ToUint64(n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { return s.floatToUint(&f64_u64, n, x, ft, tt) } -func (s *state) float32ToUint32(n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) float32ToUint32(n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { return s.floatToUint(&f32_u32, n, x, ft, tt) } -func (s *state) float64ToUint32(n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) float64ToUint32(n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { return s.floatToUint(&f64_u32, n, x, ft, tt) } -func (s *state) floatToUint(cvttab *f2uCvtTab, n *Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { +func (s *state) floatToUint(cvttab *f2uCvtTab, n ir.Node, x *ssa.Value, ft, tt *types.Type) *ssa.Value { // cutoff:=1<<(intY_Size-1) // if x < floatX(cutoff) { // result = uintY(x) @@ -5844,7 +6173,7 @@ func (s *state) floatToUint(cvttab *f2uCvtTab, n *Node, x *ssa.Value, ft, tt *ty // result = z | -(cutoff) // } cutoff := cvttab.floatValue(s, ft, float64(cvttab.cutoff)) - cmp := s.newValue2(cvttab.ltf, types.Types[TBOOL], x, cutoff) + cmp := s.newValue2(cvttab.ltf, types.Types[types.TBOOL], x, cutoff) b := s.endBlock() b.Kind = ssa.BlockIf b.SetControl(cmp) @@ -5872,31 +6201,31 @@ func (s *state) floatToUint(cvttab *f2uCvtTab, n *Node, x *ssa.Value, ft, tt *ty bElse.AddEdgeTo(bAfter) s.startBlock(bAfter) - return s.variable(n, n.Type) + return s.variable(n, n.Type()) } // dottype generates SSA for a type assertion node. // commaok indicates whether to panic or return a bool. // If commaok is false, resok will be nil. -func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) { - iface := s.expr(n.Left) // input interface - target := s.expr(n.Right) // target type +func (s *state) dottype(n *ir.TypeAssertExpr, commaok bool) (res, resok *ssa.Value) { + iface := s.expr(n.X) // input interface + target := s.reflectType(n.Type()) // target type byteptr := s.f.Config.Types.BytePtr - if n.Type.IsInterface() { - if n.Type.IsEmptyInterface() { + if n.Type().IsInterface() { + if n.Type().IsEmptyInterface() { // Converting to an empty interface. // Input could be an empty or nonempty interface. - if Debug_typeassert > 0 { - Warnl(n.Pos, "type assertion inlined") + if base.Debug.TypeAssert > 0 { + base.WarnfAt(n.Pos(), "type assertion inlined") } // Get itab/type field from input. itab := s.newValue1(ssa.OpITab, byteptr, iface) // Conversion succeeds iff that field is not nil. - cond := s.newValue2(ssa.OpNeqPtr, types.Types[TBOOL], itab, s.constNil(byteptr)) + cond := s.newValue2(ssa.OpNeqPtr, types.Types[types.TBOOL], itab, s.constNil(byteptr)) - if n.Left.Type.IsEmptyInterface() && commaok { + if n.X.Type().IsEmptyInterface() && commaok { // Converting empty interface to empty interface with ,ok is just a nil check. return iface, cond } @@ -5914,32 +6243,32 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) { if !commaok { // On failure, panic by calling panicnildottype. s.startBlock(bFail) - s.rtcall(panicnildottype, false, nil, target) + s.rtcall(ir.Syms.Panicnildottype, false, nil, target) // On success, return (perhaps modified) input interface. s.startBlock(bOk) - if n.Left.Type.IsEmptyInterface() { + if n.X.Type().IsEmptyInterface() { res = iface // Use input interface unchanged. return } // Load type out of itab, build interface with existing idata. - off := s.newValue1I(ssa.OpOffPtr, byteptr, int64(Widthptr), itab) + off := s.newValue1I(ssa.OpOffPtr, byteptr, int64(types.PtrSize), itab) typ := s.load(byteptr, off) idata := s.newValue1(ssa.OpIData, byteptr, iface) - res = s.newValue2(ssa.OpIMake, n.Type, typ, idata) + res = s.newValue2(ssa.OpIMake, n.Type(), typ, idata) return } s.startBlock(bOk) // nonempty -> empty // Need to load type from itab - off := s.newValue1I(ssa.OpOffPtr, byteptr, int64(Widthptr), itab) - s.vars[&typVar] = s.load(byteptr, off) + off := s.newValue1I(ssa.OpOffPtr, byteptr, int64(types.PtrSize), itab) + s.vars[typVar] = s.load(byteptr, off) s.endBlock() // itab is nil, might as well use that as the nil result. s.startBlock(bFail) - s.vars[&typVar] = itab + s.vars[typVar] = itab s.endBlock() // Merge point. @@ -5948,59 +6277,62 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) { bFail.AddEdgeTo(bEnd) s.startBlock(bEnd) idata := s.newValue1(ssa.OpIData, byteptr, iface) - res = s.newValue2(ssa.OpIMake, n.Type, s.variable(&typVar, byteptr), idata) + res = s.newValue2(ssa.OpIMake, n.Type(), s.variable(typVar, byteptr), idata) resok = cond - delete(s.vars, &typVar) + delete(s.vars, typVar) return } // converting to a nonempty interface needs a runtime call. - if Debug_typeassert > 0 { - Warnl(n.Pos, "type assertion not inlined") + if base.Debug.TypeAssert > 0 { + base.WarnfAt(n.Pos(), "type assertion not inlined") } - if n.Left.Type.IsEmptyInterface() { - if commaok { - call := s.rtcall(assertE2I2, true, []*types.Type{n.Type, types.Types[TBOOL]}, target, iface) - return call[0], call[1] + if !commaok { + fn := ir.Syms.AssertI2I + if n.X.Type().IsEmptyInterface() { + fn = ir.Syms.AssertE2I } - return s.rtcall(assertE2I, true, []*types.Type{n.Type}, target, iface)[0], nil + data := s.newValue1(ssa.OpIData, types.Types[types.TUNSAFEPTR], iface) + tab := s.newValue1(ssa.OpITab, byteptr, iface) + tab = s.rtcall(fn, true, []*types.Type{byteptr}, target, tab)[0] + return s.newValue2(ssa.OpIMake, n.Type(), tab, data), nil } - if commaok { - call := s.rtcall(assertI2I2, true, []*types.Type{n.Type, types.Types[TBOOL]}, target, iface) - return call[0], call[1] + fn := ir.Syms.AssertI2I2 + if n.X.Type().IsEmptyInterface() { + fn = ir.Syms.AssertE2I2 } - return s.rtcall(assertI2I, true, []*types.Type{n.Type}, target, iface)[0], nil + res = s.rtcall(fn, true, []*types.Type{n.Type()}, target, iface)[0] + resok = s.newValue2(ssa.OpNeqInter, types.Types[types.TBOOL], res, s.constInterface(n.Type())) + return } - if Debug_typeassert > 0 { - Warnl(n.Pos, "type assertion inlined") + if base.Debug.TypeAssert > 0 { + base.WarnfAt(n.Pos(), "type assertion inlined") } // Converting to a concrete type. - direct := isdirectiface(n.Type) + direct := types.IsDirectIface(n.Type()) itab := s.newValue1(ssa.OpITab, byteptr, iface) // type word of interface - if Debug_typeassert > 0 { - Warnl(n.Pos, "type assertion inlined") + if base.Debug.TypeAssert > 0 { + base.WarnfAt(n.Pos(), "type assertion inlined") } var targetITab *ssa.Value - if n.Left.Type.IsEmptyInterface() { + if n.X.Type().IsEmptyInterface() { // Looking for pointer to target type. targetITab = target } else { // Looking for pointer to itab for target type and source interface. - targetITab = s.expr(n.List.First()) + targetITab = s.expr(n.Itab) } - var tmp *Node // temporary for use with large types + var tmp ir.Node // temporary for use with large types var addr *ssa.Value // address of tmp - if commaok && !canSSAType(n.Type) { + if commaok && !TypeOK(n.Type()) { // unSSAable type, use temporary. // TODO: get rid of some of these temporaries. - tmp = tempAt(n.Pos, s.curfn, n.Type) - s.vars[&memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, tmp, s.mem()) - addr = s.addr(tmp) + tmp, addr = s.temp(n.Pos(), n.Type()) } - cond := s.newValue2(ssa.OpEqPtr, types.Types[TBOOL], itab, targetITab) + cond := s.newValue2(ssa.OpEqPtr, types.Types[types.TBOOL], itab, targetITab) b := s.endBlock() b.Kind = ssa.BlockIf b.SetControl(cond) @@ -6014,20 +6346,20 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) { if !commaok { // on failure, panic by calling panicdottype s.startBlock(bFail) - taddr := s.expr(n.Right.Right) - if n.Left.Type.IsEmptyInterface() { - s.rtcall(panicdottypeE, false, nil, itab, target, taddr) + taddr := s.reflectType(n.X.Type()) + if n.X.Type().IsEmptyInterface() { + s.rtcall(ir.Syms.PanicdottypeE, false, nil, itab, target, taddr) } else { - s.rtcall(panicdottypeI, false, nil, itab, target, taddr) + s.rtcall(ir.Syms.PanicdottypeI, false, nil, itab, target, taddr) } // on success, return data from interface s.startBlock(bOk) if direct { - return s.newValue1(ssa.OpIData, n.Type, iface), nil + return s.newValue1(ssa.OpIData, n.Type(), iface), nil } - p := s.newValue1(ssa.OpIData, types.NewPtr(n.Type), iface) - return s.load(n.Type, p), nil + p := s.newValue1(ssa.OpIData, types.NewPtr(n.Type()), iface) + return s.load(n.Type(), p), nil } // commaok is the more complicated case because we have @@ -6035,121 +6367,121 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) { bEnd := s.f.NewBlock(ssa.BlockPlain) // Note that we need a new valVar each time (unlike okVar where we can // reuse the variable) because it might have a different type every time. - valVar := &Node{Op: ONAME, Sym: &types.Sym{Name: "val"}} + valVar := ssaMarker("val") // type assertion succeeded s.startBlock(bOk) if tmp == nil { if direct { - s.vars[valVar] = s.newValue1(ssa.OpIData, n.Type, iface) + s.vars[valVar] = s.newValue1(ssa.OpIData, n.Type(), iface) } else { - p := s.newValue1(ssa.OpIData, types.NewPtr(n.Type), iface) - s.vars[valVar] = s.load(n.Type, p) + p := s.newValue1(ssa.OpIData, types.NewPtr(n.Type()), iface) + s.vars[valVar] = s.load(n.Type(), p) } } else { - p := s.newValue1(ssa.OpIData, types.NewPtr(n.Type), iface) - s.move(n.Type, addr, p) + p := s.newValue1(ssa.OpIData, types.NewPtr(n.Type()), iface) + s.move(n.Type(), addr, p) } - s.vars[&okVar] = s.constBool(true) + s.vars[okVar] = s.constBool(true) s.endBlock() bOk.AddEdgeTo(bEnd) // type assertion failed s.startBlock(bFail) if tmp == nil { - s.vars[valVar] = s.zeroVal(n.Type) + s.vars[valVar] = s.zeroVal(n.Type()) } else { - s.zero(n.Type, addr) + s.zero(n.Type(), addr) } - s.vars[&okVar] = s.constBool(false) + s.vars[okVar] = s.constBool(false) s.endBlock() bFail.AddEdgeTo(bEnd) // merge point s.startBlock(bEnd) if tmp == nil { - res = s.variable(valVar, n.Type) + res = s.variable(valVar, n.Type()) delete(s.vars, valVar) } else { - res = s.load(n.Type, addr) - s.vars[&memVar] = s.newValue1A(ssa.OpVarKill, types.TypeMem, tmp, s.mem()) + res = s.load(n.Type(), addr) + s.vars[memVar] = s.newValue1A(ssa.OpVarKill, types.TypeMem, tmp.(*ir.Name), s.mem()) } - resok = s.variable(&okVar, types.Types[TBOOL]) - delete(s.vars, &okVar) + resok = s.variable(okVar, types.Types[types.TBOOL]) + delete(s.vars, okVar) return res, resok } +// temp allocates a temp of type t at position pos +func (s *state) temp(pos src.XPos, t *types.Type) (*ir.Name, *ssa.Value) { + tmp := typecheck.TempAt(pos, s.curfn, t) + s.vars[memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, tmp, s.mem()) + addr := s.addr(tmp) + return tmp, addr +} + // variable returns the value of a variable at the current location. -func (s *state) variable(name *Node, t *types.Type) *ssa.Value { - v := s.vars[name] +func (s *state) variable(n ir.Node, t *types.Type) *ssa.Value { + v := s.vars[n] if v != nil { return v } - v = s.fwdVars[name] + v = s.fwdVars[n] if v != nil { return v } if s.curBlock == s.f.Entry { // No variable should be live at entry. - s.Fatalf("Value live at entry. It shouldn't be. func %s, node %v, value %v", s.f.Name, name, v) + s.Fatalf("Value live at entry. It shouldn't be. func %s, node %v, value %v", s.f.Name, n, v) } // Make a FwdRef, which records a value that's live on block input. // We'll find the matching definition as part of insertPhis. - v = s.newValue0A(ssa.OpFwdRef, t, name) - s.fwdVars[name] = v - s.addNamedValue(name, v) + v = s.newValue0A(ssa.OpFwdRef, t, fwdRefAux{N: n}) + s.fwdVars[n] = v + if n.Op() == ir.ONAME { + s.addNamedValue(n.(*ir.Name), v) + } return v } func (s *state) mem() *ssa.Value { - return s.variable(&memVar, types.TypeMem) + return s.variable(memVar, types.TypeMem) } -func (s *state) addNamedValue(n *Node, v *ssa.Value) { - if n.Class() == Pxxx { - // Don't track our dummy nodes (&memVar etc.). +func (s *state) addNamedValue(n *ir.Name, v *ssa.Value) { + if n.Class == ir.Pxxx { + // Don't track our marker nodes (memVar etc.). return } - if n.IsAutoTmp() { + if ir.IsAutoTmp(n) { // Don't track temporary variables. return } - if n.Class() == PPARAMOUT { + if n.Class == ir.PPARAMOUT { // Don't track named output values. This prevents return values // from being assigned too early. See #14591 and #14762. TODO: allow this. return } - if n.Class() == PAUTO && n.Xoffset != 0 { - s.Fatalf("AUTO var with offset %v %d", n, n.Xoffset) - } - loc := ssa.LocalSlot{N: n, Type: n.Type, Off: 0} + loc := ssa.LocalSlot{N: n, Type: n.Type(), Off: 0} values, ok := s.f.NamedValues[loc] if !ok { - s.f.Names = append(s.f.Names, loc) + s.f.Names = append(s.f.Names, &loc) + s.f.CanonicalLocalSlots[loc] = &loc } s.f.NamedValues[loc] = append(values, v) } -// Generate a disconnected call to a runtime routine and a return. -func gencallret(pp *Progs, sym *obj.LSym) *obj.Prog { - p := pp.Prog(obj.ACALL) - p.To.Type = obj.TYPE_MEM - p.To.Name = obj.NAME_EXTERN - p.To.Sym = sym - p = pp.Prog(obj.ARET) - return p -} - // Branch is an unresolved branch. type Branch struct { P *obj.Prog // branch instruction B *ssa.Block // target } -// SSAGenState contains state needed during Prog generation. -type SSAGenState struct { - pp *Progs +// State contains state needed during Prog generation. +type State struct { + ABI obj.ABI + + pp *objw.Progs // Branches remembers all the branch instructions we've seen // and where they would like to go. @@ -6158,14 +6490,15 @@ type SSAGenState struct { // bstart remembers where each block starts (indexed by block ID) bstart []*obj.Prog - // Some architectures require a 64-bit temporary for FP-related register shuffling. Examples include PPC and Sparc V8. - ScratchFpMem *Node - maxarg int64 // largest frame size for arguments to calls made by the function // Map from GC safe points to liveness index, generated by // liveness analysis. - livenessMap LivenessMap + livenessMap liveness.Map + + // partLiveArgs includes arguments that may be partially live, for which we + // need to generate instructions that spill the argument registers. + partLiveArgs map[*ir.Name]bool // lineRunStart records the beginning of the current run of instructions // within a single block sharing the same line number @@ -6176,10 +6509,14 @@ type SSAGenState struct { OnWasmStackSkipped int } +func (s *State) FuncInfo() *obj.FuncInfo { + return s.pp.CurFunc.LSym.Func() +} + // Prog appends a new Prog. -func (s *SSAGenState) Prog(as obj.As) *obj.Prog { +func (s *State) Prog(as obj.As) *obj.Prog { p := s.pp.Prog(as) - if ssa.LosesStmtMark(as) { + if objw.LosesStmtMark(as) { return p } // Float a statement start to the beginning of any same-line run. @@ -6194,19 +6531,19 @@ func (s *SSAGenState) Prog(as obj.As) *obj.Prog { } // Pc returns the current Prog. -func (s *SSAGenState) Pc() *obj.Prog { - return s.pp.next +func (s *State) Pc() *obj.Prog { + return s.pp.Next } // SetPos sets the current source position. -func (s *SSAGenState) SetPos(pos src.XPos) { - s.pp.pos = pos +func (s *State) SetPos(pos src.XPos) { + s.pp.Pos = pos } // Br emits a single branch instruction and returns the instruction. // Not all architectures need the returned instruction, but otherwise // the boilerplate is common to all. -func (s *SSAGenState) Br(op obj.As, target *ssa.Block) *obj.Prog { +func (s *State) Br(op obj.As, target *ssa.Block) *obj.Prog { p := s.Prog(op) p.To.Type = obj.TYPE_BRANCH s.Branches = append(s.Branches, Branch{P: p, B: target}) @@ -6218,7 +6555,7 @@ func (s *SSAGenState) Br(op obj.As, target *ssa.Block) *obj.Prog { // Spill/fill/copy instructions from the register allocator, // phi functions, and instructions with a no-pos position // are examples of instructions that can cause churn. -func (s *SSAGenState) DebugFriendlySetPosFrom(v *ssa.Value) { +func (s *State) DebugFriendlySetPosFrom(v *ssa.Value) { switch v.Op { case ssa.OpPhi, ssa.OpCopy, ssa.OpLoadReg, ssa.OpStoreReg: // These are not statements @@ -6232,81 +6569,179 @@ func (s *SSAGenState) DebugFriendlySetPosFrom(v *ssa.Value) { // in the generated code. if p.IsStmt() != src.PosIsStmt { p = p.WithNotStmt() - // Calls use the pos attached to v, but copy the statement mark from SSAGenState + // Calls use the pos attached to v, but copy the statement mark from State } s.SetPos(p) } else { - s.SetPos(s.pp.pos.WithNotStmt()) + s.SetPos(s.pp.Pos.WithNotStmt()) } } } -// byXoffset implements sort.Interface for []*Node using Xoffset as the ordering. -type byXoffset []*Node - -func (s byXoffset) Len() int { return len(s) } -func (s byXoffset) Less(i, j int) bool { return s[i].Xoffset < s[j].Xoffset } -func (s byXoffset) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func emitStackObjects(e *ssafn, pp *Progs) { - var vars []*Node - for _, n := range e.curfn.Func.Dcl { - if livenessShouldTrack(n) && n.Name.Addrtaken() { - vars = append(vars, n) - } - } - if len(vars) == 0 { +// emit argument info (locations on stack) for traceback. +func emitArgInfo(e *ssafn, f *ssa.Func, pp *objw.Progs) { + ft := e.curfn.Type() + if ft.NumRecvs() == 0 && ft.NumParams() == 0 { return } - // Sort variables from lowest to highest address. - sort.Sort(byXoffset(vars)) + x := EmitArgInfo(e.curfn, f.OwnAux.ABIInfo()) + e.curfn.LSym.Func().ArgInfo = x - // Populate the stack object data. - // Format must match runtime/stack.go:stackObjectRecord. - x := e.curfn.Func.lsym.Func().StackObjects - off := 0 - off = duintptr(x, off, uint64(len(vars))) - for _, v := range vars { - // Note: arguments and return values have non-negative Xoffset, - // in which case the offset is relative to argp. - // Locals have a negative Xoffset, in which case the offset is relative to varp. - off = duintptr(x, off, uint64(v.Xoffset)) - if !typesym(v.Type).Siggen() { - e.Fatalf(v.Pos, "stack object's type symbol not generated for type %s", v.Type) - } - off = dsymptr(x, off, dtypesym(v.Type), 0) - } - - // Emit a funcdata pointing at the stack object data. + // Emit a funcdata pointing at the arg info data. p := pp.Prog(obj.AFUNCDATA) - Addrconst(&p.From, objabi.FUNCDATA_StackObjects) + p.From.SetConst(objabi.FUNCDATA_ArgInfo) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN p.To.Sym = x +} - if debuglive != 0 { - for _, v := range vars { - Warnl(v.Pos, "stack object %v %s", v, v.Type.String()) +// emit argument info (locations on stack) of f for traceback. +func EmitArgInfo(f *ir.Func, abiInfo *abi.ABIParamResultInfo) *obj.LSym { + x := base.Ctxt.Lookup(fmt.Sprintf("%s.arginfo%d", f.LSym.Name, f.ABI)) + + PtrSize := int64(types.PtrSize) + uintptrTyp := types.Types[types.TUINTPTR] + + isAggregate := func(t *types.Type) bool { + return t.IsStruct() || t.IsArray() || t.IsComplex() || t.IsInterface() || t.IsString() || t.IsSlice() + } + + // Populate the data. + // The data is a stream of bytes, which contains the offsets and sizes of the + // non-aggregate arguments or non-aggregate fields/elements of aggregate-typed + // arguments, along with special "operators". Specifically, + // - for each non-aggrgate arg/field/element, its offset from FP (1 byte) and + // size (1 byte) + // - special operators: + // - 0xff - end of sequence + // - 0xfe - print { (at the start of an aggregate-typed argument) + // - 0xfd - print } (at the end of an aggregate-typed argument) + // - 0xfc - print ... (more args/fields/elements) + // - 0xfb - print _ (offset too large) + // These constants need to be in sync with runtime.traceback.go:printArgs. + const ( + _endSeq = 0xff + _startAgg = 0xfe + _endAgg = 0xfd + _dotdotdot = 0xfc + _offsetTooLarge = 0xfb + _special = 0xf0 // above this are operators, below this are ordinary offsets + ) + + const ( + limit = 10 // print no more than 10 args/components + maxDepth = 5 // no more than 5 layers of nesting + + // maxLen is a (conservative) upper bound of the byte stream length. For + // each arg/component, it has no more than 2 bytes of data (size, offset), + // and no more than one {, }, ... at each level (it cannot have both the + // data and ... unless it is the last one, just be conservative). Plus 1 + // for _endSeq. + maxLen = (maxDepth*3+2)*limit + 1 + ) + + wOff := 0 + n := 0 + writebyte := func(o uint8) { wOff = objw.Uint8(x, wOff, o) } + + // Write one non-aggrgate arg/field/element. + write1 := func(sz, offset int64) { + if offset >= _special { + writebyte(_offsetTooLarge) + } else { + writebyte(uint8(offset)) + writebyte(uint8(sz)) } + n++ } + + // Visit t recursively and write it out. + // Returns whether to continue visiting. + var visitType func(baseOffset int64, t *types.Type, depth int) bool + visitType = func(baseOffset int64, t *types.Type, depth int) bool { + if n >= limit { + writebyte(_dotdotdot) + return false + } + if !isAggregate(t) { + write1(t.Size(), baseOffset) + return true + } + writebyte(_startAgg) + depth++ + if depth >= maxDepth { + writebyte(_dotdotdot) + writebyte(_endAgg) + n++ + return true + } + switch { + case t.IsInterface(), t.IsString(): + _ = visitType(baseOffset, uintptrTyp, depth) && + visitType(baseOffset+PtrSize, uintptrTyp, depth) + case t.IsSlice(): + _ = visitType(baseOffset, uintptrTyp, depth) && + visitType(baseOffset+PtrSize, uintptrTyp, depth) && + visitType(baseOffset+PtrSize*2, uintptrTyp, depth) + case t.IsComplex(): + _ = visitType(baseOffset, types.FloatForComplex(t), depth) && + visitType(baseOffset+t.Size()/2, types.FloatForComplex(t), depth) + case t.IsArray(): + if t.NumElem() == 0 { + n++ // {} counts as a component + break + } + for i := int64(0); i < t.NumElem(); i++ { + if !visitType(baseOffset, t.Elem(), depth) { + break + } + baseOffset += t.Elem().Size() + } + case t.IsStruct(): + if t.NumFields() == 0 { + n++ // {} counts as a component + break + } + for _, field := range t.Fields().Slice() { + if !visitType(baseOffset+field.Offset, field.Type, depth) { + break + } + } + } + writebyte(_endAgg) + return true + } + + for _, a := range abiInfo.InParams() { + if !visitType(a.FrameOffset(abiInfo), a.Type, 0) { + break + } + } + writebyte(_endSeq) + if wOff > maxLen { + base.Fatalf("ArgInfo too large") + } + + return x } // genssa appends entries to pp for each instruction in f. -func genssa(f *ssa.Func, pp *Progs) { - var s SSAGenState +func genssa(f *ssa.Func, pp *objw.Progs) { + var s State + s.ABI = f.OwnAux.Fn.ABI() e := f.Frontend().(*ssafn) - s.livenessMap = liveness(e, f, pp) - emitStackObjects(e, pp) + s.livenessMap, s.partLiveArgs = liveness.Compute(e.curfn, f, e.stkptrsize, pp) + emitArgInfo(e, f, pp) - openDeferInfo := e.curfn.Func.lsym.Func().OpenCodedDeferInfo + openDeferInfo := e.curfn.LSym.Func().OpenCodedDeferInfo if openDeferInfo != nil { // This function uses open-coded defers -- write out the funcdata // info that we computed at the end of genssa. p := pp.Prog(obj.AFUNCDATA) - Addrconst(&p.From, objabi.FUNCDATA_OpenCodedDeferInfo) + p.From.SetConst(objabi.FUNCDATA_OpenCodedDeferInfo) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN p.To.Sym = openDeferInfo @@ -6322,12 +6757,10 @@ func genssa(f *ssa.Func, pp *Progs) { progToValue = make(map[*obj.Prog]*ssa.Value, f.NumValues()) progToBlock = make(map[*obj.Prog]*ssa.Block, f.NumBlocks()) f.Logf("genssa %s\n", f.Name) - progToBlock[s.pp.next] = f.Blocks[0] + progToBlock[s.pp.Next] = f.Blocks[0] } - s.ScratchFpMem = e.scratchFpMem - - if Ctxt.Flag_locationlists { + if base.Ctxt.Flag_locationlists { if cap(f.Cache.ValueToProgAfter) < f.NumValues() { f.Cache.ValueToProgAfter = make([]*obj.Prog, f.NumValues()) } @@ -6360,7 +6793,7 @@ func genssa(f *ssa.Func, pp *Progs) { // Emit basic blocks for i, b := range f.Blocks { - s.bstart[b.ID] = s.pp.next + s.bstart[b.ID] = s.pp.Next s.lineRunStart = nil // Attach a "default" liveness info. Normally this will be @@ -6369,14 +6802,18 @@ func genssa(f *ssa.Func, pp *Progs) { // instruction. We won't use the actual liveness map on a // control instruction. Just mark it something that is // preemptible, unless this function is "all unsafe". - s.pp.nextLive = LivenessIndex{-1, allUnsafe(f)} + s.pp.NextLive = objw.LivenessIndex{StackMapIndex: -1, IsUnsafePoint: liveness.IsUnsafe(f)} // Emit values in block - thearch.SSAMarkMoves(&s, b) + Arch.SSAMarkMoves(&s, b) for _, v := range b.Values { - x := s.pp.next + x := s.pp.Next s.DebugFriendlySetPosFrom(v) + if v.Op.ResultInArg0() && v.ResultReg() != v.Args[0].Reg() { + v.Fatalf("input[0] and output not in same register %s", v.LongString()) + } + switch v.Op { case ssa.OpInitMem: // memory arg needs no code @@ -6384,7 +6821,7 @@ func genssa(f *ssa.Func, pp *Progs) { // input args need no code case ssa.OpSP, ssa.OpSB: // nothing to do - case ssa.OpSelect0, ssa.OpSelect1: + case ssa.OpSelect0, ssa.OpSelect1, ssa.OpSelectN, ssa.OpMakeResult: // nothing to do case ssa.OpGetG: // nothing to do when there's a g register, @@ -6399,7 +6836,7 @@ func genssa(f *ssa.Func, pp *Progs) { v.Fatalf("OpConvert should be a no-op: %s; %s", v.Args[0].LongString(), v.LongString()) } case ssa.OpInlMark: - p := thearch.Ginsnop(s.pp) + p := Arch.Ginsnop(s.pp) if inlMarks == nil { inlMarks = map[*obj.Prog]int32{} inlMarksByPos = map[src.XPos][]*obj.Prog{} @@ -6410,32 +6847,32 @@ func genssa(f *ssa.Func, pp *Progs) { inlMarksByPos[pos] = append(inlMarksByPos[pos], p) default: - // Attach this safe point to the next - // instruction. - s.pp.nextLive = s.livenessMap.Get(v) - - // Special case for first line in function; move it to the start. - if firstPos != src.NoXPos { + // Special case for first line in function; move it to the start (which cannot be a register-valued instruction) + if firstPos != src.NoXPos && v.Op != ssa.OpArgIntReg && v.Op != ssa.OpArgFloatReg && v.Op != ssa.OpLoadReg && v.Op != ssa.OpStoreReg { s.SetPos(firstPos) firstPos = src.NoXPos } + // Attach this safe point to the next + // instruction. + s.pp.NextLive = s.livenessMap.Get(v) + // let the backend handle it - thearch.SSAGenValue(&s, v) + Arch.SSAGenValue(&s, v) } - if Ctxt.Flag_locationlists { - valueToProgAfter[v.ID] = s.pp.next + if base.Ctxt.Flag_locationlists { + valueToProgAfter[v.ID] = s.pp.Next } if f.PrintOrHtmlSSA { - for ; x != s.pp.next; x = x.Link { + for ; x != s.pp.Next; x = x.Link { progToValue[x] = v } } } // If this is an empty infinite loop, stick a hardware NOP in there so that debuggers are less confused. - if s.bstart[b.ID] == s.pp.next && len(b.Succs) == 1 && b.Succs[0].Block() == b { - p := thearch.Ginsnop(s.pp) + if s.bstart[b.ID] == s.pp.Next && len(b.Succs) == 1 && b.Succs[0].Block() == b { + p := Arch.Ginsnop(s.pp) p.Pos = p.Pos.WithIsStmt() if b.Pos == src.NoXPos { b.Pos = p.Pos // It needs a file, otherwise a no-file non-zero line causes confusion. See #35652. @@ -6447,18 +6884,18 @@ func genssa(f *ssa.Func, pp *Progs) { } // Emit control flow instructions for block var next *ssa.Block - if i < len(f.Blocks)-1 && Debug.N == 0 { + if i < len(f.Blocks)-1 && base.Flag.N == 0 { // If -N, leave next==nil so every block with successors // ends in a JMP (except call blocks - plive doesn't like // select{send,recv} followed by a JMP call). Helps keep // line numbers for otherwise empty blocks. next = f.Blocks[i+1] } - x := s.pp.next + x := s.pp.Next s.SetPos(b.Pos) - thearch.SSAGenBlock(&s, b, next) + Arch.SSAGenBlock(&s, b, next) if f.PrintOrHtmlSSA { - for ; x != s.pp.next; x = x.Link { + for ; x != s.pp.Next; x = x.Link { progToBlock[x] = b } } @@ -6468,14 +6905,27 @@ func genssa(f *ssa.Func, pp *Progs) { // still be inside the function in question. So if // it ends in a call which doesn't return, add a // nop (which will never execute) after the call. - thearch.Ginsnop(pp) + Arch.Ginsnop(pp) } if openDeferInfo != nil { // When doing open-coded defers, generate a disconnected call to // deferreturn and a return. This will be used to during panic // recovery to unwind the stack and return back to the runtime. - s.pp.nextLive = s.livenessMap.deferreturn - gencallret(pp, Deferreturn) + s.pp.NextLive = s.livenessMap.DeferReturn + p := pp.Prog(obj.ACALL) + p.To.Type = obj.TYPE_MEM + p.To.Name = obj.NAME_EXTERN + p.To.Sym = ir.Syms.Deferreturn + + // Load results into registers. So when a deferred function + // recovers a panic, it will return to caller with right results. + // The results are already in memory, because they are not SSA'd + // when the function has defers (see canSSAName). + if f.OwnAux.ABIInfo().OutRegistersUsed() != 0 { + Arch.LoadRegResults(&s, f) + } + + pp.Prog(obj.ARET) } if inlMarks != nil { @@ -6483,7 +6933,7 @@ func genssa(f *ssa.Func, pp *Progs) { // going to emit anyway, and use those instructions instead of the // inline marks. for p := pp.Text; p != nil; p = p.Link { - if p.As == obj.ANOP || p.As == obj.AFUNCDATA || p.As == obj.APCDATA || p.As == obj.ATEXT || p.As == obj.APCALIGN || thearch.LinkArch.Family == sys.Wasm { + if p.As == obj.ANOP || p.As == obj.AFUNCDATA || p.As == obj.APCDATA || p.As == obj.ATEXT || p.As == obj.APCALIGN || Arch.LinkArch.Family == sys.Wasm { // Don't use 0-sized instructions as inline marks, because we need // to identify inline mark instructions by pc offset. // (Some of these instructions are sometimes zero-sized, sometimes not. @@ -6506,7 +6956,7 @@ func genssa(f *ssa.Func, pp *Progs) { // some of the inline marks. // Use this instruction instead. p.Pos = p.Pos.WithIsStmt() // promote position to a statement - pp.curfn.Func.lsym.Func().AddInlMark(p, inlMarks[m]) + pp.CurFunc.LSym.Func().AddInlMark(p, inlMarks[m]) // Make the inline mark a real nop, so it doesn't generate any code. m.As = obj.ANOP m.Pos = src.NoXPos @@ -6518,18 +6968,28 @@ func genssa(f *ssa.Func, pp *Progs) { // Any unmatched inline marks now need to be added to the inlining tree (and will generate a nop instruction). for _, p := range inlMarkList { if p.As != obj.ANOP { - pp.curfn.Func.lsym.Func().AddInlMark(p, inlMarks[p]) + pp.CurFunc.LSym.Func().AddInlMark(p, inlMarks[p]) } } } - if Ctxt.Flag_locationlists { - e.curfn.Func.DebugInfo = ssa.BuildFuncDebug(Ctxt, f, Debug_locationlist > 1, stackOffset) + if base.Ctxt.Flag_locationlists { + var debugInfo *ssa.FuncDebug + if e.curfn.ABI == obj.ABIInternal && base.Flag.N != 0 { + debugInfo = ssa.BuildFuncDebugNoOptimized(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset) + } else { + debugInfo = ssa.BuildFuncDebug(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset) + } + e.curfn.DebugInfo = debugInfo bstart := s.bstart + idToIdx := make([]int, f.NumBlocks()) + for i, b := range f.Blocks { + idToIdx[b.ID] = i + } // Note that at this moment, Prog.Pc is a sequence number; it's // not a real PC until after assembly, so this mapping has to // be done later. - e.curfn.Func.DebugInfo.GetPC = func(b, v ssa.ID) int64 { + debugInfo.GetPC = func(b, v ssa.ID) int64 { switch v { case ssa.BlockStart.ID: if b == f.Entry.ID { @@ -6538,7 +6998,11 @@ func genssa(f *ssa.Func, pp *Progs) { } return bstart[b].Pc case ssa.BlockEnd.ID: - return e.curfn.Func.lsym.Size + blk := f.Blocks[idToIdx[b]] + nv := len(blk.Values) + return valueToProgAfter[blk.Values[nv-1].ID].Pc + case ssa.FuncEnd.ID: + return e.curfn.LSym.Size default: return valueToProgAfter[v].Pc } @@ -6606,63 +7070,116 @@ func genssa(f *ssa.Func, pp *Progs) { f.HTMLWriter.WriteColumn("genssa", "genssa", "ssa-prog", buf.String()) } - defframe(&s, e) + defframe(&s, e, f) f.HTMLWriter.Close() f.HTMLWriter = nil } -func defframe(s *SSAGenState, e *ssafn) { +func defframe(s *State, e *ssafn, f *ssa.Func) { pp := s.pp - frame := Rnd(s.maxarg+e.stksize, int64(Widthreg)) - if thearch.PadFrame != nil { - frame = thearch.PadFrame(frame) + frame := types.Rnd(s.maxarg+e.stksize, int64(types.RegSize)) + if Arch.PadFrame != nil { + frame = Arch.PadFrame(frame) } // Fill in argument and frame size. pp.Text.To.Type = obj.TYPE_TEXTSIZE - pp.Text.To.Val = int32(Rnd(e.curfn.Type.ArgWidth(), int64(Widthreg))) + pp.Text.To.Val = int32(types.Rnd(f.OwnAux.ArgWidth(), int64(types.RegSize))) pp.Text.To.Offset = frame + p := pp.Text + + // Insert code to spill argument registers if the named slot may be partially + // live. That is, the named slot is considered live by liveness analysis, + // (because a part of it is live), but we may not spill all parts into the + // slot. This can only happen with aggregate-typed arguments that are SSA-able + // and not address-taken (for non-SSA-able or address-taken arguments we always + // spill upfront). + // Note: spilling is unnecessary in the -N/no-optimize case, since all values + // will be considered non-SSAable and spilled up front. + // TODO(register args) Make liveness more fine-grained to that partial spilling is okay. + if f.OwnAux.ABIInfo().InRegistersUsed() != 0 && base.Flag.N == 0 { + // First, see if it is already spilled before it may be live. Look for a spill + // in the entry block up to the first safepoint. + type nameOff struct { + n *ir.Name + off int64 + } + partLiveArgsSpilled := make(map[nameOff]bool) + for _, v := range f.Entry.Values { + if v.Op.IsCall() { + break + } + if v.Op != ssa.OpStoreReg || v.Args[0].Op != ssa.OpArgIntReg { + continue + } + n, off := ssa.AutoVar(v) + if n.Class != ir.PPARAM || n.Addrtaken() || !TypeOK(n.Type()) || !s.partLiveArgs[n] { + continue + } + partLiveArgsSpilled[nameOff{n, off}] = true + } + + // Then, insert code to spill registers if not already. + for _, a := range f.OwnAux.ABIInfo().InParams() { + n, ok := a.Name.(*ir.Name) + if !ok || n.Addrtaken() || !TypeOK(n.Type()) || !s.partLiveArgs[n] || len(a.Registers) <= 1 { + continue + } + rts, offs := a.RegisterTypesAndOffsets() + for i := range a.Registers { + if !rts[i].HasPointers() { + continue + } + if partLiveArgsSpilled[nameOff{n, offs[i]}] { + continue // already spilled + } + reg := ssa.ObjRegForAbiReg(a.Registers[i], f.Config) + p = Arch.SpillArgReg(pp, p, f, rts[i], reg, n, offs[i]) + } + } + } + // Insert code to zero ambiguously live variables so that the // garbage collector only sees initialized values when it // looks for pointers. - p := pp.Text var lo, hi int64 // Opaque state for backend to use. Current backends use it to // keep track of which helper registers have been zeroed. var state uint32 - // Iterate through declarations. They are sorted in decreasing Xoffset order. - for _, n := range e.curfn.Func.Dcl { - if !n.Name.Needzero() { + // Iterate through declarations. Autos are sorted in decreasing + // frame offset order. + for _, n := range e.curfn.Dcl { + if !n.Needzero() { continue } - if n.Class() != PAUTO { - e.Fatalf(n.Pos, "needzero class %d", n.Class()) + if n.Class != ir.PAUTO { + e.Fatalf(n.Pos(), "needzero class %d", n.Class) } - if n.Type.Size()%int64(Widthptr) != 0 || n.Xoffset%int64(Widthptr) != 0 || n.Type.Size() == 0 { - e.Fatalf(n.Pos, "var %L has size %d offset %d", n, n.Type.Size(), n.Xoffset) + if n.Type().Size()%int64(types.PtrSize) != 0 || n.FrameOffset()%int64(types.PtrSize) != 0 || n.Type().Size() == 0 { + e.Fatalf(n.Pos(), "var %L has size %d offset %d", n, n.Type().Size(), n.Offset_) } - if lo != hi && n.Xoffset+n.Type.Size() >= lo-int64(2*Widthreg) { + if lo != hi && n.FrameOffset()+n.Type().Size() >= lo-int64(2*types.RegSize) { // Merge with range we already have. - lo = n.Xoffset + lo = n.FrameOffset() continue } // Zero old range - p = thearch.ZeroRange(pp, p, frame+lo, hi-lo, &state) + p = Arch.ZeroRange(pp, p, frame+lo, hi-lo, &state) // Set new range. - lo = n.Xoffset - hi = lo + n.Type.Size() + lo = n.FrameOffset() + hi = lo + n.Type().Size() } // Zero final range. - thearch.ZeroRange(pp, p, frame+lo, hi-lo, &state) + Arch.ZeroRange(pp, p, frame+lo, hi-lo, &state) } // For generating consecutive jump instructions to model a specific branching @@ -6671,14 +7188,14 @@ type IndexJump struct { Index int } -func (s *SSAGenState) oneJump(b *ssa.Block, jump *IndexJump) { +func (s *State) oneJump(b *ssa.Block, jump *IndexJump) { p := s.Br(jump.Jump, b.Succs[jump.Index].Block()) p.Pos = b.Pos } // CombJump generates combinational instructions (2 at present) for a block jump, // thereby the behaviour of non-standard condition codes could be simulated -func (s *SSAGenState) CombJump(b, next *ssa.Block, jumps *[2][2]IndexJump) { +func (s *State) CombJump(b, next *ssa.Block, jumps *[2][2]IndexJump) { switch next { case b.Succs[0].Block(): s.oneJump(b, &jumps[0][0]) @@ -6724,16 +7241,20 @@ func AddAux2(a *obj.Addr, v *ssa.Value, offset int64) { case *obj.LSym: a.Name = obj.NAME_EXTERN a.Sym = n - case *Node: - if n.Class() == PPARAM || n.Class() == PPARAMOUT { + case *ir.Name: + if n.Class == ir.PPARAM || (n.Class == ir.PPARAMOUT && !n.IsOutputParamInRegisters()) { a.Name = obj.NAME_PARAM - a.Sym = n.Orig.Sym.Linksym() - a.Offset += n.Xoffset + a.Sym = ir.Orig(n).(*ir.Name).Linksym() + a.Offset += n.FrameOffset() break } a.Name = obj.NAME_AUTO - a.Sym = n.Sym.Linksym() - a.Offset += n.Xoffset + if n.Class == ir.PPARAMOUT { + a.Sym = ir.Orig(n).(*ir.Name).Linksym() + } else { + a.Sym = n.Linksym() + } + a.Offset += n.FrameOffset() default: v.Fatalf("aux in %s not implemented %#v", v, v.Aux) } @@ -6751,17 +7272,17 @@ func (s *state) extendIndex(idx, len *ssa.Value, kind ssa.BoundsKind, bounded bo // high word and branch to out-of-bounds failure if it is not 0. var lo *ssa.Value if idx.Type.IsSigned() { - lo = s.newValue1(ssa.OpInt64Lo, types.Types[TINT], idx) + lo = s.newValue1(ssa.OpInt64Lo, types.Types[types.TINT], idx) } else { - lo = s.newValue1(ssa.OpInt64Lo, types.Types[TUINT], idx) + lo = s.newValue1(ssa.OpInt64Lo, types.Types[types.TUINT], idx) } - if bounded || Debug.B != 0 { + if bounded || base.Flag.B != 0 { return lo } bNext := s.f.NewBlock(ssa.BlockPlain) bPanic := s.f.NewBlock(ssa.BlockExit) - hi := s.newValue1(ssa.OpInt64Hi, types.Types[TUINT32], idx) - cmp := s.newValue2(ssa.OpEq32, types.Types[TBOOL], hi, s.constInt32(types.Types[TUINT32], 0)) + hi := s.newValue1(ssa.OpInt64Hi, types.Types[types.TUINT32], idx) + cmp := s.newValue2(ssa.OpEq32, types.Types[types.TBOOL], hi, s.constInt32(types.Types[types.TUINT32], 0)) if !idx.Type.IsSigned() { switch kind { case ssa.BoundsIndex: @@ -6830,7 +7351,7 @@ func (s *state) extendIndex(idx, len *ssa.Value, kind ssa.BoundsKind, bounded bo s.Fatalf("bad unsigned index extension %s", idx.Type) } } - return s.newValue1(op, types.Types[TINT], idx) + return s.newValue1(op, types.Types[types.TINT], idx) } // CheckLoweredPhi checks that regalloc and stackalloc correctly handled phi values. @@ -6851,54 +7372,53 @@ func CheckLoweredPhi(v *ssa.Value) { } } -// CheckLoweredGetClosurePtr checks that v is the first instruction in the function's entry block. +// CheckLoweredGetClosurePtr checks that v is the first instruction in the function's entry block, +// except for incoming in-register arguments. // The output of LoweredGetClosurePtr is generally hardwired to the correct register. // That register contains the closure pointer on closure entry. func CheckLoweredGetClosurePtr(v *ssa.Value) { entry := v.Block.Func.Entry - if entry != v.Block || entry.Values[0] != v { - Fatalf("in %s, badly placed LoweredGetClosurePtr: %v %v", v.Block.Func.Name, v.Block, v) + if entry != v.Block { + base.Fatalf("in %s, badly placed LoweredGetClosurePtr: %v %v", v.Block.Func.Name, v.Block, v) + } + for _, w := range entry.Values { + if w == v { + break + } + switch w.Op { + case ssa.OpArgIntReg, ssa.OpArgFloatReg: + // okay + default: + base.Fatalf("in %s, badly placed LoweredGetClosurePtr: %v %v", v.Block.Func.Name, v.Block, v) + } } } -// AutoVar returns a *Node and int64 representing the auto variable and offset within it -// where v should be spilled. -func AutoVar(v *ssa.Value) (*Node, int64) { - loc := v.Block.Func.RegAlloc[v.ID].(ssa.LocalSlot) - if v.Type.Size() > loc.Type.Size() { - v.Fatalf("spill/restore type %s doesn't fit in slot type %s", v.Type, loc.Type) +// CheckArgReg ensures that v is in the function's entry block. +func CheckArgReg(v *ssa.Value) { + entry := v.Block.Func.Entry + if entry != v.Block { + base.Fatalf("in %s, badly placed ArgIReg or ArgFReg: %v %v", v.Block.Func.Name, v.Block, v) } - return loc.N.(*Node), loc.Off } func AddrAuto(a *obj.Addr, v *ssa.Value) { - n, off := AutoVar(v) + n, off := ssa.AutoVar(v) a.Type = obj.TYPE_MEM - a.Sym = n.Sym.Linksym() - a.Reg = int16(thearch.REGSP) - a.Offset = n.Xoffset + off - if n.Class() == PPARAM || n.Class() == PPARAMOUT { + a.Sym = n.Linksym() + a.Reg = int16(Arch.REGSP) + a.Offset = n.FrameOffset() + off + if n.Class == ir.PPARAM || (n.Class == ir.PPARAMOUT && !n.IsOutputParamInRegisters()) { a.Name = obj.NAME_PARAM } else { a.Name = obj.NAME_AUTO } } -func (s *SSAGenState) AddrScratch(a *obj.Addr) { - if s.ScratchFpMem == nil { - panic("no scratch memory available; forgot to declare usesScratch for Op?") - } - a.Type = obj.TYPE_MEM - a.Name = obj.NAME_AUTO - a.Sym = s.ScratchFpMem.Sym.Linksym() - a.Reg = int16(thearch.REGSP) - a.Offset = s.ScratchFpMem.Xoffset -} - // Call returns a new CALL instruction for the SSA value v. // It uses PrepareCall to prepare the call. -func (s *SSAGenState) Call(v *ssa.Value) *obj.Prog { - pPosIsStmt := s.pp.pos.IsStmt() // The statement-ness fo the call comes from ssaGenState +func (s *State) Call(v *ssa.Value) *obj.Prog { + pPosIsStmt := s.pp.Pos.IsStmt() // The statement-ness fo the call comes from ssaGenState s.PrepareCall(v) p := s.Prog(obj.ACALL) @@ -6913,13 +7433,13 @@ func (s *SSAGenState) Call(v *ssa.Value) *obj.Prog { p.To.Sym = sym.Fn } else { // TODO(mdempsky): Can these differences be eliminated? - switch thearch.LinkArch.Family { + switch Arch.LinkArch.Family { case sys.AMD64, sys.I386, sys.PPC64, sys.RISCV64, sys.S390X, sys.Wasm: p.To.Type = obj.TYPE_REG case sys.ARM, sys.ARM64, sys.MIPS, sys.MIPS64: p.To.Type = obj.TYPE_MEM default: - Fatalf("unknown indirect call family") + base.Fatalf("unknown indirect call family") } p.To.Reg = v.Args[0].Reg() } @@ -6929,18 +7449,18 @@ func (s *SSAGenState) Call(v *ssa.Value) *obj.Prog { // PrepareCall prepares to emit a CALL instruction for v and does call-related bookkeeping. // It must be called immediately before emitting the actual CALL instruction, // since it emits PCDATA for the stack map at the call (calls are safe points). -func (s *SSAGenState) PrepareCall(v *ssa.Value) { +func (s *State) PrepareCall(v *ssa.Value) { idx := s.livenessMap.Get(v) if !idx.StackMapValid() { // See Liveness.hasStackMap. - if sym, ok := v.Aux.(*ssa.AuxCall); !ok || !(sym.Fn == typedmemclr || sym.Fn == typedmemmove) { - Fatalf("missing stack map index for %v", v.LongString()) + if sym, ok := v.Aux.(*ssa.AuxCall); !ok || !(sym.Fn == ir.Syms.Typedmemclr || sym.Fn == ir.Syms.Typedmemmove) { + base.Fatalf("missing stack map index for %v", v.LongString()) } } call, ok := v.Aux.(*ssa.AuxCall) - if ok && call.Fn == Deferreturn { + if ok && call.Fn == ir.Syms.Deferreturn { // Deferred calls will appear to be returning to // the CALL deferreturn(SB) that we are about to emit. // However, the stack trace code will show the line @@ -6949,14 +7469,14 @@ func (s *SSAGenState) PrepareCall(v *ssa.Value) { // insert an actual hardware NOP that will have the right line number. // This is different from obj.ANOP, which is a virtual no-op // that doesn't make it into the instruction stream. - thearch.Ginsnopdefer(s.pp) + Arch.Ginsnopdefer(s.pp) } if ok { // Record call graph information for nowritebarrierrec // analysis. if nowritebarrierrecCheck != nil { - nowritebarrierrecCheck.recordCall(s.pp.curfn, call.Fn, v.Pos) + nowritebarrierrecCheck.recordCall(s.pp.CurFunc, call.Fn, v.Pos) } } @@ -6967,30 +7487,26 @@ func (s *SSAGenState) PrepareCall(v *ssa.Value) { // UseArgs records the fact that an instruction needs a certain amount of // callee args space for its use. -func (s *SSAGenState) UseArgs(n int64) { +func (s *State) UseArgs(n int64) { if s.maxarg < n { s.maxarg = n } } // fieldIdx finds the index of the field referred to by the ODOT node n. -func fieldIdx(n *Node) int { - t := n.Left.Type - f := n.Sym +func fieldIdx(n *ir.SelectorExpr) int { + t := n.X.Type() if !t.IsStruct() { panic("ODOT's LHS is not a struct") } - var i int - for _, t1 := range t.Fields().Slice() { - if t1.Sym != f { - i++ - continue - } - if t1.Offset != n.Xoffset { - panic("field offset doesn't match") + for i, f := range t.Fields().Slice() { + if f.Sym == n.Sel { + if f.Offset != n.Offset() { + panic("field offset doesn't match") + } + return i } - return i } panic(fmt.Sprintf("can't find field in expr %v\n", n)) @@ -7001,12 +7517,11 @@ func fieldIdx(n *Node) int { // ssafn holds frontend information about a function that the backend is processing. // It also exports a bunch of compiler services for the ssa backend. type ssafn struct { - curfn *Node - strings map[string]*obj.LSym // map from constant string to data symbols - scratchFpMem *Node // temp for floating point register / memory moves on some architectures - stksize int64 // stack size for current frame - stkptrsize int64 // prefix of stack containing pointers - log bool // print ssa debug to the stdout + curfn *ir.Func + strings map[string]*obj.LSym // map from constant string to data symbols + stksize int64 // stack size for current frame + stkptrsize int64 // prefix of stack containing pointers + log bool // print ssa debug to the stdout } // StringData returns a symbol which @@ -7018,132 +7533,47 @@ func (e *ssafn) StringData(s string) *obj.LSym { if e.strings == nil { e.strings = make(map[string]*obj.LSym) } - data := stringsym(e.curfn.Pos, s) + data := staticdata.StringSym(e.curfn.Pos(), s) e.strings[s] = data return data } -func (e *ssafn) Auto(pos src.XPos, t *types.Type) ssa.GCNode { - n := tempAt(pos, e.curfn, t) // Note: adds new auto to e.curfn.Func.Dcl list - return n -} - -func (e *ssafn) SplitString(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot) { - ptrType := types.NewPtr(types.Types[TUINT8]) - lenType := types.Types[TINT] - // Split this string up into two separate variables. - p := e.SplitSlot(&name, ".ptr", 0, ptrType) - l := e.SplitSlot(&name, ".len", ptrType.Size(), lenType) - return p, l -} - -func (e *ssafn) SplitInterface(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot) { - n := name.N.(*Node) - u := types.Types[TUINTPTR] - t := types.NewPtr(types.Types[TUINT8]) - // Split this interface up into two separate variables. - f := ".itab" - if n.Type.IsEmptyInterface() { - f = ".type" - } - c := e.SplitSlot(&name, f, 0, u) // see comment in plive.go:onebitwalktype1. - d := e.SplitSlot(&name, ".data", u.Size(), t) - return c, d -} - -func (e *ssafn) SplitSlice(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot, ssa.LocalSlot) { - ptrType := types.NewPtr(name.Type.Elem()) - lenType := types.Types[TINT] - p := e.SplitSlot(&name, ".ptr", 0, ptrType) - l := e.SplitSlot(&name, ".len", ptrType.Size(), lenType) - c := e.SplitSlot(&name, ".cap", ptrType.Size()+lenType.Size(), lenType) - return p, l, c -} - -func (e *ssafn) SplitComplex(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot) { - s := name.Type.Size() / 2 - var t *types.Type - if s == 8 { - t = types.Types[TFLOAT64] - } else { - t = types.Types[TFLOAT32] - } - r := e.SplitSlot(&name, ".real", 0, t) - i := e.SplitSlot(&name, ".imag", t.Size(), t) - return r, i -} - -func (e *ssafn) SplitInt64(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot) { - var t *types.Type - if name.Type.IsSigned() { - t = types.Types[TINT32] - } else { - t = types.Types[TUINT32] - } - if thearch.LinkArch.ByteOrder == binary.BigEndian { - return e.SplitSlot(&name, ".hi", 0, t), e.SplitSlot(&name, ".lo", t.Size(), types.Types[TUINT32]) - } - return e.SplitSlot(&name, ".hi", t.Size(), t), e.SplitSlot(&name, ".lo", 0, types.Types[TUINT32]) -} - -func (e *ssafn) SplitStruct(name ssa.LocalSlot, i int) ssa.LocalSlot { - st := name.Type - // Note: the _ field may appear several times. But - // have no fear, identically-named but distinct Autos are - // ok, albeit maybe confusing for a debugger. - return e.SplitSlot(&name, "."+st.FieldName(i), st.FieldOff(i), st.FieldType(i)) -} - -func (e *ssafn) SplitArray(name ssa.LocalSlot) ssa.LocalSlot { - n := name.N.(*Node) - at := name.Type - if at.NumElem() != 1 { - e.Fatalf(n.Pos, "bad array size") - } - et := at.Elem() - return e.SplitSlot(&name, "[0]", 0, et) +func (e *ssafn) Auto(pos src.XPos, t *types.Type) *ir.Name { + return typecheck.TempAt(pos, e.curfn, t) // Note: adds new auto to e.curfn.Func.Dcl list } func (e *ssafn) DerefItab(it *obj.LSym, offset int64) *obj.LSym { - return itabsym(it, offset) + return reflectdata.ITabSym(it, offset) } // SplitSlot returns a slot representing the data of parent starting at offset. func (e *ssafn) SplitSlot(parent *ssa.LocalSlot, suffix string, offset int64, t *types.Type) ssa.LocalSlot { - node := parent.N.(*Node) + node := parent.N - if node.Class() != PAUTO || node.Name.Addrtaken() { + if node.Class != ir.PAUTO || node.Addrtaken() { // addressed things and non-autos retain their parents (i.e., cannot truly be split) return ssa.LocalSlot{N: node, Type: t, Off: parent.Off + offset} } - s := &types.Sym{Name: node.Sym.Name + suffix, Pkg: localpkg} - - n := &Node{ - Name: new(Name), - Op: ONAME, - Pos: parent.N.(*Node).Pos, - } - n.Orig = n - - s.Def = asTypesNode(n) - asNode(s.Def).Name.SetUsed(true) - n.Sym = s - n.Type = t - n.SetClass(PAUTO) - n.Esc = EscNever - n.Name.Curfn = e.curfn - e.curfn.Func.Dcl = append(e.curfn.Func.Dcl, n) - dowidth(t) + s := &types.Sym{Name: node.Sym().Name + suffix, Pkg: types.LocalPkg} + n := ir.NewNameAt(parent.N.Pos(), s) + s.Def = n + ir.AsNode(s.Def).Name().SetUsed(true) + n.SetType(t) + n.Class = ir.PAUTO + n.SetEsc(ir.EscNever) + n.Curfn = e.curfn + e.curfn.Dcl = append(e.curfn.Dcl, n) + types.CalcSize(t) return ssa.LocalSlot{N: n, Type: t, Off: 0, SplitOf: parent, SplitOffset: offset} } func (e *ssafn) CanSSA(t *types.Type) bool { - return canSSAType(t) + return TypeOK(t) } func (e *ssafn) Line(pos src.XPos) string { - return linestr(pos) + return base.FmtPos(pos) } // Log logs a message from the compiler. @@ -7159,73 +7589,151 @@ func (e *ssafn) Log() bool { // Fatal reports a compiler error and exits. func (e *ssafn) Fatalf(pos src.XPos, msg string, args ...interface{}) { - lineno = pos - nargs := append([]interface{}{e.curfn.funcname()}, args...) - Fatalf("'%s': "+msg, nargs...) + base.Pos = pos + nargs := append([]interface{}{ir.FuncName(e.curfn)}, args...) + base.Fatalf("'%s': "+msg, nargs...) } // Warnl reports a "warning", which is usually flag-triggered // logging output for the benefit of tests. func (e *ssafn) Warnl(pos src.XPos, fmt_ string, args ...interface{}) { - Warnl(pos, fmt_, args...) + base.WarnfAt(pos, fmt_, args...) } func (e *ssafn) Debug_checknil() bool { - return Debug_checknil != 0 + return base.Debug.Nil != 0 } func (e *ssafn) UseWriteBarrier() bool { - return use_writebarrier + return base.Flag.WB } func (e *ssafn) Syslook(name string) *obj.LSym { switch name { case "goschedguarded": - return goschedguarded + return ir.Syms.Goschedguarded case "writeBarrier": - return writeBarrier + return ir.Syms.WriteBarrier case "gcWriteBarrier": - return gcWriteBarrier + return ir.Syms.GCWriteBarrier case "typedmemmove": - return typedmemmove + return ir.Syms.Typedmemmove case "typedmemclr": - return typedmemclr + return ir.Syms.Typedmemclr } e.Fatalf(src.NoXPos, "unknown Syslook func %v", name) return nil } func (e *ssafn) SetWBPos(pos src.XPos) { - e.curfn.Func.setWBPos(pos) + e.curfn.SetWBPos(pos) } func (e *ssafn) MyImportPath() string { - return myimportpath + return base.Ctxt.Pkgpath } -func (n *Node) Typ() *types.Type { - return n.Type +func clobberBase(n ir.Node) ir.Node { + if n.Op() == ir.ODOT { + n := n.(*ir.SelectorExpr) + if n.X.Type().NumFields() == 1 { + return clobberBase(n.X) + } + } + if n.Op() == ir.OINDEX { + n := n.(*ir.IndexExpr) + if n.X.Type().IsArray() && n.X.Type().NumElem() == 1 { + return clobberBase(n.X) + } + } + return n } -func (n *Node) StorageClass() ssa.StorageClass { - switch n.Class() { - case PPARAM: - return ssa.ClassParam - case PPARAMOUT: - return ssa.ClassParamOut - case PAUTO: - return ssa.ClassAuto - default: - Fatalf("untranslatable storage class for %v: %s", n, n.Class()) - return 0 + +// callTargetLSym returns the correct LSym to call 'callee' using its ABI. +func callTargetLSym(callee *ir.Name) *obj.LSym { + if callee.Func == nil { + // TODO(austin): This happens in a few cases of + // compiler-generated functions. These are all + // ABIInternal. It would be better if callee.Func was + // never nil and we didn't need this case. + return callee.Linksym() } + + return callee.LinksymABI(callee.Func.ABI) } -func clobberBase(n *Node) *Node { - if n.Op == ODOT && n.Left.Type.NumFields() == 1 { - return clobberBase(n.Left) +func min8(a, b int8) int8 { + if a < b { + return a } - if n.Op == OINDEX && n.Left.Type.IsArray() && n.Left.Type.NumElem() == 1 { - return clobberBase(n.Left) + return b +} + +func max8(a, b int8) int8 { + if a > b { + return a } - return n + return b } + +// deferstruct makes a runtime._defer structure, with additional space for +// stksize bytes of args. +func deferstruct(stksize int64) *types.Type { + makefield := func(name string, typ *types.Type) *types.Field { + // Unlike the global makefield function, this one needs to set Pkg + // because these types might be compared (in SSA CSE sorting). + // TODO: unify this makefield and the global one above. + sym := &types.Sym{Name: name, Pkg: types.LocalPkg} + return types.NewField(src.NoXPos, sym, typ) + } + argtype := types.NewArray(types.Types[types.TUINT8], stksize) + argtype.Width = stksize + argtype.Align = 1 + // These fields must match the ones in runtime/runtime2.go:_defer and + // cmd/compile/internal/gc/ssa.go:(*state).call. + fields := []*types.Field{ + makefield("siz", types.Types[types.TUINT32]), + makefield("started", types.Types[types.TBOOL]), + makefield("heap", types.Types[types.TBOOL]), + makefield("openDefer", types.Types[types.TBOOL]), + makefield("sp", types.Types[types.TUINTPTR]), + makefield("pc", types.Types[types.TUINTPTR]), + // Note: the types here don't really matter. Defer structures + // are always scanned explicitly during stack copying and GC, + // so we make them uintptr type even though they are real pointers. + makefield("fn", types.Types[types.TUINTPTR]), + makefield("_panic", types.Types[types.TUINTPTR]), + makefield("link", types.Types[types.TUINTPTR]), + makefield("framepc", types.Types[types.TUINTPTR]), + makefield("varp", types.Types[types.TUINTPTR]), + makefield("fd", types.Types[types.TUINTPTR]), + makefield("args", argtype), + } + + // build struct holding the above fields + s := types.NewStruct(types.NoPkg, fields) + s.SetNoalg(true) + types.CalcStructSize(s) + return s +} + +// SlotAddr uses LocalSlot information to initialize an obj.Addr +// The resulting addr is used in a non-standard context -- in the prologue +// of a function, before the frame has been constructed, so the standard +// addressing for the parameters will be wrong. +func SpillSlotAddr(spill ssa.Spill, baseReg int16, extraOffset int64) obj.Addr { + return obj.Addr{ + Name: obj.NAME_NONE, + Type: obj.TYPE_MEM, + Reg: baseReg, + Offset: spill.Offset + extraOffset, + } +} + +var ( + BoundsCheckFunc [ssa.BoundsKindCount]*obj.LSym + ExtendCheckFunc [ssa.BoundsKindCount]*obj.LSym +) + +// GCWriteBarrierReg maps from registers to gcWriteBarrier implementation LSyms. +var GCWriteBarrierReg map[int16]*obj.LSym diff --git a/src/cmd/compile/internal/staticdata/data.go b/src/cmd/compile/internal/staticdata/data.go new file mode 100644 index 0000000000000000000000000000000000000000..abb0bba646e0caacf183e436cc084784b5907250 --- /dev/null +++ b/src/cmd/compile/internal/staticdata/data.go @@ -0,0 +1,372 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package staticdata + +import ( + "crypto/sha256" + "fmt" + "go/constant" + "internal/buildcfg" + "io" + "io/ioutil" + "os" + "sort" + "strconv" + "sync" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/src" +) + +// InitAddrOffset writes the static name symbol lsym to n, it does not modify n. +// It's the caller responsibility to make sure lsym is from ONAME/PEXTERN node. +func InitAddrOffset(n *ir.Name, noff int64, lsym *obj.LSym, off int64) { + if n.Op() != ir.ONAME { + base.Fatalf("InitAddr n op %v", n.Op()) + } + if n.Sym() == nil { + base.Fatalf("InitAddr nil n sym") + } + s := n.Linksym() + s.WriteAddr(base.Ctxt, noff, types.PtrSize, lsym, off) +} + +// InitAddr is InitAddrOffset, with offset fixed to 0. +func InitAddr(n *ir.Name, noff int64, lsym *obj.LSym) { + InitAddrOffset(n, noff, lsym, 0) +} + +// InitSlice writes a static slice symbol {lsym, lencap, lencap} to n+noff, it does not modify n. +// It's the caller responsibility to make sure lsym is from ONAME node. +func InitSlice(n *ir.Name, noff int64, lsym *obj.LSym, lencap int64) { + s := n.Linksym() + s.WriteAddr(base.Ctxt, noff, types.PtrSize, lsym, 0) + s.WriteInt(base.Ctxt, noff+types.SliceLenOffset, types.PtrSize, lencap) + s.WriteInt(base.Ctxt, noff+types.SliceCapOffset, types.PtrSize, lencap) +} + +func InitSliceBytes(nam *ir.Name, off int64, s string) { + if nam.Op() != ir.ONAME { + base.Fatalf("InitSliceBytes %v", nam) + } + InitSlice(nam, off, slicedata(nam.Pos(), s).Linksym(), int64(len(s))) +} + +const ( + stringSymPrefix = "go.string." + stringSymPattern = ".gostring.%d.%x" +) + +// StringSym returns a symbol containing the string s. +// The symbol contains the string data, not a string header. +func StringSym(pos src.XPos, s string) (data *obj.LSym) { + var symname string + if len(s) > 100 { + // Huge strings are hashed to avoid long names in object files. + // Indulge in some paranoia by writing the length of s, too, + // as protection against length extension attacks. + // Same pattern is known to fileStringSym below. + h := sha256.New() + io.WriteString(h, s) + symname = fmt.Sprintf(stringSymPattern, len(s), h.Sum(nil)) + } else { + // Small strings get named directly by their contents. + symname = strconv.Quote(s) + } + + symdata := base.Ctxt.Lookup(stringSymPrefix + symname) + if !symdata.OnList() { + off := dstringdata(symdata, 0, s, pos, "string") + objw.Global(symdata, int32(off), obj.DUPOK|obj.RODATA|obj.LOCAL) + symdata.Set(obj.AttrContentAddressable, true) + } + + return symdata +} + +// fileStringSym returns a symbol for the contents and the size of file. +// If readonly is true, the symbol shares storage with any literal string +// or other file with the same content and is placed in a read-only section. +// If readonly is false, the symbol is a read-write copy separate from any other, +// for use as the backing store of a []byte. +// The content hash of file is copied into hash. (If hash is nil, nothing is copied.) +// The returned symbol contains the data itself, not a string header. +func fileStringSym(pos src.XPos, file string, readonly bool, hash []byte) (*obj.LSym, int64, error) { + f, err := os.Open(file) + if err != nil { + return nil, 0, err + } + defer f.Close() + info, err := f.Stat() + if err != nil { + return nil, 0, err + } + if !info.Mode().IsRegular() { + return nil, 0, fmt.Errorf("not a regular file") + } + size := info.Size() + if size <= 1*1024 { + data, err := ioutil.ReadAll(f) + if err != nil { + return nil, 0, err + } + if int64(len(data)) != size { + return nil, 0, fmt.Errorf("file changed between reads") + } + var sym *obj.LSym + if readonly { + sym = StringSym(pos, string(data)) + } else { + sym = slicedata(pos, string(data)).Linksym() + } + if len(hash) > 0 { + sum := sha256.Sum256(data) + copy(hash, sum[:]) + } + return sym, size, nil + } + if size > 2e9 { + // ggloblsym takes an int32, + // and probably the rest of the toolchain + // can't handle such big symbols either. + // See golang.org/issue/9862. + return nil, 0, fmt.Errorf("file too large") + } + + // File is too big to read and keep in memory. + // Compute hash if needed for read-only content hashing or if the caller wants it. + var sum []byte + if readonly || len(hash) > 0 { + h := sha256.New() + n, err := io.Copy(h, f) + if err != nil { + return nil, 0, err + } + if n != size { + return nil, 0, fmt.Errorf("file changed between reads") + } + sum = h.Sum(nil) + copy(hash, sum) + } + + var symdata *obj.LSym + if readonly { + symname := fmt.Sprintf(stringSymPattern, size, sum) + symdata = base.Ctxt.Lookup(stringSymPrefix + symname) + if !symdata.OnList() { + info := symdata.NewFileInfo() + info.Name = file + info.Size = size + objw.Global(symdata, int32(size), obj.DUPOK|obj.RODATA|obj.LOCAL) + // Note: AttrContentAddressable cannot be set here, + // because the content-addressable-handling code + // does not know about file symbols. + } + } else { + // Emit a zero-length data symbol + // and then fix up length and content to use file. + symdata = slicedata(pos, "").Linksym() + symdata.Size = size + symdata.Type = objabi.SNOPTRDATA + info := symdata.NewFileInfo() + info.Name = file + info.Size = size + } + + return symdata, size, nil +} + +var slicedataGen int + +func slicedata(pos src.XPos, s string) *ir.Name { + slicedataGen++ + symname := fmt.Sprintf(".gobytes.%d", slicedataGen) + sym := types.LocalPkg.Lookup(symname) + symnode := typecheck.NewName(sym) + sym.Def = symnode + + lsym := symnode.Linksym() + off := dstringdata(lsym, 0, s, pos, "slice") + objw.Global(lsym, int32(off), obj.NOPTR|obj.LOCAL) + + return symnode +} + +func dstringdata(s *obj.LSym, off int, t string, pos src.XPos, what string) int { + // Objects that are too large will cause the data section to overflow right away, + // causing a cryptic error message by the linker. Check for oversize objects here + // and provide a useful error message instead. + if int64(len(t)) > 2e9 { + base.ErrorfAt(pos, "%v with length %v is too big", what, len(t)) + return 0 + } + + s.WriteString(base.Ctxt, int64(off), len(t), t) + return off + len(t) +} + +var ( + funcsymsmu sync.Mutex // protects funcsyms and associated package lookups (see func funcsym) + funcsyms []*ir.Name // functions that need function value symbols +) + +// FuncLinksym returns n·f, the function value symbol for n. +func FuncLinksym(n *ir.Name) *obj.LSym { + if n.Op() != ir.ONAME || n.Class != ir.PFUNC { + base.Fatalf("expected func name: %v", n) + } + s := n.Sym() + + // funcsymsmu here serves to protect not just mutations of funcsyms (below), + // but also the package lookup of the func sym name, + // since this function gets called concurrently from the backend. + // There are no other concurrent package lookups in the backend, + // except for the types package, which is protected separately. + // Reusing funcsymsmu to also cover this package lookup + // avoids a general, broader, expensive package lookup mutex. + // Note NeedFuncSym also does package look-up of func sym names, + // but that it is only called serially, from the front end. + funcsymsmu.Lock() + sf, existed := s.Pkg.LookupOK(ir.FuncSymName(s)) + // Don't export s·f when compiling for dynamic linking. + // When dynamically linking, the necessary function + // symbols will be created explicitly with NeedFuncSym. + // See the NeedFuncSym comment for details. + if !base.Ctxt.Flag_dynlink && !existed { + funcsyms = append(funcsyms, n) + } + funcsymsmu.Unlock() + + return sf.Linksym() +} + +func GlobalLinksym(n *ir.Name) *obj.LSym { + if n.Op() != ir.ONAME || n.Class != ir.PEXTERN { + base.Fatalf("expected global variable: %v", n) + } + return n.Linksym() +} + +// NeedFuncSym ensures that fn·f is exported, if needed. +// It is only used with -dynlink. +// When not compiling for dynamic linking, +// the funcsyms are created as needed by +// the packages that use them. +// Normally we emit the fn·f stubs as DUPOK syms, +// but DUPOK doesn't work across shared library boundaries. +// So instead, when dynamic linking, we only create +// the fn·f stubs in fn's package. +func NeedFuncSym(fn *ir.Func) { + if base.Ctxt.InParallel { + // The append below probably just needs to lock + // funcsymsmu, like in FuncSym. + base.Fatalf("NeedFuncSym must be called in serial") + } + if fn.ABI != obj.ABIInternal && buildcfg.Experiment.RegabiWrappers { + // Function values must always reference ABIInternal + // entry points, so it doesn't make sense to create a + // funcsym for other ABIs. + // + // (If we're using ABI aliases, it doesn't matter.) + base.Fatalf("expected ABIInternal: %v has %v", fn.Nname, fn.ABI) + } + if ir.IsBlank(fn.Nname) { + // Blank functions aren't unique, so we can't make a + // funcsym for them. + base.Fatalf("NeedFuncSym called for _") + } + if !base.Ctxt.Flag_dynlink { + return + } + s := fn.Nname.Sym() + if base.Flag.CompilingRuntime && (s.Name == "getg" || s.Name == "getclosureptr" || s.Name == "getcallerpc" || s.Name == "getcallersp") || + (base.Ctxt.Pkgpath == "internal/abi" && (s.Name == "FuncPCABI0" || s.Name == "FuncPCABIInternal")) { + // runtime.getg(), getclosureptr(), getcallerpc(), getcallersp(), + // and internal/abi.FuncPCABIxxx() are not real functions and so + // do not get funcsyms. + return + } + funcsyms = append(funcsyms, fn.Nname) +} + +func WriteFuncSyms() { + sort.Slice(funcsyms, func(i, j int) bool { + return funcsyms[i].Linksym().Name < funcsyms[j].Linksym().Name + }) + for _, nam := range funcsyms { + s := nam.Sym() + sf := s.Pkg.Lookup(ir.FuncSymName(s)).Linksym() + // Function values must always reference ABIInternal + // entry points. + target := s.Linksym() + if target.ABI() != obj.ABIInternal { + base.Fatalf("expected ABIInternal: %v has %v", target, target.ABI()) + } + objw.SymPtr(sf, 0, target, 0) + objw.Global(sf, int32(types.PtrSize), obj.DUPOK|obj.RODATA) + } +} + +// InitConst writes the static literal c to n. +// Neither n nor c is modified. +func InitConst(n *ir.Name, noff int64, c ir.Node, wid int) { + if n.Op() != ir.ONAME { + base.Fatalf("InitConst n op %v", n.Op()) + } + if n.Sym() == nil { + base.Fatalf("InitConst nil n sym") + } + if c.Op() == ir.ONIL { + return + } + if c.Op() != ir.OLITERAL { + base.Fatalf("InitConst c op %v", c.Op()) + } + s := n.Linksym() + switch u := c.Val(); u.Kind() { + case constant.Bool: + i := int64(obj.Bool2int(constant.BoolVal(u))) + s.WriteInt(base.Ctxt, noff, wid, i) + + case constant.Int: + s.WriteInt(base.Ctxt, noff, wid, ir.IntVal(c.Type(), u)) + + case constant.Float: + f, _ := constant.Float64Val(u) + switch c.Type().Kind() { + case types.TFLOAT32: + s.WriteFloat32(base.Ctxt, noff, float32(f)) + case types.TFLOAT64: + s.WriteFloat64(base.Ctxt, noff, f) + } + + case constant.Complex: + re, _ := constant.Float64Val(constant.Real(u)) + im, _ := constant.Float64Val(constant.Imag(u)) + switch c.Type().Kind() { + case types.TCOMPLEX64: + s.WriteFloat32(base.Ctxt, noff, float32(re)) + s.WriteFloat32(base.Ctxt, noff+4, float32(im)) + case types.TCOMPLEX128: + s.WriteFloat64(base.Ctxt, noff, re) + s.WriteFloat64(base.Ctxt, noff+8, im) + } + + case constant.String: + i := constant.StringVal(u) + symdata := StringSym(n.Pos(), i) + s.WriteAddr(base.Ctxt, noff, types.PtrSize, symdata, 0) + s.WriteInt(base.Ctxt, noff+int64(types.PtrSize), types.PtrSize, int64(len(i))) + + default: + base.Fatalf("InitConst unhandled OLITERAL %v", c) + } +} diff --git a/src/cmd/compile/internal/staticdata/embed.go b/src/cmd/compile/internal/staticdata/embed.go new file mode 100644 index 0000000000000000000000000000000000000000..8936c4f5b44e44c7f03edf81a137e49eb68ac564 --- /dev/null +++ b/src/cmd/compile/internal/staticdata/embed.go @@ -0,0 +1,181 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package staticdata + +import ( + "path" + "sort" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/types" + "cmd/internal/obj" +) + +const ( + embedUnknown = iota + embedBytes + embedString + embedFiles +) + +func embedFileList(v *ir.Name, kind int) []string { + // Build list of files to store. + have := make(map[string]bool) + var list []string + for _, e := range *v.Embed { + for _, pattern := range e.Patterns { + files, ok := base.Flag.Cfg.Embed.Patterns[pattern] + if !ok { + base.ErrorfAt(e.Pos, "invalid go:embed: build system did not map pattern: %s", pattern) + } + for _, file := range files { + if base.Flag.Cfg.Embed.Files[file] == "" { + base.ErrorfAt(e.Pos, "invalid go:embed: build system did not map file: %s", file) + continue + } + if !have[file] { + have[file] = true + list = append(list, file) + } + if kind == embedFiles { + for dir := path.Dir(file); dir != "." && !have[dir]; dir = path.Dir(dir) { + have[dir] = true + list = append(list, dir+"/") + } + } + } + } + } + sort.Slice(list, func(i, j int) bool { + return embedFileLess(list[i], list[j]) + }) + + if kind == embedString || kind == embedBytes { + if len(list) > 1 { + base.ErrorfAt(v.Pos(), "invalid go:embed: multiple files for type %v", v.Type()) + return nil + } + } + + return list +} + +// embedKind determines the kind of embedding variable. +func embedKind(typ *types.Type) int { + if typ.Sym() != nil && typ.Sym().Name == "FS" && (typ.Sym().Pkg.Path == "embed" || (typ.Sym().Pkg == types.LocalPkg && base.Ctxt.Pkgpath == "embed")) { + return embedFiles + } + if typ.Kind() == types.TSTRING { + return embedString + } + if typ.Sym() == nil && typ.IsSlice() && typ.Elem().Kind() == types.TUINT8 { + return embedBytes + } + return embedUnknown +} + +func embedFileNameSplit(name string) (dir, elem string, isDir bool) { + if name[len(name)-1] == '/' { + isDir = true + name = name[:len(name)-1] + } + i := len(name) - 1 + for i >= 0 && name[i] != '/' { + i-- + } + if i < 0 { + return ".", name, isDir + } + return name[:i], name[i+1:], isDir +} + +// embedFileLess implements the sort order for a list of embedded files. +// See the comment inside ../../../../embed/embed.go's Files struct for rationale. +func embedFileLess(x, y string) bool { + xdir, xelem, _ := embedFileNameSplit(x) + ydir, yelem, _ := embedFileNameSplit(y) + return xdir < ydir || xdir == ydir && xelem < yelem +} + +// WriteEmbed emits the init data for a //go:embed variable, +// which is either a string, a []byte, or an embed.FS. +func WriteEmbed(v *ir.Name) { + // TODO(mdempsky): User errors should be reported by the frontend. + + commentPos := (*v.Embed)[0].Pos + if !types.AllowsGoVersion(types.LocalPkg, 1, 16) { + prevPos := base.Pos + base.Pos = commentPos + base.ErrorfVers("go1.16", "go:embed") + base.Pos = prevPos + return + } + if base.Flag.Cfg.Embed.Patterns == nil { + base.ErrorfAt(commentPos, "invalid go:embed: build system did not supply embed configuration") + return + } + kind := embedKind(v.Type()) + if kind == embedUnknown { + base.ErrorfAt(v.Pos(), "go:embed cannot apply to var of type %v", v.Type()) + return + } + + files := embedFileList(v, kind) + switch kind { + case embedString, embedBytes: + file := files[0] + fsym, size, err := fileStringSym(v.Pos(), base.Flag.Cfg.Embed.Files[file], kind == embedString, nil) + if err != nil { + base.ErrorfAt(v.Pos(), "embed %s: %v", file, err) + } + sym := v.Linksym() + off := 0 + off = objw.SymPtr(sym, off, fsym, 0) // data string + off = objw.Uintptr(sym, off, uint64(size)) // len + if kind == embedBytes { + objw.Uintptr(sym, off, uint64(size)) // cap for slice + } + + case embedFiles: + slicedata := base.Ctxt.Lookup(`"".` + v.Sym().Name + `.files`) + off := 0 + // []files pointed at by Files + off = objw.SymPtr(slicedata, off, slicedata, 3*types.PtrSize) // []file, pointing just past slice + off = objw.Uintptr(slicedata, off, uint64(len(files))) + off = objw.Uintptr(slicedata, off, uint64(len(files))) + + // embed/embed.go type file is: + // name string + // data string + // hash [16]byte + // Emit one of these per file in the set. + const hashSize = 16 + hash := make([]byte, hashSize) + for _, file := range files { + off = objw.SymPtr(slicedata, off, StringSym(v.Pos(), file), 0) // file string + off = objw.Uintptr(slicedata, off, uint64(len(file))) + if strings.HasSuffix(file, "/") { + // entry for directory - no data + off = objw.Uintptr(slicedata, off, 0) + off = objw.Uintptr(slicedata, off, 0) + off += hashSize + } else { + fsym, size, err := fileStringSym(v.Pos(), base.Flag.Cfg.Embed.Files[file], true, hash) + if err != nil { + base.ErrorfAt(v.Pos(), "embed %s: %v", file, err) + } + off = objw.SymPtr(slicedata, off, fsym, 0) // data string + off = objw.Uintptr(slicedata, off, uint64(size)) + off = int(slicedata.WriteBytes(base.Ctxt, int64(off), hash)) + } + } + objw.Global(slicedata, int32(off), obj.RODATA|obj.LOCAL) + sym := v.Linksym() + objw.SymPtr(sym, 0, slicedata, 0) + } +} diff --git a/src/cmd/compile/internal/staticinit/sched.go b/src/cmd/compile/internal/staticinit/sched.go new file mode 100644 index 0000000000000000000000000000000000000000..0c97b6de747660541c604beb3c7f4046f8dae10a --- /dev/null +++ b/src/cmd/compile/internal/staticinit/sched.go @@ -0,0 +1,609 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package staticinit + +import ( + "fmt" + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/staticdata" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/src" +) + +type Entry struct { + Xoffset int64 // struct, array only + Expr ir.Node // bytes of run-time computed expressions +} + +type Plan struct { + E []Entry +} + +// An Schedule is used to decompose assignment statements into +// static and dynamic initialization parts. Static initializations are +// handled by populating variables' linker symbol data, while dynamic +// initializations are accumulated to be executed in order. +type Schedule struct { + // Out is the ordered list of dynamic initialization + // statements. + Out []ir.Node + + Plans map[ir.Node]*Plan + Temps map[ir.Node]*ir.Name +} + +func (s *Schedule) append(n ir.Node) { + s.Out = append(s.Out, n) +} + +// StaticInit adds an initialization statement n to the schedule. +func (s *Schedule) StaticInit(n ir.Node) { + if !s.tryStaticInit(n) { + if base.Flag.Percent != 0 { + ir.Dump("nonstatic", n) + } + s.append(n) + } +} + +// tryStaticInit attempts to statically execute an initialization +// statement and reports whether it succeeded. +func (s *Schedule) tryStaticInit(nn ir.Node) bool { + // Only worry about simple "l = r" assignments. Multiple + // variable/expression OAS2 assignments have already been + // replaced by multiple simple OAS assignments, and the other + // OAS2* assignments mostly necessitate dynamic execution + // anyway. + if nn.Op() != ir.OAS { + return false + } + n := nn.(*ir.AssignStmt) + if ir.IsBlank(n.X) && !AnySideEffects(n.Y) { + // Discard. + return true + } + lno := ir.SetPos(n) + defer func() { base.Pos = lno }() + nam := n.X.(*ir.Name) + return s.StaticAssign(nam, 0, n.Y, nam.Type()) +} + +// like staticassign but we are copying an already +// initialized value r. +func (s *Schedule) staticcopy(l *ir.Name, loff int64, rn *ir.Name, typ *types.Type) bool { + if rn.Class == ir.PFUNC { + // TODO if roff != 0 { panic } + staticdata.InitAddr(l, loff, staticdata.FuncLinksym(rn)) + return true + } + if rn.Class != ir.PEXTERN || rn.Sym().Pkg != types.LocalPkg { + return false + } + if rn.Defn.Op() != ir.OAS { + return false + } + if rn.Type().IsString() { // perhaps overwritten by cmd/link -X (#34675) + return false + } + if rn.Embed != nil { + return false + } + orig := rn + r := rn.Defn.(*ir.AssignStmt).Y + if r == nil { + // No explicit initialization value. Probably zeroed but perhaps + // supplied externally and of unknown value. + return false + } + + for r.Op() == ir.OCONVNOP && !types.Identical(r.Type(), typ) { + r = r.(*ir.ConvExpr).X + } + + switch r.Op() { + case ir.OMETHEXPR: + r = r.(*ir.SelectorExpr).FuncName() + fallthrough + case ir.ONAME: + r := r.(*ir.Name) + if s.staticcopy(l, loff, r, typ) { + return true + } + // We may have skipped past one or more OCONVNOPs, so + // use conv to ensure r is assignable to l (#13263). + dst := ir.Node(l) + if loff != 0 || !types.Identical(typ, l.Type()) { + dst = ir.NewNameOffsetExpr(base.Pos, l, loff, typ) + } + s.append(ir.NewAssignStmt(base.Pos, dst, typecheck.Conv(r, typ))) + return true + + case ir.ONIL: + return true + + case ir.OLITERAL: + if ir.IsZero(r) { + return true + } + staticdata.InitConst(l, loff, r, int(typ.Width)) + return true + + case ir.OADDR: + r := r.(*ir.AddrExpr) + if a, ok := r.X.(*ir.Name); ok && a.Op() == ir.ONAME { + staticdata.InitAddr(l, loff, staticdata.GlobalLinksym(a)) + return true + } + + case ir.OPTRLIT: + r := r.(*ir.AddrExpr) + switch r.X.Op() { + case ir.OARRAYLIT, ir.OSLICELIT, ir.OSTRUCTLIT, ir.OMAPLIT: + // copy pointer + staticdata.InitAddr(l, loff, staticdata.GlobalLinksym(s.Temps[r])) + return true + } + + case ir.OSLICELIT: + r := r.(*ir.CompLitExpr) + // copy slice + staticdata.InitSlice(l, loff, staticdata.GlobalLinksym(s.Temps[r]), r.Len) + return true + + case ir.OARRAYLIT, ir.OSTRUCTLIT: + r := r.(*ir.CompLitExpr) + p := s.Plans[r] + for i := range p.E { + e := &p.E[i] + typ := e.Expr.Type() + if e.Expr.Op() == ir.OLITERAL || e.Expr.Op() == ir.ONIL { + staticdata.InitConst(l, loff+e.Xoffset, e.Expr, int(typ.Width)) + continue + } + x := e.Expr + if x.Op() == ir.OMETHEXPR { + x = x.(*ir.SelectorExpr).FuncName() + } + if x.Op() == ir.ONAME && s.staticcopy(l, loff+e.Xoffset, x.(*ir.Name), typ) { + continue + } + // Requires computation, but we're + // copying someone else's computation. + ll := ir.NewNameOffsetExpr(base.Pos, l, loff+e.Xoffset, typ) + rr := ir.NewNameOffsetExpr(base.Pos, orig, e.Xoffset, typ) + ir.SetPos(rr) + s.append(ir.NewAssignStmt(base.Pos, ll, rr)) + } + + return true + } + + return false +} + +func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Type) bool { + if r == nil { + // No explicit initialization value. Either zero or supplied + // externally. + return true + } + for r.Op() == ir.OCONVNOP { + r = r.(*ir.ConvExpr).X + } + + assign := func(pos src.XPos, a *ir.Name, aoff int64, v ir.Node) { + if s.StaticAssign(a, aoff, v, v.Type()) { + return + } + var lhs ir.Node + if ir.IsBlank(a) { + // Don't use NameOffsetExpr with blank (#43677). + lhs = ir.BlankNode + } else { + lhs = ir.NewNameOffsetExpr(pos, a, aoff, v.Type()) + } + s.append(ir.NewAssignStmt(pos, lhs, v)) + } + + switch r.Op() { + case ir.ONAME: + r := r.(*ir.Name) + return s.staticcopy(l, loff, r, typ) + + case ir.OMETHEXPR: + r := r.(*ir.SelectorExpr) + return s.staticcopy(l, loff, r.FuncName(), typ) + + case ir.ONIL: + return true + + case ir.OLITERAL: + if ir.IsZero(r) { + return true + } + staticdata.InitConst(l, loff, r, int(typ.Width)) + return true + + case ir.OADDR: + r := r.(*ir.AddrExpr) + if name, offset, ok := StaticLoc(r.X); ok && name.Class == ir.PEXTERN { + staticdata.InitAddrOffset(l, loff, name.Linksym(), offset) + return true + } + fallthrough + + case ir.OPTRLIT: + r := r.(*ir.AddrExpr) + switch r.X.Op() { + case ir.OARRAYLIT, ir.OSLICELIT, ir.OMAPLIT, ir.OSTRUCTLIT: + // Init pointer. + a := StaticName(r.X.Type()) + + s.Temps[r] = a + staticdata.InitAddr(l, loff, a.Linksym()) + + // Init underlying literal. + assign(base.Pos, a, 0, r.X) + return true + } + //dump("not static ptrlit", r); + + case ir.OSTR2BYTES: + r := r.(*ir.ConvExpr) + if l.Class == ir.PEXTERN && r.X.Op() == ir.OLITERAL { + sval := ir.StringVal(r.X) + staticdata.InitSliceBytes(l, loff, sval) + return true + } + + case ir.OSLICELIT: + r := r.(*ir.CompLitExpr) + s.initplan(r) + // Init slice. + ta := types.NewArray(r.Type().Elem(), r.Len) + ta.SetNoalg(true) + a := StaticName(ta) + s.Temps[r] = a + staticdata.InitSlice(l, loff, a.Linksym(), r.Len) + // Fall through to init underlying array. + l = a + loff = 0 + fallthrough + + case ir.OARRAYLIT, ir.OSTRUCTLIT: + r := r.(*ir.CompLitExpr) + s.initplan(r) + + p := s.Plans[r] + for i := range p.E { + e := &p.E[i] + if e.Expr.Op() == ir.OLITERAL || e.Expr.Op() == ir.ONIL { + staticdata.InitConst(l, loff+e.Xoffset, e.Expr, int(e.Expr.Type().Width)) + continue + } + ir.SetPos(e.Expr) + assign(base.Pos, l, loff+e.Xoffset, e.Expr) + } + + return true + + case ir.OMAPLIT: + break + + case ir.OCLOSURE: + r := r.(*ir.ClosureExpr) + if ir.IsTrivialClosure(r) { + if base.Debug.Closure > 0 { + base.WarnfAt(r.Pos(), "closure converted to global") + } + // Closures with no captured variables are globals, + // so the assignment can be done at link time. + // TODO if roff != 0 { panic } + staticdata.InitAddr(l, loff, staticdata.FuncLinksym(r.Func.Nname)) + return true + } + ir.ClosureDebugRuntimeCheck(r) + + case ir.OCONVIFACE: + // This logic is mirrored in isStaticCompositeLiteral. + // If you change something here, change it there, and vice versa. + + // Determine the underlying concrete type and value we are converting from. + r := r.(*ir.ConvExpr) + val := ir.Node(r) + for val.Op() == ir.OCONVIFACE { + val = val.(*ir.ConvExpr).X + } + + if val.Type().IsInterface() { + // val is an interface type. + // If val is nil, we can statically initialize l; + // both words are zero and so there no work to do, so report success. + // If val is non-nil, we have no concrete type to record, + // and we won't be able to statically initialize its value, so report failure. + return val.Op() == ir.ONIL + } + + reflectdata.MarkTypeUsedInInterface(val.Type(), l.Linksym()) + + var itab *ir.AddrExpr + if typ.IsEmptyInterface() { + itab = reflectdata.TypePtr(val.Type()) + } else { + itab = reflectdata.ITabAddr(val.Type(), typ) + } + + // Create a copy of l to modify while we emit data. + + // Emit itab, advance offset. + staticdata.InitAddr(l, loff, itab.X.(*ir.LinksymOffsetExpr).Linksym) + + // Emit data. + if types.IsDirectIface(val.Type()) { + if val.Op() == ir.ONIL { + // Nil is zero, nothing to do. + return true + } + // Copy val directly into n. + ir.SetPos(val) + assign(base.Pos, l, loff+int64(types.PtrSize), val) + } else { + // Construct temp to hold val, write pointer to temp into n. + a := StaticName(val.Type()) + s.Temps[val] = a + assign(base.Pos, a, 0, val) + staticdata.InitAddr(l, loff+int64(types.PtrSize), a.Linksym()) + } + + return true + } + + //dump("not static", r); + return false +} + +func (s *Schedule) initplan(n ir.Node) { + if s.Plans[n] != nil { + return + } + p := new(Plan) + s.Plans[n] = p + switch n.Op() { + default: + base.Fatalf("initplan") + + case ir.OARRAYLIT, ir.OSLICELIT: + n := n.(*ir.CompLitExpr) + var k int64 + for _, a := range n.List { + if a.Op() == ir.OKEY { + kv := a.(*ir.KeyExpr) + k = typecheck.IndexConst(kv.Key) + if k < 0 { + base.Fatalf("initplan arraylit: invalid index %v", kv.Key) + } + a = kv.Value + } + s.addvalue(p, k*n.Type().Elem().Width, a) + k++ + } + + case ir.OSTRUCTLIT: + n := n.(*ir.CompLitExpr) + for _, a := range n.List { + if a.Op() != ir.OSTRUCTKEY { + base.Fatalf("initplan structlit") + } + a := a.(*ir.StructKeyExpr) + if a.Field.IsBlank() { + continue + } + s.addvalue(p, a.Offset, a.Value) + } + + case ir.OMAPLIT: + n := n.(*ir.CompLitExpr) + for _, a := range n.List { + if a.Op() != ir.OKEY { + base.Fatalf("initplan maplit") + } + a := a.(*ir.KeyExpr) + s.addvalue(p, -1, a.Value) + } + } +} + +func (s *Schedule) addvalue(p *Plan, xoffset int64, n ir.Node) { + // special case: zero can be dropped entirely + if ir.IsZero(n) { + return + } + + // special case: inline struct and array (not slice) literals + if isvaluelit(n) { + s.initplan(n) + q := s.Plans[n] + for _, qe := range q.E { + // qe is a copy; we are not modifying entries in q.E + qe.Xoffset += xoffset + p.E = append(p.E, qe) + } + return + } + + // add to plan + p.E = append(p.E, Entry{Xoffset: xoffset, Expr: n}) +} + +// from here down is the walk analysis +// of composite literals. +// most of the work is to generate +// data statements for the constant +// part of the composite literal. + +var statuniqgen int // name generator for static temps + +// StaticName returns a name backed by a (writable) static data symbol. +// Use readonlystaticname for read-only node. +func StaticName(t *types.Type) *ir.Name { + // Don't use LookupNum; it interns the resulting string, but these are all unique. + n := typecheck.NewName(typecheck.Lookup(fmt.Sprintf("%s%d", obj.StaticNamePref, statuniqgen))) + statuniqgen++ + typecheck.Declare(n, ir.PEXTERN) + n.SetType(t) + return n +} + +// StaticLoc returns the static address of n, if n has one, or else nil. +func StaticLoc(n ir.Node) (name *ir.Name, offset int64, ok bool) { + if n == nil { + return nil, 0, false + } + + switch n.Op() { + case ir.ONAME: + n := n.(*ir.Name) + return n, 0, true + + case ir.OMETHEXPR: + n := n.(*ir.SelectorExpr) + return StaticLoc(n.FuncName()) + + case ir.ODOT: + n := n.(*ir.SelectorExpr) + if name, offset, ok = StaticLoc(n.X); !ok { + break + } + offset += n.Offset() + return name, offset, true + + case ir.OINDEX: + n := n.(*ir.IndexExpr) + if n.X.Type().IsSlice() { + break + } + if name, offset, ok = StaticLoc(n.X); !ok { + break + } + l := getlit(n.Index) + if l < 0 { + break + } + + // Check for overflow. + if n.Type().Width != 0 && types.MaxWidth/n.Type().Width <= int64(l) { + break + } + offset += int64(l) * n.Type().Width + return name, offset, true + } + + return nil, 0, false +} + +// AnySideEffects reports whether n contains any operations that could have observable side effects. +func AnySideEffects(n ir.Node) bool { + return ir.Any(n, func(n ir.Node) bool { + switch n.Op() { + // Assume side effects unless we know otherwise. + default: + return true + + // No side effects here (arguments are checked separately). + case ir.ONAME, + ir.ONONAME, + ir.OTYPE, + ir.OPACK, + ir.OLITERAL, + ir.ONIL, + ir.OADD, + ir.OSUB, + ir.OOR, + ir.OXOR, + ir.OADDSTR, + ir.OADDR, + ir.OANDAND, + ir.OBYTES2STR, + ir.ORUNES2STR, + ir.OSTR2BYTES, + ir.OSTR2RUNES, + ir.OCAP, + ir.OCOMPLIT, + ir.OMAPLIT, + ir.OSTRUCTLIT, + ir.OARRAYLIT, + ir.OSLICELIT, + ir.OPTRLIT, + ir.OCONV, + ir.OCONVIFACE, + ir.OCONVNOP, + ir.ODOT, + ir.OEQ, + ir.ONE, + ir.OLT, + ir.OLE, + ir.OGT, + ir.OGE, + ir.OKEY, + ir.OSTRUCTKEY, + ir.OLEN, + ir.OMUL, + ir.OLSH, + ir.ORSH, + ir.OAND, + ir.OANDNOT, + ir.ONEW, + ir.ONOT, + ir.OBITNOT, + ir.OPLUS, + ir.ONEG, + ir.OOROR, + ir.OPAREN, + ir.ORUNESTR, + ir.OREAL, + ir.OIMAG, + ir.OCOMPLEX: + return false + + // Only possible side effect is division by zero. + case ir.ODIV, ir.OMOD: + n := n.(*ir.BinaryExpr) + if n.Y.Op() != ir.OLITERAL || constant.Sign(n.Y.Val()) == 0 { + return true + } + + // Only possible side effect is panic on invalid size, + // but many makechan and makemap use size zero, which is definitely OK. + case ir.OMAKECHAN, ir.OMAKEMAP: + n := n.(*ir.MakeExpr) + if !ir.IsConst(n.Len, constant.Int) || constant.Sign(n.Len.Val()) != 0 { + return true + } + + // Only possible side effect is panic on invalid size. + // TODO(rsc): Merge with previous case (probably breaks toolstash -cmp). + case ir.OMAKESLICE, ir.OMAKESLICECOPY: + return true + } + return false + }) +} + +func getlit(lit ir.Node) int { + if ir.IsSmallIntConst(lit) { + return int(ir.Int64Val(lit)) + } + return -1 +} + +func isvaluelit(n ir.Node) bool { + return n.Op() == ir.OARRAYLIT || n.Op() == ir.OSTRUCTLIT +} diff --git a/src/cmd/compile/internal/syntax/dumper.go b/src/cmd/compile/internal/syntax/dumper.go index 01453d5a7ad0186c4795f5d9011d821fe3401a5e..d5247886dae53117ccf5934cd53dd68d499ea4ec 100644 --- a/src/cmd/compile/internal/syntax/dumper.go +++ b/src/cmd/compile/internal/syntax/dumper.go @@ -26,7 +26,7 @@ func Fdump(w io.Writer, n Node) (err error) { defer func() { if e := recover(); e != nil { - err = e.(localError).err // re-panics if it's not a localError + err = e.(writeError).err // re-panics if it's not a writeError } }() @@ -82,16 +82,16 @@ func (p *dumper) Write(data []byte) (n int, err error) { return } -// localError wraps locally caught errors so we can distinguish +// writeError wraps locally caught write errors so we can distinguish // them from genuine panics which we don't want to return as errors. -type localError struct { +type writeError struct { err error } // printf is a convenience wrapper that takes care of print errors. func (p *dumper) printf(format string, args ...interface{}) { if _, err := fmt.Fprintf(p, format, args...); err != nil { - panic(localError{err}) + panic(writeError{err}) } } diff --git a/src/cmd/compile/internal/syntax/dumper_test.go b/src/cmd/compile/internal/syntax/dumper_test.go index f84bd2d7056f03f6456017fed65a65793cc809ad..22680dce786c52214dce93b05b579cecc8f89b51 100644 --- a/src/cmd/compile/internal/syntax/dumper_test.go +++ b/src/cmd/compile/internal/syntax/dumper_test.go @@ -13,7 +13,7 @@ func TestDump(t *testing.T) { t.Skip("skipping test in short mode") } - // provide a dummy error handler so parsing doesn't stop after first error + // provide a no-op error handler so parsing doesn't stop after first error ast, err := ParseFile(*src_, func(error) {}, nil, CheckBranches) if err != nil { t.Error(err) diff --git a/src/cmd/compile/internal/syntax/error_test.go b/src/cmd/compile/internal/syntax/error_test.go index 72b1ad6333de12157ee8d616554da97e2df2c3ba..e4bedf54fdc04fec00253a41a00640c5fdafc7c8 100644 --- a/src/cmd/compile/internal/syntax/error_test.go +++ b/src/cmd/compile/internal/syntax/error_test.go @@ -128,6 +128,10 @@ func testSyntaxErrors(t *testing.T, filename string) { } defer f.Close() + var mode Mode + if strings.HasSuffix(filename, ".go2") { + mode = AllowGenerics + } ParseFile(filename, func(err error) { e, ok := err.(Error) if !ok { @@ -160,9 +164,9 @@ func testSyntaxErrors(t *testing.T, filename string) { // we have a match - eliminate this error delete(declared, pos) } else { - t.Errorf("%s: unexpected error: %s", orig, e.Msg) + t.Errorf("%s:%s: unexpected error: %s", filename, orig, e.Msg) } - }, nil, 0) + }, nil, mode) if *print { fmt.Println() @@ -171,7 +175,7 @@ func testSyntaxErrors(t *testing.T, filename string) { // report expected but not reported errors for pos, pattern := range declared { - t.Errorf("%s: missing error: %s", pos, pattern) + t.Errorf("%s:%s: missing error: %s", filename, pos, pattern) } } diff --git a/src/cmd/compile/internal/syntax/nodes.go b/src/cmd/compile/internal/syntax/nodes.go index 815630fcd412edbc2344c4ef468a188a851bb816..fb9786daa325c580d5402603e9241ae67ab36ddc 100644 --- a/src/cmd/compile/internal/syntax/nodes.go +++ b/src/cmd/compile/internal/syntax/nodes.go @@ -37,7 +37,7 @@ type File struct { Pragma Pragma PkgName *Name DeclList []Decl - Lines uint + EOF Pos node } @@ -55,8 +55,8 @@ type ( ImportDecl struct { Group *Group // nil means not part of a group Pragma Pragma - LocalPkgName *Name // including "."; nil means no rename present - Path *BasicLit + LocalPkgName *Name // including "."; nil means no rename present + Path *BasicLit // Path.Bad || Path.Kind == StringLit; nil means no path decl } @@ -74,11 +74,12 @@ type ( // Name Type TypeDecl struct { - Group *Group // nil means not part of a group - Pragma Pragma - Name *Name - Alias bool - Type Expr + Group *Group // nil means not part of a group + Pragma Pragma + Name *Name + TParamList []*Field // nil means no type parameters + Alias bool + Type Expr decl } @@ -99,11 +100,12 @@ type ( // func Receiver Name Type { Body } // func Receiver Name Type FuncDecl struct { - Pragma Pragma - Recv *Field // nil means regular function - Name *Name - Type *FuncType - Body *BlockStmt // nil means no body (forward declaration) + Pragma Pragma + Recv *Field // nil means regular function + Name *Name + TParamList []*Field // nil means no type parameters + Type *FuncType + Body *BlockStmt // nil means no body (forward declaration) decl } ) @@ -114,12 +116,19 @@ func (*decl) aDecl() {} // All declarations belonging to the same group point to the same Group node. type Group struct { - dummy int // not empty so we are guaranteed different Group instances + _ int // not empty so we are guaranteed different Group instances } // ---------------------------------------------------------------------------- // Expressions +func NewName(pos Pos, value string) *Name { + n := new(Name) + n.pos = pos + n.Value = value + return n +} + type ( Expr interface { Node @@ -182,6 +191,7 @@ type ( } // X[Index] + // X[T1, T2, ...] (with Ti = Index.(*ListExpr).ElemList[i]) IndexExpr struct { X Expr Index Expr @@ -272,7 +282,7 @@ type ( // interface { MethodList[0]; MethodList[1]; ... } InterfaceType struct { - MethodList []*Field + MethodList []*Field // a field named "type" means a type constraint expr } @@ -357,7 +367,7 @@ type ( AssignStmt struct { Op Operator // 0 means no operation - Lhs, Rhs Expr // Rhs == ImplicitOne means Lhs++ (Op == Add) or Lhs-- (Op == Sub) + Lhs, Rhs Expr // Rhs == nil means Lhs++ (Op == Add) or Lhs-- (Op == Sub) simpleStmt } diff --git a/src/cmd/compile/internal/syntax/operator_string.go b/src/cmd/compile/internal/syntax/operator_string.go index 3c759b2e9befb74399a2bfdfdabd3457ac087c15..f045d8c55243ea307922e2372d0deae119b43207 100644 --- a/src/cmd/compile/internal/syntax/operator_string.go +++ b/src/cmd/compile/internal/syntax/operator_string.go @@ -1,12 +1,41 @@ -// Code generated by "stringer -type Operator -linecomment"; DO NOT EDIT. +// Code generated by "stringer -type Operator -linecomment tokens.go"; DO NOT EDIT. package syntax import "strconv" -const _Operator_name = ":!<-||&&==!=<<=>>=+-|^*/%&&^<<>>" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Def-1] + _ = x[Not-2] + _ = x[Recv-3] + _ = x[Tilde-4] + _ = x[OrOr-5] + _ = x[AndAnd-6] + _ = x[Eql-7] + _ = x[Neq-8] + _ = x[Lss-9] + _ = x[Leq-10] + _ = x[Gtr-11] + _ = x[Geq-12] + _ = x[Add-13] + _ = x[Sub-14] + _ = x[Or-15] + _ = x[Xor-16] + _ = x[Mul-17] + _ = x[Div-18] + _ = x[Rem-19] + _ = x[And-20] + _ = x[AndNot-21] + _ = x[Shl-22] + _ = x[Shr-23] +} + +const _Operator_name = ":!<-~||&&==!=<<=>>=+-|^*/%&&^<<>>" -var _Operator_index = [...]uint8{0, 1, 2, 4, 6, 8, 10, 12, 13, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 28, 30, 32} +var _Operator_index = [...]uint8{0, 1, 2, 4, 5, 7, 9, 11, 13, 14, 16, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 31, 33} func (i Operator) String() string { i -= 1 diff --git a/src/cmd/compile/internal/syntax/parser.go b/src/cmd/compile/internal/syntax/parser.go index 1485b700596aa47617dd92453ba23881d7855ce3..e7b8840b337e679bb11e32ef844c1bf32764412a 100644 --- a/src/cmd/compile/internal/syntax/parser.go +++ b/src/cmd/compile/internal/syntax/parser.go @@ -445,7 +445,7 @@ func (p *parser) fileOrNil() *File { // p.tok == _EOF p.clearPragma() - f.Lines = p.line + f.EOF = p.pos() return f } @@ -458,21 +458,22 @@ func isEmptyFuncDecl(dcl Decl) bool { // ---------------------------------------------------------------------------- // Declarations -// list parses a possibly empty, sep-separated list, optionally -// followed by sep and enclosed by ( and ) or { and }. open is -// one of _Lparen, or _Lbrace, sep is one of _Comma or _Semi, -// and close is expected to be the (closing) opposite of open. -// For each list element, f is called. After f returns true, no -// more list elements are accepted. list returns the position -// of the closing token. +// list parses a possibly empty, sep-separated list of elements, optionally +// followed by sep, and closed by close (or EOF). sep must be one of _Comma +// or _Semi, and close must be one of _Rparen, _Rbrace, or _Rbrack. // -// list = "(" { f sep } ")" | -// "{" { f sep } "}" . // sep is optional before ")" or "}" +// For each list element, f is called. Specifically, unless we're at close +// (or EOF), f is called at least once. After f returns true, no more list +// elements are accepted. list returns the position of the closing token. // -func (p *parser) list(open, sep, close token, f func() bool) Pos { - p.want(open) +// list = [ f { sep f } [sep] ] close . +// +func (p *parser) list(sep, close token, f func() bool) Pos { + if debug && (sep != _Comma && sep != _Semi || close != _Rparen && close != _Rbrace && close != _Rbrack) { + panic("invalid sep or close argument for list") + } - var done bool + done := false for p.tok != _EOF && p.tok != close && !done { done = f() // sep is optional before close @@ -496,22 +497,18 @@ func (p *parser) appendGroup(list []Decl, f func(*Group) Decl) []Decl { if p.tok == _Lparen { g := new(Group) p.clearPragma() - p.list(_Lparen, _Semi, _Rparen, func() bool { - list = append(list, f(g)) + p.next() // must consume "(" after calling clearPragma! + p.list(_Semi, _Rparen, func() bool { + if x := f(g); x != nil { + list = append(list, x) + } return false }) } else { - list = append(list, f(nil)) - } - - if debug { - for _, d := range list { - if d == nil { - panic("nil list entry") - } + if x := f(nil); x != nil { + list = append(list, x) } } - return list } @@ -531,15 +528,20 @@ func (p *parser) importDecl(group *Group) Decl { case _Name: d.LocalPkgName = p.name() case _Dot: - d.LocalPkgName = p.newName(".") + d.LocalPkgName = NewName(p.pos(), ".") p.next() } d.Path = p.oliteral() if d.Path == nil { p.syntaxError("missing import path") p.advance(_Semi, _Rparen) - return nil + return d + } + if !d.Path.Bad && d.Path.Kind != StringLit { + p.syntaxError("import path must be a string") + d.Path.Bad = true } + // d.Path.Bad || d.Path.Kind == StringLit return d } @@ -566,7 +568,7 @@ func (p *parser) constDecl(group *Group) Decl { return d } -// TypeSpec = identifier [ "=" ] Type . +// TypeSpec = identifier [ TypeParams ] [ "=" ] Type . func (p *parser) typeDecl(group *Group) Decl { if trace { defer p.trace("typeDecl")() @@ -578,8 +580,42 @@ func (p *parser) typeDecl(group *Group) Decl { d.Pragma = p.takePragma() d.Name = p.name() - d.Alias = p.gotAssign() - d.Type = p.typeOrNil() + if p.tok == _Lbrack { + // array/slice or generic type + pos := p.pos() + p.next() + switch p.tok { + case _Rbrack: + p.next() + d.Type = p.sliceType(pos) + case _Name: + // array or generic type + p.xnest++ + x := p.expr() + p.xnest-- + if name0, ok := x.(*Name); p.mode&AllowGenerics != 0 && ok && p.tok != _Rbrack { + // generic type + d.TParamList = p.paramList(name0, _Rbrack, true) + pos := p.pos() + if p.gotAssign() { + p.syntaxErrorAt(pos, "generic type cannot be alias") + } + d.Type = p.typeOrNil() + } else { + // x is the array length expression + if debug && x == nil { + panic("internal error: nil expression") + } + d.Type = p.arrayType(pos, x) + } + default: + d.Type = p.arrayType(pos, nil) + } + } else { + d.Alias = p.gotAssign() + d.Type = p.typeOrNil() + } + if d.Type == nil { d.Type = p.badExpr() p.syntaxError("in type declaration") @@ -613,7 +649,7 @@ func (p *parser) varDecl(group *Group) Decl { return d } -// FunctionDecl = "func" FunctionName ( Function | Signature ) . +// FunctionDecl = "func" FunctionName [ TypeParams ] ( Function | Signature ) . // FunctionName = identifier . // Function = Signature FunctionBody . // MethodDecl = "func" Receiver MethodName ( Function | Signature ) . @@ -627,8 +663,8 @@ func (p *parser) funcDeclOrNil() *FuncDecl { f.pos = p.pos() f.Pragma = p.takePragma() - if p.tok == _Lparen { - rcvr := p.paramList() + if p.got(_Lparen) { + rcvr := p.paramList(nil, _Rparen, false) switch len(rcvr) { case 0: p.error("method has no receiver") @@ -647,6 +683,14 @@ func (p *parser) funcDeclOrNil() *FuncDecl { } f.Name = p.name() + if p.mode&AllowGenerics != 0 && p.got(_Lbrack) { + if p.tok == _Rbrack { + p.syntaxError("empty type parameter list") + p.next() + } else { + f.TParamList = p.paramList(nil, _Rbrack, true) + } + } f.Type = p.funcType() if p.tok == _Lbrace { f.Body = p.funcBody() @@ -691,9 +735,9 @@ func (p *parser) binaryExpr(prec int) Expr { t := new(Operation) t.pos = p.pos() t.Op = p.op - t.X = x tprec := p.prec p.next() + t.X = x t.Y = p.binaryExpr(tprec) x = t } @@ -850,13 +894,7 @@ func (p *parser) operand(keep_parens bool) Expr { // Optimization: Record presence of ()'s only where needed // for error reporting. Don't bother in other cases; it is // just a waste of memory and time. - - // Parentheses are not permitted on lhs of := . - // switch x.Op { - // case ONAME, ONONAME, OPACK, OTYPE, OLITERAL, OTYPESW: - // keep_parens = true - // } - + // // Parentheses are not permitted around T in a composite // literal T{}. If the next token is a {, assume x is a // composite literal type T (it may not be, { could be @@ -879,19 +917,19 @@ func (p *parser) operand(keep_parens bool) Expr { case _Func: pos := p.pos() p.next() - t := p.funcType() + ftyp := p.funcType() if p.tok == _Lbrace { p.xnest++ f := new(FuncLit) f.pos = pos - f.Type = t + f.Type = ftyp f.Body = p.funcBody() p.xnest-- return f } - return t + return ftyp case _Lbrack, _Chan, _Map, _Struct, _Interface: return p.type_() // othertype @@ -971,29 +1009,52 @@ loop: case _Lbrack: p.next() - p.xnest++ + + if p.tok == _Rbrack { + // invalid empty instance, slice or index expression; accept but complain + p.syntaxError("expecting operand") + p.next() + break + } var i Expr if p.tok != _Colon { - i = p.expr() - if p.got(_Rbrack) { - // x[i] - t := new(IndexExpr) - t.pos = pos - t.X = x - t.Index = i - x = t + if p.mode&AllowGenerics == 0 { + p.xnest++ + i = p.expr() p.xnest-- - break + if p.got(_Rbrack) { + // x[i] + t := new(IndexExpr) + t.pos = pos + t.X = x + t.Index = i + x = t + break + } + } else { + var comma bool + i, comma = p.typeList() + if comma || p.tok == _Rbrack { + p.want(_Rbrack) + // x[i,] or x[i, j, ...] + t := new(IndexExpr) + t.pos = pos + t.X = x + t.Index = i + x = t + break + } } } // x[i:... + p.want(_Colon) + p.xnest++ t := new(SliceExpr) t.pos = pos t.X = x t.Index[0] = i - p.want(_Colon) if p.tok != _Colon && p.tok != _Rbrack { // x[i:j... t.Index[1] = p.expr() @@ -1014,14 +1075,14 @@ loop: t.Index[2] = p.badExpr() } } + p.xnest-- p.want(_Rbrack) - x = t - p.xnest-- case _Lparen: t := new(CallExpr) t.pos = pos + p.next() t.Fun = x t.ArgList, t.HasDots = p.argList() x = t @@ -1035,7 +1096,12 @@ loop: switch t.(type) { case *Name, *SelectorExpr: if p.xnest >= 0 { - // x is considered a composite literal type + // x is possibly a composite literal type + complit_ok = true + } + case *IndexExpr: + if p.xnest >= 0 { + // x is possibly a composite literal type complit_ok = true } case *ArrayType, *SliceType, *StructType, *MapType: @@ -1085,7 +1151,8 @@ func (p *parser) complitexpr() *CompositeLit { x.pos = p.pos() p.xnest++ - x.Rbrace = p.list(_Lbrace, _Comma, _Rbrace, func() bool { + p.want(_Lbrace) + x.Rbrace = p.list(_Comma, _Rbrace, func() bool { // value e := p.bare_complitexpr() if p.tok == _Colon { @@ -1170,26 +1237,10 @@ func (p *parser) typeOrNil() Expr { // '[' oexpr ']' ntype // '[' _DotDotDot ']' ntype p.next() - p.xnest++ if p.got(_Rbrack) { - // []T - p.xnest-- - t := new(SliceType) - t.pos = pos - t.Elem = p.type_() - return t - } - - // [n]T - t := new(ArrayType) - t.pos = pos - if !p.got(_DotDotDot) { - t.Len = p.expr() + return p.sliceType(pos) } - p.want(_Rbrack) - p.xnest-- - t.Elem = p.type_() - return t + return p.arrayType(pos, nil) case _Chan: // _Chan non_recvchantype @@ -1221,7 +1272,7 @@ func (p *parser) typeOrNil() Expr { return p.interfaceType() case _Name: - return p.dotname(p.name()) + return p.qualifiedName(nil) case _Lparen: p.next() @@ -1233,6 +1284,26 @@ func (p *parser) typeOrNil() Expr { return nil } +func (p *parser) typeInstance(typ Expr) Expr { + if trace { + defer p.trace("typeInstance")() + } + + pos := p.pos() + p.want(_Lbrack) + x := new(IndexExpr) + x.pos = pos + x.X = typ + if p.tok == _Rbrack { + p.syntaxError("expecting type") + x.Index = p.badExpr() + } else { + x.Index, _ = p.typeList() + } + p.want(_Rbrack) + return x +} + func (p *parser) funcType() *FuncType { if trace { defer p.trace("funcType")() @@ -1240,12 +1311,41 @@ func (p *parser) funcType() *FuncType { typ := new(FuncType) typ.pos = p.pos() - typ.ParamList = p.paramList() + p.want(_Lparen) + typ.ParamList = p.paramList(nil, _Rparen, false) typ.ResultList = p.funcResult() return typ } +// "[" has already been consumed, and pos is its position. +// If len != nil it is the already consumed array length. +func (p *parser) arrayType(pos Pos, len Expr) Expr { + if trace { + defer p.trace("arrayType")() + } + + if len == nil && !p.got(_DotDotDot) { + p.xnest++ + len = p.expr() + p.xnest-- + } + p.want(_Rbrack) + t := new(ArrayType) + t.pos = pos + t.Len = len + t.Elem = p.type_() + return t +} + +// "[" and "]" have already been consumed, and pos is the position of "[". +func (p *parser) sliceType(pos Pos) Expr { + t := new(SliceType) + t.pos = pos + t.Elem = p.type_() + return t +} + func (p *parser) chanElem() Expr { if trace { defer p.trace("chanElem")() @@ -1261,22 +1361,6 @@ func (p *parser) chanElem() Expr { return typ } -func (p *parser) dotname(name *Name) Expr { - if trace { - defer p.trace("dotname")() - } - - if p.tok == _Dot { - s := new(SelectorExpr) - s.pos = p.pos() - p.next() - s.X = name - s.Sel = p.name() - return s - } - return name -} - // StructType = "struct" "{" { FieldDecl ";" } "}" . func (p *parser) structType() *StructType { if trace { @@ -1287,7 +1371,8 @@ func (p *parser) structType() *StructType { typ.pos = p.pos() p.want(_Struct) - p.list(_Lbrace, _Semi, _Rbrace, func() bool { + p.want(_Lbrace) + p.list(_Semi, _Rbrace, func() bool { p.fieldDecl(typ) return false }) @@ -1295,7 +1380,9 @@ func (p *parser) structType() *StructType { return typ } -// InterfaceType = "interface" "{" { MethodSpec ";" } "}" . +// InterfaceType = "interface" "{" { ( MethodDecl | EmbeddedElem | TypeList ) ";" } "}" . +// TypeList = "type" Type { "," Type } . +// TODO(gri) remove TypeList syntax if we accept #45346 func (p *parser) interfaceType() *InterfaceType { if trace { defer p.trace("interfaceType")() @@ -1305,10 +1392,67 @@ func (p *parser) interfaceType() *InterfaceType { typ.pos = p.pos() p.want(_Interface) - p.list(_Lbrace, _Semi, _Rbrace, func() bool { - if m := p.methodDecl(); m != nil { - typ.MethodList = append(typ.MethodList, m) + p.want(_Lbrace) + p.list(_Semi, _Rbrace, func() bool { + switch p.tok { + case _Name: + f := p.methodDecl() + if f.Name == nil && p.mode&AllowGenerics != 0 { + f = p.embeddedElem(f) + } + typ.MethodList = append(typ.MethodList, f) + return false + + case _Lparen: + // TODO(gri) Need to decide how to adjust this restriction. + p.syntaxError("cannot parenthesize embedded type") + f := new(Field) + f.pos = p.pos() + p.next() + f.Type = p.qualifiedName(nil) + p.want(_Rparen) + typ.MethodList = append(typ.MethodList, f) + return false + + case _Operator: + if p.op == Tilde && p.mode&AllowGenerics != 0 { + typ.MethodList = append(typ.MethodList, p.embeddedElem(nil)) + return false + } + + case _Type: + // TODO(gri) remove TypeList syntax if we accept #45346 + if p.mode&AllowGenerics != 0 { + type_ := NewName(p.pos(), "type") // cannot have a method named "type" + p.next() + if p.tok != _Semi && p.tok != _Rbrace { + f := new(Field) + f.pos = p.pos() + f.Name = type_ + f.Type = p.type_() + typ.MethodList = append(typ.MethodList, f) + for p.got(_Comma) { + f := new(Field) + f.pos = p.pos() + f.Name = type_ + f.Type = p.type_() + typ.MethodList = append(typ.MethodList, f) + } + } else { + p.syntaxError("expecting type") + } + return false + } } + + if p.mode&AllowGenerics != 0 { + p.syntaxError("expecting method, type list, or embedded element") + p.advance(_Semi, _Rbrace, _Type) // TODO(gri) remove _Type if we don't accept it anymore + return false + } + + p.syntaxError("expecting method or interface name") + p.advance(_Semi, _Rbrace) return false }) @@ -1321,8 +1465,8 @@ func (p *parser) funcResult() []*Field { defer p.trace("funcResult")() } - if p.tok == _Lparen { - return p.paramList() + if p.got(_Lparen) { + return p.paramList(nil, _Rparen, false) } pos := p.pos() @@ -1368,59 +1512,71 @@ func (p *parser) fieldDecl(styp *StructType) { case _Name: name := p.name() if p.tok == _Dot || p.tok == _Literal || p.tok == _Semi || p.tok == _Rbrace { - // embed oliteral + // embedded type typ := p.qualifiedName(name) tag := p.oliteral() p.addField(styp, pos, nil, typ, tag) - return + break } - // new_name_list ntype oliteral + // name1, name2, ... Type [ tag ] names := p.nameList(name) - typ := p.type_() + var typ Expr + + // Careful dance: We don't know if we have an embedded instantiated + // type T[P1, P2, ...] or a field T of array/slice type [P]E or []E. + if p.mode&AllowGenerics != 0 && len(names) == 1 && p.tok == _Lbrack { + typ = p.arrayOrTArgs() + if typ, ok := typ.(*IndexExpr); ok { + // embedded type T[P1, P2, ...] + typ.X = name // name == names[0] + tag := p.oliteral() + p.addField(styp, pos, nil, typ, tag) + break + } + } else { + // T P + typ = p.type_() + } + tag := p.oliteral() for _, name := range names { p.addField(styp, name.Pos(), name, typ, tag) } - case _Lparen: + case _Star: p.next() - if p.tok == _Star { - // '(' '*' embed ')' oliteral - pos := p.pos() - p.next() - typ := newIndirect(pos, p.qualifiedName(nil)) - p.want(_Rparen) - tag := p.oliteral() - p.addField(styp, pos, nil, typ, tag) + var typ Expr + if p.tok == _Lparen { + // *(T) p.syntaxError("cannot parenthesize embedded type") - + p.next() + typ = p.qualifiedName(nil) + p.got(_Rparen) // no need to complain if missing } else { - // '(' embed ')' oliteral - typ := p.qualifiedName(nil) - p.want(_Rparen) - tag := p.oliteral() - p.addField(styp, pos, nil, typ, tag) - p.syntaxError("cannot parenthesize embedded type") + // *T + typ = p.qualifiedName(nil) } + tag := p.oliteral() + p.addField(styp, pos, nil, newIndirect(pos, typ), tag) - case _Star: + case _Lparen: + p.syntaxError("cannot parenthesize embedded type") p.next() - if p.got(_Lparen) { - // '*' '(' embed ')' oliteral - typ := newIndirect(pos, p.qualifiedName(nil)) - p.want(_Rparen) - tag := p.oliteral() - p.addField(styp, pos, nil, typ, tag) - p.syntaxError("cannot parenthesize embedded type") - + var typ Expr + if p.tok == _Star { + // (*T) + pos := p.pos() + p.next() + typ = newIndirect(pos, p.qualifiedName(nil)) } else { - // '*' embed oliteral - typ := newIndirect(pos, p.qualifiedName(nil)) - tag := p.oliteral() - p.addField(styp, pos, nil, typ, tag) + // (T) + typ = p.qualifiedName(nil) } + p.got(_Rparen) // no need to complain if missing + tag := p.oliteral() + p.addField(styp, pos, nil, typ, tag) default: p.syntaxError("expecting field name or embedded type") @@ -1428,6 +1584,39 @@ func (p *parser) fieldDecl(styp *StructType) { } } +func (p *parser) arrayOrTArgs() Expr { + if trace { + defer p.trace("arrayOrTArgs")() + } + + pos := p.pos() + p.want(_Lbrack) + if p.got(_Rbrack) { + return p.sliceType(pos) + } + + // x [n]E or x[n,], x[n1, n2], ... + n, comma := p.typeList() + p.want(_Rbrack) + if !comma { + if elem := p.typeOrNil(); elem != nil { + // x [n]E + t := new(ArrayType) + t.pos = pos + t.Len = n + t.Elem = elem + return t + } + } + + // x[n,], x[n1, n2], ... + t := new(IndexExpr) + t.pos = pos + // t.X will be filled in by caller + t.Index = n + return t +} + func (p *parser) oliteral() *BasicLit { if p.tok == _Literal { b := new(BasicLit) @@ -1449,51 +1638,165 @@ func (p *parser) methodDecl() *Field { defer p.trace("methodDecl")() } + f := new(Field) + f.pos = p.pos() + name := p.name() + + // accept potential name list but complain + // TODO(gri) We probably don't need this special check anymore. + // Nobody writes this kind of code. It's from ancient + // Go beginnings. + hasNameList := false + for p.got(_Comma) { + p.name() + hasNameList = true + } + if hasNameList { + p.syntaxError("name list not allowed in interface type") + // already progressed, no need to advance + } + switch p.tok { - case _Name: - name := p.name() + case _Lparen: + // method + f.Name = name + f.Type = p.funcType() - // accept potential name list but complain - hasNameList := false - for p.got(_Comma) { - p.name() - hasNameList = true - } - if hasNameList { - p.syntaxError("name list not allowed in interface type") - // already progressed, no need to advance - } + case _Lbrack: + if p.mode&AllowGenerics != 0 { + // Careful dance: We don't know if we have a generic method m[T C](x T) + // or an embedded instantiated type T[P1, P2] (we accept generic methods + // for generality and robustness of parsing). + pos := p.pos() + p.next() - f := new(Field) - f.pos = name.Pos() - if p.tok != _Lparen { - // packname - f.Type = p.qualifiedName(name) - return f + // Empty type parameter or argument lists are not permitted. + // Treat as if [] were absent. + if p.tok == _Rbrack { + // name[] + pos := p.pos() + p.next() + if p.tok == _Lparen { + // name[]( + p.errorAt(pos, "empty type parameter list") + f.Name = name + f.Type = p.funcType() + } else { + p.errorAt(pos, "empty type argument list") + f.Type = name + } + break + } + + // A type argument list looks like a parameter list with only + // types. Parse a parameter list and decide afterwards. + list := p.paramList(nil, _Rbrack, false) + if len(list) == 0 { + // The type parameter list is not [] but we got nothing + // due to other errors (reported by paramList). Treat + // as if [] were absent. + if p.tok == _Lparen { + f.Name = name + f.Type = p.funcType() + } else { + f.Type = name + } + break + } + + // len(list) > 0 + if list[0].Name != nil { + // generic method + f.Name = name + f.Type = p.funcType() + // TODO(gri) Record list as type parameter list with f.Type + // if we want to type-check the generic method. + // For now, report an error so this is not a silent event. + p.errorAt(pos, "interface method cannot have type parameters") + break + } + + // embedded instantiated type + t := new(IndexExpr) + t.pos = pos + t.X = name + if len(list) == 1 { + t.Index = list[0].Type + } else { + // len(list) > 1 + l := new(ListExpr) + l.pos = list[0].Pos() + l.ElemList = make([]Expr, len(list)) + for i := range list { + l.ElemList[i] = list[i].Type + } + t.Index = l + } + f.Type = t + break } + fallthrough - f.Name = name - f.Type = p.funcType() - return f + default: + // embedded type + f.Type = p.qualifiedName(name) + } - case _Lparen: - p.syntaxError("cannot parenthesize embedded type") - f := new(Field) + return f +} + +// EmbeddedElem = MethodSpec | EmbeddedTerm { "|" EmbeddedTerm } . +func (p *parser) embeddedElem(f *Field) *Field { + if trace { + defer p.trace("embeddedElem")() + } + + if f == nil { + f = new(Field) f.pos = p.pos() + f.Type = p.embeddedTerm() + } + + for p.tok == _Operator && p.op == Or { + t := new(Operation) + t.pos = p.pos() + t.Op = Or p.next() - f.Type = p.qualifiedName(nil) - p.want(_Rparen) - return f + t.X = f.Type + t.Y = p.embeddedTerm() + f.Type = t + } - default: - p.syntaxError("expecting method or interface name") - p.advance(_Semi, _Rbrace) - return nil + return f +} + +// EmbeddedTerm = [ "~" ] Type . +func (p *parser) embeddedTerm() Expr { + if trace { + defer p.trace("embeddedTerm")() + } + + if p.tok == _Operator && p.op == Tilde { + t := new(Operation) + t.pos = p.pos() + t.Op = Tilde + p.next() + t.X = p.type_() + return t + } + + t := p.typeOrNil() + if t == nil { + t = p.badExpr() + p.syntaxError("expecting ~ term or type") + p.advance(_Operator, _Semi, _Rparen, _Rbrack, _Rbrace) } + + return t } // ParameterDecl = [ IdentifierList ] [ "..." ] Type . -func (p *parser) paramDeclOrNil() *Field { +func (p *parser) paramDeclOrNil(name *Name) *Field { if trace { defer p.trace("paramDecl")() } @@ -1501,73 +1804,68 @@ func (p *parser) paramDeclOrNil() *Field { f := new(Field) f.pos = p.pos() - switch p.tok { - case _Name: - f.Name = p.name() - switch p.tok { - case _Name, _Star, _Arrow, _Func, _Lbrack, _Chan, _Map, _Struct, _Interface, _Lparen: - // sym name_or_type - f.Type = p.type_() + if p.tok == _Name || name != nil { + if name == nil { + name = p.name() + } - case _DotDotDot: - // sym dotdotdot - f.Type = p.dotsType() + if p.mode&AllowGenerics != 0 && p.tok == _Lbrack { + f.Type = p.arrayOrTArgs() + if typ, ok := f.Type.(*IndexExpr); ok { + typ.X = name + } else { + f.Name = name + } + return f + } - case _Dot: + if p.tok == _Dot { // name_or_type - // from dotname - f.Type = p.dotname(f.Name) - f.Name = nil + f.Type = p.qualifiedName(name) + return f } - case _Arrow, _Star, _Func, _Lbrack, _Chan, _Map, _Struct, _Interface, _Lparen: - // name_or_type - f.Type = p.type_() - - case _DotDotDot: - // dotdotdot - f.Type = p.dotsType() - - default: - p.syntaxError("expecting )") - p.advance(_Comma, _Rparen) - return nil + f.Name = name } - return f -} - -// ...Type -func (p *parser) dotsType() *DotsType { - if trace { - defer p.trace("dotsType")() + if p.tok == _DotDotDot { + t := new(DotsType) + t.pos = p.pos() + p.next() + t.Elem = p.typeOrNil() + if t.Elem == nil { + t.Elem = p.badExpr() + p.syntaxError("... is missing type") + } + f.Type = t + return f } - t := new(DotsType) - t.pos = p.pos() - - p.want(_DotDotDot) - t.Elem = p.typeOrNil() - if t.Elem == nil { - t.Elem = p.badExpr() - p.syntaxError("final argument in variadic function missing type") + f.Type = p.typeOrNil() + if f.Name != nil || f.Type != nil { + return f } - return t + p.syntaxError("expecting )") + p.advance(_Comma, _Rparen) + return nil } // Parameters = "(" [ ParameterList [ "," ] ] ")" . // ParameterList = ParameterDecl { "," ParameterDecl } . -func (p *parser) paramList() (list []*Field) { +// "(" or "[" has already been consumed. +// If name != nil, it is the first name after "(" or "[". +// In the result list, either all fields have a name, or no field has a name. +func (p *parser) paramList(name *Name, close token, requireNames bool) (list []*Field) { if trace { defer p.trace("paramList")() } - pos := p.pos() - - var named int // number of parameters that have an explicit name and type - p.list(_Lparen, _Comma, _Rparen, func() bool { - if par := p.paramDeclOrNil(); par != nil { + var named int // number of parameters that have an explicit name and type/bound + p.list(_Comma, close, func() bool { + par := p.paramDeclOrNil(name) + name = nil // 1st name was consumed if present + if par != nil { if debug && par.Name == nil && par.Type == nil { panic("parameter without name or type") } @@ -1579,7 +1877,11 @@ func (p *parser) paramList() (list []*Field) { return false }) - // distribute parameter types + if len(list) == 0 { + return + } + + // distribute parameter types (len(list) > 0) if named == 0 { // all unnamed => found names are named types for _, par := range list { @@ -1588,31 +1890,38 @@ func (p *parser) paramList() (list []*Field) { par.Name = nil } } + if requireNames { + p.syntaxErrorAt(list[0].Type.Pos(), "type parameters must be named") + } } else if named != len(list) { - // some named => all must be named - ok := true + // some named => all must have names and types + var pos Pos // left-most error position (or unknown) var typ Expr for i := len(list) - 1; i >= 0; i-- { if par := list[i]; par.Type != nil { typ = par.Type if par.Name == nil { - ok = false - n := p.newName("_") - n.pos = typ.Pos() // correct position - par.Name = n + pos = typ.Pos() + par.Name = NewName(pos, "_") } } else if typ != nil { par.Type = typ } else { // par.Type == nil && typ == nil => we only have a par.Name - ok = false + pos = par.Name.Pos() t := p.badExpr() - t.pos = par.Name.Pos() // correct position + t.pos = pos // correct position par.Type = t } } - if !ok { - p.syntaxErrorAt(pos, "mixed named and unnamed function parameters") + if pos.IsKnown() { + var msg string + if requireNames { + msg = "type parameters must be named" + } else { + msg = "mixed named and unnamed parameters" + } + p.syntaxErrorAt(pos, msg) } } @@ -1628,10 +1937,6 @@ func (p *parser) badExpr() *BadExpr { // ---------------------------------------------------------------------------- // Statements -// We represent x++, x-- as assignments x += ImplicitOne, x -= ImplicitOne. -// ImplicitOne should not be used elsewhere. -var ImplicitOne = &BasicLit{Value: "1"} - // SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl . func (p *parser) simpleStmt(lhs Expr, keyword token) SimpleStmt { if trace { @@ -1664,7 +1969,7 @@ func (p *parser) simpleStmt(lhs Expr, keyword token) SimpleStmt { // lhs++ or lhs-- op := p.op p.next() - return p.newAssignStmt(pos, op, lhs, ImplicitOne) + return p.newAssignStmt(pos, op, lhs, nil) case _Arrow: // lhs <- rhs @@ -2208,14 +2513,18 @@ func (p *parser) stmtList() (l []Stmt) { return } -// Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" . +// argList parses a possibly empty, comma-separated list of arguments, +// optionally followed by a comma (if not empty), and closed by ")". +// The last argument may be followed by "...". +// +// argList = [ arg { "," arg } [ "..." ] [ "," ] ] ")" . func (p *parser) argList() (list []Expr, hasDots bool) { if trace { defer p.trace("argList")() } p.xnest++ - p.list(_Lparen, _Comma, _Rparen, func() bool { + p.list(_Comma, _Rparen, func() bool { list = append(list, p.expr()) hasDots = p.got(_DotDotDot) return hasDots @@ -2228,23 +2537,16 @@ func (p *parser) argList() (list []Expr, hasDots bool) { // ---------------------------------------------------------------------------- // Common productions -func (p *parser) newName(value string) *Name { - n := new(Name) - n.pos = p.pos() - n.Value = value - return n -} - func (p *parser) name() *Name { // no tracing to avoid overly verbose output if p.tok == _Name { - n := p.newName(p.lit) + n := NewName(p.pos(), p.lit) p.next() return n } - n := p.newName("_") + n := NewName(p.pos(), "_") p.syntaxError("expecting name") p.advance() return n @@ -2275,18 +2577,32 @@ func (p *parser) qualifiedName(name *Name) Expr { defer p.trace("qualifiedName")() } + var x Expr switch { case name != nil: - // name is provided + x = name case p.tok == _Name: - name = p.name() + x = p.name() default: - name = p.newName("_") + x = NewName(p.pos(), "_") p.syntaxError("expecting name") p.advance(_Dot, _Semi, _Rbrace) } - return p.dotname(name) + if p.tok == _Dot { + s := new(SelectorExpr) + s.pos = p.pos() + p.next() + s.X = x + s.Sel = p.name() + x = s + } + + if p.mode&AllowGenerics != 0 && p.tok == _Lbrack { + x = p.typeInstance(x) + } + + return x } // ExpressionList = Expression { "," Expression } . @@ -2309,6 +2625,41 @@ func (p *parser) exprList() Expr { return x } +// typeList parses a non-empty, comma-separated list of expressions, +// optionally followed by a comma. The first list element may be any +// expression, all other list elements must be type expressions. +// If there is more than one argument, the result is a *ListExpr. +// The comma result indicates whether there was a (separating or +// trailing) comma. +// +// typeList = arg { "," arg } [ "," ] . +func (p *parser) typeList() (x Expr, comma bool) { + if trace { + defer p.trace("typeList")() + } + + p.xnest++ + x = p.expr() + if p.got(_Comma) { + comma = true + if t := p.typeOrNil(); t != nil { + list := []Expr{x, t} + for p.got(_Comma) { + if t = p.typeOrNil(); t == nil { + break + } + list = append(list, t) + } + l := new(ListExpr) + l.pos = x.Pos() // == list[0].Pos() + l.ElemList = list + x = l + } + } + p.xnest-- + return +} + // unparen removes all parentheses around an expression. func unparen(x Expr) Expr { for { diff --git a/src/cmd/compile/internal/syntax/parser_test.go b/src/cmd/compile/internal/syntax/parser_test.go index 81945faee95e2d55ac2dfca3f7ffdd2e5901d552..340ca6bb6f637b05d5756a44f09a371eec69819f 100644 --- a/src/cmd/compile/internal/syntax/parser_test.go +++ b/src/cmd/compile/internal/syntax/parser_test.go @@ -26,10 +26,35 @@ var ( ) func TestParse(t *testing.T) { - ParseFile(*src_, func(err error) { t.Error(err) }, nil, 0) + ParseFile(*src_, func(err error) { t.Error(err) }, nil, AllowGenerics) } -func TestStdLib(t *testing.T) { +func TestVerify(t *testing.T) { + ast, err := ParseFile(*src_, func(err error) { t.Error(err) }, nil, AllowGenerics) + if err != nil { + return // error already reported + } + verifyPrint(t, *src_, ast) +} + +func TestParseGo2(t *testing.T) { + dir := filepath.Join(testdata, "go2") + list, err := ioutil.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + for _, fi := range list { + name := fi.Name() + if !fi.IsDir() && !strings.HasPrefix(name, ".") { + ParseFile(filepath.Join(dir, name), func(err error) { t.Error(err) }, nil, AllowGenerics) + } + } +} + +func TestStdLib(t *testing.T) { testStdLib(t, 0) } +func TestStdLibGeneric(t *testing.T) { testStdLib(t, AllowGenerics) } + +func testStdLib(t *testing.T, mode Mode) { if testing.Short() { t.Skip("skipping test in short mode") } @@ -68,15 +93,15 @@ func TestStdLib(t *testing.T) { if debug { fmt.Printf("parsing %s\n", filename) } - ast, err := ParseFile(filename, nil, nil, 0) + ast, err := ParseFile(filename, nil, nil, mode) if err != nil { t.Error(err) return } if *verify { - verifyPrint(filename, ast) + verifyPrint(t, filename, ast) } - results <- parseResult{filename, ast.Lines} + results <- parseResult{filename, ast.EOF.Line()} }) } }() @@ -142,12 +167,13 @@ func walkDirs(t *testing.T, dir string, action func(string)) { } } -func verifyPrint(filename string, ast1 *File) { +func verifyPrint(t *testing.T, filename string, ast1 *File) { var buf1 bytes.Buffer - _, err := Fprint(&buf1, ast1, true) + _, err := Fprint(&buf1, ast1, LineForm) if err != nil { panic(err) } + bytes1 := buf1.Bytes() ast2, err := Parse(NewFileBase(filename), &buf1, nil, nil, 0) if err != nil { @@ -155,20 +181,22 @@ func verifyPrint(filename string, ast1 *File) { } var buf2 bytes.Buffer - _, err = Fprint(&buf2, ast2, true) + _, err = Fprint(&buf2, ast2, LineForm) if err != nil { panic(err) } + bytes2 := buf2.Bytes() - if bytes.Compare(buf1.Bytes(), buf2.Bytes()) != 0 { + if bytes.Compare(bytes1, bytes2) != 0 { fmt.Printf("--- %s ---\n", filename) - fmt.Printf("%s\n", buf1.Bytes()) + fmt.Printf("%s\n", bytes1) fmt.Println() fmt.Printf("--- %s ---\n", filename) - fmt.Printf("%s\n", buf2.Bytes()) + fmt.Printf("%s\n", bytes2) fmt.Println() - panic("not equal") + + t.Error("printed syntax trees do not match") } } diff --git a/src/cmd/compile/internal/syntax/pos.go b/src/cmd/compile/internal/syntax/pos.go index c683c7fcfc1fbde013bb5f14f5220c2ac1cca9af..baebcc995c759bbcb4719600e658b0a4920f881a 100644 --- a/src/cmd/compile/internal/syntax/pos.go +++ b/src/cmd/compile/internal/syntax/pos.go @@ -26,6 +26,7 @@ func MakePos(base *PosBase, line, col uint) Pos { return Pos{base, sat32(line), // TODO(gri) IsKnown makes an assumption about linebase < 1. // Maybe we should check for Base() != nil instead. +func (pos Pos) Pos() Pos { return pos } func (pos Pos) IsKnown() bool { return pos.line > 0 } func (pos Pos) Base() *PosBase { return pos.base } func (pos Pos) Line() uint { return uint(pos.line) } @@ -58,6 +59,45 @@ func (pos Pos) RelCol() uint { return pos.Col() } +// Cmp compares the positions p and q and returns a result r as follows: +// +// r < 0: p is before q +// r == 0: p and q are the same position (but may not be identical) +// r > 0: p is after q +// +// If p and q are in different files, p is before q if the filename +// of p sorts lexicographically before the filename of q. +func (p Pos) Cmp(q Pos) int { + pname := p.RelFilename() + qname := q.RelFilename() + switch { + case pname < qname: + return -1 + case pname > qname: + return +1 + } + + pline := p.Line() + qline := q.Line() + switch { + case pline < qline: + return -1 + case pline > qline: + return +1 + } + + pcol := p.Col() + qcol := q.Col() + switch { + case pcol < qcol: + return -1 + case pcol > qcol: + return +1 + } + + return 0 +} + func (pos Pos) String() string { rel := position_{pos.RelFilename(), pos.RelLine(), pos.RelCol()} abs := position_{pos.Base().Pos().RelFilename(), pos.Line(), pos.Col()} diff --git a/src/cmd/compile/internal/syntax/positions.go b/src/cmd/compile/internal/syntax/positions.go new file mode 100644 index 0000000000000000000000000000000000000000..b00f86c67cdab32322449056a2caf9c7e7aa7a50 --- /dev/null +++ b/src/cmd/compile/internal/syntax/positions.go @@ -0,0 +1,364 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements helper functions for scope position computations. + +package syntax + +// StartPos returns the start position of n. +func StartPos(n Node) Pos { + // Cases for nodes which don't need a correction are commented out. + for m := n; ; { + switch n := m.(type) { + case nil: + panic("internal error: nil") + + // packages + case *File: + // file block starts at the beginning of the file + return MakePos(n.Pos().Base(), 1, 1) + + // declarations + // case *ImportDecl: + // case *ConstDecl: + // case *TypeDecl: + // case *VarDecl: + // case *FuncDecl: + + // expressions + // case *BadExpr: + // case *Name: + // case *BasicLit: + case *CompositeLit: + if n.Type != nil { + m = n.Type + continue + } + return n.Pos() + // case *KeyValueExpr: + // case *FuncLit: + // case *ParenExpr: + case *SelectorExpr: + m = n.X + case *IndexExpr: + m = n.X + // case *SliceExpr: + case *AssertExpr: + m = n.X + case *TypeSwitchGuard: + if n.Lhs != nil { + m = n.Lhs + continue + } + m = n.X + case *Operation: + if n.Y != nil { + m = n.X + continue + } + return n.Pos() + case *CallExpr: + m = n.Fun + case *ListExpr: + if len(n.ElemList) > 0 { + m = n.ElemList[0] + continue + } + return n.Pos() + // types + // case *ArrayType: + // case *SliceType: + // case *DotsType: + // case *StructType: + // case *Field: + // case *InterfaceType: + // case *FuncType: + // case *MapType: + // case *ChanType: + + // statements + // case *EmptyStmt: + // case *LabeledStmt: + // case *BlockStmt: + // case *ExprStmt: + case *SendStmt: + m = n.Chan + // case *DeclStmt: + case *AssignStmt: + m = n.Lhs + // case *BranchStmt: + // case *CallStmt: + // case *ReturnStmt: + // case *IfStmt: + // case *ForStmt: + // case *SwitchStmt: + // case *SelectStmt: + + // helper nodes + case *RangeClause: + if n.Lhs != nil { + m = n.Lhs + continue + } + m = n.X + // case *CaseClause: + // case *CommClause: + + default: + return n.Pos() + } + } +} + +// EndPos returns the approximate end position of n in the source. +// For some nodes (*Name, *BasicLit) it returns the position immediately +// following the node; for others (*BlockStmt, *SwitchStmt, etc.) it +// returns the position of the closing '}'; and for some (*ParenExpr) +// the returned position is the end position of the last enclosed +// expression. +// Thus, EndPos should not be used for exact demarcation of the +// end of a node in the source; it is mostly useful to determine +// scope ranges where there is some leeway. +func EndPos(n Node) Pos { + for m := n; ; { + switch n := m.(type) { + case nil: + panic("internal error: nil") + + // packages + case *File: + return n.EOF + + // declarations + case *ImportDecl: + m = n.Path + case *ConstDecl: + if n.Values != nil { + m = n.Values + continue + } + if n.Type != nil { + m = n.Type + continue + } + if l := len(n.NameList); l > 0 { + m = n.NameList[l-1] + continue + } + return n.Pos() + case *TypeDecl: + m = n.Type + case *VarDecl: + if n.Values != nil { + m = n.Values + continue + } + if n.Type != nil { + m = n.Type + continue + } + if l := len(n.NameList); l > 0 { + m = n.NameList[l-1] + continue + } + return n.Pos() + case *FuncDecl: + if n.Body != nil { + m = n.Body + continue + } + m = n.Type + + // expressions + case *BadExpr: + return n.Pos() + case *Name: + p := n.Pos() + return MakePos(p.Base(), p.Line(), p.Col()+uint(len(n.Value))) + case *BasicLit: + p := n.Pos() + return MakePos(p.Base(), p.Line(), p.Col()+uint(len(n.Value))) + case *CompositeLit: + return n.Rbrace + case *KeyValueExpr: + m = n.Value + case *FuncLit: + m = n.Body + case *ParenExpr: + m = n.X + case *SelectorExpr: + m = n.Sel + case *IndexExpr: + m = n.Index + case *SliceExpr: + for i := len(n.Index) - 1; i >= 0; i-- { + if x := n.Index[i]; x != nil { + m = x + continue + } + } + m = n.X + case *AssertExpr: + m = n.Type + case *TypeSwitchGuard: + m = n.X + case *Operation: + if n.Y != nil { + m = n.Y + continue + } + m = n.X + case *CallExpr: + if l := lastExpr(n.ArgList); l != nil { + m = l + continue + } + m = n.Fun + case *ListExpr: + if l := lastExpr(n.ElemList); l != nil { + m = l + continue + } + return n.Pos() + + // types + case *ArrayType: + m = n.Elem + case *SliceType: + m = n.Elem + case *DotsType: + m = n.Elem + case *StructType: + if l := lastField(n.FieldList); l != nil { + m = l + continue + } + return n.Pos() + // TODO(gri) need to take TagList into account + case *Field: + if n.Type != nil { + m = n.Type + continue + } + m = n.Name + case *InterfaceType: + if l := lastField(n.MethodList); l != nil { + m = l + continue + } + return n.Pos() + case *FuncType: + if l := lastField(n.ResultList); l != nil { + m = l + continue + } + if l := lastField(n.ParamList); l != nil { + m = l + continue + } + return n.Pos() + case *MapType: + m = n.Value + case *ChanType: + m = n.Elem + + // statements + case *EmptyStmt: + return n.Pos() + case *LabeledStmt: + m = n.Stmt + case *BlockStmt: + return n.Rbrace + case *ExprStmt: + m = n.X + case *SendStmt: + m = n.Value + case *DeclStmt: + if l := lastDecl(n.DeclList); l != nil { + m = l + continue + } + return n.Pos() + case *AssignStmt: + m = n.Rhs + if m == nil { + p := EndPos(n.Lhs) + return MakePos(p.Base(), p.Line(), p.Col()+2) + } + case *BranchStmt: + if n.Label != nil { + m = n.Label + continue + } + return n.Pos() + case *CallStmt: + m = n.Call + case *ReturnStmt: + if n.Results != nil { + m = n.Results + continue + } + return n.Pos() + case *IfStmt: + if n.Else != nil { + m = n.Else + continue + } + m = n.Then + case *ForStmt: + m = n.Body + case *SwitchStmt: + return n.Rbrace + case *SelectStmt: + return n.Rbrace + + // helper nodes + case *RangeClause: + m = n.X + case *CaseClause: + if l := lastStmt(n.Body); l != nil { + m = l + continue + } + return n.Colon + case *CommClause: + if l := lastStmt(n.Body); l != nil { + m = l + continue + } + return n.Colon + + default: + return n.Pos() + } + } +} + +func lastDecl(list []Decl) Decl { + if l := len(list); l > 0 { + return list[l-1] + } + return nil +} + +func lastExpr(list []Expr) Expr { + if l := len(list); l > 0 { + return list[l-1] + } + return nil +} + +func lastStmt(list []Stmt) Stmt { + if l := len(list); l > 0 { + return list[l-1] + } + return nil +} + +func lastField(list []*Field) *Field { + if l := len(list); l > 0 { + return list[l-1] + } + return nil +} diff --git a/src/cmd/compile/internal/syntax/printer.go b/src/cmd/compile/internal/syntax/printer.go index 8ff3bfa79443523e9009e23cbe95ce3647088965..e557f5d9247b521a67984f5b60d8963174b45a1d 100644 --- a/src/cmd/compile/internal/syntax/printer.go +++ b/src/cmd/compile/internal/syntax/printer.go @@ -13,19 +13,28 @@ import ( "strings" ) -// TODO(gri) Consider removing the linebreaks flag from this signature. -// Its likely rarely used in common cases. +// Form controls print formatting. +type Form uint -func Fprint(w io.Writer, x Node, linebreaks bool) (n int, err error) { +const ( + _ Form = iota // default + LineForm // use spaces instead of linebreaks where possible + ShortForm // like LineForm but print "…" for non-empty function or composite literal bodies +) + +// Fprint prints node x to w in the specified form. +// It returns the number of bytes written, and whether there was an error. +func Fprint(w io.Writer, x Node, form Form) (n int, err error) { p := printer{ output: w, - linebreaks: linebreaks, + form: form, + linebreaks: form == 0, } defer func() { n = p.written if e := recover(); e != nil { - err = e.(localError).err // re-panics if it's not a localError + err = e.(writeError).err // re-panics if it's not a writeError } }() @@ -35,11 +44,13 @@ func Fprint(w io.Writer, x Node, linebreaks bool) (n int, err error) { return } +// String is a convenience functions that prints n in ShortForm +// and returns the printed string. func String(n Node) string { var buf bytes.Buffer - _, err := Fprint(&buf, n, false) + _, err := Fprint(&buf, n, ShortForm) if err != nil { - panic(err) // TODO(gri) print something sensible into buf instead + fmt.Fprintf(&buf, "<<< ERROR: %s", err) } return buf.String() } @@ -65,7 +76,8 @@ type whitespace struct { type printer struct { output io.Writer - written int // number of bytes written + written int // number of bytes written + form Form linebreaks bool // print linebreaks instead of semis indent int // current indentation level @@ -81,7 +93,7 @@ func (p *printer) write(data []byte) { n, err := p.output.Write(data) p.written += n if err != nil { - panic(localError{err}) + panic(writeError{err}) } } @@ -355,17 +367,34 @@ func (p *printer) printRawNode(n Node) { p.print(_Name, n.Value) // _Name requires actual value following immediately case *FuncLit: - p.print(n.Type, blank, n.Body) + p.print(n.Type, blank) + if n.Body != nil { + if p.form == ShortForm { + p.print(_Lbrace) + if len(n.Body.List) > 0 { + p.print(_Name, "…") + } + p.print(_Rbrace) + } else { + p.print(n.Body) + } + } case *CompositeLit: if n.Type != nil { p.print(n.Type) } p.print(_Lbrace) - if n.NKeys > 0 && n.NKeys == len(n.ElemList) { - p.printExprLines(n.ElemList) + if p.form == ShortForm { + if len(n.ElemList) > 0 { + p.print(_Name, "…") + } } else { - p.printExprList(n.ElemList) + if n.NKeys > 0 && n.NKeys == len(n.ElemList) { + p.printExprLines(n.ElemList) + } else { + p.printExprList(n.ElemList) + } } p.print(_Rbrace) @@ -450,9 +479,13 @@ func (p *printer) printRawNode(n Node) { } p.print(_Lbrace) if len(n.FieldList) > 0 { - p.print(newline, indent) - p.printFieldList(n.FieldList, n.TagList) - p.print(outdent, newline) + if p.linebreaks { + p.print(newline, indent) + p.printFieldList(n.FieldList, n.TagList, _Semi) + p.print(outdent, newline) + } else { + p.printFieldList(n.FieldList, n.TagList, _Semi) + } } p.print(_Rbrace) @@ -461,14 +494,38 @@ func (p *printer) printRawNode(n Node) { p.printSignature(n) case *InterfaceType: + // separate type list and method list + var types []Expr + var methods []*Field + for _, f := range n.MethodList { + if f.Name != nil && f.Name.Value == "type" { + types = append(types, f.Type) + } else { + // method or embedded interface + methods = append(methods, f) + } + } + + multiLine := len(n.MethodList) > 0 && p.linebreaks p.print(_Interface) - if len(n.MethodList) > 0 && p.linebreaks { + if multiLine { p.print(blank) } p.print(_Lbrace) - if len(n.MethodList) > 0 { + if multiLine { p.print(newline, indent) - p.printMethodList(n.MethodList) + } + if len(types) > 0 { + p.print(_Type, blank) + p.printExprList(types) + if len(methods) > 0 { + p.print(_Semi, blank) + } + } + if len(methods) > 0 { + p.printMethodList(methods) + } + if multiLine { p.print(outdent, newline) } p.print(_Rbrace) @@ -484,7 +541,15 @@ func (p *printer) printRawNode(n Node) { if n.Dir == SendOnly { p.print(_Arrow) } - p.print(blank, n.Elem) + p.print(blank) + if e, _ := n.Elem.(*ChanType); n.Dir == 0 && e != nil && e.Dir == RecvOnly { + // don't print chan (<-chan T) as chan <-chan T + p.print(_Lparen) + p.print(n.Elem) + p.print(_Rparen) + } else { + p.print(n.Elem) + } // statements case *DeclStmt: @@ -504,7 +569,7 @@ func (p *printer) printRawNode(n Node) { case *AssignStmt: p.print(n.Lhs) - if n.Rhs == ImplicitOne { + if n.Rhs == nil { // TODO(gri) This is going to break the mayCombine // check once we enable that again. p.print(n.Op, n.Op) // ++ or -- @@ -622,7 +687,13 @@ func (p *printer) printRawNode(n Node) { if n.Group == nil { p.print(_Type, blank) } - p.print(n.Name, blank) + p.print(n.Name) + if n.TParamList != nil { + p.print(_Lbrack) + p.printFieldList(n.TParamList, nil, _Comma) + p.print(_Rbrack) + } + p.print(blank) if n.Alias { p.print(_Assign, blank) } @@ -651,6 +722,11 @@ func (p *printer) printRawNode(n Node) { p.print(_Rparen, blank) } p.print(n.Name) + if n.TParamList != nil { + p.print(_Lbrack) + p.printFieldList(n.TParamList, nil, _Comma) + p.print(_Rbrack) + } p.printSignature(n.Type) if n.Body != nil { p.print(blank, n.Body) @@ -701,14 +777,14 @@ func (p *printer) printFields(fields []*Field, tags []*BasicLit, i, j int) { } } -func (p *printer) printFieldList(fields []*Field, tags []*BasicLit) { +func (p *printer) printFieldList(fields []*Field, tags []*BasicLit, sep token) { i0 := 0 var typ Expr for i, f := range fields { if f.Name == nil || f.Type != typ { if i0 < i { p.printFields(fields, tags, i0, i) - p.print(_Semi, newline) + p.print(sep, newline) i0 = i } typ = f.Type diff --git a/src/cmd/compile/internal/syntax/printer_test.go b/src/cmd/compile/internal/syntax/printer_test.go index c3b9aca229c3358a69ef17b7b72b3fbf8458fb87..ec4b1de573f6bec08883996037bccf320cb210ad 100644 --- a/src/cmd/compile/internal/syntax/printer_test.go +++ b/src/cmd/compile/internal/syntax/printer_test.go @@ -18,25 +18,83 @@ func TestPrint(t *testing.T) { t.Skip("skipping test in short mode") } - // provide a dummy error handler so parsing doesn't stop after first error + // provide a no-op error handler so parsing doesn't stop after first error ast, err := ParseFile(*src_, func(error) {}, nil, 0) if err != nil { t.Error(err) } if ast != nil { - Fprint(testOut(), ast, true) + Fprint(testOut(), ast, LineForm) fmt.Println() } } +type shortBuffer struct { + buf []byte +} + +func (w *shortBuffer) Write(data []byte) (n int, err error) { + w.buf = append(w.buf, data...) + n = len(data) + if len(w.buf) > 10 { + err = io.ErrShortBuffer + } + return +} + +func TestPrintError(t *testing.T) { + const src = "package p; var x int" + ast, err := Parse(nil, strings.NewReader(src), nil, nil, 0) + if err != nil { + t.Fatal(err) + } + + var buf shortBuffer + _, err = Fprint(&buf, ast, 0) + if err == nil || err != io.ErrShortBuffer { + t.Errorf("got err = %s, want %s", err, io.ErrShortBuffer) + } +} + +var stringTests = []string{ + "package p", + "package p; type _ int; type T1 = struct{}; type ( _ *struct{}; T2 = float32 )", + + // generic type declarations + "package p; type _[T any] struct{}", + "package p; type _[A, B, C interface{m()}] struct{}", + "package p; type _[T any, A, B, C interface{m()}, X, Y, Z interface{type int}] struct{}", + + // generic function declarations + "package p; func _[T any]()", + "package p; func _[A, B, C interface{m()}]()", + "package p; func _[T any, A, B, C interface{m()}, X, Y, Z interface{type int}]()", + + // methods with generic receiver types + "package p; func (R[T]) _()", + "package p; func (*R[A, B, C]) _()", + "package p; func (_ *R[A, B, C]) _()", + + // channels + "package p; type _ chan chan int", + "package p; type _ chan (<-chan int)", + "package p; type _ chan chan<- int", + + "package p; type _ <-chan chan int", + "package p; type _ <-chan <-chan int", + "package p; type _ <-chan chan<- int", + + "package p; type _ chan<- chan int", + "package p; type _ chan<- <-chan int", + "package p; type _ chan<- chan<- int", + + // TODO(gri) expand +} + func TestPrintString(t *testing.T) { - for _, want := range []string{ - "package p", - "package p; type _ = int; type T1 = struct{}; type ( _ = *struct{}; T2 = float32 )", - // TODO(gri) expand - } { - ast, err := Parse(nil, strings.NewReader(want), nil, nil, 0) + for _, want := range stringTests { + ast, err := Parse(nil, strings.NewReader(want), nil, nil, AllowGenerics) if err != nil { t.Error(err) continue @@ -53,3 +111,117 @@ func testOut() io.Writer { } return ioutil.Discard } + +func dup(s string) [2]string { return [2]string{s, s} } + +var exprTests = [][2]string{ + // basic type literals + dup("x"), + dup("true"), + dup("42"), + dup("3.1415"), + dup("2.71828i"), + dup(`'a'`), + dup(`"foo"`), + dup("`bar`"), + + // func and composite literals + dup("func() {}"), + dup("[]int{}"), + {"func(x int) complex128 { return 0 }", "func(x int) complex128 {…}"}, + {"[]int{1, 2, 3}", "[]int{…}"}, + + // type expressions + dup("[1 << 10]byte"), + dup("[]int"), + dup("*int"), + dup("struct{x int}"), + dup("func()"), + dup("func(int, float32) string"), + dup("interface{m()}"), + dup("interface{m() string; n(x int)}"), + dup("interface{type int}"), + dup("interface{type int, float64, string}"), + dup("interface{type int; m()}"), + dup("interface{type int, float64, string; m() string; n(x int)}"), + dup("map[string]int"), + dup("chan E"), + dup("<-chan E"), + dup("chan<- E"), + + // new interfaces + dup("interface{int}"), + dup("interface{~int}"), + dup("interface{~int}"), + dup("interface{int | string}"), + dup("interface{~int | ~string; float64; m()}"), + dup("interface{type a, b, c; ~int | ~string; float64; m()}"), + dup("interface{~T[int, string] | string}"), + + // non-type expressions + dup("(x)"), + dup("x.f"), + dup("a[i]"), + + dup("s[:]"), + dup("s[i:]"), + dup("s[:j]"), + dup("s[i:j]"), + dup("s[:j:k]"), + dup("s[i:j:k]"), + + dup("x.(T)"), + + dup("x.([10]int)"), + dup("x.([...]int)"), + + dup("x.(struct{})"), + dup("x.(struct{x int; y, z float32; E})"), + + dup("x.(func())"), + dup("x.(func(x int))"), + dup("x.(func() int)"), + dup("x.(func(x, y int, z float32) (r int))"), + dup("x.(func(a, b, c int))"), + dup("x.(func(x ...T))"), + + dup("x.(interface{})"), + dup("x.(interface{m(); n(x int); E})"), + dup("x.(interface{m(); n(x int) T; E; F})"), + + dup("x.(map[K]V)"), + + dup("x.(chan E)"), + dup("x.(<-chan E)"), + dup("x.(chan<- chan int)"), + dup("x.(chan<- <-chan int)"), + dup("x.(<-chan chan int)"), + dup("x.(chan (<-chan int))"), + + dup("f()"), + dup("f(x)"), + dup("int(x)"), + dup("f(x, x + y)"), + dup("f(s...)"), + dup("f(a, s...)"), + + dup("*x"), + dup("&x"), + dup("x + y"), + dup("x + y << (2 * s)"), +} + +func TestShortString(t *testing.T) { + for _, test := range exprTests { + src := "package p; var _ = " + test[0] + ast, err := Parse(nil, strings.NewReader(src), nil, nil, AllowGenerics) + if err != nil { + t.Errorf("%s: %s", test[0], err) + continue + } + x := ast.DeclList[0].(*VarDecl).Values + if got := String(x); got != test[1] { + t.Errorf("%s: got %s, want %s", test[0], got, test[1]) + } + } +} diff --git a/src/cmd/compile/internal/syntax/scanner.go b/src/cmd/compile/internal/syntax/scanner.go index 9fe49659844964794b4a7365df8965d1a63faecf..218bc24e61fb7b545575aedab29bc9073d5e8269 100644 --- a/src/cmd/compile/internal/syntax/scanner.go +++ b/src/cmd/compile/internal/syntax/scanner.go @@ -343,6 +343,11 @@ redo: s.op, s.prec = Not, 0 s.tok = _Operator + case '~': + s.nextch() + s.op, s.prec = Tilde, 0 + s.tok = _Operator + default: s.errorf("invalid character %#U", s.ch) s.nextch() diff --git a/src/cmd/compile/internal/syntax/scanner_test.go b/src/cmd/compile/internal/syntax/scanner_test.go index 04338629d47a52e7115f299bc4e34b195a1b4f81..2deb3bbf8463446273cd4be9811f348abeaeee95 100644 --- a/src/cmd/compile/internal/syntax/scanner_test.go +++ b/src/cmd/compile/internal/syntax/scanner_test.go @@ -232,6 +232,9 @@ var sampleTokens = [...]struct { {_Literal, "`\r`", 0, 0}, // operators + {_Operator, "!", Not, 0}, + {_Operator, "~", Tilde, 0}, + {_Operator, "||", OrOr, precOrOr}, {_Operator, "&&", AndAnd, precAndAnd}, @@ -547,7 +550,7 @@ func TestNumbers(t *testing.T) { t.Errorf("%q: got error but bad not set", test.src) } - // compute lit where where s.lit is not defined + // compute lit where s.lit is not defined var lit string switch s.tok { case _Name, _Literal: @@ -601,7 +604,7 @@ func TestScanErrors(t *testing.T) { {"\U0001d7d8" /* 𝟘 */, "identifier cannot begin with digit U+1D7D8 '𝟘'", 0, 0}, {"foo\U0001d7d8_½" /* foo𝟘_½ */, "invalid character U+00BD '½' in identifier", 0, 8 /* byte offset */}, - {"x + ~y", "invalid character U+007E '~'", 0, 4}, + {"x + #y", "invalid character U+0023 '#'", 0, 4}, {"foo$bar = 0", "invalid character U+0024 '$'", 0, 3}, {"0123456789", "invalid digit '8' in octal literal", 0, 8}, {"0123456789. /* foobar", "comment not terminated", 0, 12}, // valid float constant diff --git a/src/cmd/compile/internal/syntax/syntax.go b/src/cmd/compile/internal/syntax/syntax.go index e51b5538b3b18221a111830519f12358528f6d7b..f3d4c09ed5e045fad13d469def95cb386b73cd6a 100644 --- a/src/cmd/compile/internal/syntax/syntax.go +++ b/src/cmd/compile/internal/syntax/syntax.go @@ -16,6 +16,7 @@ type Mode uint // Modes supported by the parser. const ( CheckBranches Mode = 1 << iota // check correct use of labels, break, continue, and goto statements + AllowGenerics ) // Error describes a syntax error. Error implements the error interface. diff --git a/src/cmd/compile/internal/syntax/testdata/go2/chans.go2 b/src/cmd/compile/internal/syntax/testdata/go2/chans.go2 new file mode 100644 index 0000000000000000000000000000000000000000..fad2bcec9d083ff6dde4c731628ac2a35ef093d3 --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/go2/chans.go2 @@ -0,0 +1,62 @@ +package chans + +import "runtime" + +// Ranger returns a Sender and a Receiver. The Receiver provides a +// Next method to retrieve values. The Sender provides a Send method +// to send values and a Close method to stop sending values. The Next +// method indicates when the Sender has been closed, and the Send +// method indicates when the Receiver has been freed. +// +// This is a convenient way to exit a goroutine sending values when +// the receiver stops reading them. +func Ranger[T any]() (*Sender[T], *Receiver[T]) { + c := make(chan T) + d := make(chan bool) + s := &Sender[T]{values: c, done: d} + r := &Receiver[T]{values: c, done: d} + runtime.SetFinalizer(r, r.finalize) + return s, r +} + +// A sender is used to send values to a Receiver. +type Sender[T any] struct { + values chan<- T + done <-chan bool +} + +// Send sends a value to the receiver. It returns whether any more +// values may be sent; if it returns false the value was not sent. +func (s *Sender[T]) Send(v T) bool { + select { + case s.values <- v: + return true + case <-s.done: + return false + } +} + +// Close tells the receiver that no more values will arrive. +// After Close is called, the Sender may no longer be used. +func (s *Sender[T]) Close() { + close(s.values) +} + +// A Receiver receives values from a Sender. +type Receiver[T any] struct { + values <-chan T + done chan<- bool +} + +// Next returns the next value from the channel. The bool result +// indicates whether the value is valid, or whether the Sender has +// been closed and no more values will be received. +func (r *Receiver[T]) Next() (T, bool) { + v, ok := <-r.values + return v, ok +} + +// finalize is a finalizer for the receiver. +func (r *Receiver[T]) finalize() { + close(r.done) +} diff --git a/src/cmd/compile/internal/syntax/testdata/go2/linalg.go2 b/src/cmd/compile/internal/syntax/testdata/go2/linalg.go2 new file mode 100644 index 0000000000000000000000000000000000000000..0d27603a5837a2747922d9c1e52802b840ca8c4e --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/go2/linalg.go2 @@ -0,0 +1,83 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linalg + +import "math" + +// Numeric is type bound that matches any numeric type. +// It would likely be in a constraints package in the standard library. +type Numeric interface { + type int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, uintptr, + float32, float64, + complex64, complex128 +} + +func DotProduct[T Numeric](s1, s2 []T) T { + if len(s1) != len(s2) { + panic("DotProduct: slices of unequal length") + } + var r T + for i := range s1 { + r += s1[i] * s2[i] + } + return r +} + +// NumericAbs matches numeric types with an Abs method. +type NumericAbs[T any] interface { + Numeric + + Abs() T +} + +// AbsDifference computes the absolute value of the difference of +// a and b, where the absolute value is determined by the Abs method. +func AbsDifference[T NumericAbs[T]](a, b T) T { + d := a - b + return d.Abs() +} + +// OrderedNumeric is a type bound that matches numeric types that support the < operator. +type OrderedNumeric interface { + type int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, uintptr, + float32, float64 +} + +// Complex is a type bound that matches the two complex types, which do not have a < operator. +type Complex interface { + type complex64, complex128 +} + +// OrderedAbs is a helper type that defines an Abs method for +// ordered numeric types. +type OrderedAbs[T OrderedNumeric] T + +func (a OrderedAbs[T]) Abs() OrderedAbs[T] { + if a < 0 { + return -a + } + return a +} + +// ComplexAbs is a helper type that defines an Abs method for +// complex types. +type ComplexAbs[T Complex] T + +func (a ComplexAbs[T]) Abs() ComplexAbs[T] { + r := float64(real(a)) + i := float64(imag(a)) + d := math.Sqrt(r * r + i * i) + return ComplexAbs[T](complex(d, 0)) +} + +func OrderedAbsDifference[T OrderedNumeric](a, b T) T { + return T(AbsDifference(OrderedAbs[T](a), OrderedAbs[T](b))) +} + +func ComplexAbsDifference[T Complex](a, b T) T { + return T(AbsDifference(ComplexAbs[T](a), ComplexAbs[T](b))) +} diff --git a/src/cmd/compile/internal/syntax/testdata/go2/map.go2 b/src/cmd/compile/internal/syntax/testdata/go2/map.go2 new file mode 100644 index 0000000000000000000000000000000000000000..814d9539fdc94a5e68cda76c95eb9ec44e821080 --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/go2/map.go2 @@ -0,0 +1,113 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package orderedmap provides an ordered map, implemented as a binary tree. +package orderedmap + +// TODO(gri) fix imports for tests +import "chans" // ERROR could not import + +// Map is an ordered map. +type Map[K, V any] struct { + root *node[K, V] + compare func(K, K) int +} + +// node is the type of a node in the binary tree. +type node[K, V any] struct { + key K + val V + left, right *node[K, V] +} + +// New returns a new map. +func New[K, V any](compare func(K, K) int) *Map[K, V] { + return &Map[K, V]{compare: compare} +} + +// find looks up key in the map, and returns either a pointer +// to the node holding key, or a pointer to the location where +// such a node would go. +func (m *Map[K, V]) find(key K) **node[K, V] { + pn := &m.root + for *pn != nil { + switch cmp := m.compare(key, (*pn).key); { + case cmp < 0: + pn = &(*pn).left + case cmp > 0: + pn = &(*pn).right + default: + return pn + } + } + return pn +} + +// Insert inserts a new key/value into the map. +// If the key is already present, the value is replaced. +// Returns true if this is a new key, false if already present. +func (m *Map[K, V]) Insert(key K, val V) bool { + pn := m.find(key) + if *pn != nil { + (*pn).val = val + return false + } + *pn = &node[K, V]{key: key, val: val} + return true +} + +// Find returns the value associated with a key, or zero if not present. +// The found result reports whether the key was found. +func (m *Map[K, V]) Find(key K) (V, bool) { + pn := m.find(key) + if *pn == nil { + var zero V // see the discussion of zero values, above + return zero, false + } + return (*pn).val, true +} + +// keyValue is a pair of key and value used when iterating. +type keyValue[K, V any] struct { + key K + val V +} + +// InOrder returns an iterator that does an in-order traversal of the map. +func (m *Map[K, V]) InOrder() *Iterator[K, V] { + sender, receiver := chans.Ranger[keyValue[K, V]]() + var f func(*node[K, V]) bool + f = func(n *node[K, V]) bool { + if n == nil { + return true + } + // Stop sending values if sender.Send returns false, + // meaning that nothing is listening at the receiver end. + return f(n.left) && + sender.Send(keyValue[K, V]{n.key, n.val}) && + f(n.right) + } + go func() { + f(m.root) + sender.Close() + }() + return &Iterator[K, V]{receiver} +} + +// Iterator is used to iterate over the map. +type Iterator[K, V any] struct { + r *chans.Receiver[keyValue[K, V]] +} + +// Next returns the next key and value pair, and a boolean indicating +// whether they are valid or whether we have reached the end. +func (it *Iterator[K, V]) Next() (K, V, bool) { + keyval, ok := it.r.Next() + if !ok { + var zerok K + var zerov V + return zerok, zerov, false + } + return keyval.key, keyval.val, true +} diff --git a/src/cmd/compile/internal/syntax/testdata/go2/map2.go2 b/src/cmd/compile/internal/syntax/testdata/go2/map2.go2 new file mode 100644 index 0000000000000000000000000000000000000000..2833445662de813d6d3eeed95978c8342316b29f --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/go2/map2.go2 @@ -0,0 +1,146 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is like map.go2, but instead if importing chans, it contains +// the necessary functionality at the end of the file. + +// Package orderedmap provides an ordered map, implemented as a binary tree. +package orderedmap + +// Map is an ordered map. +type Map[K, V any] struct { + root *node[K, V] + compare func(K, K) int +} + +// node is the type of a node in the binary tree. +type node[K, V any] struct { + key K + val V + left, right *node[K, V] +} + +// New returns a new map. +func New[K, V any](compare func(K, K) int) *Map[K, V] { + return &Map[K, V]{compare: compare} +} + +// find looks up key in the map, and returns either a pointer +// to the node holding key, or a pointer to the location where +// such a node would go. +func (m *Map[K, V]) find(key K) **node[K, V] { + pn := &m.root + for *pn != nil { + switch cmp := m.compare(key, (*pn).key); { + case cmp < 0: + pn = &(*pn).left + case cmp > 0: + pn = &(*pn).right + default: + return pn + } + } + return pn +} + +// Insert inserts a new key/value into the map. +// If the key is already present, the value is replaced. +// Returns true if this is a new key, false if already present. +func (m *Map[K, V]) Insert(key K, val V) bool { + pn := m.find(key) + if *pn != nil { + (*pn).val = val + return false + } + *pn = &node[K, V]{key: key, val: val} + return true +} + +// Find returns the value associated with a key, or zero if not present. +// The found result reports whether the key was found. +func (m *Map[K, V]) Find(key K) (V, bool) { + pn := m.find(key) + if *pn == nil { + var zero V // see the discussion of zero values, above + return zero, false + } + return (*pn).val, true +} + +// keyValue is a pair of key and value used when iterating. +type keyValue[K, V any] struct { + key K + val V +} + +// InOrder returns an iterator that does an in-order traversal of the map. +func (m *Map[K, V]) InOrder() *Iterator[K, V] { + sender, receiver := chans_Ranger[keyValue[K, V]]() + var f func(*node[K, V]) bool + f = func(n *node[K, V]) bool { + if n == nil { + return true + } + // Stop sending values if sender.Send returns false, + // meaning that nothing is listening at the receiver end. + return f(n.left) && + sender.Send(keyValue[K, V]{n.key, n.val}) && + f(n.right) + } + go func() { + f(m.root) + sender.Close() + }() + return &Iterator[K, V]{receiver} +} + +// Iterator is used to iterate over the map. +type Iterator[K, V any] struct { + r *chans_Receiver[keyValue[K, V]] +} + +// Next returns the next key and value pair, and a boolean indicating +// whether they are valid or whether we have reached the end. +func (it *Iterator[K, V]) Next() (K, V, bool) { + keyval, ok := it.r.Next() + if !ok { + var zerok K + var zerov V + return zerok, zerov, false + } + return keyval.key, keyval.val, true +} + +// chans + +func chans_Ranger[T any]() (*chans_Sender[T], *chans_Receiver[T]) + +// A sender is used to send values to a Receiver. +type chans_Sender[T any] struct { + values chan<- T + done <-chan bool +} + +func (s *chans_Sender[T]) Send(v T) bool { + select { + case s.values <- v: + return true + case <-s.done: + return false + } +} + +func (s *chans_Sender[T]) Close() { + close(s.values) +} + +type chans_Receiver[T any] struct { + values <-chan T + done chan<- bool +} + +func (r *chans_Receiver[T]) Next() (T, bool) { + v, ok := <-r.values + return v, ok +} \ No newline at end of file diff --git a/src/cmd/compile/internal/syntax/testdata/go2/slices.go2 b/src/cmd/compile/internal/syntax/testdata/go2/slices.go2 new file mode 100644 index 0000000000000000000000000000000000000000..2bacd1c2aa8aab19163eb5cb856bfdeb36ea313a --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/go2/slices.go2 @@ -0,0 +1,68 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package slices implements various slice algorithms. +package slices + +// Map turns a []T1 to a []T2 using a mapping function. +func Map[T1, T2 any](s []T1, f func(T1) T2) []T2 { + r := make([]T2, len(s)) + for i, v := range s { + r[i] = f(v) + } + return r +} + +// Reduce reduces a []T1 to a single value using a reduction function. +func Reduce[T1, T2 any](s []T1, initializer T2, f func(T2, T1) T2) T2 { + r := initializer + for _, v := range s { + r = f(r, v) + } + return r +} + +// Filter filters values from a slice using a filter function. +func Filter[T any](s []T, f func(T) bool) []T { + var r []T + for _, v := range s { + if f(v) { + r = append(r, v) + } + } + return r +} + +// Example uses + +func limiter(x int) byte { + switch { + case x < 0: + return 0 + default: + return byte(x) + case x > 255: + return 255 + } +} + +var input = []int{-4, 68954, 7, 44, 0, -555, 6945} +var limited1 = Map[int, byte](input, limiter) +var limited2 = Map(input, limiter) // using type inference + +func reducer(x float64, y int) float64 { + return x + float64(y) +} + +var reduced1 = Reduce[int, float64](input, 0, reducer) +var reduced2 = Reduce(input, 1i /* ERROR overflows */, reducer) // using type inference +var reduced3 = Reduce(input, 1, reducer) // using type inference + +func filter(x int) bool { + return x&1 != 0 +} + +var filtered1 = Filter[int](input, filter) +var filtered2 = Filter(input, filter) // using type inference + diff --git a/src/cmd/compile/internal/syntax/testdata/go2/smoketest.go2 b/src/cmd/compile/internal/syntax/testdata/go2/smoketest.go2 new file mode 100644 index 0000000000000000000000000000000000000000..e5cfba061254165a62c45bc04a2ea055033785d1 --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/go2/smoketest.go2 @@ -0,0 +1,83 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains basic generic code snippets. + +package p + +// type parameter lists +type B[P any] struct{} +type _[P interface{}] struct{} +type _[P B] struct{} +type _[P B[P]] struct{} + +type _[A, B, C any] struct{} +type _[A, B, C B] struct{} +type _[A, B, C B[A, B, C]] struct{} +type _[A1, A2 B1, A3 B2, A4, A5, A6 B3] struct{} + +type _[A interface{}] struct{} +type _[A, B interface{ m() }] struct{} + +type _[A, B, C any] struct{} + +// in functions +func _[P any]() +func _[P interface{}]() +func _[P B]() +func _[P B[P]]() + +// in methods +func (T) _[P any]() +func (T) _[P interface{}]() +func (T) _[P B]() +func (T) _[P B[P]]() + +// type instantiations +type _ T[int] + +// in expressions +var _ = T[int]{} + +// in embedded types +type _ struct{ T[int] } + +// interfaces +type _ interface{ + m() + type int +} + +type _ interface{ + type int, float, string + type complex128 + underlying(underlying underlying) underlying +} + +type _ interface{ + T + T[int] +} + +// tricky cases +func _(T[P], T[P1, P2]) +func _(a [N]T) + +type _ struct{ + T[P] + T[P1, P2] + f [N] +} +type _ interface{ + m() + + // generic methods - disabled for now + // m[] /* ERROR empty type parameter list */ () + // m[ /* ERROR cannot have type parameters */ P any](P) + + // instantiated types + // T[] /* ERROR empty type argument list */ + T[P] + T[P1, P2] +} diff --git a/src/cmd/compile/internal/syntax/testdata/go2/typeinst.go2 b/src/cmd/compile/internal/syntax/testdata/go2/typeinst.go2 new file mode 100644 index 0000000000000000000000000000000000000000..a422d5e5684efe871115fa739d96082604c21cf3 --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/go2/typeinst.go2 @@ -0,0 +1,60 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type myInt int + +// Parameterized type declarations + +type T1[P any] P + +type T2[P any] struct { + f P + g int // int should still be in scope chain +} + +type List[P any] []P + +// Alias type declarations cannot have type parameters. Syntax error. +// TODO(gri) Disabled for now as we don't check syntax error here. +// type A1[P any] = /* ERROR cannot be alias */ P + +// But an alias may refer to a generic, uninstantiated type. +type A2 = List +var _ A2[int] +var _ A2 /* ERROR without instantiation */ + +type A3 = List[int] +var _ A3 + +// Parameterized type instantiations + +var x int +type _ x /* ERROR not a type */ [int] + +type _ int /* ERROR not a generic type */ [int] +type _ myInt /* ERROR not a generic type */ [int] + +// TODO(gri) better error messages +type _ T1[int] +type _ T1[x /* ERROR not a type */ ] +type _ T1 /* ERROR got 2 arguments but 1 type parameters */ [int, float32] + +var _ T2[int] = T2[int]{} + +var _ List[int] = []int{1, 2, 3} +var _ List[[]int] = [][]int{{1, 2, 3}} +var _ List[List[List[int]]] + +// Parameterized types containing parameterized types + +type T3[P any] List[P] + +var _ T3[int] = T3[int](List[int]{1, 2, 3}) + +// Self-recursive generic types are not permitted + +type self1[P any] self1 /* ERROR illegal cycle */ [P] +type self2[P any] *self2[P] // this is ok diff --git a/src/cmd/compile/internal/syntax/testdata/go2/typeinst2.go2 b/src/cmd/compile/internal/syntax/testdata/go2/typeinst2.go2 new file mode 100644 index 0000000000000000000000000000000000000000..6e2104a5150da8fead95d91dc7cdad447fc59f8d --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/go2/typeinst2.go2 @@ -0,0 +1,256 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type List[E any] []E +var _ List[List[List[int]]] +var _ List[List[List[int]]] = []List[List[int]]{} + +type ( + T1[P1 any] struct { + f1 T2[P1, float32] + } + + T2[P2, P3 any] struct { + f2 P2 + f3 P3 + } +) + +func _() { + var x1 T1[int] + var x2 T2[int, float32] + + x1.f1.f2 = 0 + x1.f1 = x2 +} + +type T3[P any] T1[T2[P, P]] + +func _() { + var x1 T3[int] + var x2 T2[int, int] + x1.f1.f2 = x2 +} + +func f[P any] (x P) List[P] { + return List[P]{x} +} + +var ( + _ []int = f(0) + _ []float32 = f[float32](10) + _ List[complex128] = f(1i) + _ []List[int] = f(List[int]{}) + _ List[List[int]] = []List[int]{} + _ = []List[int]{} +) + +// Parameterized types with methods + +func (l List[E]) Head() (_ E, _ bool) { + if len(l) > 0 { + return l[0], true + } + return +} + +// A test case for instantiating types with other types (extracted from map.go2) + +type Pair[K any] struct { + key K +} + +type Receiver[T any] struct { + values T +} + +type Iterator[K any] struct { + r Receiver[Pair[K]] +} + +func Values [T any] (r Receiver[T]) T { + return r.values +} + +func (it Iterator[K]) Next() K { + return Values[Pair[K]](it.r).key +} + +// A more complex test case testing type bounds (extracted from linalg.go2 and reduced to essence) + +type NumericAbs[T any] interface { + Abs() T +} + +func AbsDifference[T NumericAbs[T]](x T) + +type OrderedAbs[T any] T + +func (a OrderedAbs[T]) Abs() OrderedAbs[T] + +func OrderedAbsDifference[T any](x T) { + AbsDifference(OrderedAbs[T](x)) +} + +// same code, reduced to essence + +func g[P interface{ m() P }](x P) + +type T4[P any] P + +func (_ T4[P]) m() T4[P] + +func _[Q any](x Q) { + g(T4[Q](x)) +} + +// Another test case that caused problems in the past + +type T5[_ interface { a() }, _ interface{}] struct{} + +type A[P any] struct{ x P } + +func (_ A[P]) a() {} + +var _ T5[A[int], int] + +// Invoking methods with parameterized receiver types uses +// type inference to determine the actual type arguments matching +// the receiver type parameters from the actual receiver argument. +// Go does implicit address-taking and dereferenciation depending +// on the actual receiver and the method's receiver type. To make +// type inference work, the type-checker matches "pointer-ness" +// of the actual receiver and the method's receiver type. +// The following code tests this mechanism. + +type R1[A any] struct{} +func (_ R1[A]) vm() +func (_ *R1[A]) pm() + +func _[T any](r R1[T], p *R1[T]) { + r.vm() + r.pm() + p.vm() + p.pm() +} + +type R2[A, B any] struct{} +func (_ R2[A, B]) vm() +func (_ *R2[A, B]) pm() + +func _[T any](r R2[T, int], p *R2[string, T]) { + r.vm() + r.pm() + p.vm() + p.pm() +} + +// An interface can (explicitly) declare at most one type list. +type _ interface { + m0() + type int, string, bool + type /* ERROR multiple type lists */ float32, float64 + m1() + m2() + type /* ERROR multiple type lists */ complex64, complex128 + type /* ERROR multiple type lists */ rune +} + +// Interface type lists may contain each type at most once. +// (If there are multiple lists, we assume the author intended +// for them to be all in a single list, and we report the error +// as well.) +type _ interface { + type int, int /* ERROR duplicate type int */ + type /* ERROR multiple type lists */ int /* ERROR duplicate type int */ +} + +type _ interface { + type struct{f int}, struct{g int}, struct /* ERROR duplicate type */ {f int} +} + +// Interface type lists can contain any type, incl. *Named types. +// Verify that we use the underlying type to compute the operational type. +type MyInt int +func add1[T interface{type MyInt}](x T) T { + return x + 1 +} + +type MyString string +func double[T interface{type MyInt, MyString}](x T) T { + return x + x +} + +// Embedding of interfaces with type lists leads to interfaces +// with type lists that are the intersection of the embedded +// type lists. + +type E0 interface { + type int, bool, string +} + +type E1 interface { + type int, float64, string +} + +type E2 interface { + type float64 +} + +type I0 interface { + E0 +} + +func f0[T I0]() +var _ = f0[int] +var _ = f0[bool] +var _ = f0[string] +var _ = f0[float64 /* ERROR does not satisfy I0 */ ] + +type I01 interface { + E0 + E1 +} + +func f01[T I01]() +var _ = f01[int] +var _ = f01[bool /* ERROR does not satisfy I0 */ ] +var _ = f01[string] +var _ = f01[float64 /* ERROR does not satisfy I0 */ ] + +type I012 interface { + E0 + E1 + E2 +} + +func f012[T I012]() +var _ = f012[int /* ERROR does not satisfy I012 */ ] +var _ = f012[bool /* ERROR does not satisfy I012 */ ] +var _ = f012[string /* ERROR does not satisfy I012 */ ] +var _ = f012[float64 /* ERROR does not satisfy I012 */ ] + +type I12 interface { + E1 + E2 +} + +func f12[T I12]() +var _ = f12[int /* ERROR does not satisfy I12 */ ] +var _ = f12[bool /* ERROR does not satisfy I12 */ ] +var _ = f12[string /* ERROR does not satisfy I12 */ ] +var _ = f12[float64] + +type I0_ interface { + E0 + type int +} + +func f0_[T I0_]() +var _ = f0_[int] +var _ = f0_[bool /* ERROR does not satisfy I0_ */ ] +var _ = f0_[string /* ERROR does not satisfy I0_ */ ] +var _ = f0_[float64 /* ERROR does not satisfy I0_ */ ] diff --git a/src/cmd/compile/internal/syntax/testdata/go2/typeparams.go2 b/src/cmd/compile/internal/syntax/testdata/go2/typeparams.go2 new file mode 100644 index 0000000000000000000000000000000000000000..f78037f0f5d033c63236e8246644b2b401509713 --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/go2/typeparams.go2 @@ -0,0 +1,451 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// import "io" // for type assertion tests + +// The predeclared identifier "any" is only visible as a constraint +// in a type parameter list. +var _ any // ERROR undeclared +func _[_ any /* ok here */ , _ interface{any /* ERROR undeclared */ }](any /* ERROR undeclared */ ) { + var _ any /* ERROR undeclared */ +} + +func identity[T any](x T) T { return x } + +func _[_ any](x int) int +func _[T any](T /* ERROR redeclared */ T)() +func _[T, T /* ERROR redeclared */ any]() + +func reverse[T any](list []T) []T { + rlist := make([]T, len(list)) + i := len(list) + for _, x := range list { + i-- + rlist[i] = x + } + return rlist +} + +var _ = reverse /* ERROR cannot use generic function reverse */ +var _ = reverse[int, float32 /* ERROR got 2 type arguments */ ] ([]int{1, 2, 3}) +var _ = reverse[int]([ /* ERROR cannot use */ ]float32{1, 2, 3}) +var f = reverse[chan int] +var _ = f(0 /* ERROR cannot convert 0 .* to \[\]chan int */ ) + +func swap[A, B any](a A, b B) (B, A) { return b, a } + +var _ = swap /* ERROR single value is expected */ [int, float32](1, 2) +var f32, i = swap[int, float32](swap(float32, int)(1, 2)) +var _ float32 = f32 +var _ int = i + +func swapswap[A, B any](a A, b B) (A, B) { + return swap[B, A](b, a) +} + +type F[A, B any] func(A, B) (B, A) + +func min[T interface{ type int }](x, y T) T { + if x < y { + return x + } + return y +} + +func _[T interface{type int, float32}](x, y T) bool { return x < y } +func _[T any](x, y T) bool { return x /* ERROR cannot compare */ < y } +func _[T interface{type int, float32, bool}](x, y T) bool { return x /* ERROR cannot compare */ < y } + +func _[T C1[T]](x, y T) bool { return x /* ERROR cannot compare */ < y } +func _[T C2[T]](x, y T) bool { return x < y } + +type C1[T any] interface{} +type C2[T any] interface{ type int, float32 } + +func new[T any]() *T { + var x T + return &x +} + +var _ = new /* ERROR cannot use generic function new */ +var _ *int = new[int]() + +func _[T any](map[T /* ERROR invalid map key type T \(missing comparable constraint\) */]int) // w/o constraint we don't know if T is comparable + +func f1[T1 any](struct{T1}) int +var _ = f1(int)(struct{T1}{}) +type T1 = int + +func f2[t1 any](struct{t1; x float32}) int +var _ = f2(t1)(struct{t1; x float32}{}) +type t1 = int + + +func f3[A, B, C any](A, struct{x B}, func(A, struct{x B}, *C)) int + +var _ = f3[int, rune, bool](1, struct{x rune}{}, nil) + +// indexing + +func _[T any] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] } +func _[T interface{ type int }] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] } +func _[T interface{ type string }] (x T, i int) { _ = x[i] } +func _[T interface{ type []int }] (x T, i int) { _ = x[i] } +func _[T interface{ type [10]int, *[20]int, map[string]int }] (x T, i int) { _ = x[i] } +func _[T interface{ type string, []byte }] (x T, i int) { _ = x[i] } +func _[T interface{ type []int, [1]rune }] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] } +func _[T interface{ type string, []rune }] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] } + +// slicing +// TODO(gri) implement this + +func _[T interface{ type string }] (x T, i, j, k int) { _ = x /* ERROR invalid operation */ [i:j:k] } + +// len/cap built-ins + +func _[T any](x T) { _ = len(x /* ERROR invalid argument */ ) } +func _[T interface{ type int }](x T) { _ = len(x /* ERROR invalid argument */ ) } +func _[T interface{ type string, []byte, int }](x T) { _ = len(x /* ERROR invalid argument */ ) } +func _[T interface{ type string }](x T) { _ = len(x) } +func _[T interface{ type [10]int }](x T) { _ = len(x) } +func _[T interface{ type []byte }](x T) { _ = len(x) } +func _[T interface{ type map[int]int }](x T) { _ = len(x) } +func _[T interface{ type chan int }](x T) { _ = len(x) } +func _[T interface{ type string, []byte, chan int }](x T) { _ = len(x) } + +func _[T any](x T) { _ = cap(x /* ERROR invalid argument */ ) } +func _[T interface{ type int }](x T) { _ = cap(x /* ERROR invalid argument */ ) } +func _[T interface{ type string, []byte, int }](x T) { _ = cap(x /* ERROR invalid argument */ ) } +func _[T interface{ type string }](x T) { _ = cap(x /* ERROR invalid argument */ ) } +func _[T interface{ type [10]int }](x T) { _ = cap(x) } +func _[T interface{ type []byte }](x T) { _ = cap(x) } +func _[T interface{ type map[int]int }](x T) { _ = cap(x /* ERROR invalid argument */ ) } +func _[T interface{ type chan int }](x T) { _ = cap(x) } +func _[T interface{ type []byte, chan int }](x T) { _ = cap(x) } + +// range iteration + +func _[T interface{}](x T) { + for range x /* ERROR cannot range */ {} +} + +func _[T interface{ type string, []string }](x T) { + for range x {} + for i := range x { _ = i } + for i, _ := range x { _ = i } + for i, e := range x /* ERROR must have the same element type */ { _ = i } + for _, e := range x /* ERROR must have the same element type */ {} + var e rune + _ = e + for _, (e) = range x /* ERROR must have the same element type */ {} +} + + +func _[T interface{ type string, []rune, map[int]rune }](x T) { + for _, e := range x { _ = e } + for i, e := range x { _ = i; _ = e } +} + +func _[T interface{ type string, []rune, map[string]rune }](x T) { + for _, e := range x { _ = e } + for i, e := range x /* ERROR must have the same key type */ { _ = e } +} + +func _[T interface{ type string, chan int }](x T) { + for range x {} + for i := range x { _ = i } + for i, _ := range x { _ = i } // TODO(gri) should get an error here: channels only return one value +} + +func _[T interface{ type string, chan<-int }](x T) { + for i := range x /* ERROR send-only channel */ { _ = i } +} + +// type inference checks + +var _ = new() /* ERROR cannot infer T */ + +func f4[A, B, C any](A, B) C + +var _ = f4(1, 2) /* ERROR cannot infer C */ +var _ = f4[int, float32, complex128](1, 2) + +func f5[A, B, C any](A, []*B, struct{f []C}) int + +var _ = f5[int, float32, complex128](0, nil, struct{f []complex128}{}) +var _ = f5(0, nil, struct{f []complex128}{}) // ERROR cannot infer +var _ = f5(0, []*float32{new[float32]()}, struct{f []complex128}{}) + +func f6[A any](A, []A) int + +var _ = f6(0, nil) + +func f6nil[A any](A) int + +var _ = f6nil(nil) // ERROR cannot infer + +// type inference with variadic functions + +func f7[T any](...T) T + +var _ int = f7() /* ERROR cannot infer T */ +var _ int = f7(1) +var _ int = f7(1, 2) +var _ int = f7([]int{}...) +var _ int = f7 /* ERROR cannot use */ ([]float64{}...) +var _ float64 = f7([]float64{}...) +var _ = f7[float64](1, 2.3) +var _ = f7(float64(1), 2.3) +var _ = f7(1, 2.3 /* ERROR does not match */ ) +var _ = f7(1.2, 3 /* ERROR does not match */ ) + +func f8[A, B any](A, B, ...B) int + +var _ = f8(1) /* ERROR not enough arguments */ +var _ = f8(1, 2.3) +var _ = f8(1, 2.3, 3.4, 4.5) +var _ = f8(1, 2.3, 3.4, 4 /* ERROR does not match */ ) +var _ = f8(int, float64)(1, 2.3, 3.4, 4) + +var _ = f8(int, float64)(0, 0, nil...) // test case for #18268 + +// init functions cannot have type parameters + +func init() {} +func init[/* ERROR func init must have no type parameters */ _ any]() {} +func init[/* ERROR func init must have no type parameters */ P any]() {} + +type T struct {} + +func (T) m1() {} +// The type checker accepts method type parameters if configured accordingly. +func (T) m2[_ any]() {} +func (T) m3[P any]() {} + +// type inference across parameterized types + +type S1[P any] struct { f P } + +func f9[P any](x S1[P]) + +func _() { + f9[int](S1[int]{42}) + f9(S1[int]{42}) +} + +type S2[A, B, C any] struct{} + +func f10[X, Y, Z any](a S2[X, int, Z], b S2[X, Y, bool]) + +func _[P any]() { + f10[int, float32, string](S2[int, int, string]{}, S2[int, float32, bool]{}) + f10(S2[int, int, string]{}, S2[int, float32, bool]{}) + f10(S2[P, int, P]{}, S2[P, float32, bool]{}) +} + +// corner case for type inference +// (was bug: after instanting f11, the type-checker didn't mark f11 as non-generic) + +func f11[T any]() + +func _() { + f11[int]() +} + +// the previous example was extracted from + +func f12[T interface{m() T}]() + +type A[T any] T + +func (a A[T]) m() A[T] + +func _[T any]() { + f12(A[T])() +} + +// method expressions + +func (_ S1[P]) m() + +func _() { + m := S1[int].m + m(struct { f int }{42}) +} + +func _[T any] (x T) { + m := S1[T].m + m(S1[T]{x}) +} + +// type parameters in methods (generalization) + +type R0 struct{} + +func (R0) _[T any](x T) +func (R0 /* ERROR invalid receiver */ ) _[R0 any]() // scope of type parameters starts at "func" + +type R1[A, B any] struct{} + +func (_ R1[A, B]) m0(A, B) +func (_ R1[A, B]) m1[T any](A, B, T) T +func (_ R1 /* ERROR not a generic type */ [R1, _]) _() +func (_ R1[A, B]) _[A /* ERROR redeclared */ any](B) + +func _() { + var r R1[int, string] + r.m1[rune](42, "foo", 'a') + r.m1[rune](42, "foo", 1.2 /* ERROR truncated to rune */) + r.m1(42, "foo", 1.2) // using type inference + var _ float64 = r.m1(42, "foo", 1.2) +} + +type I1[A any] interface { + m1(A) +} + +var _ I1[int] = r1[int]{} + +type r1[T any] struct{} + +func (_ r1[T]) m1(T) + +type I2[A, B any] interface { + m1(A) + m2(A) B +} + +var _ I2[int, float32] = R2[int, float32]{} + +type R2[P, Q any] struct{} + +func (_ R2[X, Y]) m1(X) +func (_ R2[X, Y]) m2(X) Y + +// type assertions and type switches over generic types +// NOTE: These are currently disabled because it's unclear what the correct +// approach is, and one can always work around by assigning the variable to +// an interface first. + +// // ReadByte1 corresponds to the ReadByte example in the draft design. +// func ReadByte1[T io.Reader](r T) (byte, error) { +// if br, ok := r.(io.ByteReader); ok { +// return br.ReadByte() +// } +// var b [1]byte +// _, err := r.Read(b[:]) +// return b[0], err +// } +// +// // ReadBytes2 is like ReadByte1 but uses a type switch instead. +// func ReadByte2[T io.Reader](r T) (byte, error) { +// switch br := r.(type) { +// case io.ByteReader: +// return br.ReadByte() +// } +// var b [1]byte +// _, err := r.Read(b[:]) +// return b[0], err +// } +// +// // type assertions and type switches over generic types are strict +// type I3 interface { +// m(int) +// } +// +// type I4 interface { +// m() int // different signature from I3.m +// } +// +// func _[T I3](x I3, p T) { +// // type assertions and type switches over interfaces are not strict +// _ = x.(I4) +// switch x.(type) { +// case I4: +// } +// +// // type assertions and type switches over generic types are strict +// _ = p /* ERROR cannot have dynamic type I4 */.(I4) +// switch p.(type) { +// case I4 /* ERROR cannot have dynamic type I4 */ : +// } +// } + +// type assertions and type switches over generic types lead to errors for now + +func _[T any](x T) { + _ = x /* ERROR not an interface */ .(int) + switch x /* ERROR not an interface */ .(type) { + } + + // work-around + var t interface{} = x + _ = t.(int) + switch t.(type) { + } +} + +func _[T interface{type int}](x T) { + _ = x /* ERROR not an interface */ .(int) + switch x /* ERROR not an interface */ .(type) { + } + + // work-around + var t interface{} = x + _ = t.(int) + switch t.(type) { + } +} + +// error messages related to type bounds mention those bounds +type C[P any] interface{} + +func _[P C[P]] (x P) { + x.m /* ERROR x.m undefined */ () +} + +type I interface {} + +func _[P I] (x P) { + x.m /* ERROR interface I has no method m */ () +} + +func _[P interface{}] (x P) { + x.m /* ERROR type bound for P has no method m */ () +} + +func _[P any] (x P) { + x.m /* ERROR type bound for P has no method m */ () +} + +// automatic distinguishing between array and generic types +// NOTE: Disabled when using unified parameter list syntax. +/* +const P = 10 +type A1 [P]byte +func _(a A1) { + assert(len(a) == 10) +} + +type A2 [P]struct{ + f [P]byte +} +func _(a A2) { + assert(len(a) == 10) + assert(len(a[0].f) == 10) +} + +type A3 [P]func(x [P]A3) +func _(a A3) { + assert(len(a) == 10) +} + +type T2[P] struct{ P } +var _ T2[int] + +type T3[P] func(P) +var _ T3[int] +*/ \ No newline at end of file diff --git a/src/cmd/compile/internal/syntax/testdata/interface.go2 b/src/cmd/compile/internal/syntax/testdata/interface.go2 new file mode 100644 index 0000000000000000000000000000000000000000..a817327a43f4c289e55b541880974981c1b54278 --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/interface.go2 @@ -0,0 +1,36 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains test cases for interfaces containing +// constraint elements. +// +// For now, we accept both ordinary type lists and the +// more complex constraint elements. + +package p + +type _ interface { + m() + type int + type int, string + E +} + +type _ interface { + m() + ~int + int | string + int | ~string + ~int | ~string +} + + +type _ interface { + m() + ~int + T[int, string] | string + int | ~T[string, struct{}] + ~int | ~string + type bool, int, float64 +} diff --git a/src/cmd/compile/internal/syntax/testdata/issue43674.src b/src/cmd/compile/internal/syntax/testdata/issue43674.src new file mode 100644 index 0000000000000000000000000000000000000000..51c692ae69fd251a676ee6f0d20cbf424881e26f --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/issue43674.src @@ -0,0 +1,13 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _(... /* ERROR [.][.][.] is missing type */ ) +func _(... /* ERROR [.][.][.] is missing type */ , int) + +func _(a, b ... /* ERROR [.][.][.] is missing type */ ) +func _(a, b ... /* ERROR [.][.][.] is missing type */ , x int) + +func _()(... /* ERROR [.][.][.] is missing type */ ) diff --git a/src/cmd/compile/internal/syntax/testdata/tparams.go2 b/src/cmd/compile/internal/syntax/testdata/tparams.go2 new file mode 100644 index 0000000000000000000000000000000000000000..42031c32774f9419625b21b3ea14d005a2cf5449 --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/tparams.go2 @@ -0,0 +1,22 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type t[ /* ERROR type parameters must be named */ a, b] struct{} +type t[a t, b t, /* ERROR type parameters must be named */ c] struct{} +type t struct { + t [n]byte + t[a] + t[a, b] +} +type t interface { + t[a] + m /* ERROR method cannot have type parameters */ [_ _, /* ERROR mixed */ _]() + t[a, b] +} + +func f[ /* ERROR empty type parameter list */ ]() +func f[ /* ERROR type parameters must be named */ a, b]() +func f[a t, b t, /* ERROR type parameters must be named */ c]() diff --git a/src/cmd/compile/internal/syntax/testing.go b/src/cmd/compile/internal/syntax/testing.go new file mode 100644 index 0000000000000000000000000000000000000000..6a97dc0c2a64a52c30291fddbb583f556ab0c473 --- /dev/null +++ b/src/cmd/compile/internal/syntax/testing.go @@ -0,0 +1,72 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements testing support. + +package syntax + +import ( + "io" + "regexp" + "strings" +) + +// CommentsDo parses the given source and calls the provided handler for each +// comment or error. If the text provided to handler starts with a '/' it is +// the comment text; otherwise it is the error message. +func CommentsDo(src io.Reader, handler func(line, col uint, text string)) { + var s scanner + s.init(src, handler, comments) + for s.tok != _EOF { + s.next() + } +} + +// ERROR comments must start with text `ERROR "msg"` or `ERROR msg`. +// Space around "msg" or msg is ignored. +var errRx = regexp.MustCompile(`^ *ERROR *"?([^"]*)"?`) + +// ErrorMap collects all comments with comment text of the form +// `ERROR "msg"` or `ERROR msg` from the given src and returns them +// as []Error lists in a map indexed by line number. The position +// for each Error is the position of the token immediately preceding +// the comment, the Error message is the message msg extracted from +// the comment, with all errors that are on the same line collected +// in a slice, in source order. If there is no preceding token (the +// `ERROR` comment appears in the beginning of the file), then the +// recorded position is unknown (line, col = 0, 0). If there are no +// ERROR comments, the result is nil. +func ErrorMap(src io.Reader) (errmap map[uint][]Error) { + // position of previous token + var base *PosBase + var prev struct{ line, col uint } + + var s scanner + s.init(src, func(_, _ uint, text string) { + if text[0] != '/' { + return // error, ignore + } + if text[1] == '*' { + text = text[:len(text)-2] // strip trailing */ + } + if s := errRx.FindStringSubmatch(text[2:]); len(s) == 2 { + pos := MakePos(base, prev.line, prev.col) + err := Error{pos, strings.TrimSpace(s[1])} + if errmap == nil { + errmap = make(map[uint][]Error) + } + errmap[prev.line] = append(errmap[prev.line], err) + } + }, comments) + + for s.tok != _EOF { + s.next() + if s.tok == _Semi && s.lit != "semicolon" { + continue // ignore automatically inserted semicolons + } + prev.line, prev.col = s.line, s.col + } + + return +} diff --git a/src/cmd/compile/internal/syntax/testing_test.go b/src/cmd/compile/internal/syntax/testing_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d34e5eafafd241ae604c3889495cceb6f8d35204 --- /dev/null +++ b/src/cmd/compile/internal/syntax/testing_test.go @@ -0,0 +1,45 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package syntax + +import ( + "fmt" + "strings" + "testing" +) + +func TestErrorMap(t *testing.T) { + const src = `/* ERROR 0:0 */ /* ERROR "0:0" */ // ERROR 0:0 +// ERROR "0:0" +x /* ERROR 3:1 */ // ignore automatically inserted semicolon here +/* ERROR 3:1 */ // position of x on previous line + x /* ERROR 5:4 */ ; // do not ignore this semicolon +/* ERROR 5:22 */ // position of ; on previous line + package /* ERROR 7:2 */ // indented with tab + import /* ERROR 8:9 */ // indented with blanks +` + m := ErrorMap(strings.NewReader(src)) + got := 0 // number of errors found + for line, errlist := range m { + for _, err := range errlist { + if err.Pos.Line() != line { + t.Errorf("%v: got map line %d; want %d", err, err.Pos.Line(), line) + continue + } + // err.Pos.Line() == line + msg := fmt.Sprintf("%d:%d", line, err.Pos.Col()) + if err.Msg != msg { + t.Errorf("%v: got msg %q; want %q", err, err.Msg, msg) + continue + } + } + got += len(errlist) + } + + want := strings.Count(src, "ERROR") + if got != want { + t.Errorf("ErrorMap got %d errors; want %d", got, want) + } +} diff --git a/src/cmd/compile/internal/syntax/token_string.go b/src/cmd/compile/internal/syntax/token_string.go index 3cf5473febfd0d495763549ee6a9e7d7f12e53c9..ef295eb24b2bc9acb4b0d44ca9893f3bcb5067c2 100644 --- a/src/cmd/compile/internal/syntax/token_string.go +++ b/src/cmd/compile/internal/syntax/token_string.go @@ -1,9 +1,62 @@ -// Code generated by "stringer -type token -linecomment"; DO NOT EDIT. +// Code generated by "stringer -type token -linecomment tokens.go"; DO NOT EDIT. package syntax import "strconv" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[_EOF-1] + _ = x[_Name-2] + _ = x[_Literal-3] + _ = x[_Operator-4] + _ = x[_AssignOp-5] + _ = x[_IncOp-6] + _ = x[_Assign-7] + _ = x[_Define-8] + _ = x[_Arrow-9] + _ = x[_Star-10] + _ = x[_Lparen-11] + _ = x[_Lbrack-12] + _ = x[_Lbrace-13] + _ = x[_Rparen-14] + _ = x[_Rbrack-15] + _ = x[_Rbrace-16] + _ = x[_Comma-17] + _ = x[_Semi-18] + _ = x[_Colon-19] + _ = x[_Dot-20] + _ = x[_DotDotDot-21] + _ = x[_Break-22] + _ = x[_Case-23] + _ = x[_Chan-24] + _ = x[_Const-25] + _ = x[_Continue-26] + _ = x[_Default-27] + _ = x[_Defer-28] + _ = x[_Else-29] + _ = x[_Fallthrough-30] + _ = x[_For-31] + _ = x[_Func-32] + _ = x[_Go-33] + _ = x[_Goto-34] + _ = x[_If-35] + _ = x[_Import-36] + _ = x[_Interface-37] + _ = x[_Map-38] + _ = x[_Package-39] + _ = x[_Range-40] + _ = x[_Return-41] + _ = x[_Select-42] + _ = x[_Struct-43] + _ = x[_Switch-44] + _ = x[_Type-45] + _ = x[_Var-46] + _ = x[tokenCount-47] +} + const _token_name = "EOFnameliteralopop=opop=:=<-*([{)]},;:....breakcasechanconstcontinuedefaultdeferelsefallthroughforfuncgogotoifimportinterfacemappackagerangereturnselectstructswitchtypevar" var _token_index = [...]uint8{0, 3, 7, 14, 16, 19, 23, 24, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 42, 47, 51, 55, 60, 68, 75, 80, 84, 95, 98, 102, 104, 108, 110, 116, 125, 128, 135, 140, 146, 152, 158, 164, 168, 171, 171} diff --git a/src/cmd/compile/internal/syntax/tokens.go b/src/cmd/compile/internal/syntax/tokens.go index 3b97cb66f24b976f8e6b99bbc64cc45c982f55b2..60eae36ec924919266557be0c5156aba6dcc5204 100644 --- a/src/cmd/compile/internal/syntax/tokens.go +++ b/src/cmd/compile/internal/syntax/tokens.go @@ -6,7 +6,7 @@ package syntax type token uint -//go:generate stringer -type token -linecomment +//go:generate stringer -type token -linecomment tokens.go const ( _ token = iota @@ -105,15 +105,16 @@ const ( type Operator uint -//go:generate stringer -type Operator -linecomment +//go:generate stringer -type Operator -linecomment tokens.go const ( _ Operator = iota // Def is the : in := - Def // : - Not // ! - Recv // <- + Def // : + Not // ! + Recv // <- + Tilde // ~ // precOrOr OrOr // || diff --git a/src/cmd/compile/internal/syntax/walk.go b/src/cmd/compile/internal/syntax/walk.go new file mode 100644 index 0000000000000000000000000000000000000000..c26e97a0d8f88771986be07a4a300ff98b993f4e --- /dev/null +++ b/src/cmd/compile/internal/syntax/walk.go @@ -0,0 +1,318 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements syntax tree walking. + +package syntax + +import "fmt" + +// Walk traverses a syntax in pre-order: It starts by calling f(root); +// root must not be nil. If f returns false (== "continue"), Walk calls +// f recursively for each of the non-nil children of that node; if f +// returns true (== "stop"), Walk does not traverse the respective node's +// children. +// Some nodes may be shared among multiple parent nodes (e.g., types in +// field lists such as type T in "a, b, c T"). Such shared nodes are +// walked multiple times. +// TODO(gri) Revisit this design. It may make sense to walk those nodes +// only once. A place where this matters is types2.TestResolveIdents. +func Walk(root Node, f func(Node) bool) { + w := walker{f} + w.node(root) +} + +type walker struct { + f func(Node) bool +} + +func (w *walker) node(n Node) { + if n == nil { + panic("invalid syntax tree: nil node") + } + + if w.f(n) { + return + } + + switch n := n.(type) { + // packages + case *File: + w.node(n.PkgName) + w.declList(n.DeclList) + + // declarations + case *ImportDecl: + if n.LocalPkgName != nil { + w.node(n.LocalPkgName) + } + w.node(n.Path) + + case *ConstDecl: + w.nameList(n.NameList) + if n.Type != nil { + w.node(n.Type) + } + if n.Values != nil { + w.node(n.Values) + } + + case *TypeDecl: + w.node(n.Name) + w.fieldList(n.TParamList) + w.node(n.Type) + + case *VarDecl: + w.nameList(n.NameList) + if n.Type != nil { + w.node(n.Type) + } + if n.Values != nil { + w.node(n.Values) + } + + case *FuncDecl: + if n.Recv != nil { + w.node(n.Recv) + } + w.node(n.Name) + w.fieldList(n.TParamList) + w.node(n.Type) + if n.Body != nil { + w.node(n.Body) + } + + // expressions + case *BadExpr: // nothing to do + case *Name: // nothing to do + case *BasicLit: // nothing to do + + case *CompositeLit: + if n.Type != nil { + w.node(n.Type) + } + w.exprList(n.ElemList) + + case *KeyValueExpr: + w.node(n.Key) + w.node(n.Value) + + case *FuncLit: + w.node(n.Type) + w.node(n.Body) + + case *ParenExpr: + w.node(n.X) + + case *SelectorExpr: + w.node(n.X) + w.node(n.Sel) + + case *IndexExpr: + w.node(n.X) + w.node(n.Index) + + case *SliceExpr: + w.node(n.X) + for _, x := range n.Index { + if x != nil { + w.node(x) + } + } + + case *AssertExpr: + w.node(n.X) + w.node(n.Type) + + case *TypeSwitchGuard: + if n.Lhs != nil { + w.node(n.Lhs) + } + w.node(n.X) + + case *Operation: + w.node(n.X) + if n.Y != nil { + w.node(n.Y) + } + + case *CallExpr: + w.node(n.Fun) + w.exprList(n.ArgList) + + case *ListExpr: + w.exprList(n.ElemList) + + // types + case *ArrayType: + if n.Len != nil { + w.node(n.Len) + } + w.node(n.Elem) + + case *SliceType: + w.node(n.Elem) + + case *DotsType: + w.node(n.Elem) + + case *StructType: + w.fieldList(n.FieldList) + for _, t := range n.TagList { + if t != nil { + w.node(t) + } + } + + case *Field: + if n.Name != nil { + w.node(n.Name) + } + w.node(n.Type) + + case *InterfaceType: + w.fieldList(n.MethodList) + + case *FuncType: + w.fieldList(n.ParamList) + w.fieldList(n.ResultList) + + case *MapType: + w.node(n.Key) + w.node(n.Value) + + case *ChanType: + w.node(n.Elem) + + // statements + case *EmptyStmt: // nothing to do + + case *LabeledStmt: + w.node(n.Label) + w.node(n.Stmt) + + case *BlockStmt: + w.stmtList(n.List) + + case *ExprStmt: + w.node(n.X) + + case *SendStmt: + w.node(n.Chan) + w.node(n.Value) + + case *DeclStmt: + w.declList(n.DeclList) + + case *AssignStmt: + w.node(n.Lhs) + if n.Rhs != nil { + w.node(n.Rhs) + } + + case *BranchStmt: + if n.Label != nil { + w.node(n.Label) + } + // Target points to nodes elsewhere in the syntax tree + + case *CallStmt: + w.node(n.Call) + + case *ReturnStmt: + if n.Results != nil { + w.node(n.Results) + } + + case *IfStmt: + if n.Init != nil { + w.node(n.Init) + } + w.node(n.Cond) + w.node(n.Then) + if n.Else != nil { + w.node(n.Else) + } + + case *ForStmt: + if n.Init != nil { + w.node(n.Init) + } + if n.Cond != nil { + w.node(n.Cond) + } + if n.Post != nil { + w.node(n.Post) + } + w.node(n.Body) + + case *SwitchStmt: + if n.Init != nil { + w.node(n.Init) + } + if n.Tag != nil { + w.node(n.Tag) + } + for _, s := range n.Body { + w.node(s) + } + + case *SelectStmt: + for _, s := range n.Body { + w.node(s) + } + + // helper nodes + case *RangeClause: + if n.Lhs != nil { + w.node(n.Lhs) + } + w.node(n.X) + + case *CaseClause: + if n.Cases != nil { + w.node(n.Cases) + } + w.stmtList(n.Body) + + case *CommClause: + if n.Comm != nil { + w.node(n.Comm) + } + w.stmtList(n.Body) + + default: + panic(fmt.Sprintf("internal error: unknown node type %T", n)) + } +} + +func (w *walker) declList(list []Decl) { + for _, n := range list { + w.node(n) + } +} + +func (w *walker) exprList(list []Expr) { + for _, n := range list { + w.node(n) + } +} + +func (w *walker) stmtList(list []Stmt) { + for _, n := range list { + w.node(n) + } +} + +func (w *walker) nameList(list []*Name) { + for _, n := range list { + w.node(n) + } +} + +func (w *walker) fieldList(list []*Field) { + for _, n := range list { + w.node(n) + } +} diff --git a/src/cmd/compile/internal/test/abiutils_test.go b/src/cmd/compile/internal/test/abiutils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b752c486126dc37acfb08bef3ee8205f5ab032e2 --- /dev/null +++ b/src/cmd/compile/internal/test/abiutils_test.go @@ -0,0 +1,397 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package test + +import ( + "bufio" + "cmd/compile/internal/abi" + "cmd/compile/internal/base" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/obj/x86" + "cmd/internal/src" + "fmt" + "os" + "testing" +) + +// AMD64 registers available: +// - integer: RAX, RBX, RCX, RDI, RSI, R8, R9, r10, R11 +// - floating point: X0 - X14 +var configAMD64 = abi.NewABIConfig(9, 15, 0) + +func TestMain(m *testing.M) { + ssagen.Arch.LinkArch = &x86.Linkamd64 + ssagen.Arch.REGSP = x86.REGSP + ssagen.Arch.MAXWIDTH = 1 << 50 + types.MaxWidth = ssagen.Arch.MAXWIDTH + base.Ctxt = obj.Linknew(ssagen.Arch.LinkArch) + base.Ctxt.DiagFunc = base.Errorf + base.Ctxt.DiagFlush = base.FlushErrors + base.Ctxt.Bso = bufio.NewWriter(os.Stdout) + types.PtrSize = ssagen.Arch.LinkArch.PtrSize + types.RegSize = ssagen.Arch.LinkArch.RegSize + typecheck.InitUniverse() + os.Exit(m.Run()) +} + +func TestABIUtilsBasic1(t *testing.T) { + + // func(x int32) int32 + i32 := types.Types[types.TINT32] + ft := mkFuncType(nil, []*types.Type{i32}, []*types.Type{i32}) + + // expected results + exp := makeExpectedDump(` + IN 0: R{ I0 } spilloffset: 0 typ: int32 + OUT 0: R{ I0 } spilloffset: -1 typ: int32 + offsetToSpillArea: 0 spillAreaSize: 8 +`) + + abitest(t, ft, exp) +} + +func TestABIUtilsBasic2(t *testing.T) { + // func(p1 int8, p2 int16, p3 int32, p4 int64, + // p5 float32, p6 float32, p7 float64, p8 float64, + // p9 int8, p10 int16, p11 int32, p12 int64, + // p13 float32, p14 float32, p15 float64, p16 float64, + // p17 complex128, p18 complex128, p19 complex12, p20 complex128, + // p21 complex64, p22 int8, p23 in16, p24 int32, p25 int64, + // p26 int8, p27 in16, p28 int32, p29 int64) + // (r1 int32, r2 float64, r3 float64) { + i8 := types.Types[types.TINT8] + i16 := types.Types[types.TINT16] + i32 := types.Types[types.TINT32] + i64 := types.Types[types.TINT64] + f32 := types.Types[types.TFLOAT32] + f64 := types.Types[types.TFLOAT64] + c64 := types.Types[types.TCOMPLEX64] + c128 := types.Types[types.TCOMPLEX128] + ft := mkFuncType(nil, + []*types.Type{ + i8, i16, i32, i64, + f32, f32, f64, f64, + i8, i16, i32, i64, + f32, f32, f64, f64, + c128, c128, c128, c128, c64, + i8, i16, i32, i64, + i8, i16, i32, i64}, + []*types.Type{i32, f64, f64}) + exp := makeExpectedDump(` + IN 0: R{ I0 } spilloffset: 0 typ: int8 + IN 1: R{ I1 } spilloffset: 2 typ: int16 + IN 2: R{ I2 } spilloffset: 4 typ: int32 + IN 3: R{ I3 } spilloffset: 8 typ: int64 + IN 4: R{ F0 } spilloffset: 16 typ: float32 + IN 5: R{ F1 } spilloffset: 20 typ: float32 + IN 6: R{ F2 } spilloffset: 24 typ: float64 + IN 7: R{ F3 } spilloffset: 32 typ: float64 + IN 8: R{ I4 } spilloffset: 40 typ: int8 + IN 9: R{ I5 } spilloffset: 42 typ: int16 + IN 10: R{ I6 } spilloffset: 44 typ: int32 + IN 11: R{ I7 } spilloffset: 48 typ: int64 + IN 12: R{ F4 } spilloffset: 56 typ: float32 + IN 13: R{ F5 } spilloffset: 60 typ: float32 + IN 14: R{ F6 } spilloffset: 64 typ: float64 + IN 15: R{ F7 } spilloffset: 72 typ: float64 + IN 16: R{ F8 F9 } spilloffset: 80 typ: complex128 + IN 17: R{ F10 F11 } spilloffset: 96 typ: complex128 + IN 18: R{ F12 F13 } spilloffset: 112 typ: complex128 + IN 19: R{ } offset: 0 typ: complex128 + IN 20: R{ } offset: 16 typ: complex64 + IN 21: R{ I8 } spilloffset: 128 typ: int8 + IN 22: R{ } offset: 24 typ: int16 + IN 23: R{ } offset: 28 typ: int32 + IN 24: R{ } offset: 32 typ: int64 + IN 25: R{ } offset: 40 typ: int8 + IN 26: R{ } offset: 42 typ: int16 + IN 27: R{ } offset: 44 typ: int32 + IN 28: R{ } offset: 48 typ: int64 + OUT 0: R{ I0 } spilloffset: -1 typ: int32 + OUT 1: R{ F0 } spilloffset: -1 typ: float64 + OUT 2: R{ F1 } spilloffset: -1 typ: float64 + offsetToSpillArea: 56 spillAreaSize: 136 +`) + + abitest(t, ft, exp) +} + +func TestABIUtilsArrays(t *testing.T) { + // func(p1 [1]int32, p2 [0]int32, p3 [1][1]int32, p4 [2]int32) + // (r1 [2]int32, r2 [1]int32, r3 [0]int32, r4 [1][1]int32) { + i32 := types.Types[types.TINT32] + ae := types.NewArray(i32, 0) + a1 := types.NewArray(i32, 1) + a2 := types.NewArray(i32, 2) + aa1 := types.NewArray(a1, 1) + ft := mkFuncType(nil, []*types.Type{a1, ae, aa1, a2}, + []*types.Type{a2, a1, ae, aa1}) + + exp := makeExpectedDump(` + IN 0: R{ I0 } spilloffset: 0 typ: [1]int32 + IN 1: R{ } offset: 0 typ: [0]int32 + IN 2: R{ I1 } spilloffset: 4 typ: [1][1]int32 + IN 3: R{ } offset: 0 typ: [2]int32 + OUT 0: R{ } offset: 8 typ: [2]int32 + OUT 1: R{ I0 } spilloffset: -1 typ: [1]int32 + OUT 2: R{ } offset: 16 typ: [0]int32 + OUT 3: R{ I1 } spilloffset: -1 typ: [1][1]int32 + offsetToSpillArea: 16 spillAreaSize: 8 +`) + + abitest(t, ft, exp) +} + +func TestABIUtilsStruct1(t *testing.T) { + // type s struct { f1 int8; f2 int8; f3 struct {}; f4 int8; f5 int16) } + // func(p1 int6, p2 s, p3 int64) + // (r1 s, r2 int8, r3 int32) { + i8 := types.Types[types.TINT8] + i16 := types.Types[types.TINT16] + i32 := types.Types[types.TINT32] + i64 := types.Types[types.TINT64] + s := mkstruct([]*types.Type{i8, i8, mkstruct([]*types.Type{}), i8, i16}) + ft := mkFuncType(nil, []*types.Type{i8, s, i64}, + []*types.Type{s, i8, i32}) + + exp := makeExpectedDump(` + IN 0: R{ I0 } spilloffset: 0 typ: int8 + IN 1: R{ I1 I2 I3 I4 } spilloffset: 2 typ: struct { int8; int8; struct {}; int8; int16 } + IN 2: R{ I5 } spilloffset: 8 typ: int64 + OUT 0: R{ I0 I1 I2 I3 } spilloffset: -1 typ: struct { int8; int8; struct {}; int8; int16 } + OUT 1: R{ I4 } spilloffset: -1 typ: int8 + OUT 2: R{ I5 } spilloffset: -1 typ: int32 + offsetToSpillArea: 0 spillAreaSize: 16 +`) + + abitest(t, ft, exp) +} + +func TestABIUtilsStruct2(t *testing.T) { + // type s struct { f1 int64; f2 struct { } } + // type fs struct { f1 float64; f2 s; f3 struct { } } + // func(p1 s, p2 s, p3 fs) + // (r1 fs, r2 fs) + f64 := types.Types[types.TFLOAT64] + i64 := types.Types[types.TINT64] + s := mkstruct([]*types.Type{i64, mkstruct([]*types.Type{})}) + fs := mkstruct([]*types.Type{f64, s, mkstruct([]*types.Type{})}) + ft := mkFuncType(nil, []*types.Type{s, s, fs}, + []*types.Type{fs, fs}) + + exp := makeExpectedDump(` + IN 0: R{ I0 } spilloffset: 0 typ: struct { int64; struct {} } + IN 1: R{ I1 } spilloffset: 16 typ: struct { int64; struct {} } + IN 2: R{ F0 I2 } spilloffset: 32 typ: struct { float64; struct { int64; struct {} }; struct {} } + OUT 0: R{ F0 I0 } spilloffset: -1 typ: struct { float64; struct { int64; struct {} }; struct {} } + OUT 1: R{ F1 I1 } spilloffset: -1 typ: struct { float64; struct { int64; struct {} }; struct {} } + offsetToSpillArea: 0 spillAreaSize: 64 +`) + + abitest(t, ft, exp) +} + +// TestABIUtilsEmptyFieldAtEndOfStruct is testing to make sure +// the abi code is doing the right thing for struct types that have +// a trailing zero-sized field (where the we need to add padding). +func TestABIUtilsEmptyFieldAtEndOfStruct(t *testing.T) { + // type s struct { f1 [2]int64; f2 struct { } } + // type s2 struct { f1 [3]int16; f2 struct { } } + // type fs struct { f1 float64; f s; f3 struct { } } + // func(p1 s, p2 s, p3 fs) (r1 fs, r2 fs) + f64 := types.Types[types.TFLOAT64] + i64 := types.Types[types.TINT64] + i16 := types.Types[types.TINT16] + tb := types.Types[types.TBOOL] + ab2 := types.NewArray(tb, 2) + a2 := types.NewArray(i64, 2) + a3 := types.NewArray(i16, 3) + s := mkstruct([]*types.Type{a2, mkstruct([]*types.Type{})}) + s2 := mkstruct([]*types.Type{a3, mkstruct([]*types.Type{})}) + fs := mkstruct([]*types.Type{f64, s, mkstruct([]*types.Type{})}) + ft := mkFuncType(nil, []*types.Type{s, ab2, s2, fs, fs}, + []*types.Type{fs, ab2, fs}) + + exp := makeExpectedDump(` + IN 0: R{ } offset: 0 typ: struct { [2]int64; struct {} } + IN 1: R{ } offset: 24 typ: [2]bool + IN 2: R{ } offset: 26 typ: struct { [3]int16; struct {} } + IN 3: R{ } offset: 40 typ: struct { float64; struct { [2]int64; struct {} }; struct {} } + IN 4: R{ } offset: 80 typ: struct { float64; struct { [2]int64; struct {} }; struct {} } + OUT 0: R{ } offset: 120 typ: struct { float64; struct { [2]int64; struct {} }; struct {} } + OUT 1: R{ } offset: 160 typ: [2]bool + OUT 2: R{ } offset: 168 typ: struct { float64; struct { [2]int64; struct {} }; struct {} } + offsetToSpillArea: 208 spillAreaSize: 0 +`) + + abitest(t, ft, exp) + + // Check to make sure that NumParamRegs yields 2 and not 3 + // for struct "s" (e.g. that it handles the padding properly). + nps := configAMD64.NumParamRegs(s) + if nps != 2 { + t.Errorf("NumParams(%v) returned %d expected %d\n", + s, nps, 2) + } +} + +func TestABIUtilsSliceString(t *testing.T) { + // func(p1 []int32, p2 int8, p3 []int32, p4 int8, p5 string, + // p6 int64, p6 []intr32) (r1 string, r2 int64, r3 string, r4 []int32) + i32 := types.Types[types.TINT32] + sli32 := types.NewSlice(i32) + str := types.New(types.TSTRING) + i8 := types.Types[types.TINT8] + i64 := types.Types[types.TINT64] + ft := mkFuncType(nil, []*types.Type{sli32, i8, sli32, i8, str, i8, i64, sli32}, + []*types.Type{str, i64, str, sli32}) + + exp := makeExpectedDump(` + IN 0: R{ I0 I1 I2 } spilloffset: 0 typ: []int32 + IN 1: R{ I3 } spilloffset: 24 typ: int8 + IN 2: R{ I4 I5 I6 } spilloffset: 32 typ: []int32 + IN 3: R{ I7 } spilloffset: 56 typ: int8 + IN 4: R{ } offset: 0 typ: string + IN 5: R{ I8 } spilloffset: 57 typ: int8 + IN 6: R{ } offset: 16 typ: int64 + IN 7: R{ } offset: 24 typ: []int32 + OUT 0: R{ I0 I1 } spilloffset: -1 typ: string + OUT 1: R{ I2 } spilloffset: -1 typ: int64 + OUT 2: R{ I3 I4 } spilloffset: -1 typ: string + OUT 3: R{ I5 I6 I7 } spilloffset: -1 typ: []int32 + offsetToSpillArea: 48 spillAreaSize: 64 +`) + + abitest(t, ft, exp) +} + +func TestABIUtilsMethod(t *testing.T) { + // type s1 struct { f1 int16; f2 int16; f3 int16 } + // func(p1 *s1, p2 [7]*s1, p3 float64, p4 int16, p5 int16, p6 int16) + // (r1 [7]*s1, r2 float64, r3 int64) + i16 := types.Types[types.TINT16] + i64 := types.Types[types.TINT64] + f64 := types.Types[types.TFLOAT64] + s1 := mkstruct([]*types.Type{i16, i16, i16}) + ps1 := types.NewPtr(s1) + a7 := types.NewArray(ps1, 7) + ft := mkFuncType(s1, []*types.Type{ps1, a7, f64, i16, i16, i16}, + []*types.Type{a7, f64, i64}) + + exp := makeExpectedDump(` + IN 0: R{ I0 I1 I2 } spilloffset: 0 typ: struct { int16; int16; int16 } + IN 1: R{ I3 } spilloffset: 8 typ: *struct { int16; int16; int16 } + IN 2: R{ } offset: 0 typ: [7]*struct { int16; int16; int16 } + IN 3: R{ F0 } spilloffset: 16 typ: float64 + IN 4: R{ I4 } spilloffset: 24 typ: int16 + IN 5: R{ I5 } spilloffset: 26 typ: int16 + IN 6: R{ I6 } spilloffset: 28 typ: int16 + OUT 0: R{ } offset: 56 typ: [7]*struct { int16; int16; int16 } + OUT 1: R{ F0 } spilloffset: -1 typ: float64 + OUT 2: R{ I0 } spilloffset: -1 typ: int64 + offsetToSpillArea: 112 spillAreaSize: 32 +`) + + abitest(t, ft, exp) +} + +func TestABIUtilsInterfaces(t *testing.T) { + // type s1 { f1 int16; f2 int16; f3 bool) + // type nei interface { ...() string } + // func(p1 s1, p2 interface{}, p3 interface{}, p4 nei, + // p5 *interface{}, p6 nei, p7 int64) + // (r1 interface{}, r2 nei, r3 bool) + ei := types.Types[types.TINTER] // interface{} + pei := types.NewPtr(ei) // *interface{} + fldt := mkFuncType(types.FakeRecvType(), []*types.Type{}, + []*types.Type{types.UntypedString}) + field := types.NewField(src.NoXPos, nil, fldt) + nei := types.NewInterface(types.LocalPkg, []*types.Field{field}) + i16 := types.Types[types.TINT16] + tb := types.Types[types.TBOOL] + s1 := mkstruct([]*types.Type{i16, i16, tb}) + ft := mkFuncType(nil, []*types.Type{s1, ei, ei, nei, pei, nei, i16}, + []*types.Type{ei, nei, pei}) + + exp := makeExpectedDump(` + IN 0: R{ I0 I1 I2 } spilloffset: 0 typ: struct { int16; int16; bool } + IN 1: R{ I3 I4 } spilloffset: 8 typ: interface {} + IN 2: R{ I5 I6 } spilloffset: 24 typ: interface {} + IN 3: R{ I7 I8 } spilloffset: 40 typ: interface { () untyped string } + IN 4: R{ } offset: 0 typ: *interface {} + IN 5: R{ } offset: 8 typ: interface { () untyped string } + IN 6: R{ } offset: 24 typ: int16 + OUT 0: R{ I0 I1 } spilloffset: -1 typ: interface {} + OUT 1: R{ I2 I3 } spilloffset: -1 typ: interface { () untyped string } + OUT 2: R{ I4 } spilloffset: -1 typ: *interface {} + offsetToSpillArea: 32 spillAreaSize: 56 +`) + + abitest(t, ft, exp) +} + +func TestABINumParamRegs(t *testing.T) { + i8 := types.Types[types.TINT8] + i16 := types.Types[types.TINT16] + i32 := types.Types[types.TINT32] + i64 := types.Types[types.TINT64] + f32 := types.Types[types.TFLOAT32] + f64 := types.Types[types.TFLOAT64] + c64 := types.Types[types.TCOMPLEX64] + c128 := types.Types[types.TCOMPLEX128] + + s := mkstruct([]*types.Type{i8, i8, mkstruct([]*types.Type{}), i8, i16}) + a := types.NewArray(s, 3) + + nrtest(t, i8, 1) + nrtest(t, i16, 1) + nrtest(t, i32, 1) + nrtest(t, i64, 1) + nrtest(t, f32, 1) + nrtest(t, f64, 1) + nrtest(t, c64, 2) + nrtest(t, c128, 2) + nrtest(t, s, 4) + nrtest(t, a, 12) + +} + +func TestABIUtilsComputePadding(t *testing.T) { + // type s1 { f1 int8; f2 int16; f3 struct{}; f4 int32; f5 int64 } + i8 := types.Types[types.TINT8] + i16 := types.Types[types.TINT16] + i32 := types.Types[types.TINT32] + i64 := types.Types[types.TINT64] + emptys := mkstruct([]*types.Type{}) + s1 := mkstruct([]*types.Type{i8, i16, emptys, i32, i64}) + // func (p1 int32, p2 s1, p3 emptys, p4 [1]int32) + a1 := types.NewArray(i32, 1) + ft := mkFuncType(nil, []*types.Type{i32, s1, emptys, a1}, []*types.Type{}) + + // Run abitest() just to document what we're expected to see. + exp := makeExpectedDump(` + IN 0: R{ I0 } spilloffset: 0 typ: int32 + IN 1: R{ I1 I2 I3 I4 } spilloffset: 8 typ: struct { int8; int16; struct {}; int32; int64 } + IN 2: R{ } offset: 0 typ: struct {} + IN 3: R{ I5 } spilloffset: 24 typ: [1]int32 + offsetToSpillArea: 0 spillAreaSize: 32 +`) + abitest(t, ft, exp) + + // Analyze with full set of registers, then call ComputePadding + // on the second param, verifying the results. + regRes := configAMD64.ABIAnalyze(ft, false) + padding := make([]uint64, 32) + parm := regRes.InParams()[1] + padding = parm.ComputePadding(padding) + want := "[1 1 1 0]" + got := fmt.Sprintf("%+v", padding) + if got != want { + t.Errorf("padding mismatch: wanted %q got %q\n", got, want) + } +} diff --git a/src/cmd/compile/internal/test/abiutilsaux_test.go b/src/cmd/compile/internal/test/abiutilsaux_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b9456331332b3e33ec5dd1cf0ed10b67fee22673 --- /dev/null +++ b/src/cmd/compile/internal/test/abiutilsaux_test.go @@ -0,0 +1,132 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package test + +// This file contains utility routines and harness infrastructure used +// by the ABI tests in "abiutils_test.go". + +import ( + "cmd/compile/internal/abi" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" + "fmt" + "strings" + "testing" + "text/scanner" +) + +func mkParamResultField(t *types.Type, s *types.Sym, which ir.Class) *types.Field { + field := types.NewField(src.NoXPos, s, t) + n := typecheck.NewName(s) + n.Class = which + field.Nname = n + n.SetType(t) + return field +} + +// mkstruct is a helper routine to create a struct type with fields +// of the types specified in 'fieldtypes'. +func mkstruct(fieldtypes []*types.Type) *types.Type { + fields := make([]*types.Field, len(fieldtypes)) + for k, t := range fieldtypes { + if t == nil { + panic("bad -- field has no type") + } + f := types.NewField(src.NoXPos, nil, t) + fields[k] = f + } + s := types.NewStruct(types.LocalPkg, fields) + return s +} + +func mkFuncType(rcvr *types.Type, ins []*types.Type, outs []*types.Type) *types.Type { + q := typecheck.Lookup("?") + inf := []*types.Field{} + for _, it := range ins { + inf = append(inf, mkParamResultField(it, q, ir.PPARAM)) + } + outf := []*types.Field{} + for _, ot := range outs { + outf = append(outf, mkParamResultField(ot, q, ir.PPARAMOUT)) + } + var rf *types.Field + if rcvr != nil { + rf = mkParamResultField(rcvr, q, ir.PPARAM) + } + return types.NewSignature(types.LocalPkg, rf, nil, inf, outf) +} + +type expectedDump struct { + dump string + file string + line int +} + +func tokenize(src string) []string { + var s scanner.Scanner + s.Init(strings.NewReader(src)) + res := []string{} + for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { + res = append(res, s.TokenText()) + } + return res +} + +func verifyParamResultOffset(t *testing.T, f *types.Field, r abi.ABIParamAssignment, which string, idx int) int { + n := ir.AsNode(f.Nname).(*ir.Name) + if n.FrameOffset() != int64(r.Offset()) { + t.Errorf("%s %d: got offset %d wanted %d t=%v", + which, idx, r.Offset(), n.Offset_, f.Type) + return 1 + } + return 0 +} + +func makeExpectedDump(e string) expectedDump { + return expectedDump{dump: e} +} + +func difftokens(atoks []string, etoks []string) string { + if len(atoks) != len(etoks) { + return fmt.Sprintf("expected %d tokens got %d", + len(etoks), len(atoks)) + } + for i := 0; i < len(etoks); i++ { + if etoks[i] == atoks[i] { + continue + } + + return fmt.Sprintf("diff at token %d: expected %q got %q", + i, etoks[i], atoks[i]) + } + return "" +} + +func nrtest(t *testing.T, ft *types.Type, expected int) { + types.CalcSize(ft) + got := configAMD64.NumParamRegs(ft) + if got != expected { + t.Errorf("]\nexpected num regs = %d, got %d, type %v", expected, got, ft) + } +} + +func abitest(t *testing.T, ft *types.Type, exp expectedDump) { + + types.CalcSize(ft) + + // Analyze with full set of registers. + regRes := configAMD64.ABIAnalyze(ft, false) + regResString := strings.TrimSpace(regRes.String()) + + // Check results. + reason := difftokens(tokenize(regResString), tokenize(exp.dump)) + if reason != "" { + t.Errorf("\nexpected:\n%s\ngot:\n%s\nreason: %s", + strings.TrimSpace(exp.dump), regResString, reason) + } + +} diff --git a/src/cmd/compile/internal/test/align_test.go b/src/cmd/compile/internal/test/align_test.go new file mode 100644 index 0000000000000000000000000000000000000000..32afc92973622705d416d492c0677bc9646340fe --- /dev/null +++ b/src/cmd/compile/internal/test/align_test.go @@ -0,0 +1,96 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test to make sure that equality functions (and hash +// functions) don't do unaligned reads on architectures +// that can't do unaligned reads. See issue 46283. + +package test + +import "testing" + +type T1 struct { + x float32 + a, b, c, d int16 // memequal64 +} +type T2 struct { + x float32 + a, b, c, d int32 // memequal128 +} + +type A2 [2]byte // eq uses a 2-byte load +type A4 [4]byte // eq uses a 4-byte load +type A8 [8]byte // eq uses an 8-byte load + +//go:noinline +func cmpT1(p, q *T1) { + if *p != *q { + panic("comparison test wrong") + } +} + +//go:noinline +func cmpT2(p, q *T2) { + if *p != *q { + panic("comparison test wrong") + } +} + +//go:noinline +func cmpA2(p, q *A2) { + if *p != *q { + panic("comparison test wrong") + } +} + +//go:noinline +func cmpA4(p, q *A4) { + if *p != *q { + panic("comparison test wrong") + } +} + +//go:noinline +func cmpA8(p, q *A8) { + if *p != *q { + panic("comparison test wrong") + } +} + +func TestAlignEqual(t *testing.T) { + cmpT1(&T1{}, &T1{}) + cmpT2(&T2{}, &T2{}) + + m1 := map[T1]bool{} + m1[T1{}] = true + m1[T1{}] = false + if len(m1) != 1 { + t.Fatalf("len(m1)=%d, want 1", len(m1)) + } + m2 := map[T2]bool{} + m2[T2{}] = true + m2[T2{}] = false + if len(m2) != 1 { + t.Fatalf("len(m2)=%d, want 1", len(m2)) + } + + type X2 struct { + y byte + z A2 + } + var x2 X2 + cmpA2(&x2.z, &A2{}) + type X4 struct { + y byte + z A4 + } + var x4 X4 + cmpA4(&x4.z, &A4{}) + type X8 struct { + y byte + z A8 + } + var x8 X8 + cmpA8(&x8.z, &A8{}) +} diff --git a/src/cmd/compile/internal/gc/bench_test.go b/src/cmd/compile/internal/test/bench_test.go similarity index 50% rename from src/cmd/compile/internal/gc/bench_test.go rename to src/cmd/compile/internal/test/bench_test.go index 8c4288128f25b00bc41e67d8db6a9a43da9a5320..472460009170e2f80d37915fb7feda9194cef1b6 100644 --- a/src/cmd/compile/internal/gc/bench_test.go +++ b/src/cmd/compile/internal/test/bench_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test import "testing" @@ -62,3 +62,63 @@ func BenchmarkConstModify(b *testing.B) { } } } + +func BenchmarkBitSet(b *testing.B) { + const N = 64 * 8 + a := make([]uint64, N/64) + for i := 0; i < b.N; i++ { + for j := uint64(0); j < N; j++ { + a[j/64] |= 1 << (j % 64) + } + } +} + +func BenchmarkBitClear(b *testing.B) { + const N = 64 * 8 + a := make([]uint64, N/64) + for i := 0; i < b.N; i++ { + for j := uint64(0); j < N; j++ { + a[j/64] &^= 1 << (j % 64) + } + } +} + +func BenchmarkBitToggle(b *testing.B) { + const N = 64 * 8 + a := make([]uint64, N/64) + for i := 0; i < b.N; i++ { + for j := uint64(0); j < N; j++ { + a[j/64] ^= 1 << (j % 64) + } + } +} + +func BenchmarkBitSetConst(b *testing.B) { + const N = 64 + a := make([]uint64, N) + for i := 0; i < b.N; i++ { + for j := range a { + a[j] |= 1 << 37 + } + } +} + +func BenchmarkBitClearConst(b *testing.B) { + const N = 64 + a := make([]uint64, N) + for i := 0; i < b.N; i++ { + for j := range a { + a[j] &^= 1 << 37 + } + } +} + +func BenchmarkBitToggleConst(b *testing.B) { + const N = 64 + a := make([]uint64, N) + for i := 0; i < b.N; i++ { + for j := range a { + a[j] ^= 1 << 37 + } + } +} diff --git a/src/cmd/compile/internal/test/clobberdead_test.go b/src/cmd/compile/internal/test/clobberdead_test.go new file mode 100644 index 0000000000000000000000000000000000000000..88b7d34623a77c82cf0a1e5647c7b2afe7891a54 --- /dev/null +++ b/src/cmd/compile/internal/test/clobberdead_test.go @@ -0,0 +1,55 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package test + +import ( + "internal/testenv" + "io/ioutil" + "os/exec" + "path/filepath" + "testing" +) + +const helloSrc = ` +package main +import "fmt" +func main() { fmt.Println("hello") } +` + +func TestClobberDead(t *testing.T) { + // Test that clobberdead mode generates correct program. + runHello(t, "-clobberdead") +} + +func TestClobberDeadReg(t *testing.T) { + // Test that clobberdeadreg mode generates correct program. + runHello(t, "-clobberdeadreg") +} + +func runHello(t *testing.T, flag string) { + if testing.Short() { + // This test rebuilds the runtime with a special flag, which + // takes a while. + t.Skip("skip in short mode") + } + testenv.MustHaveGoRun(t) + t.Parallel() + + tmpdir := t.TempDir() + src := filepath.Join(tmpdir, "x.go") + err := ioutil.WriteFile(src, []byte(helloSrc), 0644) + if err != nil { + t.Fatalf("write file failed: %v", err) + } + + cmd := exec.Command(testenv.GoToolPath(t), "run", "-gcflags=all="+flag, src) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("go run failed: %v\n%s", err, out) + } + if string(out) != "hello\n" { + t.Errorf("wrong output: got %q, want %q", out, "hello\n") + } +} diff --git a/src/cmd/compile/internal/gc/constFold_test.go b/src/cmd/compile/internal/test/constFold_test.go similarity index 99% rename from src/cmd/compile/internal/gc/constFold_test.go rename to src/cmd/compile/internal/test/constFold_test.go index 59f905dad9623192fbb57dca3b3fd66bbf57f619..7159f0ed33a375960c782f69940446d4c7ae5278 100644 --- a/src/cmd/compile/internal/gc/constFold_test.go +++ b/src/cmd/compile/internal/test/constFold_test.go @@ -1,7 +1,7 @@ // run // Code generated by gen/constFoldGen.go. DO NOT EDIT. -package gc +package test import "testing" diff --git a/src/cmd/compile/internal/gc/dep_test.go b/src/cmd/compile/internal/test/dep_test.go similarity index 58% rename from src/cmd/compile/internal/gc/dep_test.go rename to src/cmd/compile/internal/test/dep_test.go index c1dac9338652bf186ac42d7d0eb6d6cdd5dff132..698a848db6c2dc12a6180d8ed4eeaaf59d0bd8b5 100644 --- a/src/cmd/compile/internal/gc/dep_test.go +++ b/src/cmd/compile/internal/test/dep_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test import ( "internal/testenv" @@ -18,8 +18,13 @@ func TestDeps(t *testing.T) { } for _, dep := range strings.Fields(strings.Trim(string(out), "[]")) { switch dep { - case "go/build", "go/token": - t.Errorf("undesired dependency on %q", dep) + case "go/build", "go/scanner": + // cmd/compile/internal/importer introduces a dependency + // on go/build and go/token; cmd/compile/internal/ uses + // go/constant which uses go/token in its API. Once we + // got rid of those dependencies, enable this check again. + // TODO(gri) fix this + // t.Errorf("undesired dependency on %q", dep) } } } diff --git a/src/cmd/compile/internal/gc/fixedbugs_test.go b/src/cmd/compile/internal/test/fixedbugs_test.go similarity index 97% rename from src/cmd/compile/internal/gc/fixedbugs_test.go rename to src/cmd/compile/internal/test/fixedbugs_test.go index 8ac4436947f78cce3b01b2566319d8a6edc01fd2..376b45edfcf00c9f0bfce4b0668d06fcdd5d43d4 100644 --- a/src/cmd/compile/internal/gc/fixedbugs_test.go +++ b/src/cmd/compile/internal/test/fixedbugs_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test import ( "internal/testenv" @@ -75,7 +75,7 @@ func TestIssue16214(t *testing.T) { cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-S", "-o", filepath.Join(dir, "out.o"), src) out, err := cmd.CombinedOutput() if err != nil { - t.Fatalf("fail to run go tool compile: %v", err) + t.Fatalf("go tool compile: %v\n%s", err, out) } if strings.Contains(string(out), "unknown line number") { diff --git a/src/cmd/compile/internal/gc/float_test.go b/src/cmd/compile/internal/test/float_test.go similarity index 99% rename from src/cmd/compile/internal/gc/float_test.go rename to src/cmd/compile/internal/test/float_test.go index c619d2570507f223d8345464ca16555259911a76..884a983bdd7a57ccb432848c072cc86e4d240ad4 100644 --- a/src/cmd/compile/internal/gc/float_test.go +++ b/src/cmd/compile/internal/test/float_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test import ( "math" diff --git a/src/cmd/compile/internal/gc/global_test.go b/src/cmd/compile/internal/test/global_test.go similarity index 95% rename from src/cmd/compile/internal/gc/global_test.go rename to src/cmd/compile/internal/test/global_test.go index edad6d042a36d824cea6e0da40c3ad8aa643697e..93de894f37e12329f55022d15780f6589b28ef17 100644 --- a/src/cmd/compile/internal/gc/global_test.go +++ b/src/cmd/compile/internal/test/global_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test import ( "bytes" @@ -50,7 +50,7 @@ func main() { cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", dst, src) out, err := cmd.CombinedOutput() if err != nil { - t.Fatalf("could not build target: %v", err) + t.Fatalf("could not build target: %v\n%s", err, out) } // Check destination to see if scanf code was included. @@ -95,7 +95,7 @@ func main() { cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags", "-S", "-o", filepath.Join(dir, "test"), src) out, err := cmd.CombinedOutput() if err != nil { - t.Fatalf("could not build target: %v", err) + t.Fatalf("could not build target: %v\n%s", err, out) } patterns := []string{ diff --git a/src/cmd/compile/internal/gc/iface_test.go b/src/cmd/compile/internal/test/iface_test.go similarity index 98% rename from src/cmd/compile/internal/gc/iface_test.go rename to src/cmd/compile/internal/test/iface_test.go index 21c6587217f0ae07b80021f6fffadfb7f4f70e56..ebc4f891c963a17de4c5d668f7248e9cbc8ae47c 100644 --- a/src/cmd/compile/internal/gc/iface_test.go +++ b/src/cmd/compile/internal/test/iface_test.go @@ -2,15 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test + +import "testing" // Test to make sure we make copies of the values we // put in interfaces. -import ( - "testing" -) - var x int func TestEfaceConv1(t *testing.T) { diff --git a/src/cmd/compile/internal/gc/inl_test.go b/src/cmd/compile/internal/test/inl_test.go similarity index 96% rename from src/cmd/compile/internal/gc/inl_test.go rename to src/cmd/compile/internal/test/inl_test.go index 02735e50fb7f0636a25eeaa01f5c698a53a74a26..6f100033cf5b8b9666c458ce82b894db12d2a914 100644 --- a/src/cmd/compile/internal/gc/inl_test.go +++ b/src/cmd/compile/internal/test/inl_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test import ( "bufio" @@ -16,7 +16,7 @@ import ( "testing" ) -// TestIntendedInlining tests that specific runtime functions are inlined. +// TestIntendedInlining tests that specific functions are inlined. // This allows refactoring for code clarity and re-use without fear that // changes to the compiler will cause silent performance regressions. func TestIntendedInlining(t *testing.T) { @@ -155,6 +155,9 @@ func TestIntendedInlining(t *testing.T) { "(*rngSource).Int63", "(*rngSource).Uint64", }, + "net": { + "(*UDPConn).ReadFromUDP", + }, } if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" { @@ -172,8 +175,8 @@ func TestIntendedInlining(t *testing.T) { want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32") } if bits.UintSize == 64 { - // rotl_31 is only defined on 64-bit architectures - want["runtime"] = append(want["runtime"], "rotl_31") + // mix is only defined on 64-bit architectures + want["runtime"] = append(want["runtime"], "mix") } switch runtime.GOARCH { diff --git a/src/cmd/compile/internal/gc/lang_test.go b/src/cmd/compile/internal/test/lang_test.go similarity index 99% rename from src/cmd/compile/internal/gc/lang_test.go rename to src/cmd/compile/internal/test/lang_test.go index 72e7f07a21c0028ce8a6352cc63ae2a2584d08a5..67c1551292298355ab90969f5d6fa513988f2b12 100644 --- a/src/cmd/compile/internal/gc/lang_test.go +++ b/src/cmd/compile/internal/test/lang_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test import ( "internal/testenv" diff --git a/src/cmd/compile/internal/gc/logic_test.go b/src/cmd/compile/internal/test/logic_test.go similarity index 99% rename from src/cmd/compile/internal/gc/logic_test.go rename to src/cmd/compile/internal/test/logic_test.go index 78d2dd2fa81fad8b6f12fd22579138d15244e91f..1d7043ff605f14deb20817ed898687e865529066 100644 --- a/src/cmd/compile/internal/gc/logic_test.go +++ b/src/cmd/compile/internal/test/logic_test.go @@ -1,4 +1,4 @@ -package gc +package test import "testing" diff --git a/src/cmd/compile/internal/gc/reproduciblebuilds_test.go b/src/cmd/compile/internal/test/reproduciblebuilds_test.go similarity index 99% rename from src/cmd/compile/internal/gc/reproduciblebuilds_test.go rename to src/cmd/compile/internal/test/reproduciblebuilds_test.go index 8101e440793ee9e529a6318f342a83a0866a19e2..4d84f9cdeffc9acc4539753b6ff68cb5f87ab4ea 100644 --- a/src/cmd/compile/internal/gc/reproduciblebuilds_test.go +++ b/src/cmd/compile/internal/test/reproduciblebuilds_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc_test +package test import ( "bytes" diff --git a/src/cmd/compile/internal/gc/shift_test.go b/src/cmd/compile/internal/test/shift_test.go similarity index 99% rename from src/cmd/compile/internal/gc/shift_test.go rename to src/cmd/compile/internal/test/shift_test.go index ce2eedf1521d9d9af087b8e7adfc5901261a3785..ea88f0a70ae22359d3be78e02a93a27f1928cfd4 100644 --- a/src/cmd/compile/internal/gc/shift_test.go +++ b/src/cmd/compile/internal/test/shift_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test import ( "reflect" diff --git a/src/cmd/compile/internal/gc/ssa_test.go b/src/cmd/compile/internal/test/ssa_test.go similarity index 99% rename from src/cmd/compile/internal/gc/ssa_test.go rename to src/cmd/compile/internal/test/ssa_test.go index 7f7c9464d44fa7fc03626d34db06e46aa5e530ef..2f3e24c2d37ca0b6c9b6226e6611de369ff39cc9 100644 --- a/src/cmd/compile/internal/gc/ssa_test.go +++ b/src/cmd/compile/internal/test/ssa_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test import ( "bytes" diff --git a/src/cmd/compile/internal/gc/testdata/addressed_test.go b/src/cmd/compile/internal/test/testdata/addressed_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/addressed_test.go rename to src/cmd/compile/internal/test/testdata/addressed_test.go diff --git a/src/cmd/compile/internal/gc/testdata/append_test.go b/src/cmd/compile/internal/test/testdata/append_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/append_test.go rename to src/cmd/compile/internal/test/testdata/append_test.go diff --git a/src/cmd/compile/internal/gc/testdata/arithBoundary_test.go b/src/cmd/compile/internal/test/testdata/arithBoundary_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/arithBoundary_test.go rename to src/cmd/compile/internal/test/testdata/arithBoundary_test.go diff --git a/src/cmd/compile/internal/gc/testdata/arithConst_test.go b/src/cmd/compile/internal/test/testdata/arithConst_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/arithConst_test.go rename to src/cmd/compile/internal/test/testdata/arithConst_test.go diff --git a/src/cmd/compile/internal/gc/testdata/arith_test.go b/src/cmd/compile/internal/test/testdata/arith_test.go similarity index 97% rename from src/cmd/compile/internal/gc/testdata/arith_test.go rename to src/cmd/compile/internal/test/testdata/arith_test.go index 158fedc28efec5fb67260f644b1693f03102539f..7d54a9181d1ef2e072c70e47a955ae017070325d 100644 --- a/src/cmd/compile/internal/gc/testdata/arith_test.go +++ b/src/cmd/compile/internal/test/testdata/arith_test.go @@ -1452,3 +1452,46 @@ func testDivisibility(t *testing.T) { } } } + +//go:noinline +func genREV16_1(c uint64) uint64 { + b := ((c & 0xff00ff00ff00ff00) >> 8) | ((c & 0x00ff00ff00ff00ff) << 8) + return b +} + +//go:noinline +func genREV16_2(c uint64) uint64 { + b := ((c & 0xff00ff00) >> 8) | ((c & 0x00ff00ff) << 8) + return b +} + +//go:noinline +func genREV16W(c uint32) uint32 { + b := ((c & 0xff00ff00) >> 8) | ((c & 0x00ff00ff) << 8) + return b +} + +func TestREV16(t *testing.T) { + x := uint64(0x8f7f6f5f4f3f2f1f) + want1 := uint64(0x7f8f5f6f3f4f1f2f) + want2 := uint64(0x3f4f1f2f) + + got1 := genREV16_1(x) + if got1 != want1 { + t.Errorf("genREV16_1(%#x) = %#x want %#x", x, got1, want1) + } + got2 := genREV16_2(x) + if got2 != want2 { + t.Errorf("genREV16_2(%#x) = %#x want %#x", x, got2, want2) + } +} + +func TestREV16W(t *testing.T) { + x := uint32(0x4f3f2f1f) + want := uint32(0x3f4f1f2f) + + got := genREV16W(x) + if got != want { + t.Errorf("genREV16W(%#x) = %#x want %#x", x, got, want) + } +} diff --git a/src/cmd/compile/internal/gc/testdata/array_test.go b/src/cmd/compile/internal/test/testdata/array_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/array_test.go rename to src/cmd/compile/internal/test/testdata/array_test.go diff --git a/src/cmd/compile/internal/gc/testdata/assert_test.go b/src/cmd/compile/internal/test/testdata/assert_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/assert_test.go rename to src/cmd/compile/internal/test/testdata/assert_test.go diff --git a/src/cmd/compile/internal/gc/testdata/break_test.go b/src/cmd/compile/internal/test/testdata/break_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/break_test.go rename to src/cmd/compile/internal/test/testdata/break_test.go diff --git a/src/cmd/compile/internal/gc/testdata/chan_test.go b/src/cmd/compile/internal/test/testdata/chan_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/chan_test.go rename to src/cmd/compile/internal/test/testdata/chan_test.go diff --git a/src/cmd/compile/internal/gc/testdata/closure_test.go b/src/cmd/compile/internal/test/testdata/closure_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/closure_test.go rename to src/cmd/compile/internal/test/testdata/closure_test.go diff --git a/src/cmd/compile/internal/gc/testdata/cmpConst_test.go b/src/cmd/compile/internal/test/testdata/cmpConst_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/cmpConst_test.go rename to src/cmd/compile/internal/test/testdata/cmpConst_test.go diff --git a/src/cmd/compile/internal/gc/testdata/cmp_test.go b/src/cmd/compile/internal/test/testdata/cmp_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/cmp_test.go rename to src/cmd/compile/internal/test/testdata/cmp_test.go diff --git a/src/cmd/compile/internal/gc/testdata/compound_test.go b/src/cmd/compile/internal/test/testdata/compound_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/compound_test.go rename to src/cmd/compile/internal/test/testdata/compound_test.go diff --git a/src/cmd/compile/internal/gc/testdata/copy_test.go b/src/cmd/compile/internal/test/testdata/copy_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/copy_test.go rename to src/cmd/compile/internal/test/testdata/copy_test.go diff --git a/src/cmd/compile/internal/gc/testdata/ctl_test.go b/src/cmd/compile/internal/test/testdata/ctl_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/ctl_test.go rename to src/cmd/compile/internal/test/testdata/ctl_test.go diff --git a/src/cmd/compile/internal/gc/testdata/deferNoReturn_test.go b/src/cmd/compile/internal/test/testdata/deferNoReturn_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/deferNoReturn_test.go rename to src/cmd/compile/internal/test/testdata/deferNoReturn_test.go diff --git a/src/cmd/compile/internal/gc/testdata/divbyzero_test.go b/src/cmd/compile/internal/test/testdata/divbyzero_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/divbyzero_test.go rename to src/cmd/compile/internal/test/testdata/divbyzero_test.go diff --git a/src/cmd/compile/internal/gc/testdata/dupLoad_test.go b/src/cmd/compile/internal/test/testdata/dupLoad_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/dupLoad_test.go rename to src/cmd/compile/internal/test/testdata/dupLoad_test.go diff --git a/src/cmd/compile/internal/gc/testdata/flowgraph_generator1.go b/src/cmd/compile/internal/test/testdata/flowgraph_generator1.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/flowgraph_generator1.go rename to src/cmd/compile/internal/test/testdata/flowgraph_generator1.go diff --git a/src/cmd/compile/internal/gc/testdata/fp_test.go b/src/cmd/compile/internal/test/testdata/fp_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/fp_test.go rename to src/cmd/compile/internal/test/testdata/fp_test.go diff --git a/src/cmd/compile/internal/gc/testdata/gen/arithBoundaryGen.go b/src/cmd/compile/internal/test/testdata/gen/arithBoundaryGen.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/gen/arithBoundaryGen.go rename to src/cmd/compile/internal/test/testdata/gen/arithBoundaryGen.go diff --git a/src/cmd/compile/internal/gc/testdata/gen/arithConstGen.go b/src/cmd/compile/internal/test/testdata/gen/arithConstGen.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/gen/arithConstGen.go rename to src/cmd/compile/internal/test/testdata/gen/arithConstGen.go diff --git a/src/cmd/compile/internal/gc/testdata/gen/cmpConstGen.go b/src/cmd/compile/internal/test/testdata/gen/cmpConstGen.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/gen/cmpConstGen.go rename to src/cmd/compile/internal/test/testdata/gen/cmpConstGen.go diff --git a/src/cmd/compile/internal/gc/testdata/gen/constFoldGen.go b/src/cmd/compile/internal/test/testdata/gen/constFoldGen.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/gen/constFoldGen.go rename to src/cmd/compile/internal/test/testdata/gen/constFoldGen.go diff --git a/src/cmd/compile/internal/gc/testdata/gen/copyGen.go b/src/cmd/compile/internal/test/testdata/gen/copyGen.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/gen/copyGen.go rename to src/cmd/compile/internal/test/testdata/gen/copyGen.go diff --git a/src/cmd/compile/internal/gc/testdata/gen/zeroGen.go b/src/cmd/compile/internal/test/testdata/gen/zeroGen.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/gen/zeroGen.go rename to src/cmd/compile/internal/test/testdata/gen/zeroGen.go diff --git a/src/cmd/compile/internal/gc/testdata/loadstore_test.go b/src/cmd/compile/internal/test/testdata/loadstore_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/loadstore_test.go rename to src/cmd/compile/internal/test/testdata/loadstore_test.go diff --git a/src/cmd/compile/internal/gc/testdata/map_test.go b/src/cmd/compile/internal/test/testdata/map_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/map_test.go rename to src/cmd/compile/internal/test/testdata/map_test.go diff --git a/src/cmd/compile/internal/gc/testdata/namedReturn_test.go b/src/cmd/compile/internal/test/testdata/namedReturn_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/namedReturn_test.go rename to src/cmd/compile/internal/test/testdata/namedReturn_test.go diff --git a/src/cmd/compile/internal/gc/testdata/phi_test.go b/src/cmd/compile/internal/test/testdata/phi_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/phi_test.go rename to src/cmd/compile/internal/test/testdata/phi_test.go diff --git a/src/cmd/compile/internal/gc/testdata/regalloc_test.go b/src/cmd/compile/internal/test/testdata/regalloc_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/regalloc_test.go rename to src/cmd/compile/internal/test/testdata/regalloc_test.go diff --git a/src/cmd/compile/internal/gc/testdata/reproducible/issue20272.go b/src/cmd/compile/internal/test/testdata/reproducible/issue20272.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/reproducible/issue20272.go rename to src/cmd/compile/internal/test/testdata/reproducible/issue20272.go diff --git a/src/cmd/compile/internal/gc/testdata/reproducible/issue27013.go b/src/cmd/compile/internal/test/testdata/reproducible/issue27013.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/reproducible/issue27013.go rename to src/cmd/compile/internal/test/testdata/reproducible/issue27013.go diff --git a/src/cmd/compile/internal/gc/testdata/reproducible/issue30202.go b/src/cmd/compile/internal/test/testdata/reproducible/issue30202.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/reproducible/issue30202.go rename to src/cmd/compile/internal/test/testdata/reproducible/issue30202.go diff --git a/src/cmd/compile/internal/gc/testdata/reproducible/issue38068.go b/src/cmd/compile/internal/test/testdata/reproducible/issue38068.go similarity index 95% rename from src/cmd/compile/internal/gc/testdata/reproducible/issue38068.go rename to src/cmd/compile/internal/test/testdata/reproducible/issue38068.go index db5ca7dcbe7f6ef498105ded20af6550f39daca8..b87daed8e9882b4727f8fc9f01cf9e1dcdcec6f5 100644 --- a/src/cmd/compile/internal/gc/testdata/reproducible/issue38068.go +++ b/src/cmd/compile/internal/test/testdata/reproducible/issue38068.go @@ -53,7 +53,7 @@ func G(x *A, n int) { return } // Address-taken local of type A, which will insure that the - // compiler's dtypesym() routine will create a method wrapper. + // compiler's writeType() routine will create a method wrapper. var a, b A a.next = x a.prev = &b diff --git a/src/cmd/compile/internal/gc/testdata/short_test.go b/src/cmd/compile/internal/test/testdata/short_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/short_test.go rename to src/cmd/compile/internal/test/testdata/short_test.go diff --git a/src/cmd/compile/internal/gc/testdata/slice_test.go b/src/cmd/compile/internal/test/testdata/slice_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/slice_test.go rename to src/cmd/compile/internal/test/testdata/slice_test.go diff --git a/src/cmd/compile/internal/gc/testdata/sqrtConst_test.go b/src/cmd/compile/internal/test/testdata/sqrtConst_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/sqrtConst_test.go rename to src/cmd/compile/internal/test/testdata/sqrtConst_test.go diff --git a/src/cmd/compile/internal/gc/testdata/string_test.go b/src/cmd/compile/internal/test/testdata/string_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/string_test.go rename to src/cmd/compile/internal/test/testdata/string_test.go diff --git a/src/cmd/compile/internal/gc/testdata/unsafe_test.go b/src/cmd/compile/internal/test/testdata/unsafe_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/unsafe_test.go rename to src/cmd/compile/internal/test/testdata/unsafe_test.go diff --git a/src/cmd/compile/internal/gc/testdata/zero_test.go b/src/cmd/compile/internal/test/testdata/zero_test.go similarity index 100% rename from src/cmd/compile/internal/gc/testdata/zero_test.go rename to src/cmd/compile/internal/test/testdata/zero_test.go diff --git a/src/cmd/compile/internal/gc/truncconst_test.go b/src/cmd/compile/internal/test/truncconst_test.go similarity index 99% rename from src/cmd/compile/internal/gc/truncconst_test.go rename to src/cmd/compile/internal/test/truncconst_test.go index d1538180649dff5b8c7b76f2945923fe6f653de9..7705042ca2c84f3ef1db4ff7833035bfd22d2854 100644 --- a/src/cmd/compile/internal/gc/truncconst_test.go +++ b/src/cmd/compile/internal/test/truncconst_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package test import "testing" diff --git a/src/cmd/compile/internal/test/zerorange_test.go b/src/cmd/compile/internal/test/zerorange_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ec87136157209c377e556e23e20fbbf66a4cf9f5 --- /dev/null +++ b/src/cmd/compile/internal/test/zerorange_test.go @@ -0,0 +1,185 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package test + +import ( + "testing" +) + +var glob = 3 +var globp *int64 + +// Testing compilation of arch.ZeroRange of various sizes. + +// By storing a pointer to an int64 output param in a global, the compiler must +// ensure that output param is allocated on the heap. Also, since there is a +// defer, the pointer to each output param must be zeroed in the prologue (see +// plive.go:epilogue()). So, we will get a block of one or more stack slots that +// need to be zeroed. Hence, we are testing compilation completes successfully when +// zerorange calls of various sizes (8-136 bytes) are generated. We are not +// testing runtime correctness (which is hard to do for the current uses of +// ZeroRange). + +func TestZeroRange(t *testing.T) { + testZeroRange8(t) + testZeroRange16(t) + testZeroRange32(t) + testZeroRange64(t) + testZeroRange136(t) +} + +func testZeroRange8(t *testing.T) (r int64) { + defer func() { + glob = 4 + }() + globp = &r + return +} + +func testZeroRange16(t *testing.T) (r, s int64) { + defer func() { + glob = 4 + }() + globp = &r + globp = &s + return +} + +func testZeroRange32(t *testing.T) (r, s, t2, u int64) { + defer func() { + glob = 4 + }() + globp = &r + globp = &s + globp = &t2 + globp = &u + return +} + +func testZeroRange64(t *testing.T) (r, s, t2, u, v, w, x, y int64) { + defer func() { + glob = 4 + }() + globp = &r + globp = &s + globp = &t2 + globp = &u + globp = &v + globp = &w + globp = &x + globp = &y + return +} + +func testZeroRange136(t *testing.T) (r, s, t2, u, v, w, x, y, r1, s1, t1, u1, v1, w1, x1, y1, z1 int64) { + defer func() { + glob = 4 + }() + globp = &r + globp = &s + globp = &t2 + globp = &u + globp = &v + globp = &w + globp = &x + globp = &y + globp = &r1 + globp = &s1 + globp = &t1 + globp = &u1 + globp = &v1 + globp = &w1 + globp = &x1 + globp = &y1 + globp = &z1 + return +} + +type S struct { + x [2]uint64 + p *uint64 + y [2]uint64 + q uint64 +} + +type M struct { + x [8]uint64 + p *uint64 + y [8]uint64 + q uint64 +} + +type L struct { + x [4096]uint64 + p *uint64 + y [4096]uint64 + q uint64 +} + +//go:noinline +func triggerZerorangeLarge(f, g, h uint64) (rv0 uint64) { + ll := L{p: &f} + da := f + rv0 = f + g + h + defer func(dl L, i uint64) { + rv0 += dl.q + i + }(ll, da) + return rv0 +} + +//go:noinline +func triggerZerorangeMedium(f, g, h uint64) (rv0 uint64) { + ll := M{p: &f} + rv0 = f + g + h + defer func(dm M, i uint64) { + rv0 += dm.q + i + }(ll, f) + return rv0 +} + +//go:noinline +func triggerZerorangeSmall(f, g, h uint64) (rv0 uint64) { + ll := S{p: &f} + rv0 = f + g + h + defer func(ds S, i uint64) { + rv0 += ds.q + i + }(ll, f) + return rv0 +} + +// This test was created as a follow up to issue #45372, to help +// improve coverage of the compiler's arch-specific "zerorange" +// function, which is invoked to zero out ambiguously live portions of +// the stack frame in certain specific circumstances. +// +// In the current compiler implementation, for zerorange to be +// invoked, we need to have an ambiguously live variable that needs +// zeroing. One way to trigger this is to have a function with an +// open-coded defer, where the opendefer function has an argument that +// contains a pointer (this is what's used below). +// +// At the moment this test doesn't do any specific checking for +// code sequence, or verification that things were properly set to zero, +// this seems as though it would be too tricky and would result +// in a "brittle" test. +// +// The small/medium/large scenarios below are inspired by the amd64 +// implementation of zerorange, which generates different code +// depending on the size of the thing that needs to be zeroed out +// (I've verified at the time of the writing of this test that it +// exercises the various cases). +// +func TestZerorange45372(t *testing.T) { + if r := triggerZerorangeLarge(101, 303, 505); r != 1010 { + t.Errorf("large: wanted %d got %d", 1010, r) + } + if r := triggerZerorangeMedium(101, 303, 505); r != 1010 { + t.Errorf("medium: wanted %d got %d", 1010, r) + } + if r := triggerZerorangeSmall(101, 303, 505); r != 1010 { + t.Errorf("small: wanted %d got %d", 1010, r) + } + +} diff --git a/src/cmd/compile/internal/typebits/typebits.go b/src/cmd/compile/internal/typebits/typebits.go new file mode 100644 index 0000000000000000000000000000000000000000..1c1b077423dc973c20767f0b6d96280e07899e88 --- /dev/null +++ b/src/cmd/compile/internal/typebits/typebits.go @@ -0,0 +1,87 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typebits + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/bitvec" + "cmd/compile/internal/types" +) + +// NOTE: The bitmap for a specific type t could be cached in t after +// the first run and then simply copied into bv at the correct offset +// on future calls with the same type t. +func Set(t *types.Type, off int64, bv bitvec.BitVec) { + if t.Align > 0 && off&int64(t.Align-1) != 0 { + base.Fatalf("typebits.Set: invalid initial alignment: type %v has alignment %d, but offset is %v", t, t.Align, off) + } + if !t.HasPointers() { + // Note: this case ensures that pointers to go:notinheap types + // are not considered pointers by garbage collection and stack copying. + return + } + + switch t.Kind() { + case types.TPTR, types.TUNSAFEPTR, types.TFUNC, types.TCHAN, types.TMAP: + if off&int64(types.PtrSize-1) != 0 { + base.Fatalf("typebits.Set: invalid alignment, %v", t) + } + bv.Set(int32(off / int64(types.PtrSize))) // pointer + + case types.TSTRING: + // struct { byte *str; intgo len; } + if off&int64(types.PtrSize-1) != 0 { + base.Fatalf("typebits.Set: invalid alignment, %v", t) + } + bv.Set(int32(off / int64(types.PtrSize))) //pointer in first slot + + case types.TINTER: + // struct { Itab *tab; void *data; } + // or, when isnilinter(t)==true: + // struct { Type *type; void *data; } + if off&int64(types.PtrSize-1) != 0 { + base.Fatalf("typebits.Set: invalid alignment, %v", t) + } + // The first word of an interface is a pointer, but we don't + // treat it as such. + // 1. If it is a non-empty interface, the pointer points to an itab + // which is always in persistentalloc space. + // 2. If it is an empty interface, the pointer points to a _type. + // a. If it is a compile-time-allocated type, it points into + // the read-only data section. + // b. If it is a reflect-allocated type, it points into the Go heap. + // Reflect is responsible for keeping a reference to + // the underlying type so it won't be GCd. + // If we ever have a moving GC, we need to change this for 2b (as + // well as scan itabs to update their itab._type fields). + bv.Set(int32(off/int64(types.PtrSize) + 1)) // pointer in second slot + + case types.TSLICE: + // struct { byte *array; uintgo len; uintgo cap; } + if off&int64(types.PtrSize-1) != 0 { + base.Fatalf("typebits.Set: invalid TARRAY alignment, %v", t) + } + bv.Set(int32(off / int64(types.PtrSize))) // pointer in first slot (BitsPointer) + + case types.TARRAY: + elt := t.Elem() + if elt.Width == 0 { + // Short-circuit for #20739. + break + } + for i := int64(0); i < t.NumElem(); i++ { + Set(elt, off, bv) + off += elt.Width + } + + case types.TSTRUCT: + for _, f := range t.Fields().Slice() { + Set(f.Type, off+f.Offset, bv) + } + + default: + base.Fatalf("typebits.Set: unexpected type, %v", t) + } +} diff --git a/src/cmd/compile/internal/typecheck/bexport.go b/src/cmd/compile/internal/typecheck/bexport.go new file mode 100644 index 0000000000000000000000000000000000000000..4a84bb13fa48ebfc63eb1dd36a5b7206af21a5c5 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/bexport.go @@ -0,0 +1,102 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import "cmd/compile/internal/types" + +// ---------------------------------------------------------------------------- +// Export format + +// Tags. Must be < 0. +const ( + // Objects + packageTag = -(iota + 1) + constTag + typeTag + varTag + funcTag + endTag + + // Types + namedTag + arrayTag + sliceTag + dddTag + structTag + pointerTag + signatureTag + interfaceTag + mapTag + chanTag + + // Values + falseTag + trueTag + int64Tag + floatTag + fractionTag // not used by gc + complexTag + stringTag + nilTag + unknownTag // not used by gc (only appears in packages with errors) + + // Type aliases + aliasTag +) + +var predecl []*types.Type // initialized lazily + +func predeclared() []*types.Type { + if predecl == nil { + // initialize lazily to be sure that all + // elements have been initialized before + predecl = []*types.Type{ + // basic types + types.Types[types.TBOOL], + types.Types[types.TINT], + types.Types[types.TINT8], + types.Types[types.TINT16], + types.Types[types.TINT32], + types.Types[types.TINT64], + types.Types[types.TUINT], + types.Types[types.TUINT8], + types.Types[types.TUINT16], + types.Types[types.TUINT32], + types.Types[types.TUINT64], + types.Types[types.TUINTPTR], + types.Types[types.TFLOAT32], + types.Types[types.TFLOAT64], + types.Types[types.TCOMPLEX64], + types.Types[types.TCOMPLEX128], + types.Types[types.TSTRING], + + // basic type aliases + types.ByteType, + types.RuneType, + + // error + types.ErrorType, + + // untyped types + types.UntypedBool, + types.UntypedInt, + types.UntypedRune, + types.UntypedFloat, + types.UntypedComplex, + types.UntypedString, + types.Types[types.TNIL], + + // package unsafe + types.Types[types.TUNSAFEPTR], + + // invalid type (package contains errors) + types.Types[types.Txxx], + + // any type, for builtin export data + types.Types[types.TANY], + } + } + return predecl +} diff --git a/src/cmd/compile/internal/typecheck/builtin.go b/src/cmd/compile/internal/typecheck/builtin.go new file mode 100644 index 0000000000000000000000000000000000000000..833b17b4148ed5ab022e0b859084573227151ed3 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/builtin.go @@ -0,0 +1,374 @@ +// Code generated by mkbuiltin.go. DO NOT EDIT. + +package typecheck + +import ( + "cmd/compile/internal/types" + "cmd/internal/src" +) + +var runtimeDecls = [...]struct { + name string + tag int + typ int +}{ + {"newobject", funcTag, 4}, + {"mallocgc", funcTag, 8}, + {"panicdivide", funcTag, 9}, + {"panicshift", funcTag, 9}, + {"panicmakeslicelen", funcTag, 9}, + {"panicmakeslicecap", funcTag, 9}, + {"throwinit", funcTag, 9}, + {"panicwrap", funcTag, 9}, + {"gopanic", funcTag, 11}, + {"gorecover", funcTag, 14}, + {"goschedguarded", funcTag, 9}, + {"goPanicIndex", funcTag, 16}, + {"goPanicIndexU", funcTag, 18}, + {"goPanicSliceAlen", funcTag, 16}, + {"goPanicSliceAlenU", funcTag, 18}, + {"goPanicSliceAcap", funcTag, 16}, + {"goPanicSliceAcapU", funcTag, 18}, + {"goPanicSliceB", funcTag, 16}, + {"goPanicSliceBU", funcTag, 18}, + {"goPanicSlice3Alen", funcTag, 16}, + {"goPanicSlice3AlenU", funcTag, 18}, + {"goPanicSlice3Acap", funcTag, 16}, + {"goPanicSlice3AcapU", funcTag, 18}, + {"goPanicSlice3B", funcTag, 16}, + {"goPanicSlice3BU", funcTag, 18}, + {"goPanicSlice3C", funcTag, 16}, + {"goPanicSlice3CU", funcTag, 18}, + {"goPanicSliceConvert", funcTag, 16}, + {"printbool", funcTag, 19}, + {"printfloat", funcTag, 21}, + {"printint", funcTag, 23}, + {"printhex", funcTag, 25}, + {"printuint", funcTag, 25}, + {"printcomplex", funcTag, 27}, + {"printstring", funcTag, 29}, + {"printpointer", funcTag, 30}, + {"printuintptr", funcTag, 31}, + {"printiface", funcTag, 30}, + {"printeface", funcTag, 30}, + {"printslice", funcTag, 30}, + {"printnl", funcTag, 9}, + {"printsp", funcTag, 9}, + {"printlock", funcTag, 9}, + {"printunlock", funcTag, 9}, + {"concatstring2", funcTag, 34}, + {"concatstring3", funcTag, 35}, + {"concatstring4", funcTag, 36}, + {"concatstring5", funcTag, 37}, + {"concatstrings", funcTag, 39}, + {"cmpstring", funcTag, 40}, + {"intstring", funcTag, 43}, + {"slicebytetostring", funcTag, 44}, + {"slicebytetostringtmp", funcTag, 45}, + {"slicerunetostring", funcTag, 48}, + {"stringtoslicebyte", funcTag, 50}, + {"stringtoslicerune", funcTag, 53}, + {"slicecopy", funcTag, 54}, + {"decoderune", funcTag, 55}, + {"countrunes", funcTag, 56}, + {"convI2I", funcTag, 57}, + {"convT16", funcTag, 59}, + {"convT32", funcTag, 61}, + {"convT64", funcTag, 62}, + {"convTstring", funcTag, 63}, + {"convTslice", funcTag, 66}, + {"convT2E", funcTag, 67}, + {"convT2Enoptr", funcTag, 67}, + {"convT2I", funcTag, 67}, + {"convT2Inoptr", funcTag, 67}, + {"assertE2I", funcTag, 68}, + {"assertE2I2", funcTag, 57}, + {"assertI2I", funcTag, 68}, + {"assertI2I2", funcTag, 57}, + {"panicdottypeE", funcTag, 69}, + {"panicdottypeI", funcTag, 69}, + {"panicnildottype", funcTag, 70}, + {"ifaceeq", funcTag, 72}, + {"efaceeq", funcTag, 72}, + {"fastrand", funcTag, 73}, + {"makemap64", funcTag, 75}, + {"makemap", funcTag, 76}, + {"makemap_small", funcTag, 77}, + {"mapaccess1", funcTag, 78}, + {"mapaccess1_fast32", funcTag, 79}, + {"mapaccess1_fast64", funcTag, 80}, + {"mapaccess1_faststr", funcTag, 81}, + {"mapaccess1_fat", funcTag, 82}, + {"mapaccess2", funcTag, 83}, + {"mapaccess2_fast32", funcTag, 84}, + {"mapaccess2_fast64", funcTag, 85}, + {"mapaccess2_faststr", funcTag, 86}, + {"mapaccess2_fat", funcTag, 87}, + {"mapassign", funcTag, 78}, + {"mapassign_fast32", funcTag, 79}, + {"mapassign_fast32ptr", funcTag, 88}, + {"mapassign_fast64", funcTag, 80}, + {"mapassign_fast64ptr", funcTag, 88}, + {"mapassign_faststr", funcTag, 81}, + {"mapiterinit", funcTag, 89}, + {"mapdelete", funcTag, 89}, + {"mapdelete_fast32", funcTag, 90}, + {"mapdelete_fast64", funcTag, 91}, + {"mapdelete_faststr", funcTag, 92}, + {"mapiternext", funcTag, 93}, + {"mapclear", funcTag, 94}, + {"makechan64", funcTag, 96}, + {"makechan", funcTag, 97}, + {"chanrecv1", funcTag, 99}, + {"chanrecv2", funcTag, 100}, + {"chansend1", funcTag, 102}, + {"closechan", funcTag, 30}, + {"writeBarrier", varTag, 104}, + {"typedmemmove", funcTag, 105}, + {"typedmemclr", funcTag, 106}, + {"typedslicecopy", funcTag, 107}, + {"selectnbsend", funcTag, 108}, + {"selectnbrecv", funcTag, 109}, + {"selectsetpc", funcTag, 110}, + {"selectgo", funcTag, 111}, + {"block", funcTag, 9}, + {"makeslice", funcTag, 112}, + {"makeslice64", funcTag, 113}, + {"makeslicecopy", funcTag, 114}, + {"growslice", funcTag, 116}, + {"unsafeslice", funcTag, 117}, + {"unsafeslice64", funcTag, 118}, + {"unsafeslicecheckptr", funcTag, 118}, + {"memmove", funcTag, 119}, + {"memclrNoHeapPointers", funcTag, 120}, + {"memclrHasPointers", funcTag, 120}, + {"memequal", funcTag, 121}, + {"memequal0", funcTag, 122}, + {"memequal8", funcTag, 122}, + {"memequal16", funcTag, 122}, + {"memequal32", funcTag, 122}, + {"memequal64", funcTag, 122}, + {"memequal128", funcTag, 122}, + {"f32equal", funcTag, 123}, + {"f64equal", funcTag, 123}, + {"c64equal", funcTag, 123}, + {"c128equal", funcTag, 123}, + {"strequal", funcTag, 123}, + {"interequal", funcTag, 123}, + {"nilinterequal", funcTag, 123}, + {"memhash", funcTag, 124}, + {"memhash0", funcTag, 125}, + {"memhash8", funcTag, 125}, + {"memhash16", funcTag, 125}, + {"memhash32", funcTag, 125}, + {"memhash64", funcTag, 125}, + {"memhash128", funcTag, 125}, + {"f32hash", funcTag, 125}, + {"f64hash", funcTag, 125}, + {"c64hash", funcTag, 125}, + {"c128hash", funcTag, 125}, + {"strhash", funcTag, 125}, + {"interhash", funcTag, 125}, + {"nilinterhash", funcTag, 125}, + {"int64div", funcTag, 126}, + {"uint64div", funcTag, 127}, + {"int64mod", funcTag, 126}, + {"uint64mod", funcTag, 127}, + {"float64toint64", funcTag, 128}, + {"float64touint64", funcTag, 129}, + {"float64touint32", funcTag, 130}, + {"int64tofloat64", funcTag, 131}, + {"uint64tofloat64", funcTag, 132}, + {"uint32tofloat64", funcTag, 133}, + {"complex128div", funcTag, 134}, + {"getcallerpc", funcTag, 135}, + {"getcallersp", funcTag, 135}, + {"racefuncenter", funcTag, 31}, + {"racefuncexit", funcTag, 9}, + {"raceread", funcTag, 31}, + {"racewrite", funcTag, 31}, + {"racereadrange", funcTag, 136}, + {"racewriterange", funcTag, 136}, + {"msanread", funcTag, 136}, + {"msanwrite", funcTag, 136}, + {"msanmove", funcTag, 137}, + {"checkptrAlignment", funcTag, 138}, + {"checkptrArithmetic", funcTag, 140}, + {"libfuzzerTraceCmp1", funcTag, 141}, + {"libfuzzerTraceCmp2", funcTag, 142}, + {"libfuzzerTraceCmp4", funcTag, 143}, + {"libfuzzerTraceCmp8", funcTag, 144}, + {"libfuzzerTraceConstCmp1", funcTag, 141}, + {"libfuzzerTraceConstCmp2", funcTag, 142}, + {"libfuzzerTraceConstCmp4", funcTag, 143}, + {"libfuzzerTraceConstCmp8", funcTag, 144}, + {"x86HasPOPCNT", varTag, 6}, + {"x86HasSSE41", varTag, 6}, + {"x86HasFMA", varTag, 6}, + {"armHasVFPv4", varTag, 6}, + {"arm64HasATOMICS", varTag, 6}, +} + +// Not inlining this function removes a significant chunk of init code. +//go:noinline +func newSig(params, results []*types.Field) *types.Type { + return types.NewSignature(types.NoPkg, nil, nil, params, results) +} + +func params(tlist ...*types.Type) []*types.Field { + flist := make([]*types.Field, len(tlist)) + for i, typ := range tlist { + flist[i] = types.NewField(src.NoXPos, nil, typ) + } + return flist +} + +func runtimeTypes() []*types.Type { + var typs [145]*types.Type + typs[0] = types.ByteType + typs[1] = types.NewPtr(typs[0]) + typs[2] = types.Types[types.TANY] + typs[3] = types.NewPtr(typs[2]) + typs[4] = newSig(params(typs[1]), params(typs[3])) + typs[5] = types.Types[types.TUINTPTR] + typs[6] = types.Types[types.TBOOL] + typs[7] = types.Types[types.TUNSAFEPTR] + typs[8] = newSig(params(typs[5], typs[1], typs[6]), params(typs[7])) + typs[9] = newSig(nil, nil) + typs[10] = types.Types[types.TINTER] + typs[11] = newSig(params(typs[10]), nil) + typs[12] = types.Types[types.TINT32] + typs[13] = types.NewPtr(typs[12]) + typs[14] = newSig(params(typs[13]), params(typs[10])) + typs[15] = types.Types[types.TINT] + typs[16] = newSig(params(typs[15], typs[15]), nil) + typs[17] = types.Types[types.TUINT] + typs[18] = newSig(params(typs[17], typs[15]), nil) + typs[19] = newSig(params(typs[6]), nil) + typs[20] = types.Types[types.TFLOAT64] + typs[21] = newSig(params(typs[20]), nil) + typs[22] = types.Types[types.TINT64] + typs[23] = newSig(params(typs[22]), nil) + typs[24] = types.Types[types.TUINT64] + typs[25] = newSig(params(typs[24]), nil) + typs[26] = types.Types[types.TCOMPLEX128] + typs[27] = newSig(params(typs[26]), nil) + typs[28] = types.Types[types.TSTRING] + typs[29] = newSig(params(typs[28]), nil) + typs[30] = newSig(params(typs[2]), nil) + typs[31] = newSig(params(typs[5]), nil) + typs[32] = types.NewArray(typs[0], 32) + typs[33] = types.NewPtr(typs[32]) + typs[34] = newSig(params(typs[33], typs[28], typs[28]), params(typs[28])) + typs[35] = newSig(params(typs[33], typs[28], typs[28], typs[28]), params(typs[28])) + typs[36] = newSig(params(typs[33], typs[28], typs[28], typs[28], typs[28]), params(typs[28])) + typs[37] = newSig(params(typs[33], typs[28], typs[28], typs[28], typs[28], typs[28]), params(typs[28])) + typs[38] = types.NewSlice(typs[28]) + typs[39] = newSig(params(typs[33], typs[38]), params(typs[28])) + typs[40] = newSig(params(typs[28], typs[28]), params(typs[15])) + typs[41] = types.NewArray(typs[0], 4) + typs[42] = types.NewPtr(typs[41]) + typs[43] = newSig(params(typs[42], typs[22]), params(typs[28])) + typs[44] = newSig(params(typs[33], typs[1], typs[15]), params(typs[28])) + typs[45] = newSig(params(typs[1], typs[15]), params(typs[28])) + typs[46] = types.RuneType + typs[47] = types.NewSlice(typs[46]) + typs[48] = newSig(params(typs[33], typs[47]), params(typs[28])) + typs[49] = types.NewSlice(typs[0]) + typs[50] = newSig(params(typs[33], typs[28]), params(typs[49])) + typs[51] = types.NewArray(typs[46], 32) + typs[52] = types.NewPtr(typs[51]) + typs[53] = newSig(params(typs[52], typs[28]), params(typs[47])) + typs[54] = newSig(params(typs[3], typs[15], typs[3], typs[15], typs[5]), params(typs[15])) + typs[55] = newSig(params(typs[28], typs[15]), params(typs[46], typs[15])) + typs[56] = newSig(params(typs[28]), params(typs[15])) + typs[57] = newSig(params(typs[1], typs[2]), params(typs[2])) + typs[58] = types.Types[types.TUINT16] + typs[59] = newSig(params(typs[58]), params(typs[7])) + typs[60] = types.Types[types.TUINT32] + typs[61] = newSig(params(typs[60]), params(typs[7])) + typs[62] = newSig(params(typs[24]), params(typs[7])) + typs[63] = newSig(params(typs[28]), params(typs[7])) + typs[64] = types.Types[types.TUINT8] + typs[65] = types.NewSlice(typs[64]) + typs[66] = newSig(params(typs[65]), params(typs[7])) + typs[67] = newSig(params(typs[1], typs[3]), params(typs[2])) + typs[68] = newSig(params(typs[1], typs[1]), params(typs[1])) + typs[69] = newSig(params(typs[1], typs[1], typs[1]), nil) + typs[70] = newSig(params(typs[1]), nil) + typs[71] = types.NewPtr(typs[5]) + typs[72] = newSig(params(typs[71], typs[7], typs[7]), params(typs[6])) + typs[73] = newSig(nil, params(typs[60])) + typs[74] = types.NewMap(typs[2], typs[2]) + typs[75] = newSig(params(typs[1], typs[22], typs[3]), params(typs[74])) + typs[76] = newSig(params(typs[1], typs[15], typs[3]), params(typs[74])) + typs[77] = newSig(nil, params(typs[74])) + typs[78] = newSig(params(typs[1], typs[74], typs[3]), params(typs[3])) + typs[79] = newSig(params(typs[1], typs[74], typs[60]), params(typs[3])) + typs[80] = newSig(params(typs[1], typs[74], typs[24]), params(typs[3])) + typs[81] = newSig(params(typs[1], typs[74], typs[28]), params(typs[3])) + typs[82] = newSig(params(typs[1], typs[74], typs[3], typs[1]), params(typs[3])) + typs[83] = newSig(params(typs[1], typs[74], typs[3]), params(typs[3], typs[6])) + typs[84] = newSig(params(typs[1], typs[74], typs[60]), params(typs[3], typs[6])) + typs[85] = newSig(params(typs[1], typs[74], typs[24]), params(typs[3], typs[6])) + typs[86] = newSig(params(typs[1], typs[74], typs[28]), params(typs[3], typs[6])) + typs[87] = newSig(params(typs[1], typs[74], typs[3], typs[1]), params(typs[3], typs[6])) + typs[88] = newSig(params(typs[1], typs[74], typs[7]), params(typs[3])) + typs[89] = newSig(params(typs[1], typs[74], typs[3]), nil) + typs[90] = newSig(params(typs[1], typs[74], typs[60]), nil) + typs[91] = newSig(params(typs[1], typs[74], typs[24]), nil) + typs[92] = newSig(params(typs[1], typs[74], typs[28]), nil) + typs[93] = newSig(params(typs[3]), nil) + typs[94] = newSig(params(typs[1], typs[74]), nil) + typs[95] = types.NewChan(typs[2], types.Cboth) + typs[96] = newSig(params(typs[1], typs[22]), params(typs[95])) + typs[97] = newSig(params(typs[1], typs[15]), params(typs[95])) + typs[98] = types.NewChan(typs[2], types.Crecv) + typs[99] = newSig(params(typs[98], typs[3]), nil) + typs[100] = newSig(params(typs[98], typs[3]), params(typs[6])) + typs[101] = types.NewChan(typs[2], types.Csend) + typs[102] = newSig(params(typs[101], typs[3]), nil) + typs[103] = types.NewArray(typs[0], 3) + typs[104] = types.NewStruct(types.NoPkg, []*types.Field{types.NewField(src.NoXPos, Lookup("enabled"), typs[6]), types.NewField(src.NoXPos, Lookup("pad"), typs[103]), types.NewField(src.NoXPos, Lookup("needed"), typs[6]), types.NewField(src.NoXPos, Lookup("cgo"), typs[6]), types.NewField(src.NoXPos, Lookup("alignme"), typs[24])}) + typs[105] = newSig(params(typs[1], typs[3], typs[3]), nil) + typs[106] = newSig(params(typs[1], typs[3]), nil) + typs[107] = newSig(params(typs[1], typs[3], typs[15], typs[3], typs[15]), params(typs[15])) + typs[108] = newSig(params(typs[101], typs[3]), params(typs[6])) + typs[109] = newSig(params(typs[3], typs[98]), params(typs[6], typs[6])) + typs[110] = newSig(params(typs[71]), nil) + typs[111] = newSig(params(typs[1], typs[1], typs[71], typs[15], typs[15], typs[6]), params(typs[15], typs[6])) + typs[112] = newSig(params(typs[1], typs[15], typs[15]), params(typs[7])) + typs[113] = newSig(params(typs[1], typs[22], typs[22]), params(typs[7])) + typs[114] = newSig(params(typs[1], typs[15], typs[15], typs[7]), params(typs[7])) + typs[115] = types.NewSlice(typs[2]) + typs[116] = newSig(params(typs[1], typs[115], typs[15]), params(typs[115])) + typs[117] = newSig(params(typs[1], typs[7], typs[15]), nil) + typs[118] = newSig(params(typs[1], typs[7], typs[22]), nil) + typs[119] = newSig(params(typs[3], typs[3], typs[5]), nil) + typs[120] = newSig(params(typs[7], typs[5]), nil) + typs[121] = newSig(params(typs[3], typs[3], typs[5]), params(typs[6])) + typs[122] = newSig(params(typs[3], typs[3]), params(typs[6])) + typs[123] = newSig(params(typs[7], typs[7]), params(typs[6])) + typs[124] = newSig(params(typs[7], typs[5], typs[5]), params(typs[5])) + typs[125] = newSig(params(typs[7], typs[5]), params(typs[5])) + typs[126] = newSig(params(typs[22], typs[22]), params(typs[22])) + typs[127] = newSig(params(typs[24], typs[24]), params(typs[24])) + typs[128] = newSig(params(typs[20]), params(typs[22])) + typs[129] = newSig(params(typs[20]), params(typs[24])) + typs[130] = newSig(params(typs[20]), params(typs[60])) + typs[131] = newSig(params(typs[22]), params(typs[20])) + typs[132] = newSig(params(typs[24]), params(typs[20])) + typs[133] = newSig(params(typs[60]), params(typs[20])) + typs[134] = newSig(params(typs[26], typs[26]), params(typs[26])) + typs[135] = newSig(nil, params(typs[5])) + typs[136] = newSig(params(typs[5], typs[5]), nil) + typs[137] = newSig(params(typs[5], typs[5], typs[5]), nil) + typs[138] = newSig(params(typs[7], typs[1], typs[5]), nil) + typs[139] = types.NewSlice(typs[7]) + typs[140] = newSig(params(typs[7], typs[139]), nil) + typs[141] = newSig(params(typs[64], typs[64]), nil) + typs[142] = newSig(params(typs[58], typs[58]), nil) + typs[143] = newSig(params(typs[60], typs[60]), nil) + typs[144] = newSig(params(typs[24], typs[24]), nil) + return typs[:] +} diff --git a/src/cmd/compile/internal/gc/builtin/runtime.go b/src/cmd/compile/internal/typecheck/builtin/runtime.go similarity index 78% rename from src/cmd/compile/internal/gc/builtin/runtime.go rename to src/cmd/compile/internal/typecheck/builtin/runtime.go index acb69c7b282e13f88802dd393bad143350902d37..2b29ea3c08ca72be0b35a2db22755490f76a4f11 100644 --- a/src/cmd/compile/internal/gc/builtin/runtime.go +++ b/src/cmd/compile/internal/typecheck/builtin/runtime.go @@ -6,6 +6,7 @@ // to update builtin.go. This is not done automatically // to avoid depending on having a working compiler binary. +//go:build ignore // +build ignore package runtime @@ -45,6 +46,7 @@ func goPanicSlice3B(x int, y int) func goPanicSlice3BU(x uint, y int) func goPanicSlice3C(x int, y int) func goPanicSlice3CU(x uint, y int) +func goPanicSliceConvert(x int, y int) func printbool(bool) func printfloat(float64) @@ -86,11 +88,16 @@ func convI2I(typ *byte, elem any) (ret any) // Specialized type-to-interface conversion. // These return only a data pointer. -func convT16(val any) unsafe.Pointer // val must be uint16-like (same size and alignment as a uint16) -func convT32(val any) unsafe.Pointer // val must be uint32-like (same size and alignment as a uint32) -func convT64(val any) unsafe.Pointer // val must be uint64-like (same size and alignment as a uint64 and contains no pointers) -func convTstring(val any) unsafe.Pointer // val must be a string -func convTslice(val any) unsafe.Pointer // val must be a slice +// These functions take concrete types in the runtime. But they may +// be used for a wider range of types, which have the same memory +// layout as the parameter type. The compiler converts the +// to-be-converted type to the parameter type before calling the +// runtime function. This way, the call is ABI-insensitive. +func convT16(val uint16) unsafe.Pointer +func convT32(val uint32) unsafe.Pointer +func convT64(val uint64) unsafe.Pointer +func convTstring(val string) unsafe.Pointer +func convTslice(val []uint8) unsafe.Pointer // Type to empty-interface conversion. func convT2E(typ *byte, elem *any) (ret any) @@ -101,10 +108,10 @@ func convT2I(tab *byte, elem *any) (ret any) func convT2Inoptr(tab *byte, elem *any) (ret any) // interface type assertions x.(T) -func assertE2I(typ *byte, iface any) (ret any) -func assertE2I2(typ *byte, iface any) (ret any, b bool) -func assertI2I(typ *byte, iface any) (ret any) -func assertI2I2(typ *byte, iface any) (ret any, b bool) +func assertE2I(inter *byte, typ *byte) *byte +func assertE2I2(inter *byte, eface any) (ret any) +func assertI2I(inter *byte, tab *byte) *byte +func assertI2I2(inter *byte, iface any) (ret any) func panicdottypeE(have, want, iface *byte) func panicdottypeI(have, want, iface *byte) func panicnildottype(want *byte) @@ -121,26 +128,26 @@ func makemap64(mapType *byte, hint int64, mapbuf *any) (hmap map[any]any) func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any) func makemap_small() (hmap map[any]any) func mapaccess1(mapType *byte, hmap map[any]any, key *any) (val *any) -func mapaccess1_fast32(mapType *byte, hmap map[any]any, key any) (val *any) -func mapaccess1_fast64(mapType *byte, hmap map[any]any, key any) (val *any) -func mapaccess1_faststr(mapType *byte, hmap map[any]any, key any) (val *any) +func mapaccess1_fast32(mapType *byte, hmap map[any]any, key uint32) (val *any) +func mapaccess1_fast64(mapType *byte, hmap map[any]any, key uint64) (val *any) +func mapaccess1_faststr(mapType *byte, hmap map[any]any, key string) (val *any) func mapaccess1_fat(mapType *byte, hmap map[any]any, key *any, zero *byte) (val *any) func mapaccess2(mapType *byte, hmap map[any]any, key *any) (val *any, pres bool) -func mapaccess2_fast32(mapType *byte, hmap map[any]any, key any) (val *any, pres bool) -func mapaccess2_fast64(mapType *byte, hmap map[any]any, key any) (val *any, pres bool) -func mapaccess2_faststr(mapType *byte, hmap map[any]any, key any) (val *any, pres bool) +func mapaccess2_fast32(mapType *byte, hmap map[any]any, key uint32) (val *any, pres bool) +func mapaccess2_fast64(mapType *byte, hmap map[any]any, key uint64) (val *any, pres bool) +func mapaccess2_faststr(mapType *byte, hmap map[any]any, key string) (val *any, pres bool) func mapaccess2_fat(mapType *byte, hmap map[any]any, key *any, zero *byte) (val *any, pres bool) func mapassign(mapType *byte, hmap map[any]any, key *any) (val *any) -func mapassign_fast32(mapType *byte, hmap map[any]any, key any) (val *any) -func mapassign_fast32ptr(mapType *byte, hmap map[any]any, key any) (val *any) -func mapassign_fast64(mapType *byte, hmap map[any]any, key any) (val *any) -func mapassign_fast64ptr(mapType *byte, hmap map[any]any, key any) (val *any) -func mapassign_faststr(mapType *byte, hmap map[any]any, key any) (val *any) +func mapassign_fast32(mapType *byte, hmap map[any]any, key uint32) (val *any) +func mapassign_fast32ptr(mapType *byte, hmap map[any]any, key unsafe.Pointer) (val *any) +func mapassign_fast64(mapType *byte, hmap map[any]any, key uint64) (val *any) +func mapassign_fast64ptr(mapType *byte, hmap map[any]any, key unsafe.Pointer) (val *any) +func mapassign_faststr(mapType *byte, hmap map[any]any, key string) (val *any) func mapiterinit(mapType *byte, hmap map[any]any, hiter *any) func mapdelete(mapType *byte, hmap map[any]any, key *any) -func mapdelete_fast32(mapType *byte, hmap map[any]any, key any) -func mapdelete_fast64(mapType *byte, hmap map[any]any, key any) -func mapdelete_faststr(mapType *byte, hmap map[any]any, key any) +func mapdelete_fast32(mapType *byte, hmap map[any]any, key uint32) +func mapdelete_fast64(mapType *byte, hmap map[any]any, key uint64) +func mapdelete_faststr(mapType *byte, hmap map[any]any, key string) func mapiternext(hiter *any) func mapclear(mapType *byte, hmap map[any]any) @@ -166,8 +173,7 @@ func typedmemclr(typ *byte, dst *any) func typedslicecopy(typ *byte, dstPtr *any, dstLen int, srcPtr *any, srcLen int) int func selectnbsend(hchan chan<- any, elem *any) bool -func selectnbrecv(elem *any, hchan <-chan any) bool -func selectnbrecv2(elem *any, received *bool, hchan <-chan any) bool +func selectnbrecv(elem *any, hchan <-chan any) (bool, bool) func selectsetpc(pc *uintptr) func selectgo(cas0 *byte, order0 *byte, pc0 *uintptr, nsends int, nrecvs int, block bool) (int, bool) @@ -177,6 +183,10 @@ func makeslice(typ *byte, len int, cap int) unsafe.Pointer func makeslice64(typ *byte, len int64, cap int64) unsafe.Pointer func makeslicecopy(typ *byte, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer func growslice(typ *byte, old []any, cap int) (ary []any) +func unsafeslice(typ *byte, ptr unsafe.Pointer, len int) +func unsafeslice64(typ *byte, ptr unsafe.Pointer, len int64) +func unsafeslicecheckptr(typ *byte, ptr unsafe.Pointer, len int64) + func memmove(to *any, frm *any, length uintptr) func memclrNoHeapPointers(ptr unsafe.Pointer, n uintptr) func memclrHasPointers(ptr unsafe.Pointer, n uintptr) @@ -225,9 +235,11 @@ func uint32tofloat64(uint32) float64 func complex128div(num complex128, den complex128) (quo complex128) +func getcallerpc() uintptr +func getcallersp() uintptr + // race detection func racefuncenter(uintptr) -func racefuncenterfp() func racefuncexit() func raceread(uintptr) func racewrite(uintptr) diff --git a/src/cmd/compile/internal/gc/builtin_test.go b/src/cmd/compile/internal/typecheck/builtin_test.go similarity index 97% rename from src/cmd/compile/internal/gc/builtin_test.go rename to src/cmd/compile/internal/typecheck/builtin_test.go index 57f24b2287978d23773ce506088bbe49c8be4d6a..fb9d3e393f10e15755e95508655352534aeab985 100644 --- a/src/cmd/compile/internal/gc/builtin_test.go +++ b/src/cmd/compile/internal/typecheck/builtin_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc_test +package typecheck import ( "bytes" diff --git a/src/cmd/compile/internal/typecheck/const.go b/src/cmd/compile/internal/typecheck/const.go new file mode 100644 index 0000000000000000000000000000000000000000..761b043794062d7dbb4c985d99319b35ba24f3eb --- /dev/null +++ b/src/cmd/compile/internal/typecheck/const.go @@ -0,0 +1,941 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "fmt" + "go/constant" + "go/token" + "math" + "math/big" + "strings" + "unicode" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +func roundFloat(v constant.Value, sz int64) constant.Value { + switch sz { + case 4: + f, _ := constant.Float32Val(v) + return makeFloat64(float64(f)) + case 8: + f, _ := constant.Float64Val(v) + return makeFloat64(f) + } + base.Fatalf("unexpected size: %v", sz) + panic("unreachable") +} + +// truncate float literal fv to 32-bit or 64-bit precision +// according to type; return truncated value. +func truncfltlit(v constant.Value, t *types.Type) constant.Value { + if t.IsUntyped() || overflow(v, t) { + // If there was overflow, simply continuing would set the + // value to Inf which in turn would lead to spurious follow-on + // errors. Avoid this by returning the existing value. + return v + } + + return roundFloat(v, t.Size()) +} + +// truncate Real and Imag parts of Mpcplx to 32-bit or 64-bit +// precision, according to type; return truncated value. In case of +// overflow, calls Errorf but does not truncate the input value. +func trunccmplxlit(v constant.Value, t *types.Type) constant.Value { + if t.IsUntyped() || overflow(v, t) { + // If there was overflow, simply continuing would set the + // value to Inf which in turn would lead to spurious follow-on + // errors. Avoid this by returning the existing value. + return v + } + + fsz := t.Size() / 2 + return makeComplex(roundFloat(constant.Real(v), fsz), roundFloat(constant.Imag(v), fsz)) +} + +// TODO(mdempsky): Replace these with better APIs. +func convlit(n ir.Node, t *types.Type) ir.Node { return convlit1(n, t, false, nil) } +func DefaultLit(n ir.Node, t *types.Type) ir.Node { return convlit1(n, t, false, nil) } + +// convlit1 converts an untyped expression n to type t. If n already +// has a type, convlit1 has no effect. +// +// For explicit conversions, t must be non-nil, and integer-to-string +// conversions are allowed. +// +// For implicit conversions (e.g., assignments), t may be nil; if so, +// n is converted to its default type. +// +// If there's an error converting n to t, context is used in the error +// message. +func convlit1(n ir.Node, t *types.Type, explicit bool, context func() string) ir.Node { + if explicit && t == nil { + base.Fatalf("explicit conversion missing type") + } + if t != nil && t.IsUntyped() { + base.Fatalf("bad conversion to untyped: %v", t) + } + + if n == nil || n.Type() == nil { + // Allow sloppy callers. + return n + } + if !n.Type().IsUntyped() { + // Already typed; nothing to do. + return n + } + + // Nil is technically not a constant, so handle it specially. + if n.Type().Kind() == types.TNIL { + if n.Op() != ir.ONIL { + base.Fatalf("unexpected op: %v (%v)", n, n.Op()) + } + n = ir.Copy(n) + if t == nil { + base.Errorf("use of untyped nil") + n.SetDiag(true) + n.SetType(nil) + return n + } + + if !t.HasNil() { + // Leave for caller to handle. + return n + } + + n.SetType(t) + return n + } + + if t == nil || !ir.OKForConst[t.Kind()] { + t = defaultType(n.Type()) + } + + switch n.Op() { + default: + base.Fatalf("unexpected untyped expression: %v", n) + + case ir.OLITERAL: + v := convertVal(n.Val(), t, explicit) + if v.Kind() == constant.Unknown { + n = ir.NewConstExpr(n.Val(), n) + break + } + n = ir.NewConstExpr(v, n) + n.SetType(t) + return n + + case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.OREAL, ir.OIMAG: + ot := operandType(n.Op(), t) + if ot == nil { + n = DefaultLit(n, nil) + break + } + + n := n.(*ir.UnaryExpr) + n.X = convlit(n.X, ot) + if n.X.Type() == nil { + n.SetType(nil) + return n + } + n.SetType(t) + return n + + case ir.OADD, ir.OSUB, ir.OMUL, ir.ODIV, ir.OMOD, ir.OOR, ir.OXOR, ir.OAND, ir.OANDNOT, ir.OOROR, ir.OANDAND, ir.OCOMPLEX: + ot := operandType(n.Op(), t) + if ot == nil { + n = DefaultLit(n, nil) + break + } + + var l, r ir.Node + switch n := n.(type) { + case *ir.BinaryExpr: + n.X = convlit(n.X, ot) + n.Y = convlit(n.Y, ot) + l, r = n.X, n.Y + case *ir.LogicalExpr: + n.X = convlit(n.X, ot) + n.Y = convlit(n.Y, ot) + l, r = n.X, n.Y + } + + if l.Type() == nil || r.Type() == nil { + n.SetType(nil) + return n + } + if !types.Identical(l.Type(), r.Type()) { + base.Errorf("invalid operation: %v (mismatched types %v and %v)", n, l.Type(), r.Type()) + n.SetType(nil) + return n + } + + n.SetType(t) + return n + + case ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE: + n := n.(*ir.BinaryExpr) + if !t.IsBoolean() { + break + } + n.SetType(t) + return n + + case ir.OLSH, ir.ORSH: + n := n.(*ir.BinaryExpr) + n.X = convlit1(n.X, t, explicit, nil) + n.SetType(n.X.Type()) + if n.Type() != nil && !n.Type().IsInteger() { + base.Errorf("invalid operation: %v (shift of type %v)", n, n.Type()) + n.SetType(nil) + } + return n + } + + if !n.Diag() { + if !t.Broke() { + if explicit { + base.Errorf("cannot convert %L to type %v", n, t) + } else if context != nil { + base.Errorf("cannot use %L as type %v in %s", n, t, context()) + } else { + base.Errorf("cannot use %L as type %v", n, t) + } + } + n.SetDiag(true) + } + n.SetType(nil) + return n +} + +func operandType(op ir.Op, t *types.Type) *types.Type { + switch op { + case ir.OCOMPLEX: + if t.IsComplex() { + return types.FloatForComplex(t) + } + case ir.OREAL, ir.OIMAG: + if t.IsFloat() { + return types.ComplexForFloat(t) + } + default: + if okfor[op][t.Kind()] { + return t + } + } + return nil +} + +// convertVal converts v into a representation appropriate for t. If +// no such representation exists, it returns Val{} instead. +// +// If explicit is true, then conversions from integer to string are +// also allowed. +func convertVal(v constant.Value, t *types.Type, explicit bool) constant.Value { + switch ct := v.Kind(); ct { + case constant.Bool: + if t.IsBoolean() { + return v + } + + case constant.String: + if t.IsString() { + return v + } + + case constant.Int: + if explicit && t.IsString() { + return tostr(v) + } + fallthrough + case constant.Float, constant.Complex: + switch { + case t.IsInteger(): + v = toint(v) + overflow(v, t) + return v + case t.IsFloat(): + v = toflt(v) + v = truncfltlit(v, t) + return v + case t.IsComplex(): + v = tocplx(v) + v = trunccmplxlit(v, t) + return v + } + } + + return constant.MakeUnknown() +} + +func tocplx(v constant.Value) constant.Value { + return constant.ToComplex(v) +} + +func toflt(v constant.Value) constant.Value { + if v.Kind() == constant.Complex { + if constant.Sign(constant.Imag(v)) != 0 { + base.Errorf("constant %v truncated to real", v) + } + v = constant.Real(v) + } + + return constant.ToFloat(v) +} + +func toint(v constant.Value) constant.Value { + if v.Kind() == constant.Complex { + if constant.Sign(constant.Imag(v)) != 0 { + base.Errorf("constant %v truncated to integer", v) + } + v = constant.Real(v) + } + + if v := constant.ToInt(v); v.Kind() == constant.Int { + return v + } + + // The value of v cannot be represented as an integer; + // so we need to print an error message. + // Unfortunately some float values cannot be + // reasonably formatted for inclusion in an error + // message (example: 1 + 1e-100), so first we try to + // format the float; if the truncation resulted in + // something that looks like an integer we omit the + // value from the error message. + // (See issue #11371). + f := ir.BigFloat(v) + if f.MantExp(nil) > 2*ir.ConstPrec { + base.Errorf("integer too large") + } else { + var t big.Float + t.Parse(fmt.Sprint(v), 0) + if t.IsInt() { + base.Errorf("constant truncated to integer") + } else { + base.Errorf("constant %v truncated to integer", v) + } + } + + // Prevent follow-on errors. + // TODO(mdempsky): Use constant.MakeUnknown() instead. + return constant.MakeInt64(1) +} + +// overflow reports whether constant value v is too large +// to represent with type t, and emits an error message if so. +func overflow(v constant.Value, t *types.Type) bool { + // v has already been converted + // to appropriate form for t. + if t.IsUntyped() { + return false + } + if v.Kind() == constant.Int && constant.BitLen(v) > ir.ConstPrec { + base.Errorf("integer too large") + return true + } + if ir.ConstOverflow(v, t) { + base.Errorf("constant %v overflows %v", types.FmtConst(v, false), t) + return true + } + return false +} + +func tostr(v constant.Value) constant.Value { + if v.Kind() == constant.Int { + r := unicode.ReplacementChar + if x, ok := constant.Uint64Val(v); ok && x <= unicode.MaxRune { + r = rune(x) + } + v = constant.MakeString(string(r)) + } + return v +} + +var tokenForOp = [...]token.Token{ + ir.OPLUS: token.ADD, + ir.ONEG: token.SUB, + ir.ONOT: token.NOT, + ir.OBITNOT: token.XOR, + + ir.OADD: token.ADD, + ir.OSUB: token.SUB, + ir.OMUL: token.MUL, + ir.ODIV: token.QUO, + ir.OMOD: token.REM, + ir.OOR: token.OR, + ir.OXOR: token.XOR, + ir.OAND: token.AND, + ir.OANDNOT: token.AND_NOT, + ir.OOROR: token.LOR, + ir.OANDAND: token.LAND, + + ir.OEQ: token.EQL, + ir.ONE: token.NEQ, + ir.OLT: token.LSS, + ir.OLE: token.LEQ, + ir.OGT: token.GTR, + ir.OGE: token.GEQ, + + ir.OLSH: token.SHL, + ir.ORSH: token.SHR, +} + +// EvalConst returns a constant-evaluated expression equivalent to n. +// If n is not a constant, EvalConst returns n. +// Otherwise, EvalConst returns a new OLITERAL with the same value as n, +// and with .Orig pointing back to n. +func EvalConst(n ir.Node) ir.Node { + // Pick off just the opcodes that can be constant evaluated. + switch n.Op() { + case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT: + n := n.(*ir.UnaryExpr) + nl := n.X + if nl.Op() == ir.OLITERAL { + var prec uint + if n.Type().IsUnsigned() { + prec = uint(n.Type().Size() * 8) + } + return OrigConst(n, constant.UnaryOp(tokenForOp[n.Op()], nl.Val(), prec)) + } + + case ir.OADD, ir.OSUB, ir.OMUL, ir.ODIV, ir.OMOD, ir.OOR, ir.OXOR, ir.OAND, ir.OANDNOT: + n := n.(*ir.BinaryExpr) + nl, nr := n.X, n.Y + if nl.Op() == ir.OLITERAL && nr.Op() == ir.OLITERAL { + rval := nr.Val() + + // check for divisor underflow in complex division (see issue 20227) + if n.Op() == ir.ODIV && n.Type().IsComplex() && constant.Sign(square(constant.Real(rval))) == 0 && constant.Sign(square(constant.Imag(rval))) == 0 { + base.Errorf("complex division by zero") + n.SetType(nil) + return n + } + if (n.Op() == ir.ODIV || n.Op() == ir.OMOD) && constant.Sign(rval) == 0 { + base.Errorf("division by zero") + n.SetType(nil) + return n + } + + tok := tokenForOp[n.Op()] + if n.Op() == ir.ODIV && n.Type().IsInteger() { + tok = token.QUO_ASSIGN // integer division + } + return OrigConst(n, constant.BinaryOp(nl.Val(), tok, rval)) + } + + case ir.OOROR, ir.OANDAND: + n := n.(*ir.LogicalExpr) + nl, nr := n.X, n.Y + if nl.Op() == ir.OLITERAL && nr.Op() == ir.OLITERAL { + return OrigConst(n, constant.BinaryOp(nl.Val(), tokenForOp[n.Op()], nr.Val())) + } + + case ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE: + n := n.(*ir.BinaryExpr) + nl, nr := n.X, n.Y + if nl.Op() == ir.OLITERAL && nr.Op() == ir.OLITERAL { + return OrigBool(n, constant.Compare(nl.Val(), tokenForOp[n.Op()], nr.Val())) + } + + case ir.OLSH, ir.ORSH: + n := n.(*ir.BinaryExpr) + nl, nr := n.X, n.Y + if nl.Op() == ir.OLITERAL && nr.Op() == ir.OLITERAL { + // shiftBound from go/types; "so we can express smallestFloat64" (see issue #44057) + const shiftBound = 1023 - 1 + 52 + s, ok := constant.Uint64Val(nr.Val()) + if !ok || s > shiftBound { + base.Errorf("invalid shift count %v", nr) + n.SetType(nil) + break + } + return OrigConst(n, constant.Shift(toint(nl.Val()), tokenForOp[n.Op()], uint(s))) + } + + case ir.OCONV, ir.ORUNESTR: + n := n.(*ir.ConvExpr) + nl := n.X + if ir.OKForConst[n.Type().Kind()] && nl.Op() == ir.OLITERAL { + return OrigConst(n, convertVal(nl.Val(), n.Type(), true)) + } + + case ir.OCONVNOP: + n := n.(*ir.ConvExpr) + nl := n.X + if ir.OKForConst[n.Type().Kind()] && nl.Op() == ir.OLITERAL { + // set so n.Orig gets OCONV instead of OCONVNOP + n.SetOp(ir.OCONV) + return OrigConst(n, nl.Val()) + } + + case ir.OADDSTR: + // Merge adjacent constants in the argument list. + n := n.(*ir.AddStringExpr) + s := n.List + need := 0 + for i := 0; i < len(s); i++ { + if i == 0 || !ir.IsConst(s[i-1], constant.String) || !ir.IsConst(s[i], constant.String) { + // Can't merge s[i] into s[i-1]; need a slot in the list. + need++ + } + } + if need == len(s) { + return n + } + if need == 1 { + var strs []string + for _, c := range s { + strs = append(strs, ir.StringVal(c)) + } + return OrigConst(n, constant.MakeString(strings.Join(strs, ""))) + } + newList := make([]ir.Node, 0, need) + for i := 0; i < len(s); i++ { + if ir.IsConst(s[i], constant.String) && i+1 < len(s) && ir.IsConst(s[i+1], constant.String) { + // merge from i up to but not including i2 + var strs []string + i2 := i + for i2 < len(s) && ir.IsConst(s[i2], constant.String) { + strs = append(strs, ir.StringVal(s[i2])) + i2++ + } + + nl := ir.Copy(n).(*ir.AddStringExpr) + nl.List = s[i:i2] + newList = append(newList, OrigConst(nl, constant.MakeString(strings.Join(strs, "")))) + i = i2 - 1 + } else { + newList = append(newList, s[i]) + } + } + + nn := ir.Copy(n).(*ir.AddStringExpr) + nn.List = newList + return nn + + case ir.OCAP, ir.OLEN: + n := n.(*ir.UnaryExpr) + nl := n.X + switch nl.Type().Kind() { + case types.TSTRING: + if ir.IsConst(nl, constant.String) { + return OrigInt(n, int64(len(ir.StringVal(nl)))) + } + case types.TARRAY: + if !anyCallOrChan(nl) { + return OrigInt(n, nl.Type().NumElem()) + } + } + + case ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF: + n := n.(*ir.UnaryExpr) + return OrigInt(n, evalunsafe(n)) + + case ir.OREAL: + n := n.(*ir.UnaryExpr) + nl := n.X + if nl.Op() == ir.OLITERAL { + return OrigConst(n, constant.Real(nl.Val())) + } + + case ir.OIMAG: + n := n.(*ir.UnaryExpr) + nl := n.X + if nl.Op() == ir.OLITERAL { + return OrigConst(n, constant.Imag(nl.Val())) + } + + case ir.OCOMPLEX: + n := n.(*ir.BinaryExpr) + nl, nr := n.X, n.Y + if nl.Op() == ir.OLITERAL && nr.Op() == ir.OLITERAL { + return OrigConst(n, makeComplex(nl.Val(), nr.Val())) + } + } + + return n +} + +func makeFloat64(f float64) constant.Value { + if math.IsInf(f, 0) { + base.Fatalf("infinity is not a valid constant") + } + return constant.MakeFloat64(f) +} + +func makeComplex(real, imag constant.Value) constant.Value { + return constant.BinaryOp(constant.ToFloat(real), token.ADD, constant.MakeImag(constant.ToFloat(imag))) +} + +func square(x constant.Value) constant.Value { + return constant.BinaryOp(x, token.MUL, x) +} + +// For matching historical "constant OP overflow" error messages. +// TODO(mdempsky): Replace with error messages like go/types uses. +var overflowNames = [...]string{ + ir.OADD: "addition", + ir.OSUB: "subtraction", + ir.OMUL: "multiplication", + ir.OLSH: "shift", + ir.OXOR: "bitwise XOR", + ir.OBITNOT: "bitwise complement", +} + +// OrigConst returns an OLITERAL with orig n and value v. +func OrigConst(n ir.Node, v constant.Value) ir.Node { + lno := ir.SetPos(n) + v = convertVal(v, n.Type(), false) + base.Pos = lno + + switch v.Kind() { + case constant.Int: + if constant.BitLen(v) <= ir.ConstPrec { + break + } + fallthrough + case constant.Unknown: + what := overflowNames[n.Op()] + if what == "" { + base.Fatalf("unexpected overflow: %v", n.Op()) + } + base.ErrorfAt(n.Pos(), "constant %v overflow", what) + n.SetType(nil) + return n + } + + return ir.NewConstExpr(v, n) +} + +func OrigBool(n ir.Node, v bool) ir.Node { + return OrigConst(n, constant.MakeBool(v)) +} + +func OrigInt(n ir.Node, v int64) ir.Node { + return OrigConst(n, constant.MakeInt64(v)) +} + +// DefaultLit on both nodes simultaneously; +// if they're both ideal going in they better +// get the same type going out. +// force means must assign concrete (non-ideal) type. +// The results of defaultlit2 MUST be assigned back to l and r, e.g. +// n.Left, n.Right = defaultlit2(n.Left, n.Right, force) +func defaultlit2(l ir.Node, r ir.Node, force bool) (ir.Node, ir.Node) { + if l.Type() == nil || r.Type() == nil { + return l, r + } + + if !l.Type().IsInterface() && !r.Type().IsInterface() { + // Can't mix bool with non-bool, string with non-string. + if l.Type().IsBoolean() != r.Type().IsBoolean() { + return l, r + } + if l.Type().IsString() != r.Type().IsString() { + return l, r + } + } + + if !l.Type().IsUntyped() { + r = convlit(r, l.Type()) + return l, r + } + + if !r.Type().IsUntyped() { + l = convlit(l, r.Type()) + return l, r + } + + if !force { + return l, r + } + + // Can't mix nil with anything untyped. + if ir.IsNil(l) || ir.IsNil(r) { + return l, r + } + t := defaultType(mixUntyped(l.Type(), r.Type())) + l = convlit(l, t) + r = convlit(r, t) + return l, r +} + +func mixUntyped(t1, t2 *types.Type) *types.Type { + if t1 == t2 { + return t1 + } + + rank := func(t *types.Type) int { + switch t { + case types.UntypedInt: + return 0 + case types.UntypedRune: + return 1 + case types.UntypedFloat: + return 2 + case types.UntypedComplex: + return 3 + } + base.Fatalf("bad type %v", t) + panic("unreachable") + } + + if rank(t2) > rank(t1) { + return t2 + } + return t1 +} + +func defaultType(t *types.Type) *types.Type { + if !t.IsUntyped() || t.Kind() == types.TNIL { + return t + } + + switch t { + case types.UntypedBool: + return types.Types[types.TBOOL] + case types.UntypedString: + return types.Types[types.TSTRING] + case types.UntypedInt: + return types.Types[types.TINT] + case types.UntypedRune: + return types.RuneType + case types.UntypedFloat: + return types.Types[types.TFLOAT64] + case types.UntypedComplex: + return types.Types[types.TCOMPLEX128] + } + + base.Fatalf("bad type %v", t) + return nil +} + +// IndexConst checks if Node n contains a constant expression +// representable as a non-negative int and returns its value. +// If n is not a constant expression, not representable as an +// integer, or negative, it returns -1. If n is too large, it +// returns -2. +func IndexConst(n ir.Node) int64 { + if n.Op() != ir.OLITERAL { + return -1 + } + if !n.Type().IsInteger() && n.Type().Kind() != types.TIDEAL { + return -1 + } + + v := toint(n.Val()) + if v.Kind() != constant.Int || constant.Sign(v) < 0 { + return -1 + } + if ir.ConstOverflow(v, types.Types[types.TINT]) { + return -2 + } + return ir.IntVal(types.Types[types.TINT], v) +} + +// anyCallOrChan reports whether n contains any calls or channel operations. +func anyCallOrChan(n ir.Node) bool { + return ir.Any(n, func(n ir.Node) bool { + switch n.Op() { + case ir.OAPPEND, + ir.OCALL, + ir.OCALLFUNC, + ir.OCALLINTER, + ir.OCALLMETH, + ir.OCAP, + ir.OCLOSE, + ir.OCOMPLEX, + ir.OCOPY, + ir.ODELETE, + ir.OIMAG, + ir.OLEN, + ir.OMAKE, + ir.ONEW, + ir.OPANIC, + ir.OPRINT, + ir.OPRINTN, + ir.OREAL, + ir.ORECOVER, + ir.ORECV, + ir.OUNSAFEADD, + ir.OUNSAFESLICE: + return true + } + return false + }) +} + +// A constSet represents a set of Go constant expressions. +type constSet struct { + m map[constSetKey]src.XPos +} + +type constSetKey struct { + typ *types.Type + val interface{} +} + +// add adds constant expression n to s. If a constant expression of +// equal value and identical type has already been added, then add +// reports an error about the duplicate value. +// +// pos provides position information for where expression n occurred +// (in case n does not have its own position information). what and +// where are used in the error message. +// +// n must not be an untyped constant. +func (s *constSet) add(pos src.XPos, n ir.Node, what, where string) { + if conv := n; conv.Op() == ir.OCONVIFACE { + conv := conv.(*ir.ConvExpr) + if conv.Implicit() { + n = conv.X + } + } + + if !ir.IsConstNode(n) || n.Type() == nil { + return + } + if n.Type().IsUntyped() { + base.Fatalf("%v is untyped", n) + } + + // Consts are only duplicates if they have the same value and + // identical types. + // + // In general, we have to use types.Identical to test type + // identity, because == gives false negatives for anonymous + // types and the byte/uint8 and rune/int32 builtin type + // aliases. However, this is not a problem here, because + // constant expressions are always untyped or have a named + // type, and we explicitly handle the builtin type aliases + // below. + // + // This approach may need to be revisited though if we fix + // #21866 by treating all type aliases like byte/uint8 and + // rune/int32. + + typ := n.Type() + switch typ { + case types.ByteType: + typ = types.Types[types.TUINT8] + case types.RuneType: + typ = types.Types[types.TINT32] + } + k := constSetKey{typ, ir.ConstValue(n)} + + if ir.HasUniquePos(n) { + pos = n.Pos() + } + + if s.m == nil { + s.m = make(map[constSetKey]src.XPos) + } + + if prevPos, isDup := s.m[k]; isDup { + base.ErrorfAt(pos, "duplicate %s %s in %s\n\tprevious %s at %v", + what, nodeAndVal(n), where, + what, base.FmtPos(prevPos)) + } else { + s.m[k] = pos + } +} + +// nodeAndVal reports both an expression and its constant value, if +// the latter is non-obvious. +// +// TODO(mdempsky): This could probably be a fmt.go flag. +func nodeAndVal(n ir.Node) string { + show := fmt.Sprint(n) + val := ir.ConstValue(n) + if s := fmt.Sprintf("%#v", val); show != s { + show += " (value " + s + ")" + } + return show +} + +// evalunsafe evaluates a package unsafe operation and returns the result. +func evalunsafe(n ir.Node) int64 { + switch n.Op() { + case ir.OALIGNOF, ir.OSIZEOF: + n := n.(*ir.UnaryExpr) + n.X = Expr(n.X) + n.X = DefaultLit(n.X, nil) + tr := n.X.Type() + if tr == nil { + return 0 + } + types.CalcSize(tr) + if n.Op() == ir.OALIGNOF { + return int64(tr.Align) + } + return tr.Width + + case ir.OOFFSETOF: + // must be a selector. + n := n.(*ir.UnaryExpr) + if n.X.Op() != ir.OXDOT { + base.Errorf("invalid expression %v", n) + return 0 + } + sel := n.X.(*ir.SelectorExpr) + + // Remember base of selector to find it back after dot insertion. + // Since r->left may be mutated by typechecking, check it explicitly + // first to track it correctly. + sel.X = Expr(sel.X) + sbase := sel.X + + tsel := Expr(sel) + n.X = tsel + if tsel.Type() == nil { + return 0 + } + switch tsel.Op() { + case ir.ODOT, ir.ODOTPTR: + break + case ir.OCALLPART: + base.Errorf("invalid expression %v: argument is a method value", n) + return 0 + default: + base.Errorf("invalid expression %v", n) + return 0 + } + + // Sum offsets for dots until we reach sbase. + var v int64 + var next ir.Node + for r := tsel; r != sbase; r = next { + switch r.Op() { + case ir.ODOTPTR: + // For Offsetof(s.f), s may itself be a pointer, + // but accessing f must not otherwise involve + // indirection via embedded pointer types. + r := r.(*ir.SelectorExpr) + if r.X != sbase { + base.Errorf("invalid expression %v: selector implies indirection of embedded %v", n, r.X) + return 0 + } + fallthrough + case ir.ODOT: + r := r.(*ir.SelectorExpr) + v += r.Offset() + next = r.X + default: + ir.Dump("unsafenmagic", tsel) + base.Fatalf("impossible %v node after dot insertion", r.Op()) + } + } + return v + } + + base.Fatalf("unexpected op %v", n.Op()) + return 0 +} diff --git a/src/cmd/compile/internal/typecheck/dcl.go b/src/cmd/compile/internal/typecheck/dcl.go new file mode 100644 index 0000000000000000000000000000000000000000..5b771e3c0b1d836cb915efcd776aba3820464d41 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/dcl.go @@ -0,0 +1,484 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "fmt" + "strconv" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +var DeclContext ir.Class = ir.PEXTERN // PEXTERN/PAUTO + +func DeclFunc(sym *types.Sym, tfn ir.Ntype) *ir.Func { + if tfn.Op() != ir.OTFUNC { + base.Fatalf("expected OTFUNC node, got %v", tfn) + } + + fn := ir.NewFunc(base.Pos) + fn.Nname = ir.NewNameAt(base.Pos, sym) + fn.Nname.Func = fn + fn.Nname.Defn = fn + fn.Nname.Ntype = tfn + ir.MarkFunc(fn.Nname) + StartFuncBody(fn) + fn.Nname.Ntype = typecheckNtype(fn.Nname.Ntype) + return fn +} + +// Declare records that Node n declares symbol n.Sym in the specified +// declaration context. +func Declare(n *ir.Name, ctxt ir.Class) { + if ir.IsBlank(n) { + return + } + + s := n.Sym() + + // kludgy: TypecheckAllowed means we're past parsing. Eg reflectdata.methodWrapper may declare out of package names later. + if !inimport && !TypecheckAllowed && s.Pkg != types.LocalPkg { + base.ErrorfAt(n.Pos(), "cannot declare name %v", s) + } + + if ctxt == ir.PEXTERN { + if s.Name == "init" { + base.ErrorfAt(n.Pos(), "cannot declare init - must be func") + } + if s.Name == "main" && s.Pkg.Name == "main" { + base.ErrorfAt(n.Pos(), "cannot declare main - must be func") + } + Target.Externs = append(Target.Externs, n) + } else { + if ir.CurFunc == nil && ctxt == ir.PAUTO { + base.Pos = n.Pos() + base.Fatalf("automatic outside function") + } + if ir.CurFunc != nil && ctxt != ir.PFUNC && n.Op() == ir.ONAME { + ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, n) + } + types.Pushdcl(s) + n.Curfn = ir.CurFunc + } + + if ctxt == ir.PAUTO { + n.SetFrameOffset(0) + } + + if s.Block == types.Block { + // functype will print errors about duplicate function arguments. + // Don't repeat the error here. + if ctxt != ir.PPARAM && ctxt != ir.PPARAMOUT { + Redeclared(n.Pos(), s, "in this block") + } + } + + s.Block = types.Block + s.Lastlineno = base.Pos + s.Def = n + n.Class = ctxt + if ctxt == ir.PFUNC { + n.Sym().SetFunc(true) + } + + autoexport(n, ctxt) +} + +// Export marks n for export (or reexport). +func Export(n *ir.Name) { + if n.Sym().OnExportList() { + return + } + n.Sym().SetOnExportList(true) + + if base.Flag.E != 0 { + fmt.Printf("export symbol %v\n", n.Sym()) + } + + Target.Exports = append(Target.Exports, n) +} + +// Redeclared emits a diagnostic about symbol s being redeclared at pos. +func Redeclared(pos src.XPos, s *types.Sym, where string) { + if !s.Lastlineno.IsKnown() { + var pkgName *ir.PkgName + if s.Def == nil { + for id, pkg := range DotImportRefs { + if id.Sym().Name == s.Name { + pkgName = pkg + break + } + } + } else { + pkgName = DotImportRefs[s.Def.(*ir.Ident)] + } + base.ErrorfAt(pos, "%v redeclared %s\n"+ + "\t%v: previous declaration during import %q", s, where, base.FmtPos(pkgName.Pos()), pkgName.Pkg.Path) + } else { + prevPos := s.Lastlineno + + // When an import and a declaration collide in separate files, + // present the import as the "redeclared", because the declaration + // is visible where the import is, but not vice versa. + // See issue 4510. + if s.Def == nil { + pos, prevPos = prevPos, pos + } + + base.ErrorfAt(pos, "%v redeclared %s\n"+ + "\t%v: previous declaration", s, where, base.FmtPos(prevPos)) + } +} + +// declare the function proper +// and declare the arguments. +// called in extern-declaration context +// returns in auto-declaration context. +func StartFuncBody(fn *ir.Func) { + // change the declaration context from extern to auto + funcStack = append(funcStack, funcStackEnt{ir.CurFunc, DeclContext}) + ir.CurFunc = fn + DeclContext = ir.PAUTO + + types.Markdcl() + + if fn.Nname.Ntype != nil { + funcargs(fn.Nname.Ntype.(*ir.FuncType)) + } else { + funcargs2(fn.Type()) + } +} + +// finish the body. +// called in auto-declaration context. +// returns in extern-declaration context. +func FinishFuncBody() { + // change the declaration context from auto to previous context + types.Popdcl() + var e funcStackEnt + funcStack, e = funcStack[:len(funcStack)-1], funcStack[len(funcStack)-1] + ir.CurFunc, DeclContext = e.curfn, e.dclcontext +} + +func CheckFuncStack() { + if len(funcStack) != 0 { + base.Fatalf("funcStack is non-empty: %v", len(funcStack)) + } +} + +// Add a method, declared as a function. +// - msym is the method symbol +// - t is function type (with receiver) +// Returns a pointer to the existing or added Field; or nil if there's an error. +func addmethod(n *ir.Func, msym *types.Sym, t *types.Type, local, nointerface bool) *types.Field { + if msym == nil { + base.Fatalf("no method symbol") + } + + // get parent type sym + rf := t.Recv() // ptr to this structure + if rf == nil { + base.Errorf("missing receiver") + return nil + } + + mt := types.ReceiverBaseType(rf.Type) + if mt == nil || mt.Sym() == nil { + pa := rf.Type + t := pa + if t != nil && t.IsPtr() { + if t.Sym() != nil { + base.Errorf("invalid receiver type %v (%v is a pointer type)", pa, t) + return nil + } + t = t.Elem() + } + + switch { + case t == nil || t.Broke(): + // rely on typecheck having complained before + case t.Sym() == nil: + base.Errorf("invalid receiver type %v (%v is not a defined type)", pa, t) + case t.IsPtr(): + base.Errorf("invalid receiver type %v (%v is a pointer type)", pa, t) + case t.IsInterface(): + base.Errorf("invalid receiver type %v (%v is an interface type)", pa, t) + default: + // Should have picked off all the reasons above, + // but just in case, fall back to generic error. + base.Errorf("invalid receiver type %v (%L / %L)", pa, pa, t) + } + return nil + } + + if local && mt.Sym().Pkg != types.LocalPkg { + base.Errorf("cannot define new methods on non-local type %v", mt) + return nil + } + + if msym.IsBlank() { + return nil + } + + if mt.IsStruct() { + for _, f := range mt.Fields().Slice() { + if f.Sym == msym { + base.Errorf("type %v has both field and method named %v", mt, msym) + f.SetBroke(true) + return nil + } + } + } + + for _, f := range mt.Methods().Slice() { + if msym.Name != f.Sym.Name { + continue + } + // types.Identical only checks that incoming and result parameters match, + // so explicitly check that the receiver parameters match too. + if !types.Identical(t, f.Type) || !types.Identical(t.Recv().Type, f.Type.Recv().Type) { + base.Errorf("method redeclared: %v.%v\n\t%v\n\t%v", mt, msym, f.Type, t) + } + return f + } + + f := types.NewField(base.Pos, msym, t) + f.Nname = n.Nname + f.SetNointerface(nointerface) + + mt.Methods().Append(f) + return f +} + +func autoexport(n *ir.Name, ctxt ir.Class) { + if n.Sym().Pkg != types.LocalPkg { + return + } + if (ctxt != ir.PEXTERN && ctxt != ir.PFUNC) || DeclContext != ir.PEXTERN { + return + } + if n.Type() != nil && n.Type().IsKind(types.TFUNC) && ir.IsMethod(n) { + return + } + + if types.IsExported(n.Sym().Name) || n.Sym().Name == "init" { + Export(n) + } + if base.Flag.AsmHdr != "" && !n.Sym().Asm() { + n.Sym().SetAsm(true) + Target.Asms = append(Target.Asms, n) + } +} + +// checkdupfields emits errors for duplicately named fields or methods in +// a list of struct or interface types. +func checkdupfields(what string, fss ...[]*types.Field) { + seen := make(map[*types.Sym]bool) + for _, fs := range fss { + for _, f := range fs { + if f.Sym == nil || f.Sym.IsBlank() { + continue + } + if seen[f.Sym] { + base.ErrorfAt(f.Pos, "duplicate %s %s", what, f.Sym.Name) + continue + } + seen[f.Sym] = true + } + } +} + +// structs, functions, and methods. +// they don't belong here, but where do they belong? +func checkembeddedtype(t *types.Type) { + if t == nil { + return + } + + if t.Sym() == nil && t.IsPtr() { + t = t.Elem() + if t.IsInterface() { + base.Errorf("embedded type cannot be a pointer to interface") + } + } + + if t.IsPtr() || t.IsUnsafePtr() { + base.Errorf("embedded type cannot be a pointer") + } else if t.Kind() == types.TFORW && !t.ForwardType().Embedlineno.IsKnown() { + t.ForwardType().Embedlineno = base.Pos + } +} + +// TODO(mdempsky): Move to package types. +func FakeRecv() *types.Field { + return types.NewField(src.NoXPos, nil, types.FakeRecvType()) +} + +var fakeRecvField = FakeRecv + +var funcStack []funcStackEnt // stack of previous values of ir.CurFunc/DeclContext + +type funcStackEnt struct { + curfn *ir.Func + dclcontext ir.Class +} + +func funcarg(n *ir.Field, ctxt ir.Class) { + if n.Sym == nil { + return + } + + name := ir.NewNameAt(n.Pos, n.Sym) + n.Decl = name + name.Ntype = n.Ntype + Declare(name, ctxt) +} + +func funcarg2(f *types.Field, ctxt ir.Class) { + if f.Sym == nil { + return + } + n := ir.NewNameAt(f.Pos, f.Sym) + f.Nname = n + n.SetType(f.Type) + Declare(n, ctxt) +} + +func funcargs(nt *ir.FuncType) { + if nt.Op() != ir.OTFUNC { + base.Fatalf("funcargs %v", nt.Op()) + } + + // declare the receiver and in arguments. + if nt.Recv != nil { + funcarg(nt.Recv, ir.PPARAM) + } + for _, n := range nt.Params { + funcarg(n, ir.PPARAM) + } + + // declare the out arguments. + gen := len(nt.Params) + for _, n := range nt.Results { + if n.Sym == nil { + // Name so that escape analysis can track it. ~r stands for 'result'. + n.Sym = LookupNum("~r", gen) + gen++ + } + if n.Sym.IsBlank() { + // Give it a name so we can assign to it during return. ~b stands for 'blank'. + // The name must be different from ~r above because if you have + // func f() (_ int) + // func g() int + // f is allowed to use a plain 'return' with no arguments, while g is not. + // So the two cases must be distinguished. + n.Sym = LookupNum("~b", gen) + gen++ + } + + funcarg(n, ir.PPARAMOUT) + } +} + +// Same as funcargs, except run over an already constructed TFUNC. +// This happens during import, where the hidden_fndcl rule has +// used functype directly to parse the function's type. +func funcargs2(t *types.Type) { + if t.Kind() != types.TFUNC { + base.Fatalf("funcargs2 %v", t) + } + + for _, f := range t.Recvs().Fields().Slice() { + funcarg2(f, ir.PPARAM) + } + for _, f := range t.Params().Fields().Slice() { + funcarg2(f, ir.PPARAM) + } + for _, f := range t.Results().Fields().Slice() { + funcarg2(f, ir.PPARAMOUT) + } +} + +func Temp(t *types.Type) *ir.Name { + return TempAt(base.Pos, ir.CurFunc, t) +} + +// make a new Node off the books +func TempAt(pos src.XPos, curfn *ir.Func, t *types.Type) *ir.Name { + if curfn == nil { + base.Fatalf("no curfn for TempAt") + } + if curfn.Op() == ir.OCLOSURE { + ir.Dump("TempAt", curfn) + base.Fatalf("adding TempAt to wrong closure function") + } + if t == nil { + base.Fatalf("TempAt called with nil type") + } + if t.Kind() == types.TFUNC && t.Recv() != nil { + base.Fatalf("misuse of method type: %v", t) + } + + s := &types.Sym{ + Name: autotmpname(len(curfn.Dcl)), + Pkg: types.LocalPkg, + } + n := ir.NewNameAt(pos, s) + s.Def = n + n.SetType(t) + n.Class = ir.PAUTO + n.SetEsc(ir.EscNever) + n.Curfn = curfn + n.SetUsed(true) + n.SetAutoTemp(true) + curfn.Dcl = append(curfn.Dcl, n) + + types.CalcSize(t) + + return n +} + +// autotmpname returns the name for an autotmp variable numbered n. +func autotmpname(n int) string { + // Give each tmp a different name so that they can be registerized. + // Add a preceding . to avoid clashing with legal names. + const prefix = ".autotmp_" + // Start with a buffer big enough to hold a large n. + b := []byte(prefix + " ")[:len(prefix)] + b = strconv.AppendInt(b, int64(n), 10) + return types.InternString(b) +} + +// f is method type, with receiver. +// return function type, receiver as first argument (or not). +func NewMethodType(sig *types.Type, recv *types.Type) *types.Type { + nrecvs := 0 + if recv != nil { + nrecvs++ + } + + // TODO(mdempsky): Move this function to types. + // TODO(mdempsky): Preserve positions, names, and package from sig+recv. + + params := make([]*types.Field, nrecvs+sig.Params().Fields().Len()) + if recv != nil { + params[0] = types.NewField(base.Pos, nil, recv) + } + for i, param := range sig.Params().Fields().Slice() { + d := types.NewField(base.Pos, nil, param.Type) + d.SetIsDDD(param.IsDDD()) + params[nrecvs+i] = d + } + + results := make([]*types.Field, sig.Results().Fields().Len()) + for i, t := range sig.Results().Fields().Slice() { + results[i] = types.NewField(base.Pos, nil, t.Type) + } + + return types.NewSignature(types.LocalPkg, nil, nil, params, results) +} diff --git a/src/cmd/compile/internal/typecheck/export.go b/src/cmd/compile/internal/typecheck/export.go new file mode 100644 index 0000000000000000000000000000000000000000..63d0a1ec6c656ce23e504458eacb456e01d12783 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/export.go @@ -0,0 +1,74 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +// importalias declares symbol s as an imported type alias with type t. +// ipkg is the package being imported +func importalias(ipkg *types.Pkg, pos src.XPos, s *types.Sym, t *types.Type) *ir.Name { + return importobj(ipkg, pos, s, ir.OTYPE, ir.PEXTERN, t) +} + +// importconst declares symbol s as an imported constant with type t and value val. +// ipkg is the package being imported +func importconst(ipkg *types.Pkg, pos src.XPos, s *types.Sym, t *types.Type, val constant.Value) *ir.Name { + n := importobj(ipkg, pos, s, ir.OLITERAL, ir.PEXTERN, t) + n.SetVal(val) + return n +} + +// importfunc declares symbol s as an imported function with type t. +// ipkg is the package being imported +func importfunc(ipkg *types.Pkg, pos src.XPos, s *types.Sym, t *types.Type) *ir.Name { + n := importobj(ipkg, pos, s, ir.ONAME, ir.PFUNC, t) + n.Func = ir.NewFunc(pos) + n.Func.Nname = n + return n +} + +// importobj declares symbol s as an imported object representable by op. +// ipkg is the package being imported +func importobj(ipkg *types.Pkg, pos src.XPos, s *types.Sym, op ir.Op, ctxt ir.Class, t *types.Type) *ir.Name { + n := importsym(ipkg, pos, s, op, ctxt) + n.SetType(t) + if ctxt == ir.PFUNC { + n.Sym().SetFunc(true) + } + return n +} + +func importsym(ipkg *types.Pkg, pos src.XPos, s *types.Sym, op ir.Op, ctxt ir.Class) *ir.Name { + if n := s.PkgDef(); n != nil { + base.Fatalf("importsym of symbol that already exists: %v", n) + } + + n := ir.NewDeclNameAt(pos, op, s) + n.Class = ctxt // TODO(mdempsky): Move this into NewDeclNameAt too? + s.SetPkgDef(n) + return n +} + +// importtype returns the named type declared by symbol s. +// If no such type has been declared yet, a forward declaration is returned. +// ipkg is the package being imported +func importtype(ipkg *types.Pkg, pos src.XPos, s *types.Sym) *ir.Name { + n := importsym(ipkg, pos, s, ir.OTYPE, ir.PEXTERN) + n.SetType(types.NewNamed(n)) + return n +} + +// importvar declares symbol s as an imported variable with type t. +// ipkg is the package being imported +func importvar(ipkg *types.Pkg, pos src.XPos, s *types.Sym, t *types.Type) *ir.Name { + return importobj(ipkg, pos, s, ir.ONAME, ir.PEXTERN, t) +} diff --git a/src/cmd/compile/internal/typecheck/expr.go b/src/cmd/compile/internal/typecheck/expr.go new file mode 100644 index 0000000000000000000000000000000000000000..24d141e8a2ce9149573b481b42541d353a2be704 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/expr.go @@ -0,0 +1,881 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "fmt" + "go/constant" + "go/token" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" +) + +// tcAddr typechecks an OADDR node. +func tcAddr(n *ir.AddrExpr) ir.Node { + n.X = Expr(n.X) + if n.X.Type() == nil { + n.SetType(nil) + return n + } + + switch n.X.Op() { + case ir.OARRAYLIT, ir.OMAPLIT, ir.OSLICELIT, ir.OSTRUCTLIT: + n.SetOp(ir.OPTRLIT) + + default: + checklvalue(n.X, "take the address of") + r := ir.OuterValue(n.X) + if r.Op() == ir.ONAME { + r := r.(*ir.Name) + if ir.Orig(r) != r { + base.Fatalf("found non-orig name node %v", r) // TODO(mdempsky): What does this mean? + } + } + n.X = DefaultLit(n.X, nil) + if n.X.Type() == nil { + n.SetType(nil) + return n + } + } + + n.SetType(types.NewPtr(n.X.Type())) + return n +} + +func tcShift(n, l, r ir.Node) (ir.Node, ir.Node, *types.Type) { + if l.Type() == nil || r.Type() == nil { + return l, r, nil + } + + r = DefaultLit(r, types.Types[types.TUINT]) + t := r.Type() + if !t.IsInteger() { + base.Errorf("invalid operation: %v (shift count type %v, must be integer)", n, r.Type()) + return l, r, nil + } + if t.IsSigned() && !types.AllowsGoVersion(curpkg(), 1, 13) { + base.ErrorfVers("go1.13", "invalid operation: %v (signed shift count type %v)", n, r.Type()) + return l, r, nil + } + t = l.Type() + if t != nil && t.Kind() != types.TIDEAL && !t.IsInteger() { + base.Errorf("invalid operation: %v (shift of type %v)", n, t) + return l, r, nil + } + + // no DefaultLit for left + // the outer context gives the type + t = l.Type() + if (l.Type() == types.UntypedFloat || l.Type() == types.UntypedComplex) && r.Op() == ir.OLITERAL { + t = types.UntypedInt + } + return l, r, t +} + +func IsCmp(op ir.Op) bool { + return iscmp[op] +} + +// tcArith typechecks operands of a binary arithmetic expression. +// The result of tcArith MUST be assigned back to original operands, +// t is the type of the expression, and should be set by the caller. e.g: +// n.X, n.Y, t = tcArith(n, op, n.X, n.Y) +// n.SetType(t) +func tcArith(n ir.Node, op ir.Op, l, r ir.Node) (ir.Node, ir.Node, *types.Type) { + l, r = defaultlit2(l, r, false) + if l.Type() == nil || r.Type() == nil { + return l, r, nil + } + t := l.Type() + if t.Kind() == types.TIDEAL { + t = r.Type() + } + aop := ir.OXXX + if iscmp[n.Op()] && t.Kind() != types.TIDEAL && !types.Identical(l.Type(), r.Type()) { + // comparison is okay as long as one side is + // assignable to the other. convert so they have + // the same type. + // + // the only conversion that isn't a no-op is concrete == interface. + // in that case, check comparability of the concrete type. + // The conversion allocates, so only do it if the concrete type is huge. + converted := false + if r.Type().Kind() != types.TBLANK { + aop, _ = Assignop(l.Type(), r.Type()) + if aop != ir.OXXX { + if r.Type().IsInterface() && !l.Type().IsInterface() && !types.IsComparable(l.Type()) { + base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, op, typekind(l.Type())) + return l, r, nil + } + + types.CalcSize(l.Type()) + if r.Type().IsInterface() == l.Type().IsInterface() || l.Type().Width >= 1<<16 { + l = ir.NewConvExpr(base.Pos, aop, r.Type(), l) + l.SetTypecheck(1) + } + + t = r.Type() + converted = true + } + } + + if !converted && l.Type().Kind() != types.TBLANK { + aop, _ = Assignop(r.Type(), l.Type()) + if aop != ir.OXXX { + if l.Type().IsInterface() && !r.Type().IsInterface() && !types.IsComparable(r.Type()) { + base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, op, typekind(r.Type())) + return l, r, nil + } + + types.CalcSize(r.Type()) + if r.Type().IsInterface() == l.Type().IsInterface() || r.Type().Width >= 1<<16 { + r = ir.NewConvExpr(base.Pos, aop, l.Type(), r) + r.SetTypecheck(1) + } + + t = l.Type() + } + } + } + + if t.Kind() != types.TIDEAL && !types.Identical(l.Type(), r.Type()) { + l, r = defaultlit2(l, r, true) + if l.Type() == nil || r.Type() == nil { + return l, r, nil + } + if l.Type().IsInterface() == r.Type().IsInterface() || aop == 0 { + base.Errorf("invalid operation: %v (mismatched types %v and %v)", n, l.Type(), r.Type()) + return l, r, nil + } + } + + if t.Kind() == types.TIDEAL { + t = mixUntyped(l.Type(), r.Type()) + } + if dt := defaultType(t); !okfor[op][dt.Kind()] { + base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, op, typekind(t)) + return l, r, nil + } + + // okfor allows any array == array, map == map, func == func. + // restrict to slice/map/func == nil and nil == slice/map/func. + if l.Type().IsArray() && !types.IsComparable(l.Type()) { + base.Errorf("invalid operation: %v (%v cannot be compared)", n, l.Type()) + return l, r, nil + } + + if l.Type().IsSlice() && !ir.IsNil(l) && !ir.IsNil(r) { + base.Errorf("invalid operation: %v (slice can only be compared to nil)", n) + return l, r, nil + } + + if l.Type().IsMap() && !ir.IsNil(l) && !ir.IsNil(r) { + base.Errorf("invalid operation: %v (map can only be compared to nil)", n) + return l, r, nil + } + + if l.Type().Kind() == types.TFUNC && !ir.IsNil(l) && !ir.IsNil(r) { + base.Errorf("invalid operation: %v (func can only be compared to nil)", n) + return l, r, nil + } + + if l.Type().IsStruct() { + if f := types.IncomparableField(l.Type()); f != nil { + base.Errorf("invalid operation: %v (struct containing %v cannot be compared)", n, f.Type) + return l, r, nil + } + } + + if (op == ir.ODIV || op == ir.OMOD) && ir.IsConst(r, constant.Int) { + if constant.Sign(r.Val()) == 0 { + base.Errorf("division by zero") + return l, r, nil + } + } + + return l, r, t +} + +// The result of tcCompLit MUST be assigned back to n, e.g. +// n.Left = tcCompLit(n.Left) +func tcCompLit(n *ir.CompLitExpr) (res ir.Node) { + if base.EnableTrace && base.Flag.LowerT { + defer tracePrint("tcCompLit", n)(&res) + } + + lno := base.Pos + defer func() { + base.Pos = lno + }() + + if n.Ntype == nil { + base.ErrorfAt(n.Pos(), "missing type in composite literal") + n.SetType(nil) + return n + } + + // Save original node (including n.Right) + n.SetOrig(ir.Copy(n)) + + ir.SetPos(n.Ntype) + + // Need to handle [...]T arrays specially. + if array, ok := n.Ntype.(*ir.ArrayType); ok && array.Elem != nil && array.Len == nil { + array.Elem = typecheckNtype(array.Elem) + elemType := array.Elem.Type() + if elemType == nil { + n.SetType(nil) + return n + } + length := typecheckarraylit(elemType, -1, n.List, "array literal") + n.SetOp(ir.OARRAYLIT) + n.SetType(types.NewArray(elemType, length)) + n.Ntype = nil + return n + } + + n.Ntype = typecheckNtype(n.Ntype) + t := n.Ntype.Type() + if t == nil { + n.SetType(nil) + return n + } + n.SetType(t) + + switch t.Kind() { + default: + base.Errorf("invalid composite literal type %v", t) + n.SetType(nil) + + case types.TARRAY: + typecheckarraylit(t.Elem(), t.NumElem(), n.List, "array literal") + n.SetOp(ir.OARRAYLIT) + n.Ntype = nil + + case types.TSLICE: + length := typecheckarraylit(t.Elem(), -1, n.List, "slice literal") + n.SetOp(ir.OSLICELIT) + n.Ntype = nil + n.Len = length + + case types.TMAP: + var cs constSet + for i3, l := range n.List { + ir.SetPos(l) + if l.Op() != ir.OKEY { + n.List[i3] = Expr(l) + base.Errorf("missing key in map literal") + continue + } + l := l.(*ir.KeyExpr) + + r := l.Key + r = pushtype(r, t.Key()) + r = Expr(r) + l.Key = AssignConv(r, t.Key(), "map key") + cs.add(base.Pos, l.Key, "key", "map literal") + + r = l.Value + r = pushtype(r, t.Elem()) + r = Expr(r) + l.Value = AssignConv(r, t.Elem(), "map value") + } + + n.SetOp(ir.OMAPLIT) + n.Ntype = nil + + case types.TSTRUCT: + // Need valid field offsets for Xoffset below. + types.CalcSize(t) + + errored := false + if len(n.List) != 0 && nokeys(n.List) { + // simple list of variables + ls := n.List + for i, n1 := range ls { + ir.SetPos(n1) + n1 = Expr(n1) + ls[i] = n1 + if i >= t.NumFields() { + if !errored { + base.Errorf("too many values in %v", n) + errored = true + } + continue + } + + f := t.Field(i) + s := f.Sym + if s != nil && !types.IsExported(s.Name) && s.Pkg != types.LocalPkg { + base.Errorf("implicit assignment of unexported field '%s' in %v literal", s.Name, t) + } + // No pushtype allowed here. Must name fields for that. + n1 = AssignConv(n1, f.Type, "field value") + sk := ir.NewStructKeyExpr(base.Pos, f.Sym, n1) + sk.Offset = f.Offset + ls[i] = sk + } + if len(ls) < t.NumFields() { + base.Errorf("too few values in %v", n) + } + } else { + hash := make(map[string]bool) + + // keyed list + ls := n.List + for i, l := range ls { + ir.SetPos(l) + + if l.Op() == ir.OKEY { + kv := l.(*ir.KeyExpr) + key := kv.Key + + // Sym might have resolved to name in other top-level + // package, because of import dot. Redirect to correct sym + // before we do the lookup. + s := key.Sym() + if id, ok := key.(*ir.Ident); ok && DotImportRefs[id] != nil { + s = Lookup(s.Name) + } + + // An OXDOT uses the Sym field to hold + // the field to the right of the dot, + // so s will be non-nil, but an OXDOT + // is never a valid struct literal key. + if s == nil || s.Pkg != types.LocalPkg || key.Op() == ir.OXDOT || s.IsBlank() { + base.Errorf("invalid field name %v in struct initializer", key) + continue + } + + l = ir.NewStructKeyExpr(l.Pos(), s, kv.Value) + ls[i] = l + } + + if l.Op() != ir.OSTRUCTKEY { + if !errored { + base.Errorf("mixture of field:value and value initializers") + errored = true + } + ls[i] = Expr(ls[i]) + continue + } + l := l.(*ir.StructKeyExpr) + + f := Lookdot1(nil, l.Field, t, t.Fields(), 0) + if f == nil { + if ci := Lookdot1(nil, l.Field, t, t.Fields(), 2); ci != nil { // Case-insensitive lookup. + if visible(ci.Sym) { + base.Errorf("unknown field '%v' in struct literal of type %v (but does have %v)", l.Field, t, ci.Sym) + } else if nonexported(l.Field) && l.Field.Name == ci.Sym.Name { // Ensure exactness before the suggestion. + base.Errorf("cannot refer to unexported field '%v' in struct literal of type %v", l.Field, t) + } else { + base.Errorf("unknown field '%v' in struct literal of type %v", l.Field, t) + } + continue + } + var f *types.Field + p, _ := dotpath(l.Field, t, &f, true) + if p == nil || f.IsMethod() { + base.Errorf("unknown field '%v' in struct literal of type %v", l.Field, t) + continue + } + // dotpath returns the parent embedded types in reverse order. + var ep []string + for ei := len(p) - 1; ei >= 0; ei-- { + ep = append(ep, p[ei].field.Sym.Name) + } + ep = append(ep, l.Field.Name) + base.Errorf("cannot use promoted field %v in struct literal of type %v", strings.Join(ep, "."), t) + continue + } + fielddup(f.Sym.Name, hash) + l.Offset = f.Offset + + // No pushtype allowed here. Tried and rejected. + l.Value = Expr(l.Value) + l.Value = AssignConv(l.Value, f.Type, "field value") + } + } + + n.SetOp(ir.OSTRUCTLIT) + n.Ntype = nil + } + + return n +} + +// tcConv typechecks an OCONV node. +func tcConv(n *ir.ConvExpr) ir.Node { + types.CheckSize(n.Type()) // ensure width is calculated for backend + n.X = Expr(n.X) + n.X = convlit1(n.X, n.Type(), true, nil) + t := n.X.Type() + if t == nil || n.Type() == nil { + n.SetType(nil) + return n + } + op, why := Convertop(n.X.Op() == ir.OLITERAL, t, n.Type()) + if op == ir.OXXX { + if !n.Diag() && !n.Type().Broke() && !n.X.Diag() { + base.Errorf("cannot convert %L to type %v%s", n.X, n.Type(), why) + n.SetDiag(true) + } + n.SetOp(ir.OCONV) + n.SetType(nil) + return n + } + + n.SetOp(op) + switch n.Op() { + case ir.OCONVNOP: + if t.Kind() == n.Type().Kind() { + switch t.Kind() { + case types.TFLOAT32, types.TFLOAT64, types.TCOMPLEX64, types.TCOMPLEX128: + // Floating point casts imply rounding and + // so the conversion must be kept. + n.SetOp(ir.OCONV) + } + } + + // do not convert to []byte literal. See CL 125796. + // generated code and compiler memory footprint is better without it. + case ir.OSTR2BYTES: + // ok + + case ir.OSTR2RUNES: + if n.X.Op() == ir.OLITERAL { + return stringtoruneslit(n) + } + } + return n +} + +// tcDot typechecks an OXDOT or ODOT node. +func tcDot(n *ir.SelectorExpr, top int) ir.Node { + if n.Op() == ir.OXDOT { + n = AddImplicitDots(n) + n.SetOp(ir.ODOT) + if n.X == nil { + n.SetType(nil) + return n + } + } + + n.X = typecheck(n.X, ctxExpr|ctxType) + n.X = DefaultLit(n.X, nil) + + t := n.X.Type() + if t == nil { + base.UpdateErrorDot(ir.Line(n), fmt.Sprint(n.X), fmt.Sprint(n)) + n.SetType(nil) + return n + } + + if n.X.Op() == ir.OTYPE { + return typecheckMethodExpr(n) + } + + if t.IsPtr() && !t.Elem().IsInterface() { + t = t.Elem() + if t == nil { + n.SetType(nil) + return n + } + n.SetOp(ir.ODOTPTR) + types.CheckSize(t) + } + + if n.Sel.IsBlank() { + base.Errorf("cannot refer to blank field or method") + n.SetType(nil) + return n + } + + if Lookdot(n, t, 0) == nil { + // Legitimate field or method lookup failed, try to explain the error + switch { + case t.IsEmptyInterface(): + base.Errorf("%v undefined (type %v is interface with no methods)", n, n.X.Type()) + + case t.IsPtr() && t.Elem().IsInterface(): + // Pointer to interface is almost always a mistake. + base.Errorf("%v undefined (type %v is pointer to interface, not interface)", n, n.X.Type()) + + case Lookdot(n, t, 1) != nil: + // Field or method matches by name, but it is not exported. + base.Errorf("%v undefined (cannot refer to unexported field or method %v)", n, n.Sel) + + default: + if mt := Lookdot(n, t, 2); mt != nil && visible(mt.Sym) { // Case-insensitive lookup. + base.Errorf("%v undefined (type %v has no field or method %v, but does have %v)", n, n.X.Type(), n.Sel, mt.Sym) + } else { + base.Errorf("%v undefined (type %v has no field or method %v)", n, n.X.Type(), n.Sel) + } + } + n.SetType(nil) + return n + } + + if (n.Op() == ir.ODOTINTER || n.Op() == ir.ODOTMETH) && top&ctxCallee == 0 { + n.SetOp(ir.OCALLPART) + n.SetType(MethodValueWrapper(n).Type()) + } + return n +} + +// tcDotType typechecks an ODOTTYPE node. +func tcDotType(n *ir.TypeAssertExpr) ir.Node { + n.X = Expr(n.X) + n.X = DefaultLit(n.X, nil) + l := n.X + t := l.Type() + if t == nil { + n.SetType(nil) + return n + } + if !t.IsInterface() { + base.Errorf("invalid type assertion: %v (non-interface type %v on left)", n, t) + n.SetType(nil) + return n + } + + if n.Ntype != nil { + n.Ntype = typecheckNtype(n.Ntype) + n.SetType(n.Ntype.Type()) + n.Ntype = nil + if n.Type() == nil { + return n + } + } + + if n.Type() != nil && !n.Type().IsInterface() { + var missing, have *types.Field + var ptr int + if !implements(n.Type(), t, &missing, &have, &ptr) { + if have != nil && have.Sym == missing.Sym { + base.Errorf("impossible type assertion:\n\t%v does not implement %v (wrong type for %v method)\n"+ + "\t\thave %v%S\n\t\twant %v%S", n.Type(), t, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) + } else if ptr != 0 { + base.Errorf("impossible type assertion:\n\t%v does not implement %v (%v method has pointer receiver)", n.Type(), t, missing.Sym) + } else if have != nil { + base.Errorf("impossible type assertion:\n\t%v does not implement %v (missing %v method)\n"+ + "\t\thave %v%S\n\t\twant %v%S", n.Type(), t, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) + } else { + base.Errorf("impossible type assertion:\n\t%v does not implement %v (missing %v method)", n.Type(), t, missing.Sym) + } + n.SetType(nil) + return n + } + } + return n +} + +// tcITab typechecks an OITAB node. +func tcITab(n *ir.UnaryExpr) ir.Node { + n.X = Expr(n.X) + t := n.X.Type() + if t == nil { + n.SetType(nil) + return n + } + if !t.IsInterface() { + base.Fatalf("OITAB of %v", t) + } + n.SetType(types.NewPtr(types.Types[types.TUINTPTR])) + return n +} + +// tcIndex typechecks an OINDEX node. +func tcIndex(n *ir.IndexExpr) ir.Node { + n.X = Expr(n.X) + n.X = DefaultLit(n.X, nil) + n.X = implicitstar(n.X) + l := n.X + n.Index = Expr(n.Index) + r := n.Index + t := l.Type() + if t == nil || r.Type() == nil { + n.SetType(nil) + return n + } + switch t.Kind() { + default: + base.Errorf("invalid operation: %v (type %v does not support indexing)", n, t) + n.SetType(nil) + return n + + case types.TSTRING, types.TARRAY, types.TSLICE: + n.Index = indexlit(n.Index) + if t.IsString() { + n.SetType(types.ByteType) + } else { + n.SetType(t.Elem()) + } + why := "string" + if t.IsArray() { + why = "array" + } else if t.IsSlice() { + why = "slice" + } + + if n.Index.Type() != nil && !n.Index.Type().IsInteger() { + base.Errorf("non-integer %s index %v", why, n.Index) + return n + } + + if !n.Bounded() && ir.IsConst(n.Index, constant.Int) { + x := n.Index.Val() + if constant.Sign(x) < 0 { + base.Errorf("invalid %s index %v (index must be non-negative)", why, n.Index) + } else if t.IsArray() && constant.Compare(x, token.GEQ, constant.MakeInt64(t.NumElem())) { + base.Errorf("invalid array index %v (out of bounds for %d-element array)", n.Index, t.NumElem()) + } else if ir.IsConst(n.X, constant.String) && constant.Compare(x, token.GEQ, constant.MakeInt64(int64(len(ir.StringVal(n.X))))) { + base.Errorf("invalid string index %v (out of bounds for %d-byte string)", n.Index, len(ir.StringVal(n.X))) + } else if ir.ConstOverflow(x, types.Types[types.TINT]) { + base.Errorf("invalid %s index %v (index too large)", why, n.Index) + } + } + + case types.TMAP: + n.Index = AssignConv(n.Index, t.Key(), "map index") + n.SetType(t.Elem()) + n.SetOp(ir.OINDEXMAP) + n.Assigned = false + } + return n +} + +// tcLenCap typechecks an OLEN or OCAP node. +func tcLenCap(n *ir.UnaryExpr) ir.Node { + n.X = Expr(n.X) + n.X = DefaultLit(n.X, nil) + n.X = implicitstar(n.X) + l := n.X + t := l.Type() + if t == nil { + n.SetType(nil) + return n + } + + var ok bool + if n.Op() == ir.OLEN { + ok = okforlen[t.Kind()] + } else { + ok = okforcap[t.Kind()] + } + if !ok { + base.Errorf("invalid argument %L for %v", l, n.Op()) + n.SetType(nil) + return n + } + + n.SetType(types.Types[types.TINT]) + return n +} + +// tcRecv typechecks an ORECV node. +func tcRecv(n *ir.UnaryExpr) ir.Node { + n.X = Expr(n.X) + n.X = DefaultLit(n.X, nil) + l := n.X + t := l.Type() + if t == nil { + n.SetType(nil) + return n + } + if !t.IsChan() { + base.Errorf("invalid operation: %v (receive from non-chan type %v)", n, t) + n.SetType(nil) + return n + } + + if !t.ChanDir().CanRecv() { + base.Errorf("invalid operation: %v (receive from send-only type %v)", n, t) + n.SetType(nil) + return n + } + + n.SetType(t.Elem()) + return n +} + +// tcSPtr typechecks an OSPTR node. +func tcSPtr(n *ir.UnaryExpr) ir.Node { + n.X = Expr(n.X) + t := n.X.Type() + if t == nil { + n.SetType(nil) + return n + } + if !t.IsSlice() && !t.IsString() { + base.Fatalf("OSPTR of %v", t) + } + if t.IsString() { + n.SetType(types.NewPtr(types.Types[types.TUINT8])) + } else { + n.SetType(types.NewPtr(t.Elem())) + } + return n +} + +// tcSlice typechecks an OSLICE or OSLICE3 node. +func tcSlice(n *ir.SliceExpr) ir.Node { + n.X = DefaultLit(Expr(n.X), nil) + n.Low = indexlit(Expr(n.Low)) + n.High = indexlit(Expr(n.High)) + n.Max = indexlit(Expr(n.Max)) + hasmax := n.Op().IsSlice3() + l := n.X + if l.Type() == nil { + n.SetType(nil) + return n + } + if l.Type().IsArray() { + if !ir.IsAddressable(n.X) { + base.Errorf("invalid operation %v (slice of unaddressable value)", n) + n.SetType(nil) + return n + } + + addr := NodAddr(n.X) + addr.SetImplicit(true) + n.X = Expr(addr) + l = n.X + } + t := l.Type() + var tp *types.Type + if t.IsString() { + if hasmax { + base.Errorf("invalid operation %v (3-index slice of string)", n) + n.SetType(nil) + return n + } + n.SetType(t) + n.SetOp(ir.OSLICESTR) + } else if t.IsPtr() && t.Elem().IsArray() { + tp = t.Elem() + n.SetType(types.NewSlice(tp.Elem())) + types.CalcSize(n.Type()) + if hasmax { + n.SetOp(ir.OSLICE3ARR) + } else { + n.SetOp(ir.OSLICEARR) + } + } else if t.IsSlice() { + n.SetType(t) + } else { + base.Errorf("cannot slice %v (type %v)", l, t) + n.SetType(nil) + return n + } + + if n.Low != nil && !checksliceindex(l, n.Low, tp) { + n.SetType(nil) + return n + } + if n.High != nil && !checksliceindex(l, n.High, tp) { + n.SetType(nil) + return n + } + if n.Max != nil && !checksliceindex(l, n.Max, tp) { + n.SetType(nil) + return n + } + if !checksliceconst(n.Low, n.High) || !checksliceconst(n.Low, n.Max) || !checksliceconst(n.High, n.Max) { + n.SetType(nil) + return n + } + return n +} + +// tcSliceHeader typechecks an OSLICEHEADER node. +func tcSliceHeader(n *ir.SliceHeaderExpr) ir.Node { + // Errors here are Fatalf instead of Errorf because only the compiler + // can construct an OSLICEHEADER node. + // Components used in OSLICEHEADER that are supplied by parsed source code + // have already been typechecked in e.g. OMAKESLICE earlier. + t := n.Type() + if t == nil { + base.Fatalf("no type specified for OSLICEHEADER") + } + + if !t.IsSlice() { + base.Fatalf("invalid type %v for OSLICEHEADER", n.Type()) + } + + if n.Ptr == nil || n.Ptr.Type() == nil || !n.Ptr.Type().IsUnsafePtr() { + base.Fatalf("need unsafe.Pointer for OSLICEHEADER") + } + + n.Ptr = Expr(n.Ptr) + n.Len = DefaultLit(Expr(n.Len), types.Types[types.TINT]) + n.Cap = DefaultLit(Expr(n.Cap), types.Types[types.TINT]) + + if ir.IsConst(n.Len, constant.Int) && ir.Int64Val(n.Len) < 0 { + base.Fatalf("len for OSLICEHEADER must be non-negative") + } + + if ir.IsConst(n.Cap, constant.Int) && ir.Int64Val(n.Cap) < 0 { + base.Fatalf("cap for OSLICEHEADER must be non-negative") + } + + if ir.IsConst(n.Len, constant.Int) && ir.IsConst(n.Cap, constant.Int) && constant.Compare(n.Len.Val(), token.GTR, n.Cap.Val()) { + base.Fatalf("len larger than cap for OSLICEHEADER") + } + + return n +} + +// tcStar typechecks an ODEREF node, which may be an expression or a type. +func tcStar(n *ir.StarExpr, top int) ir.Node { + n.X = typecheck(n.X, ctxExpr|ctxType) + l := n.X + t := l.Type() + if t == nil { + n.SetType(nil) + return n + } + if l.Op() == ir.OTYPE { + n.SetOTYPE(types.NewPtr(l.Type())) + // Ensure l.Type gets CalcSize'd for the backend. Issue 20174. + types.CheckSize(l.Type()) + return n + } + + if !t.IsPtr() { + if top&(ctxExpr|ctxStmt) != 0 { + base.Errorf("invalid indirect of %L", n.X) + n.SetType(nil) + return n + } + base.Errorf("%v is not a type", l) + return n + } + + n.SetType(t.Elem()) + return n +} + +// tcUnaryArith typechecks a unary arithmetic expression. +func tcUnaryArith(n *ir.UnaryExpr) ir.Node { + n.X = Expr(n.X) + l := n.X + t := l.Type() + if t == nil { + n.SetType(nil) + return n + } + if !okfor[n.Op()][defaultType(t).Kind()] { + base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, n.Op(), typekind(t)) + n.SetType(nil) + return n + } + + n.SetType(t) + return n +} diff --git a/src/cmd/compile/internal/typecheck/func.go b/src/cmd/compile/internal/typecheck/func.go new file mode 100644 index 0000000000000000000000000000000000000000..fbcc784627d6ce8d64bb17eab49b7a679af10ab5 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/func.go @@ -0,0 +1,1035 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + + "fmt" + "go/constant" + "go/token" +) + +// package all the arguments that match a ... T parameter into a []T. +func MakeDotArgs(typ *types.Type, args []ir.Node) ir.Node { + var n ir.Node + if len(args) == 0 { + n = NodNil() + n.SetType(typ) + } else { + lit := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(typ), nil) + lit.List.Append(args...) + lit.SetImplicit(true) + n = lit + } + + n = Expr(n) + if n.Type() == nil { + base.Fatalf("mkdotargslice: typecheck failed") + } + return n +} + +// FixVariadicCall rewrites calls to variadic functions to use an +// explicit ... argument if one is not already present. +func FixVariadicCall(call *ir.CallExpr) { + fntype := call.X.Type() + if !fntype.IsVariadic() || call.IsDDD { + return + } + + vi := fntype.NumParams() - 1 + vt := fntype.Params().Field(vi).Type + + args := call.Args + extra := args[vi:] + slice := MakeDotArgs(vt, extra) + for i := range extra { + extra[i] = nil // allow GC + } + + call.Args = append(args[:vi], slice) + call.IsDDD = true +} + +// ClosureType returns the struct type used to hold all the information +// needed in the closure for clo (clo must be a OCLOSURE node). +// The address of a variable of the returned type can be cast to a func. +func ClosureType(clo *ir.ClosureExpr) *types.Type { + // Create closure in the form of a composite literal. + // supposing the closure captures an int i and a string s + // and has one float64 argument and no results, + // the generated code looks like: + // + // clos = &struct{.F uintptr; i *int; s *string}{func.1, &i, &s} + // + // The use of the struct provides type information to the garbage + // collector so that it can walk the closure. We could use (in this case) + // [3]unsafe.Pointer instead, but that would leave the gc in the dark. + // The information appears in the binary in the form of type descriptors; + // the struct is unnamed so that closures in multiple packages with the + // same struct type can share the descriptor. + fields := []*types.Field{ + types.NewField(base.Pos, Lookup(".F"), types.Types[types.TUINTPTR]), + } + for _, v := range clo.Func.ClosureVars { + typ := v.Type() + if !v.Byval() { + typ = types.NewPtr(typ) + } + fields = append(fields, types.NewField(base.Pos, v.Sym(), typ)) + } + typ := types.NewStruct(types.NoPkg, fields) + typ.SetNoalg(true) + return typ +} + +// PartialCallType returns the struct type used to hold all the information +// needed in the closure for n (n must be a OCALLPART node). +// The address of a variable of the returned type can be cast to a func. +func PartialCallType(n *ir.SelectorExpr) *types.Type { + t := types.NewStruct(types.NoPkg, []*types.Field{ + types.NewField(base.Pos, Lookup("F"), types.Types[types.TUINTPTR]), + types.NewField(base.Pos, Lookup("R"), n.X.Type()), + }) + t.SetNoalg(true) + return t +} + +// True if we are typechecking an inline body in ImportedBody below. We use this +// flag to not create a new closure function in tcClosure when we are just +// typechecking an inline body, as opposed to the body of a real function. +var inTypeCheckInl bool + +// ImportedBody returns immediately if the inlining information for fn is +// populated. Otherwise, fn must be an imported function. If so, ImportedBody +// loads in the dcls and body for fn, and typechecks as needed. +func ImportedBody(fn *ir.Func) { + if fn.Inl.Body != nil { + return + } + lno := ir.SetPos(fn.Nname) + + // When we load an inlined body, we need to allow OADDR + // operations on untyped expressions. We will fix the + // addrtaken flags on all the arguments of the OADDR with the + // computeAddrtaken call below (after we typecheck the body). + // TODO: export/import types and addrtaken marks along with inlined bodies, + // so this will be unnecessary. + IncrementalAddrtaken = false + defer func() { + if DirtyAddrtaken { + ComputeAddrtaken(fn.Inl.Body) // compute addrtaken marks once types are available + DirtyAddrtaken = false + } + IncrementalAddrtaken = true + }() + + ImportBody(fn) + + // Stmts(fn.Inl.Body) below is only for imported functions; + // their bodies may refer to unsafe as long as the package + // was marked safe during import (which was checked then). + // the ->inl of a local function has been typechecked before CanInline copied it. + pkg := fnpkg(fn.Nname) + + if pkg == types.LocalPkg || pkg == nil { + return // ImportedBody on local function + } + + if base.Flag.LowerM > 2 || base.Debug.Export != 0 { + fmt.Printf("typecheck import [%v] %L { %v }\n", fn.Sym(), fn, ir.Nodes(fn.Inl.Body)) + } + + if !go117ExportTypes { + // If we didn't export & import types, typecheck the code here. + savefn := ir.CurFunc + ir.CurFunc = fn + if inTypeCheckInl { + base.Fatalf("inTypeCheckInl should not be set recursively") + } + inTypeCheckInl = true + Stmts(fn.Inl.Body) + inTypeCheckInl = false + ir.CurFunc = savefn + } + + base.Pos = lno +} + +// Get the function's package. For ordinary functions it's on the ->sym, but for imported methods +// the ->sym can be re-used in the local package, so peel it off the receiver's type. +func fnpkg(fn *ir.Name) *types.Pkg { + if ir.IsMethod(fn) { + // method + rcvr := fn.Type().Recv().Type + + if rcvr.IsPtr() { + rcvr = rcvr.Elem() + } + if rcvr.Sym() == nil { + base.Fatalf("receiver with no sym: [%v] %L (%v)", fn.Sym(), fn, rcvr) + } + return rcvr.Sym().Pkg + } + + // non-method + return fn.Sym().Pkg +} + +// ClosureName generates a new unique name for a closure within +// outerfunc. +func ClosureName(outerfunc *ir.Func) *types.Sym { + outer := "glob." + prefix := "func" + gen := &globClosgen + + if outerfunc != nil { + if outerfunc.OClosure != nil { + prefix = "" + } + + outer = ir.FuncName(outerfunc) + + // There may be multiple functions named "_". In those + // cases, we can't use their individual Closgens as it + // would lead to name clashes. + if !ir.IsBlank(outerfunc.Nname) { + gen = &outerfunc.Closgen + } + } + + *gen++ + return Lookup(fmt.Sprintf("%s.%s%d", outer, prefix, *gen)) +} + +// globClosgen is like Func.Closgen, but for the global scope. +var globClosgen int32 + +// MethodValueWrapper returns the DCLFUNC node representing the +// wrapper function (*-fm) needed for the given method value. If the +// wrapper function hasn't already been created yet, it's created and +// added to Target.Decls. +// +// TODO(mdempsky): Move into walk. This isn't part of type checking. +func MethodValueWrapper(dot *ir.SelectorExpr) *ir.Func { + if dot.Op() != ir.OCALLPART { + base.Fatalf("MethodValueWrapper: unexpected %v (%v)", dot, dot.Op()) + } + + t0 := dot.Type() + meth := dot.Sel + rcvrtype := dot.X.Type() + sym := ir.MethodSymSuffix(rcvrtype, meth, "-fm") + + if sym.Uniq() { + return sym.Def.(*ir.Func) + } + sym.SetUniq(true) + + savecurfn := ir.CurFunc + saveLineNo := base.Pos + ir.CurFunc = nil + + // Set line number equal to the line number where the method is declared. + if pos := dot.Selection.Pos; pos.IsKnown() { + base.Pos = pos + } + // Note: !dot.Selection.Pos.IsKnown() happens for method expressions where + // the method is implicitly declared. The Error method of the + // built-in error type is one such method. We leave the line + // number at the use of the method expression in this + // case. See issue 29389. + + tfn := ir.NewFuncType(base.Pos, nil, + NewFuncParams(t0.Params(), true), + NewFuncParams(t0.Results(), false)) + + fn := DeclFunc(sym, tfn) + fn.SetDupok(true) + fn.SetNeedctxt(true) + fn.SetWrapper(true) + + // Declare and initialize variable holding receiver. + ptr := ir.NewNameAt(base.Pos, Lookup(".this")) + ptr.Class = ir.PAUTOHEAP + ptr.SetType(rcvrtype) + ptr.Curfn = fn + ptr.SetIsClosureVar(true) + ptr.SetByval(true) + fn.ClosureVars = append(fn.ClosureVars, ptr) + + call := ir.NewCallExpr(base.Pos, ir.OCALL, ir.NewSelectorExpr(base.Pos, ir.OXDOT, ptr, meth), nil) + call.Args = ir.ParamNames(tfn.Type()) + call.IsDDD = tfn.Type().IsVariadic() + + var body ir.Node = call + if t0.NumResults() != 0 { + ret := ir.NewReturnStmt(base.Pos, nil) + ret.Results = []ir.Node{call} + body = ret + } + + fn.Body = []ir.Node{body} + FinishFuncBody() + + Func(fn) + // Need to typecheck the body of the just-generated wrapper. + // typecheckslice() requires that Curfn is set when processing an ORETURN. + ir.CurFunc = fn + Stmts(fn.Body) + sym.Def = fn + Target.Decls = append(Target.Decls, fn) + ir.CurFunc = savecurfn + base.Pos = saveLineNo + + return fn +} + +// tcClosure typechecks an OCLOSURE node. It also creates the named +// function associated with the closure. +// TODO: This creation of the named function should probably really be done in a +// separate pass from type-checking. +func tcClosure(clo *ir.ClosureExpr, top int) { + fn := clo.Func + // Set current associated iota value, so iota can be used inside + // function in ConstSpec, see issue #22344 + if x := getIotaValue(); x >= 0 { + fn.Iota = x + } + + fn.SetClosureCalled(top&ctxCallee != 0) + + // Do not typecheck fn twice, otherwise, we will end up pushing + // fn to Target.Decls multiple times, causing InitLSym called twice. + // See #30709 + if fn.Typecheck() == 1 { + clo.SetType(fn.Type()) + return + } + + // Don't give a name and add to Target.Decls if we are typechecking an inlined + // body in ImportedBody(), since we only want to create the named function + // when the closure is actually inlined (and then we force a typecheck + // explicitly in (*inlsubst).node()). + if !inTypeCheckInl { + fn.Nname.SetSym(ClosureName(ir.CurFunc)) + ir.MarkFunc(fn.Nname) + } + Func(fn) + clo.SetType(fn.Type()) + + // Type check the body now, but only if we're inside a function. + // At top level (in a variable initialization: curfn==nil) we're not + // ready to type check code yet; we'll check it later, because the + // underlying closure function we create is added to Target.Decls. + if ir.CurFunc != nil && clo.Type() != nil { + oldfn := ir.CurFunc + ir.CurFunc = fn + Stmts(fn.Body) + ir.CurFunc = oldfn + } + + out := 0 + for _, v := range fn.ClosureVars { + if v.Type() == nil { + // If v.Type is nil, it means v looked like it was going to be + // used in the closure, but isn't. This happens in struct + // literals like s{f: x} where we can't distinguish whether f is + // a field identifier or expression until resolving s. + continue + } + + // type check closed variables outside the closure, so that the + // outer frame also captures them. + Expr(v.Outer) + + fn.ClosureVars[out] = v + out++ + } + fn.ClosureVars = fn.ClosureVars[:out] + + if base.Flag.W > 1 { + s := fmt.Sprintf("New closure func: %s", ir.FuncName(fn)) + ir.Dump(s, fn) + } + if !inTypeCheckInl { + // Add function to Target.Decls once only when we give it a name + Target.Decls = append(Target.Decls, fn) + } +} + +// type check function definition +// To be called by typecheck, not directly. +// (Call typecheck.Func instead.) +func tcFunc(n *ir.Func) { + if base.EnableTrace && base.Flag.LowerT { + defer tracePrint("tcFunc", n)(nil) + } + + n.Nname = AssignExpr(n.Nname).(*ir.Name) + t := n.Nname.Type() + if t == nil { + return + } + rcvr := t.Recv() + if rcvr != nil && n.Shortname != nil { + m := addmethod(n, n.Shortname, t, true, n.Pragma&ir.Nointerface != 0) + if m == nil { + return + } + + n.Nname.SetSym(ir.MethodSym(rcvr.Type, n.Shortname)) + Declare(n.Nname, ir.PFUNC) + } +} + +// tcCall typechecks an OCALL node. +func tcCall(n *ir.CallExpr, top int) ir.Node { + n.Use = ir.CallUseExpr + if top == ctxStmt { + n.Use = ir.CallUseStmt + } + Stmts(n.Init()) // imported rewritten f(g()) calls (#30907) + n.X = typecheck(n.X, ctxExpr|ctxType|ctxCallee) + if n.X.Diag() { + n.SetDiag(true) + } + + l := n.X + + if l.Op() == ir.ONAME && l.(*ir.Name).BuiltinOp != 0 { + l := l.(*ir.Name) + if n.IsDDD && l.BuiltinOp != ir.OAPPEND { + base.Errorf("invalid use of ... with builtin %v", l) + } + + // builtin: OLEN, OCAP, etc. + switch l.BuiltinOp { + default: + base.Fatalf("unknown builtin %v", l) + + case ir.OAPPEND, ir.ODELETE, ir.OMAKE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER: + n.SetOp(l.BuiltinOp) + n.X = nil + n.SetTypecheck(0) // re-typechecking new op is OK, not a loop + return typecheck(n, top) + + case ir.OCAP, ir.OCLOSE, ir.OIMAG, ir.OLEN, ir.OPANIC, ir.OREAL: + typecheckargs(n) + fallthrough + case ir.ONEW, ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF: + arg, ok := needOneArg(n, "%v", n.Op()) + if !ok { + n.SetType(nil) + return n + } + u := ir.NewUnaryExpr(n.Pos(), l.BuiltinOp, arg) + return typecheck(ir.InitExpr(n.Init(), u), top) // typecheckargs can add to old.Init + + case ir.OCOMPLEX, ir.OCOPY, ir.OUNSAFEADD, ir.OUNSAFESLICE: + typecheckargs(n) + arg1, arg2, ok := needTwoArgs(n) + if !ok { + n.SetType(nil) + return n + } + b := ir.NewBinaryExpr(n.Pos(), l.BuiltinOp, arg1, arg2) + return typecheck(ir.InitExpr(n.Init(), b), top) // typecheckargs can add to old.Init + } + panic("unreachable") + } + + n.X = DefaultLit(n.X, nil) + l = n.X + if l.Op() == ir.OTYPE { + if n.IsDDD { + if !l.Type().Broke() { + base.Errorf("invalid use of ... in type conversion to %v", l.Type()) + } + n.SetDiag(true) + } + + // pick off before type-checking arguments + arg, ok := needOneArg(n, "conversion to %v", l.Type()) + if !ok { + n.SetType(nil) + return n + } + + n := ir.NewConvExpr(n.Pos(), ir.OCONV, nil, arg) + n.SetType(l.Type()) + return tcConv(n) + } + + typecheckargs(n) + t := l.Type() + if t == nil { + n.SetType(nil) + return n + } + types.CheckSize(t) + + switch l.Op() { + case ir.ODOTINTER: + n.SetOp(ir.OCALLINTER) + + case ir.ODOTMETH: + l := l.(*ir.SelectorExpr) + n.SetOp(ir.OCALLMETH) + + // typecheckaste was used here but there wasn't enough + // information further down the call chain to know if we + // were testing a method receiver for unexported fields. + // It isn't necessary, so just do a sanity check. + tp := t.Recv().Type + + if l.X == nil || !types.Identical(l.X.Type(), tp) { + base.Fatalf("method receiver") + } + + default: + n.SetOp(ir.OCALLFUNC) + if t.Kind() != types.TFUNC { + if o := ir.Orig(l); o.Name() != nil && types.BuiltinPkg.Lookup(o.Sym().Name).Def != nil { + // be more specific when the non-function + // name matches a predeclared function + base.Errorf("cannot call non-function %L, declared at %s", + l, base.FmtPos(o.Name().Pos())) + } else { + base.Errorf("cannot call non-function %L", l) + } + n.SetType(nil) + return n + } + } + + typecheckaste(ir.OCALL, n.X, n.IsDDD, t.Params(), n.Args, func() string { return fmt.Sprintf("argument to %v", n.X) }) + if t.NumResults() == 0 { + return n + } + if t.NumResults() == 1 { + n.SetType(l.Type().Results().Field(0).Type) + + if n.Op() == ir.OCALLFUNC && n.X.Op() == ir.ONAME { + if sym := n.X.(*ir.Name).Sym(); types.IsRuntimePkg(sym.Pkg) && sym.Name == "getg" { + // Emit code for runtime.getg() directly instead of calling function. + // Most such rewrites (for example the similar one for math.Sqrt) should be done in walk, + // so that the ordering pass can make sure to preserve the semantics of the original code + // (in particular, the exact time of the function call) by introducing temporaries. + // In this case, we know getg() always returns the same result within a given function + // and we want to avoid the temporaries, so we do the rewrite earlier than is typical. + n.SetOp(ir.OGETG) + } + } + return n + } + + // multiple return + if top&(ctxMultiOK|ctxStmt) == 0 { + base.Errorf("multiple-value %v() in single-value context", l) + return n + } + + n.SetType(l.Type().Results()) + return n +} + +// tcAppend typechecks an OAPPEND node. +func tcAppend(n *ir.CallExpr) ir.Node { + typecheckargs(n) + args := n.Args + if len(args) == 0 { + base.Errorf("missing arguments to append") + n.SetType(nil) + return n + } + + t := args[0].Type() + if t == nil { + n.SetType(nil) + return n + } + + n.SetType(t) + if !t.IsSlice() { + if ir.IsNil(args[0]) { + base.Errorf("first argument to append must be typed slice; have untyped nil") + n.SetType(nil) + return n + } + + base.Errorf("first argument to append must be slice; have %L", t) + n.SetType(nil) + return n + } + + if n.IsDDD { + if len(args) == 1 { + base.Errorf("cannot use ... on first argument to append") + n.SetType(nil) + return n + } + + if len(args) != 2 { + base.Errorf("too many arguments to append") + n.SetType(nil) + return n + } + + if t.Elem().IsKind(types.TUINT8) && args[1].Type().IsString() { + args[1] = DefaultLit(args[1], types.Types[types.TSTRING]) + return n + } + + args[1] = AssignConv(args[1], t.Underlying(), "append") + return n + } + + as := args[1:] + for i, n := range as { + if n.Type() == nil { + continue + } + as[i] = AssignConv(n, t.Elem(), "append") + types.CheckSize(as[i].Type()) // ensure width is calculated for backend + } + return n +} + +// tcClose typechecks an OCLOSE node. +func tcClose(n *ir.UnaryExpr) ir.Node { + n.X = Expr(n.X) + n.X = DefaultLit(n.X, nil) + l := n.X + t := l.Type() + if t == nil { + n.SetType(nil) + return n + } + if !t.IsChan() { + base.Errorf("invalid operation: %v (non-chan type %v)", n, t) + n.SetType(nil) + return n + } + + if !t.ChanDir().CanSend() { + base.Errorf("invalid operation: %v (cannot close receive-only channel)", n) + n.SetType(nil) + return n + } + return n +} + +// tcComplex typechecks an OCOMPLEX node. +func tcComplex(n *ir.BinaryExpr) ir.Node { + l := Expr(n.X) + r := Expr(n.Y) + if l.Type() == nil || r.Type() == nil { + n.SetType(nil) + return n + } + l, r = defaultlit2(l, r, false) + if l.Type() == nil || r.Type() == nil { + n.SetType(nil) + return n + } + n.X = l + n.Y = r + + if !types.Identical(l.Type(), r.Type()) { + base.Errorf("invalid operation: %v (mismatched types %v and %v)", n, l.Type(), r.Type()) + n.SetType(nil) + return n + } + + var t *types.Type + switch l.Type().Kind() { + default: + base.Errorf("invalid operation: %v (arguments have type %v, expected floating-point)", n, l.Type()) + n.SetType(nil) + return n + + case types.TIDEAL: + t = types.UntypedComplex + + case types.TFLOAT32: + t = types.Types[types.TCOMPLEX64] + + case types.TFLOAT64: + t = types.Types[types.TCOMPLEX128] + } + n.SetType(t) + return n +} + +// tcCopy typechecks an OCOPY node. +func tcCopy(n *ir.BinaryExpr) ir.Node { + n.SetType(types.Types[types.TINT]) + n.X = Expr(n.X) + n.X = DefaultLit(n.X, nil) + n.Y = Expr(n.Y) + n.Y = DefaultLit(n.Y, nil) + if n.X.Type() == nil || n.Y.Type() == nil { + n.SetType(nil) + return n + } + + // copy([]byte, string) + if n.X.Type().IsSlice() && n.Y.Type().IsString() { + if types.Identical(n.X.Type().Elem(), types.ByteType) { + return n + } + base.Errorf("arguments to copy have different element types: %L and string", n.X.Type()) + n.SetType(nil) + return n + } + + if !n.X.Type().IsSlice() || !n.Y.Type().IsSlice() { + if !n.X.Type().IsSlice() && !n.Y.Type().IsSlice() { + base.Errorf("arguments to copy must be slices; have %L, %L", n.X.Type(), n.Y.Type()) + } else if !n.X.Type().IsSlice() { + base.Errorf("first argument to copy should be slice; have %L", n.X.Type()) + } else { + base.Errorf("second argument to copy should be slice or string; have %L", n.Y.Type()) + } + n.SetType(nil) + return n + } + + if !types.Identical(n.X.Type().Elem(), n.Y.Type().Elem()) { + base.Errorf("arguments to copy have different element types: %L and %L", n.X.Type(), n.Y.Type()) + n.SetType(nil) + return n + } + return n +} + +// tcDelete typechecks an ODELETE node. +func tcDelete(n *ir.CallExpr) ir.Node { + typecheckargs(n) + args := n.Args + if len(args) == 0 { + base.Errorf("missing arguments to delete") + n.SetType(nil) + return n + } + + if len(args) == 1 { + base.Errorf("missing second (key) argument to delete") + n.SetType(nil) + return n + } + + if len(args) != 2 { + base.Errorf("too many arguments to delete") + n.SetType(nil) + return n + } + + l := args[0] + r := args[1] + if l.Type() != nil && !l.Type().IsMap() { + base.Errorf("first argument to delete must be map; have %L", l.Type()) + n.SetType(nil) + return n + } + + args[1] = AssignConv(r, l.Type().Key(), "delete") + return n +} + +// tcMake typechecks an OMAKE node. +func tcMake(n *ir.CallExpr) ir.Node { + args := n.Args + if len(args) == 0 { + base.Errorf("missing argument to make") + n.SetType(nil) + return n + } + + n.Args = nil + l := args[0] + l = typecheck(l, ctxType) + t := l.Type() + if t == nil { + n.SetType(nil) + return n + } + + i := 1 + var nn ir.Node + switch t.Kind() { + default: + base.Errorf("cannot make type %v", t) + n.SetType(nil) + return n + + case types.TSLICE: + if i >= len(args) { + base.Errorf("missing len argument to make(%v)", t) + n.SetType(nil) + return n + } + + l = args[i] + i++ + l = Expr(l) + var r ir.Node + if i < len(args) { + r = args[i] + i++ + r = Expr(r) + } + + if l.Type() == nil || (r != nil && r.Type() == nil) { + n.SetType(nil) + return n + } + if !checkmake(t, "len", &l) || r != nil && !checkmake(t, "cap", &r) { + n.SetType(nil) + return n + } + if ir.IsConst(l, constant.Int) && r != nil && ir.IsConst(r, constant.Int) && constant.Compare(l.Val(), token.GTR, r.Val()) { + base.Errorf("len larger than cap in make(%v)", t) + n.SetType(nil) + return n + } + nn = ir.NewMakeExpr(n.Pos(), ir.OMAKESLICE, l, r) + + case types.TMAP: + if i < len(args) { + l = args[i] + i++ + l = Expr(l) + l = DefaultLit(l, types.Types[types.TINT]) + if l.Type() == nil { + n.SetType(nil) + return n + } + if !checkmake(t, "size", &l) { + n.SetType(nil) + return n + } + } else { + l = ir.NewInt(0) + } + nn = ir.NewMakeExpr(n.Pos(), ir.OMAKEMAP, l, nil) + nn.SetEsc(n.Esc()) + + case types.TCHAN: + l = nil + if i < len(args) { + l = args[i] + i++ + l = Expr(l) + l = DefaultLit(l, types.Types[types.TINT]) + if l.Type() == nil { + n.SetType(nil) + return n + } + if !checkmake(t, "buffer", &l) { + n.SetType(nil) + return n + } + } else { + l = ir.NewInt(0) + } + nn = ir.NewMakeExpr(n.Pos(), ir.OMAKECHAN, l, nil) + } + + if i < len(args) { + base.Errorf("too many arguments to make(%v)", t) + n.SetType(nil) + return n + } + + nn.SetType(t) + return nn +} + +// tcMakeSliceCopy typechecks an OMAKESLICECOPY node. +func tcMakeSliceCopy(n *ir.MakeExpr) ir.Node { + // Errors here are Fatalf instead of Errorf because only the compiler + // can construct an OMAKESLICECOPY node. + // Components used in OMAKESCLICECOPY that are supplied by parsed source code + // have already been typechecked in OMAKE and OCOPY earlier. + t := n.Type() + + if t == nil { + base.Fatalf("no type specified for OMAKESLICECOPY") + } + + if !t.IsSlice() { + base.Fatalf("invalid type %v for OMAKESLICECOPY", n.Type()) + } + + if n.Len == nil { + base.Fatalf("missing len argument for OMAKESLICECOPY") + } + + if n.Cap == nil { + base.Fatalf("missing slice argument to copy for OMAKESLICECOPY") + } + + n.Len = Expr(n.Len) + n.Cap = Expr(n.Cap) + + n.Len = DefaultLit(n.Len, types.Types[types.TINT]) + + if !n.Len.Type().IsInteger() && n.Type().Kind() != types.TIDEAL { + base.Errorf("non-integer len argument in OMAKESLICECOPY") + } + + if ir.IsConst(n.Len, constant.Int) { + if ir.ConstOverflow(n.Len.Val(), types.Types[types.TINT]) { + base.Fatalf("len for OMAKESLICECOPY too large") + } + if constant.Sign(n.Len.Val()) < 0 { + base.Fatalf("len for OMAKESLICECOPY must be non-negative") + } + } + return n +} + +// tcNew typechecks an ONEW node. +func tcNew(n *ir.UnaryExpr) ir.Node { + if n.X == nil { + // Fatalf because the OCALL above checked for us, + // so this must be an internally-generated mistake. + base.Fatalf("missing argument to new") + } + l := n.X + l = typecheck(l, ctxType) + t := l.Type() + if t == nil { + n.SetType(nil) + return n + } + n.X = l + n.SetType(types.NewPtr(t)) + return n +} + +// tcPanic typechecks an OPANIC node. +func tcPanic(n *ir.UnaryExpr) ir.Node { + n.X = Expr(n.X) + n.X = AssignConv(n.X, types.Types[types.TINTER], "argument to panic") + if n.X.Type() == nil { + n.SetType(nil) + return n + } + return n +} + +// tcPrint typechecks an OPRINT or OPRINTN node. +func tcPrint(n *ir.CallExpr) ir.Node { + typecheckargs(n) + ls := n.Args + for i1, n1 := range ls { + // Special case for print: int constant is int64, not int. + if ir.IsConst(n1, constant.Int) { + ls[i1] = DefaultLit(ls[i1], types.Types[types.TINT64]) + } else { + ls[i1] = DefaultLit(ls[i1], nil) + } + } + return n +} + +// tcRealImag typechecks an OREAL or OIMAG node. +func tcRealImag(n *ir.UnaryExpr) ir.Node { + n.X = Expr(n.X) + l := n.X + t := l.Type() + if t == nil { + n.SetType(nil) + return n + } + + // Determine result type. + switch t.Kind() { + case types.TIDEAL: + n.SetType(types.UntypedFloat) + case types.TCOMPLEX64: + n.SetType(types.Types[types.TFLOAT32]) + case types.TCOMPLEX128: + n.SetType(types.Types[types.TFLOAT64]) + default: + base.Errorf("invalid argument %L for %v", l, n.Op()) + n.SetType(nil) + return n + } + return n +} + +// tcRecover typechecks an ORECOVER node. +func tcRecover(n *ir.CallExpr) ir.Node { + if len(n.Args) != 0 { + base.Errorf("too many arguments to recover") + n.SetType(nil) + return n + } + + n.SetType(types.Types[types.TINTER]) + return n +} + +// tcUnsafeAdd typechecks an OUNSAFEADD node. +func tcUnsafeAdd(n *ir.BinaryExpr) *ir.BinaryExpr { + if !types.AllowsGoVersion(curpkg(), 1, 17) { + base.ErrorfVers("go1.17", "unsafe.Add") + n.SetType(nil) + return n + } + + n.X = AssignConv(Expr(n.X), types.Types[types.TUNSAFEPTR], "argument to unsafe.Add") + n.Y = DefaultLit(Expr(n.Y), types.Types[types.TINT]) + if n.X.Type() == nil || n.Y.Type() == nil { + n.SetType(nil) + return n + } + if !n.Y.Type().IsInteger() { + n.SetType(nil) + return n + } + n.SetType(n.X.Type()) + return n +} + +// tcUnsafeSlice typechecks an OUNSAFESLICE node. +func tcUnsafeSlice(n *ir.BinaryExpr) *ir.BinaryExpr { + if !types.AllowsGoVersion(curpkg(), 1, 17) { + base.ErrorfVers("go1.17", "unsafe.Slice") + n.SetType(nil) + return n + } + + n.X = Expr(n.X) + n.Y = Expr(n.Y) + if n.X.Type() == nil || n.Y.Type() == nil { + n.SetType(nil) + return n + } + t := n.X.Type() + if !t.IsPtr() { + base.Errorf("first argument to unsafe.Slice must be pointer; have %L", t) + } else if t.Elem().NotInHeap() { + // TODO(mdempsky): This can be relaxed, but should only affect the + // Go runtime itself. End users should only see //go:notinheap + // types due to incomplete C structs in cgo, and those types don't + // have a meaningful size anyway. + base.Errorf("unsafe.Slice of incomplete (or unallocatable) type not allowed") + } + + if !checkunsafeslice(&n.Y) { + n.SetType(nil) + return n + } + n.SetType(types.NewSlice(t.Elem())) + return n +} diff --git a/src/cmd/compile/internal/typecheck/iexport.go b/src/cmd/compile/internal/typecheck/iexport.go new file mode 100644 index 0000000000000000000000000000000000000000..64d68ef62550d8a4b5c2d3046336e48c46c8352c --- /dev/null +++ b/src/cmd/compile/internal/typecheck/iexport.go @@ -0,0 +1,1941 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Indexed package export. +// +// The indexed export data format is an evolution of the previous +// binary export data format. Its chief contribution is introducing an +// index table, which allows efficient random access of individual +// declarations and inline function bodies. In turn, this allows +// avoiding unnecessary work for compilation units that import large +// packages. +// +// +// The top-level data format is structured as: +// +// Header struct { +// Tag byte // 'i' +// Version uvarint +// StringSize uvarint +// DataSize uvarint +// } +// +// Strings [StringSize]byte +// Data [DataSize]byte +// +// MainIndex []struct{ +// PkgPath stringOff +// PkgName stringOff +// PkgHeight uvarint +// +// Decls []struct{ +// Name stringOff +// Offset declOff +// } +// } +// +// Fingerprint [8]byte +// +// uvarint means a uint64 written out using uvarint encoding. +// +// []T means a uvarint followed by that many T objects. In other +// words: +// +// Len uvarint +// Elems [Len]T +// +// stringOff means a uvarint that indicates an offset within the +// Strings section. At that offset is another uvarint, followed by +// that many bytes, which form the string value. +// +// declOff means a uvarint that indicates an offset within the Data +// section where the associated declaration can be found. +// +// +// There are five kinds of declarations, distinguished by their first +// byte: +// +// type Var struct { +// Tag byte // 'V' +// Pos Pos +// Type typeOff +// } +// +// type Func struct { +// Tag byte // 'F' +// Pos Pos +// Signature Signature +// } +// +// type Const struct { +// Tag byte // 'C' +// Pos Pos +// Value Value +// } +// +// type Type struct { +// Tag byte // 'T' +// Pos Pos +// Underlying typeOff +// +// Methods []struct{ // omitted if Underlying is an interface type +// Pos Pos +// Name stringOff +// Recv Param +// Signature Signature +// } +// } +// +// type Alias struct { +// Tag byte // 'A' +// Pos Pos +// Type typeOff +// } +// +// +// typeOff means a uvarint that either indicates a predeclared type, +// or an offset into the Data section. If the uvarint is less than +// predeclReserved, then it indicates the index into the predeclared +// types list (see predeclared in bexport.go for order). Otherwise, +// subtracting predeclReserved yields the offset of a type descriptor. +// +// Value means a type and type-specific value. See +// (*exportWriter).value for details. +// +// +// There are nine kinds of type descriptors, distinguished by an itag: +// +// type DefinedType struct { +// Tag itag // definedType +// Name stringOff +// PkgPath stringOff +// } +// +// type PointerType struct { +// Tag itag // pointerType +// Elem typeOff +// } +// +// type SliceType struct { +// Tag itag // sliceType +// Elem typeOff +// } +// +// type ArrayType struct { +// Tag itag // arrayType +// Len uint64 +// Elem typeOff +// } +// +// type ChanType struct { +// Tag itag // chanType +// Dir uint64 // 1 RecvOnly; 2 SendOnly; 3 SendRecv +// Elem typeOff +// } +// +// type MapType struct { +// Tag itag // mapType +// Key typeOff +// Elem typeOff +// } +// +// type FuncType struct { +// Tag itag // signatureType +// PkgPath stringOff +// Signature Signature +// } +// +// type StructType struct { +// Tag itag // structType +// PkgPath stringOff +// Fields []struct { +// Pos Pos +// Name stringOff +// Type typeOff +// Embedded bool +// Note stringOff +// } +// } +// +// type InterfaceType struct { +// Tag itag // interfaceType +// PkgPath stringOff +// Embeddeds []struct { +// Pos Pos +// Type typeOff +// } +// Methods []struct { +// Pos Pos +// Name stringOff +// Signature Signature +// } +// } +// +// +// type Signature struct { +// Params []Param +// Results []Param +// Variadic bool // omitted if Results is empty +// } +// +// type Param struct { +// Pos Pos +// Name stringOff +// Type typOff +// } +// +// +// Pos encodes a file:line:column triple, incorporating a simple delta +// encoding scheme within a data object. See exportWriter.pos for +// details. +// +// +// Compiler-specific details. +// +// cmd/compile writes out a second index for inline bodies and also +// appends additional compiler-specific details after declarations. +// Third-party tools are not expected to depend on these details and +// they're expected to change much more rapidly, so they're omitted +// here. See exportWriter's varExt/funcExt/etc methods for details. + +package typecheck + +import ( + "bufio" + "bytes" + "crypto/md5" + "encoding/binary" + "fmt" + "go/constant" + "io" + "math/big" + "sort" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/goobj" + "cmd/internal/src" +) + +// Current indexed export format version. Increase with each format change. +// 1: added column details to Pos +// 0: Go1.11 encoding +const iexportVersion = 1 + +// predeclReserved is the number of type offsets reserved for types +// implicitly declared in the universe block. +const predeclReserved = 32 + +// An itag distinguishes the kind of type that was written into the +// indexed export format. +type itag uint64 + +const ( + // Types + definedType itag = iota + pointerType + sliceType + arrayType + chanType + mapType + signatureType + structType + interfaceType +) + +const ( + debug = false + magic = 0x6742937dc293105 +) + +func WriteExports(out *bufio.Writer) { + p := iexporter{ + allPkgs: map[*types.Pkg]bool{}, + stringIndex: map[string]uint64{}, + declIndex: map[*types.Sym]uint64{}, + inlineIndex: map[*types.Sym]uint64{}, + typIndex: map[*types.Type]uint64{}, + } + + for i, pt := range predeclared() { + p.typIndex[pt] = uint64(i) + } + if len(p.typIndex) > predeclReserved { + base.Fatalf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved) + } + + // Initialize work queue with exported declarations. + for _, n := range Target.Exports { + p.pushDecl(n) + } + + // Loop until no more work. We use a queue because while + // writing out inline bodies, we may discover additional + // declarations that are needed. + for !p.declTodo.Empty() { + p.doDecl(p.declTodo.PopLeft()) + } + + // Append indices to data0 section. + dataLen := uint64(p.data0.Len()) + w := p.newWriter() + w.writeIndex(p.declIndex, true) + w.writeIndex(p.inlineIndex, false) + w.flush() + + if *base.Flag.LowerV { + fmt.Printf("export: hdr strings %v, data %v, index %v\n", p.strings.Len(), dataLen, p.data0.Len()) + } + + // Assemble header. + var hdr intWriter + hdr.WriteByte('i') + hdr.uint64(iexportVersion) + hdr.uint64(uint64(p.strings.Len())) + hdr.uint64(dataLen) + + // Flush output. + h := md5.New() + wr := io.MultiWriter(out, h) + io.Copy(wr, &hdr) + io.Copy(wr, &p.strings) + io.Copy(wr, &p.data0) + + // Add fingerprint (used by linker object file). + // Attach this to the end, so tools (e.g. gcimporter) don't care. + copy(base.Ctxt.Fingerprint[:], h.Sum(nil)[:]) + out.Write(base.Ctxt.Fingerprint[:]) +} + +// writeIndex writes out a symbol index. mainIndex indicates whether +// we're writing out the main index, which is also read by +// non-compiler tools and includes a complete package description +// (i.e., name and height). +func (w *exportWriter) writeIndex(index map[*types.Sym]uint64, mainIndex bool) { + // Build a map from packages to symbols from that package. + pkgSyms := map[*types.Pkg][]*types.Sym{} + + // For the main index, make sure to include every package that + // we reference, even if we're not exporting (or reexporting) + // any symbols from it. + if mainIndex { + pkgSyms[types.LocalPkg] = nil + for pkg := range w.p.allPkgs { + pkgSyms[pkg] = nil + } + } + + // Group symbols by package. + for sym := range index { + pkgSyms[sym.Pkg] = append(pkgSyms[sym.Pkg], sym) + } + + // Sort packages by path. + var pkgs []*types.Pkg + for pkg := range pkgSyms { + pkgs = append(pkgs, pkg) + } + sort.Slice(pkgs, func(i, j int) bool { + return pkgs[i].Path < pkgs[j].Path + }) + + w.uint64(uint64(len(pkgs))) + for _, pkg := range pkgs { + w.string(pkg.Path) + if mainIndex { + w.string(pkg.Name) + w.uint64(uint64(pkg.Height)) + } + + // Sort symbols within a package by name. + syms := pkgSyms[pkg] + sort.Slice(syms, func(i, j int) bool { + return syms[i].Name < syms[j].Name + }) + + w.uint64(uint64(len(syms))) + for _, sym := range syms { + w.string(sym.Name) + w.uint64(index[sym]) + } + } +} + +type iexporter struct { + // allPkgs tracks all packages that have been referenced by + // the export data, so we can ensure to include them in the + // main index. + allPkgs map[*types.Pkg]bool + + declTodo ir.NameQueue + + strings intWriter + stringIndex map[string]uint64 + + data0 intWriter + declIndex map[*types.Sym]uint64 + inlineIndex map[*types.Sym]uint64 + typIndex map[*types.Type]uint64 +} + +// stringOff returns the offset of s within the string section. +// If not already present, it's added to the end. +func (p *iexporter) stringOff(s string) uint64 { + off, ok := p.stringIndex[s] + if !ok { + off = uint64(p.strings.Len()) + p.stringIndex[s] = off + + if *base.Flag.LowerV { + fmt.Printf("export: str %v %.40q\n", off, s) + } + + p.strings.uint64(uint64(len(s))) + p.strings.WriteString(s) + } + return off +} + +// pushDecl adds n to the declaration work queue, if not already present. +func (p *iexporter) pushDecl(n *ir.Name) { + if n.Sym() == nil || n.Sym().Def != n && n.Op() != ir.OTYPE { + base.Fatalf("weird Sym: %v, %v", n, n.Sym()) + } + + // Don't export predeclared declarations. + if n.Sym().Pkg == types.BuiltinPkg || n.Sym().Pkg == ir.Pkgs.Unsafe { + return + } + + if _, ok := p.declIndex[n.Sym()]; ok { + return + } + + p.declIndex[n.Sym()] = ^uint64(0) // mark n present in work queue + p.declTodo.PushRight(n) +} + +// exportWriter handles writing out individual data section chunks. +type exportWriter struct { + p *iexporter + + data intWriter + currPkg *types.Pkg + prevFile string + prevLine int64 + prevColumn int64 + + // dclIndex maps function-scoped declarations to an int used to refer to + // them later in the function. For local variables/params, the int is + // non-negative and in order of the appearance in the Func's Dcl list. For + // closure variables, the index is negative starting at -2. + dclIndex map[*ir.Name]int + maxDclIndex int + maxClosureVarIndex int +} + +func (p *iexporter) doDecl(n *ir.Name) { + w := p.newWriter() + w.setPkg(n.Sym().Pkg, false) + + switch n.Op() { + case ir.ONAME: + switch n.Class { + case ir.PEXTERN: + // Variable. + w.tag('V') + w.pos(n.Pos()) + w.typ(n.Type()) + w.varExt(n) + + case ir.PFUNC: + if ir.IsMethod(n) { + base.Fatalf("unexpected method: %v", n) + } + + // Function. + w.tag('F') + w.pos(n.Pos()) + w.signature(n.Type()) + w.funcExt(n) + + default: + base.Fatalf("unexpected class: %v, %v", n, n.Class) + } + + case ir.OLITERAL: + // TODO(mdempsky): Extend check to all declarations. + if n.Typecheck() == 0 { + base.FatalfAt(n.Pos(), "missed typecheck: %v", n) + } + + // Constant. + w.tag('C') + w.pos(n.Pos()) + w.value(n.Type(), n.Val()) + w.constExt(n) + + case ir.OTYPE: + if types.IsDotAlias(n.Sym()) { + // Alias. + w.tag('A') + w.pos(n.Pos()) + w.typ(n.Type()) + break + } + + // Defined type. + w.tag('T') + w.pos(n.Pos()) + + underlying := n.Type().Underlying() + if underlying == types.ErrorType.Underlying() { + // For "type T error", use error as the + // underlying type instead of error's own + // underlying anonymous interface. This + // ensures consistency with how importers may + // declare error (e.g., go/types uses nil Pkg + // for predeclared objects). + underlying = types.ErrorType + } + w.typ(underlying) + + t := n.Type() + if t.IsInterface() { + w.typeExt(t) + break + } + + ms := t.Methods() + w.uint64(uint64(ms.Len())) + for _, m := range ms.Slice() { + w.pos(m.Pos) + w.selector(m.Sym) + w.param(m.Type.Recv()) + w.signature(m.Type) + } + + w.typeExt(t) + for _, m := range ms.Slice() { + w.methExt(m) + } + + default: + base.Fatalf("unexpected node: %v", n) + } + + w.finish("dcl", p.declIndex, n.Sym()) +} + +func (w *exportWriter) tag(tag byte) { + w.data.WriteByte(tag) +} + +func (w *exportWriter) finish(what string, index map[*types.Sym]uint64, sym *types.Sym) { + off := w.flush() + if *base.Flag.LowerV { + fmt.Printf("export: %v %v %v\n", what, off, sym) + } + index[sym] = off +} + +func (p *iexporter) doInline(f *ir.Name) { + w := p.newWriter() + w.setPkg(fnpkg(f), false) + + w.dclIndex = make(map[*ir.Name]int, len(f.Func.Inl.Dcl)) + w.funcBody(f.Func) + + w.finish("inl", p.inlineIndex, f.Sym()) +} + +func (w *exportWriter) pos(pos src.XPos) { + p := base.Ctxt.PosTable.Pos(pos) + file := p.Base().AbsFilename() + line := int64(p.RelLine()) + column := int64(p.RelCol()) + + // Encode position relative to the last position: column + // delta, then line delta, then file name. We reserve the + // bottom bit of the column and line deltas to encode whether + // the remaining fields are present. + // + // Note: Because data objects may be read out of order (or not + // at all), we can only apply delta encoding within a single + // object. This is handled implicitly by tracking prevFile, + // prevLine, and prevColumn as fields of exportWriter. + + deltaColumn := (column - w.prevColumn) << 1 + deltaLine := (line - w.prevLine) << 1 + + if file != w.prevFile { + deltaLine |= 1 + } + if deltaLine != 0 { + deltaColumn |= 1 + } + + w.int64(deltaColumn) + if deltaColumn&1 != 0 { + w.int64(deltaLine) + if deltaLine&1 != 0 { + w.string(file) + } + } + + w.prevFile = file + w.prevLine = line + w.prevColumn = column +} + +func (w *exportWriter) pkg(pkg *types.Pkg) { + // TODO(mdempsky): Add flag to types.Pkg to mark pseudo-packages. + if pkg == ir.Pkgs.Go { + base.Fatalf("export of pseudo-package: %q", pkg.Path) + } + + // Ensure any referenced packages are declared in the main index. + w.p.allPkgs[pkg] = true + + w.string(pkg.Path) +} + +func (w *exportWriter) qualifiedIdent(n *ir.Name) { + // Ensure any referenced declarations are written out too. + w.p.pushDecl(n) + + s := n.Sym() + w.string(s.Name) + w.pkg(s.Pkg) +} + +func (w *exportWriter) selector(s *types.Sym) { + if w.currPkg == nil { + base.Fatalf("missing currPkg") + } + + // If the selector being written is unexported, it comes with a package qualifier. + // If the selector being written is exported, it is not package-qualified. + // See the spec: https://golang.org/ref/spec#Uniqueness_of_identifiers + // As an optimization, we don't actually write the package every time - instead we + // call setPkg before a group of selectors (all of which must have the same package qualifier). + pkg := w.currPkg + if types.IsExported(s.Name) { + pkg = types.LocalPkg + } + if s.Pkg != pkg { + base.Fatalf("package mismatch in selector: %v in package %q, but want %q", s, s.Pkg.Path, pkg.Path) + } + + w.string(s.Name) +} + +func (w *exportWriter) typ(t *types.Type) { + w.data.uint64(w.p.typOff(t)) +} + +// The "exotic" functions in this section encode a wider range of +// items than the standard encoding functions above. These include +// types that do not appear in declarations, only in code, such as +// method types. These methods need to be separate from the standard +// encoding functions because we don't want to modify the encoding +// generated by the standard functions (because that exported +// information is read by tools besides the compiler). + +// exoticType exports a type to the writer. +func (w *exportWriter) exoticType(t *types.Type) { + switch { + case t == nil: + // Calls-as-statements have no type. + w.data.uint64(exoticTypeNil) + case t.IsStruct() && t.StructType().Funarg != types.FunargNone: + // These are weird structs for representing tuples of types returned + // by multi-return functions. + // They don't fit the standard struct type mold. For instance, + // they don't have any package info. + w.data.uint64(exoticTypeTuple) + w.uint64(uint64(t.StructType().Funarg)) + w.uint64(uint64(t.NumFields())) + for _, f := range t.FieldSlice() { + w.pos(f.Pos) + s := f.Sym + if s == nil { + w.uint64(0) + } else if s.Pkg == nil { + w.uint64(exoticTypeSymNoPkg) + w.string(s.Name) + } else { + w.uint64(exoticTypeSymWithPkg) + w.pkg(s.Pkg) + w.string(s.Name) + } + w.typ(f.Type) + if f.Embedded != 0 || f.Note != "" { + panic("extra info in funarg struct field") + } + } + case t.Kind() == types.TFUNC && t.Recv() != nil: + w.data.uint64(exoticTypeRecv) + // interface method types have a fake receiver type. + isFakeRecv := t.Recv().Type == types.FakeRecvType() + w.bool(isFakeRecv) + if !isFakeRecv { + w.exoticParam(t.Recv()) + } + w.exoticSignature(t) + + default: + // A regular type. + w.data.uint64(exoticTypeRegular) + w.typ(t) + } +} + +const ( + exoticTypeNil = iota + exoticTypeTuple + exoticTypeRecv + exoticTypeRegular +) +const ( + exoticTypeSymNil = iota + exoticTypeSymNoPkg + exoticTypeSymWithPkg +) + +// Export a selector, but one whose package may not match +// the package being compiled. This is a separate function +// because the standard selector() serialization format is fixed +// by the go/types reader. This one can only be used during +// inline/generic body exporting. +func (w *exportWriter) exoticSelector(s *types.Sym) { + pkg := w.currPkg + if types.IsExported(s.Name) { + pkg = types.LocalPkg + } + + w.string(s.Name) + if s.Pkg == pkg { + w.uint64(0) + } else { + w.uint64(1) + w.pkg(s.Pkg) + } +} + +func (w *exportWriter) exoticSignature(t *types.Type) { + hasPkg := t.Pkg() != nil + w.bool(hasPkg) + if hasPkg { + w.pkg(t.Pkg()) + } + w.exoticParamList(t.Params().FieldSlice()) + w.exoticParamList(t.Results().FieldSlice()) +} + +func (w *exportWriter) exoticParamList(fs []*types.Field) { + w.uint64(uint64(len(fs))) + for _, f := range fs { + w.exoticParam(f) + } + +} +func (w *exportWriter) exoticParam(f *types.Field) { + w.pos(f.Pos) + w.exoticSym(f.Sym) + w.uint64(uint64(f.Offset)) + w.exoticType(f.Type) + w.bool(f.IsDDD()) +} + +func (w *exportWriter) exoticField(f *types.Field) { + w.pos(f.Pos) + w.exoticSym(f.Sym) + w.uint64(uint64(f.Offset)) + w.exoticType(f.Type) + w.string(f.Note) +} + +func (w *exportWriter) exoticSym(s *types.Sym) { + if s == nil { + w.string("") + return + } + if s.Name == "" { + base.Fatalf("empty symbol name") + } + w.string(s.Name) + if !types.IsExported(s.Name) { + w.pkg(s.Pkg) + } +} + +func (p *iexporter) newWriter() *exportWriter { + return &exportWriter{p: p} +} + +func (w *exportWriter) flush() uint64 { + off := uint64(w.p.data0.Len()) + io.Copy(&w.p.data0, &w.data) + return off +} + +func (p *iexporter) typOff(t *types.Type) uint64 { + off, ok := p.typIndex[t] + if !ok { + w := p.newWriter() + w.doTyp(t) + rawOff := w.flush() + if *base.Flag.LowerV { + fmt.Printf("export: typ %v %v\n", rawOff, t) + } + off = predeclReserved + rawOff + p.typIndex[t] = off + } + return off +} + +func (w *exportWriter) startType(k itag) { + w.data.uint64(uint64(k)) +} + +func (w *exportWriter) doTyp(t *types.Type) { + if t.Sym() != nil { + if t.Sym().Pkg == types.BuiltinPkg || t.Sym().Pkg == ir.Pkgs.Unsafe { + base.Fatalf("builtin type missing from typIndex: %v", t) + } + + w.startType(definedType) + w.qualifiedIdent(t.Obj().(*ir.Name)) + return + } + + switch t.Kind() { + case types.TPTR: + w.startType(pointerType) + w.typ(t.Elem()) + + case types.TSLICE: + w.startType(sliceType) + w.typ(t.Elem()) + + case types.TARRAY: + w.startType(arrayType) + w.uint64(uint64(t.NumElem())) + w.typ(t.Elem()) + + case types.TCHAN: + w.startType(chanType) + w.uint64(uint64(t.ChanDir())) + w.typ(t.Elem()) + + case types.TMAP: + w.startType(mapType) + w.typ(t.Key()) + w.typ(t.Elem()) + + case types.TFUNC: + w.startType(signatureType) + w.setPkg(t.Pkg(), true) + w.signature(t) + + case types.TSTRUCT: + w.startType(structType) + w.setPkg(t.Pkg(), true) + + w.uint64(uint64(t.NumFields())) + for _, f := range t.FieldSlice() { + w.pos(f.Pos) + w.selector(f.Sym) + w.typ(f.Type) + w.bool(f.Embedded != 0) + w.string(f.Note) + } + + case types.TINTER: + var embeddeds, methods []*types.Field + for _, m := range t.Methods().Slice() { + if m.Sym != nil { + methods = append(methods, m) + } else { + embeddeds = append(embeddeds, m) + } + } + + w.startType(interfaceType) + w.setPkg(t.Pkg(), true) + + w.uint64(uint64(len(embeddeds))) + for _, f := range embeddeds { + w.pos(f.Pos) + w.typ(f.Type) + } + + w.uint64(uint64(len(methods))) + for _, f := range methods { + w.pos(f.Pos) + w.selector(f.Sym) + w.signature(f.Type) + } + + default: + base.Fatalf("unexpected type: %v", t) + } +} + +func (w *exportWriter) setPkg(pkg *types.Pkg, write bool) { + if pkg == types.NoPkg { + base.Fatalf("missing pkg") + } + + if write { + w.pkg(pkg) + } + + w.currPkg = pkg +} + +func (w *exportWriter) signature(t *types.Type) { + w.paramList(t.Params().FieldSlice()) + w.paramList(t.Results().FieldSlice()) + if n := t.Params().NumFields(); n > 0 { + w.bool(t.Params().Field(n - 1).IsDDD()) + } +} + +func (w *exportWriter) paramList(fs []*types.Field) { + w.uint64(uint64(len(fs))) + for _, f := range fs { + w.param(f) + } +} + +func (w *exportWriter) param(f *types.Field) { + w.pos(f.Pos) + w.localIdent(types.OrigSym(f.Sym)) + w.typ(f.Type) +} + +func constTypeOf(typ *types.Type) constant.Kind { + switch typ { + case types.UntypedInt, types.UntypedRune: + return constant.Int + case types.UntypedFloat: + return constant.Float + case types.UntypedComplex: + return constant.Complex + } + + switch typ.Kind() { + case types.TBOOL: + return constant.Bool + case types.TSTRING: + return constant.String + case types.TINT, types.TINT8, types.TINT16, types.TINT32, types.TINT64, + types.TUINT, types.TUINT8, types.TUINT16, types.TUINT32, types.TUINT64, types.TUINTPTR: + return constant.Int + case types.TFLOAT32, types.TFLOAT64: + return constant.Float + case types.TCOMPLEX64, types.TCOMPLEX128: + return constant.Complex + } + + base.Fatalf("unexpected constant type: %v", typ) + return 0 +} + +func (w *exportWriter) value(typ *types.Type, v constant.Value) { + ir.AssertValidTypeForConst(typ, v) + w.typ(typ) + + // Each type has only one admissible constant representation, + // so we could type switch directly on v.U here. However, + // switching on the type increases symmetry with import logic + // and provides a useful consistency check. + + switch constTypeOf(typ) { + case constant.Bool: + w.bool(constant.BoolVal(v)) + case constant.String: + w.string(constant.StringVal(v)) + case constant.Int: + w.mpint(v, typ) + case constant.Float: + w.mpfloat(v, typ) + case constant.Complex: + w.mpfloat(constant.Real(v), typ) + w.mpfloat(constant.Imag(v), typ) + } +} + +func intSize(typ *types.Type) (signed bool, maxBytes uint) { + if typ.IsUntyped() { + return true, ir.ConstPrec / 8 + } + + switch typ.Kind() { + case types.TFLOAT32, types.TCOMPLEX64: + return true, 3 + case types.TFLOAT64, types.TCOMPLEX128: + return true, 7 + } + + signed = typ.IsSigned() + maxBytes = uint(typ.Size()) + + // The go/types API doesn't expose sizes to importers, so they + // don't know how big these types are. + switch typ.Kind() { + case types.TINT, types.TUINT, types.TUINTPTR: + maxBytes = 8 + } + + return +} + +// mpint exports a multi-precision integer. +// +// For unsigned types, small values are written out as a single +// byte. Larger values are written out as a length-prefixed big-endian +// byte string, where the length prefix is encoded as its complement. +// For example, bytes 0, 1, and 2 directly represent the integer +// values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-, +// 2-, and 3-byte big-endian string follow. +// +// Encoding for signed types use the same general approach as for +// unsigned types, except small values use zig-zag encoding and the +// bottom bit of length prefix byte for large values is reserved as a +// sign bit. +// +// The exact boundary between small and large encodings varies +// according to the maximum number of bytes needed to encode a value +// of type typ. As a special case, 8-bit types are always encoded as a +// single byte. +func (w *exportWriter) mpint(x constant.Value, typ *types.Type) { + signed, maxBytes := intSize(typ) + + negative := constant.Sign(x) < 0 + if !signed && negative { + base.Fatalf("negative unsigned integer; type %v, value %v", typ, x) + } + + b := constant.Bytes(x) // little endian + for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { + b[i], b[j] = b[j], b[i] + } + + if len(b) > 0 && b[0] == 0 { + base.Fatalf("leading zeros") + } + if uint(len(b)) > maxBytes { + base.Fatalf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x) + } + + maxSmall := 256 - maxBytes + if signed { + maxSmall = 256 - 2*maxBytes + } + if maxBytes == 1 { + maxSmall = 256 + } + + // Check if x can use small value encoding. + if len(b) <= 1 { + var ux uint + if len(b) == 1 { + ux = uint(b[0]) + } + if signed { + ux <<= 1 + if negative { + ux-- + } + } + if ux < maxSmall { + w.data.WriteByte(byte(ux)) + return + } + } + + n := 256 - uint(len(b)) + if signed { + n = 256 - 2*uint(len(b)) + if negative { + n |= 1 + } + } + if n < maxSmall || n >= 256 { + base.Fatalf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n) + } + + w.data.WriteByte(byte(n)) + w.data.Write(b) +} + +// mpfloat exports a multi-precision floating point number. +// +// The number's value is decomposed into mantissa × 2**exponent, where +// mantissa is an integer. The value is written out as mantissa (as a +// multi-precision integer) and then the exponent, except exponent is +// omitted if mantissa is zero. +func (w *exportWriter) mpfloat(v constant.Value, typ *types.Type) { + f := ir.BigFloat(v) + if f.IsInf() { + base.Fatalf("infinite constant") + } + + // Break into f = mant × 2**exp, with 0.5 <= mant < 1. + var mant big.Float + exp := int64(f.MantExp(&mant)) + + // Scale so that mant is an integer. + prec := mant.MinPrec() + mant.SetMantExp(&mant, int(prec)) + exp -= int64(prec) + + manti, acc := mant.Int(nil) + if acc != big.Exact { + base.Fatalf("mantissa scaling failed for %f (%s)", f, acc) + } + w.mpint(constant.Make(manti), typ) + if manti.Sign() != 0 { + w.int64(exp) + } +} + +func (w *exportWriter) mprat(v constant.Value) { + r, ok := constant.Val(v).(*big.Rat) + if !w.bool(ok) { + return + } + // TODO(mdempsky): Come up with a more efficient binary + // encoding before bumping iexportVersion to expose to + // gcimporter. + w.string(r.String()) +} + +func (w *exportWriter) bool(b bool) bool { + var x uint64 + if b { + x = 1 + } + w.uint64(x) + return b +} + +func (w *exportWriter) int64(x int64) { w.data.int64(x) } +func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) } +func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) } + +// Compiler-specific extensions. + +func (w *exportWriter) constExt(n *ir.Name) { + // Internally, we now represent untyped float and complex + // constants with infinite-precision rational numbers using + // go/constant, but the "public" export data format known to + // gcimporter only supports 512-bit floating point constants. + // In case rationals turn out to be a bad idea and we want to + // switch back to fixed-precision constants, for now we + // continue writing out the 512-bit truncation in the public + // data section, and write the exact, rational constant in the + // compiler's extension data. Also, we only need to worry + // about exporting rationals for declared constants, because + // constants that appear in an expression will already have + // been coerced to a concrete, fixed-precision type. + // + // Eventually, assuming we stick with using rationals, we + // should bump iexportVersion to support rationals, and do the + // whole gcimporter update song-and-dance. + // + // TODO(mdempsky): Prepare vocals for that. + + switch n.Type() { + case types.UntypedFloat: + w.mprat(n.Val()) + case types.UntypedComplex: + v := n.Val() + w.mprat(constant.Real(v)) + w.mprat(constant.Imag(v)) + } +} + +func (w *exportWriter) varExt(n *ir.Name) { + w.linkname(n.Sym()) + w.symIdx(n.Sym()) +} + +func (w *exportWriter) funcExt(n *ir.Name) { + w.linkname(n.Sym()) + w.symIdx(n.Sym()) + + // Record definition ABI so cross-ABI calls can be direct. + // This is important for the performance of calling some + // common functions implemented in assembly (e.g., bytealg). + w.uint64(uint64(n.Func.ABI)) + + w.uint64(uint64(n.Func.Pragma)) + + // Escape analysis. + for _, fs := range &types.RecvsParams { + for _, f := range fs(n.Type()).FieldSlice() { + w.string(f.Note) + } + } + + // Inline body. + if n.Func.Inl != nil { + w.uint64(1 + uint64(n.Func.Inl.Cost)) + if n.Func.ExportInline() { + w.p.doInline(n) + } + + // Endlineno for inlined function. + w.pos(n.Func.Endlineno) + } else { + w.uint64(0) + } +} + +func (w *exportWriter) methExt(m *types.Field) { + w.bool(m.Nointerface()) + w.funcExt(m.Nname.(*ir.Name)) +} + +func (w *exportWriter) linkname(s *types.Sym) { + w.string(s.Linkname) +} + +func (w *exportWriter) symIdx(s *types.Sym) { + lsym := s.Linksym() + if lsym.PkgIdx > goobj.PkgIdxSelf || (lsym.PkgIdx == goobj.PkgIdxInvalid && !lsym.Indexed()) || s.Linkname != "" { + // Don't export index for non-package symbols, linkname'd symbols, + // and symbols without an index. They can only be referenced by + // name. + w.int64(-1) + } else { + // For a defined symbol, export its index. + // For re-exporting an imported symbol, pass its index through. + w.int64(int64(lsym.SymIdx)) + } +} + +func (w *exportWriter) typeExt(t *types.Type) { + // Export whether this type is marked notinheap. + w.bool(t.NotInHeap()) + // For type T, export the index of type descriptor symbols of T and *T. + if i, ok := typeSymIdx[t]; ok { + w.int64(i[0]) + w.int64(i[1]) + return + } + w.symIdx(types.TypeSym(t)) + w.symIdx(types.TypeSym(t.PtrTo())) +} + +// Inline bodies. + +func (w *exportWriter) writeNames(dcl []*ir.Name) { + w.int64(int64(len(dcl))) + for i, n := range dcl { + w.pos(n.Pos()) + w.localIdent(n.Sym()) + w.typ(n.Type()) + w.dclIndex[n] = w.maxDclIndex + i + } + w.maxDclIndex += len(dcl) +} + +func (w *exportWriter) funcBody(fn *ir.Func) { + //fmt.Printf("Exporting %s\n", fn.Nname.Sym().Name) + w.writeNames(fn.Inl.Dcl) + + w.stmtList(fn.Inl.Body) +} + +func (w *exportWriter) stmtList(list []ir.Node) { + for _, n := range list { + w.node(n) + } + w.op(ir.OEND) +} + +func (w *exportWriter) node(n ir.Node) { + if ir.OpPrec[n.Op()] < 0 { + w.stmt(n) + } else { + w.expr(n) + } +} + +// Caution: stmt will emit more than one node for statement nodes n that have a non-empty +// n.Ninit and where n cannot have a natural init section (such as in "if", "for", etc.). +func (w *exportWriter) stmt(n ir.Node) { + if len(n.Init()) > 0 && !ir.StmtWithInit(n.Op()) { + // can't use stmtList here since we don't want the final OEND + for _, n := range n.Init() { + w.stmt(n) + } + } + + switch n.Op() { + case ir.OBLOCK: + // No OBLOCK in export data. + // Inline content into this statement list, + // like the init list above. + // (At the moment neither the parser nor the typechecker + // generate OBLOCK nodes except to denote an empty + // function body, although that may change.) + n := n.(*ir.BlockStmt) + for _, n := range n.List { + w.stmt(n) + } + + case ir.ODCL: + n := n.(*ir.Decl) + if ir.IsBlank(n.X) { + return // blank declarations not useful to importers + } + w.op(ir.ODCL) + w.localName(n.X) + + case ir.OAS: + // Don't export "v = " initializing statements, hope they're always + // preceded by the DCL which will be re-parsed and typecheck to reproduce + // the "v = " again. + n := n.(*ir.AssignStmt) + if n.Y != nil { + w.op(ir.OAS) + w.pos(n.Pos()) + w.expr(n.X) + w.expr(n.Y) + } + + case ir.OASOP: + n := n.(*ir.AssignOpStmt) + w.op(ir.OASOP) + w.pos(n.Pos()) + w.op(n.AsOp) + w.expr(n.X) + if w.bool(!n.IncDec) { + w.expr(n.Y) + } + + case ir.OAS2, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV: + n := n.(*ir.AssignListStmt) + if go117ExportTypes { + w.op(n.Op()) + } else { + w.op(ir.OAS2) + } + w.pos(n.Pos()) + w.exprList(n.Lhs) + w.exprList(n.Rhs) + + case ir.ORETURN: + n := n.(*ir.ReturnStmt) + w.op(ir.ORETURN) + w.pos(n.Pos()) + w.exprList(n.Results) + + // case ORETJMP: + // unreachable - generated by compiler for trampoline routines + + case ir.OGO, ir.ODEFER: + n := n.(*ir.GoDeferStmt) + w.op(n.Op()) + w.pos(n.Pos()) + w.expr(n.Call) + + case ir.OIF: + n := n.(*ir.IfStmt) + w.op(ir.OIF) + w.pos(n.Pos()) + w.stmtList(n.Init()) + w.expr(n.Cond) + w.stmtList(n.Body) + w.stmtList(n.Else) + + case ir.OFOR: + n := n.(*ir.ForStmt) + w.op(ir.OFOR) + w.pos(n.Pos()) + w.stmtList(n.Init()) + w.exprsOrNil(n.Cond, n.Post) + w.stmtList(n.Body) + + case ir.ORANGE: + n := n.(*ir.RangeStmt) + w.op(ir.ORANGE) + w.pos(n.Pos()) + w.exprsOrNil(n.Key, n.Value) + w.expr(n.X) + w.stmtList(n.Body) + + case ir.OSELECT: + n := n.(*ir.SelectStmt) + w.op(n.Op()) + w.pos(n.Pos()) + w.stmtList(n.Init()) + w.commList(n.Cases) + + case ir.OSWITCH: + n := n.(*ir.SwitchStmt) + w.op(n.Op()) + w.pos(n.Pos()) + w.stmtList(n.Init()) + w.exprsOrNil(n.Tag, nil) + w.caseList(n.Cases, isNamedTypeSwitch(n.Tag)) + + // case OCASE: + // handled by caseList + + case ir.OFALL: + n := n.(*ir.BranchStmt) + w.op(ir.OFALL) + w.pos(n.Pos()) + + case ir.OBREAK, ir.OCONTINUE, ir.OGOTO, ir.OLABEL: + w.op(n.Op()) + w.pos(n.Pos()) + label := "" + if sym := n.Sym(); sym != nil { + label = sym.Name + } + w.string(label) + + default: + base.Fatalf("exporter: CANNOT EXPORT: %v\nPlease notify gri@\n", n.Op()) + } +} + +func isNamedTypeSwitch(x ir.Node) bool { + guard, ok := x.(*ir.TypeSwitchGuard) + return ok && guard.Tag != nil +} + +func (w *exportWriter) caseList(cases []*ir.CaseClause, namedTypeSwitch bool) { + w.uint64(uint64(len(cases))) + for _, cas := range cases { + w.pos(cas.Pos()) + w.stmtList(cas.List) + if namedTypeSwitch { + w.localName(cas.Var) + } + w.stmtList(cas.Body) + } +} + +func (w *exportWriter) commList(cases []*ir.CommClause) { + w.uint64(uint64(len(cases))) + for _, cas := range cases { + w.pos(cas.Pos()) + w.node(cas.Comm) + w.stmtList(cas.Body) + } +} + +func (w *exportWriter) exprList(list ir.Nodes) { + for _, n := range list { + w.expr(n) + } + w.op(ir.OEND) +} + +func simplifyForExport(n ir.Node) ir.Node { + switch n.Op() { + case ir.OPAREN: + n := n.(*ir.ParenExpr) + return simplifyForExport(n.X) + } + return n +} + +func (w *exportWriter) expr(n ir.Node) { + n = simplifyForExport(n) + switch n.Op() { + // expressions + // (somewhat closely following the structure of exprfmt in fmt.go) + case ir.ONIL: + n := n.(*ir.NilExpr) + if !n.Type().HasNil() { + base.Fatalf("unexpected type for nil: %v", n.Type()) + } + w.op(ir.ONIL) + w.pos(n.Pos()) + w.typ(n.Type()) + + case ir.OLITERAL: + w.op(ir.OLITERAL) + w.pos(n.Pos()) + w.value(n.Type(), n.Val()) + + case ir.ONAME: + // Package scope name. + n := n.(*ir.Name) + if (n.Class == ir.PEXTERN || n.Class == ir.PFUNC) && !ir.IsBlank(n) { + w.op(ir.ONONAME) + w.qualifiedIdent(n) + if go117ExportTypes { + w.typ(n.Type()) + } + break + } + + // Function scope name. + // We don't need a type here, as the type will be provided at the + // declaration of n. + w.op(ir.ONAME) + w.localName(n) + + // case OPACK, ONONAME: + // should have been resolved by typechecking - handled by default case + + case ir.OTYPE: + w.op(ir.OTYPE) + w.typ(n.Type()) + + case ir.OTYPESW: + n := n.(*ir.TypeSwitchGuard) + w.op(ir.OTYPESW) + w.pos(n.Pos()) + var s *types.Sym + if n.Tag != nil { + if n.Tag.Op() != ir.ONONAME { + base.Fatalf("expected ONONAME, got %v", n.Tag) + } + s = n.Tag.Sym() + } + w.localIdent(s) // declared pseudo-variable, if any + w.expr(n.X) + + // case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC: + // should have been resolved by typechecking - handled by default case + + case ir.OCLOSURE: + n := n.(*ir.ClosureExpr) + w.op(ir.OCLOSURE) + w.pos(n.Pos()) + w.signature(n.Type()) + + // Write out id for the Outer of each conditional variable. The + // conditional variable itself for this closure will be re-created + // during import. + w.int64(int64(len(n.Func.ClosureVars))) + for i, cv := range n.Func.ClosureVars { + w.pos(cv.Pos()) + w.localName(cv.Outer) + // Closure variable (which will be re-created during + // import) is given via a negative id, starting at -2, + // which is used to refer to it later in the function + // during export. -1 represents blanks. + w.dclIndex[cv] = -(i + 2) - w.maxClosureVarIndex + } + w.maxClosureVarIndex += len(n.Func.ClosureVars) + + // like w.funcBody(n.Func), but not for .Inl + w.writeNames(n.Func.Dcl) + w.stmtList(n.Func.Body) + + // case OCOMPLIT: + // should have been resolved by typechecking - handled by default case + + case ir.OPTRLIT: + n := n.(*ir.AddrExpr) + if go117ExportTypes { + w.op(ir.OPTRLIT) + } else { + w.op(ir.OADDR) + } + w.pos(n.Pos()) + w.expr(n.X) + if go117ExportTypes { + w.typ(n.Type()) + } + + case ir.OSTRUCTLIT: + n := n.(*ir.CompLitExpr) + w.op(ir.OSTRUCTLIT) + w.pos(n.Pos()) + w.typ(n.Type()) + w.fieldList(n.List) // special handling of field names + + case ir.OARRAYLIT, ir.OSLICELIT, ir.OMAPLIT: + n := n.(*ir.CompLitExpr) + if go117ExportTypes { + w.op(n.Op()) + } else { + w.op(ir.OCOMPLIT) + } + w.pos(n.Pos()) + w.typ(n.Type()) + w.exprList(n.List) + if go117ExportTypes && n.Op() == ir.OSLICELIT { + w.uint64(uint64(n.Len)) + } + case ir.OKEY: + n := n.(*ir.KeyExpr) + w.op(ir.OKEY) + w.pos(n.Pos()) + w.expr(n.Key) + w.expr(n.Value) + + // case OSTRUCTKEY: + // unreachable - handled in case OSTRUCTLIT by elemList + + case ir.OXDOT, ir.ODOT, ir.ODOTPTR, ir.ODOTINTER, ir.ODOTMETH, ir.OCALLPART, ir.OMETHEXPR: + n := n.(*ir.SelectorExpr) + if go117ExportTypes { + if n.Op() == ir.OXDOT { + base.Fatalf("shouldn't encounter XDOT in new exporter") + } + w.op(n.Op()) + } else { + w.op(ir.OXDOT) + } + w.pos(n.Pos()) + w.expr(n.X) + w.exoticSelector(n.Sel) + if go117ExportTypes { + w.exoticType(n.Type()) + if n.Op() == ir.ODOT || n.Op() == ir.ODOTPTR || n.Op() == ir.ODOTINTER { + w.exoticField(n.Selection) + } + // n.Selection is not required for OMETHEXPR, ODOTMETH, and OCALLPART. It will + // be reconstructed during import. + } + + case ir.ODOTTYPE, ir.ODOTTYPE2: + n := n.(*ir.TypeAssertExpr) + if go117ExportTypes { + w.op(n.Op()) + } else { + w.op(ir.ODOTTYPE) + } + w.pos(n.Pos()) + w.expr(n.X) + w.typ(n.Type()) + + case ir.OINDEX, ir.OINDEXMAP: + n := n.(*ir.IndexExpr) + if go117ExportTypes { + w.op(n.Op()) + } else { + w.op(ir.OINDEX) + } + w.pos(n.Pos()) + w.expr(n.X) + w.expr(n.Index) + if go117ExportTypes { + w.typ(n.Type()) + if n.Op() == ir.OINDEXMAP { + w.bool(n.Assigned) + } + } + + case ir.OSLICE, ir.OSLICESTR, ir.OSLICEARR: + n := n.(*ir.SliceExpr) + if go117ExportTypes { + w.op(n.Op()) + } else { + w.op(ir.OSLICE) + } + w.pos(n.Pos()) + w.expr(n.X) + w.exprsOrNil(n.Low, n.High) + if go117ExportTypes { + w.typ(n.Type()) + } + + case ir.OSLICE3, ir.OSLICE3ARR: + n := n.(*ir.SliceExpr) + if go117ExportTypes { + w.op(n.Op()) + } else { + w.op(ir.OSLICE3) + } + w.pos(n.Pos()) + w.expr(n.X) + w.exprsOrNil(n.Low, n.High) + w.expr(n.Max) + if go117ExportTypes { + w.typ(n.Type()) + } + + case ir.OCOPY, ir.OCOMPLEX, ir.OUNSAFEADD, ir.OUNSAFESLICE: + // treated like other builtin calls (see e.g., OREAL) + n := n.(*ir.BinaryExpr) + w.op(n.Op()) + w.pos(n.Pos()) + w.expr(n.X) + w.expr(n.Y) + if go117ExportTypes { + w.typ(n.Type()) + } else { + w.op(ir.OEND) + } + + case ir.OCONV, ir.OCONVIFACE, ir.OCONVNOP, ir.OBYTES2STR, ir.ORUNES2STR, ir.OSTR2BYTES, ir.OSTR2RUNES, ir.ORUNESTR, ir.OSLICE2ARRPTR: + n := n.(*ir.ConvExpr) + if go117ExportTypes { + w.op(n.Op()) + } else { + w.op(ir.OCONV) + } + w.pos(n.Pos()) + w.typ(n.Type()) + w.expr(n.X) + + case ir.OREAL, ir.OIMAG, ir.OCAP, ir.OCLOSE, ir.OLEN, ir.ONEW, ir.OPANIC: + n := n.(*ir.UnaryExpr) + w.op(n.Op()) + w.pos(n.Pos()) + w.expr(n.X) + if go117ExportTypes { + if n.Op() != ir.OPANIC { + w.typ(n.Type()) + } + } else { + w.op(ir.OEND) + } + + case ir.OAPPEND, ir.ODELETE, ir.ORECOVER, ir.OPRINT, ir.OPRINTN: + n := n.(*ir.CallExpr) + w.op(n.Op()) + w.pos(n.Pos()) + w.exprList(n.Args) // emits terminating OEND + // only append() calls may contain '...' arguments + if n.Op() == ir.OAPPEND { + w.bool(n.IsDDD) + } else if n.IsDDD { + base.Fatalf("exporter: unexpected '...' with %v call", n.Op()) + } + if go117ExportTypes { + if n.Op() != ir.ODELETE && n.Op() != ir.OPRINT && n.Op() != ir.OPRINTN { + w.typ(n.Type()) + } + } + + case ir.OCALL, ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER, ir.OGETG: + n := n.(*ir.CallExpr) + if go117ExportTypes { + w.op(n.Op()) + } else { + w.op(ir.OCALL) + } + w.pos(n.Pos()) + w.stmtList(n.Init()) + w.expr(n.X) + w.exprList(n.Args) + w.bool(n.IsDDD) + if go117ExportTypes { + w.exoticType(n.Type()) + w.uint64(uint64(n.Use)) + } + + case ir.OMAKEMAP, ir.OMAKECHAN, ir.OMAKESLICE: + n := n.(*ir.MakeExpr) + w.op(n.Op()) // must keep separate from OMAKE for importer + w.pos(n.Pos()) + w.typ(n.Type()) + switch { + default: + // empty list + w.op(ir.OEND) + case n.Cap != nil: + w.expr(n.Len) + w.expr(n.Cap) + w.op(ir.OEND) + case n.Len != nil && (n.Op() == ir.OMAKESLICE || !n.Len.Type().IsUntyped()): + // Note: the extra conditional exists because make(T) for + // T a map or chan type, gets an untyped zero added as + // an argument. Don't serialize that argument here. + w.expr(n.Len) + w.op(ir.OEND) + case n.Len != nil && go117ExportTypes: + w.expr(n.Len) + w.op(ir.OEND) + } + + // unary expressions + case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV: + n := n.(*ir.UnaryExpr) + w.op(n.Op()) + w.pos(n.Pos()) + w.expr(n.X) + if go117ExportTypes { + w.typ(n.Type()) + } + + case ir.OADDR: + n := n.(*ir.AddrExpr) + w.op(n.Op()) + w.pos(n.Pos()) + w.expr(n.X) + if go117ExportTypes { + w.typ(n.Type()) + } + + case ir.ODEREF: + n := n.(*ir.StarExpr) + w.op(n.Op()) + w.pos(n.Pos()) + w.expr(n.X) + if go117ExportTypes { + w.typ(n.Type()) + } + + case ir.OSEND: + n := n.(*ir.SendStmt) + w.op(n.Op()) + w.pos(n.Pos()) + w.expr(n.Chan) + w.expr(n.Value) + + // binary expressions + case ir.OADD, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OEQ, ir.OGE, ir.OGT, ir.OLE, ir.OLT, + ir.OLSH, ir.OMOD, ir.OMUL, ir.ONE, ir.OOR, ir.ORSH, ir.OSUB, ir.OXOR: + n := n.(*ir.BinaryExpr) + w.op(n.Op()) + w.pos(n.Pos()) + w.expr(n.X) + w.expr(n.Y) + if go117ExportTypes { + w.typ(n.Type()) + } + + case ir.OANDAND, ir.OOROR: + n := n.(*ir.LogicalExpr) + w.op(n.Op()) + w.pos(n.Pos()) + w.expr(n.X) + w.expr(n.Y) + if go117ExportTypes { + w.typ(n.Type()) + } + + case ir.OADDSTR: + n := n.(*ir.AddStringExpr) + w.op(ir.OADDSTR) + w.pos(n.Pos()) + w.exprList(n.List) + if go117ExportTypes { + w.typ(n.Type()) + } + + case ir.ODCLCONST: + // if exporting, DCLCONST should just be removed as its usage + // has already been replaced with literals + + default: + base.Fatalf("cannot export %v (%d) node\n"+ + "\t==> please file an issue and assign to gri@", n.Op(), int(n.Op())) + } +} + +func (w *exportWriter) op(op ir.Op) { + if debug { + w.uint64(magic) + } + w.uint64(uint64(op)) +} + +func (w *exportWriter) exprsOrNil(a, b ir.Node) { + ab := 0 + if a != nil { + ab |= 1 + } + if b != nil { + ab |= 2 + } + w.uint64(uint64(ab)) + if ab&1 != 0 { + w.expr(a) + } + if ab&2 != 0 { + w.node(b) + } +} + +func (w *exportWriter) fieldList(list ir.Nodes) { + w.uint64(uint64(len(list))) + for _, n := range list { + n := n.(*ir.StructKeyExpr) + w.pos(n.Pos()) + w.selector(n.Field) + w.expr(n.Value) + if go117ExportTypes { + w.uint64(uint64(n.Offset)) + } + } +} + +func (w *exportWriter) localName(n *ir.Name) { + if ir.IsBlank(n) { + w.int64(-1) + return + } + + i, ok := w.dclIndex[n] + if !ok { + base.FatalfAt(n.Pos(), "missing from dclIndex: %+v", n) + } + w.int64(int64(i)) +} + +func (w *exportWriter) localIdent(s *types.Sym) { + if w.currPkg == nil { + base.Fatalf("missing currPkg") + } + + // Anonymous parameters. + if s == nil { + w.string("") + return + } + + name := s.Name + if name == "_" { + w.string("_") + return + } + + // TODO(mdempsky): Fix autotmp hack. + if i := strings.LastIndex(name, "."); i >= 0 && !strings.HasPrefix(name, ".autotmp_") { + base.Fatalf("unexpected dot in identifier: %v", name) + } + + if s.Pkg != w.currPkg { + base.Fatalf("weird package in name: %v => %v from %q, not %q", s, name, s.Pkg.Path, w.currPkg.Path) + } + + w.string(name) +} + +type intWriter struct { + bytes.Buffer +} + +func (w *intWriter) int64(x int64) { + var buf [binary.MaxVarintLen64]byte + n := binary.PutVarint(buf[:], x) + w.Write(buf[:n]) +} + +func (w *intWriter) uint64(x uint64) { + var buf [binary.MaxVarintLen64]byte + n := binary.PutUvarint(buf[:], x) + w.Write(buf[:n]) +} + +// If go117ExportTypes is true, then we write type information when +// exporting function bodies, so those function bodies don't need to +// be re-typechecked on import. +// This flag adds some other info to the serialized stream as well +// which was previously recomputed during typechecking, like +// specializing opcodes (e.g. OXDOT to ODOTPTR) and ancillary +// information (e.g. length field for OSLICELIT). +const go117ExportTypes = true +const Go117ExportTypes = go117ExportTypes diff --git a/src/cmd/compile/internal/typecheck/iimport.go b/src/cmd/compile/internal/typecheck/iimport.go new file mode 100644 index 0000000000000000000000000000000000000000..37f5a7bba0ac9a351c2373c969e8dce025b57185 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/iimport.go @@ -0,0 +1,1546 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Indexed package import. +// See iexport.go for the export data format. + +package typecheck + +import ( + "encoding/binary" + "fmt" + "go/constant" + "io" + "math/big" + "os" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/bio" + "cmd/internal/goobj" + "cmd/internal/obj" + "cmd/internal/src" +) + +// An iimporterAndOffset identifies an importer and an offset within +// its data section. +type iimporterAndOffset struct { + p *iimporter + off uint64 +} + +var ( + // DeclImporter maps from imported identifiers to an importer + // and offset where that identifier's declaration can be read. + DeclImporter = map[*types.Sym]iimporterAndOffset{} + + // inlineImporter is like DeclImporter, but for inline bodies + // for function and method symbols. + inlineImporter = map[*types.Sym]iimporterAndOffset{} +) + +// expandDecl returns immediately if n is already a Name node. Otherwise, n should +// be an Ident node, and expandDecl reads in the definition of the specified +// identifier from the appropriate package. +func expandDecl(n ir.Node) ir.Node { + if n, ok := n.(*ir.Name); ok { + return n + } + + id := n.(*ir.Ident) + if n := id.Sym().PkgDef(); n != nil { + return n.(*ir.Name) + } + + r := importReaderFor(id.Sym(), DeclImporter) + if r == nil { + // Can happen if user tries to reference an undeclared name. + return n + } + + return r.doDecl(n.Sym()) +} + +// ImportBody reads in the dcls and body of an imported function (which should not +// yet have been read in). +func ImportBody(fn *ir.Func) { + if fn.Inl.Body != nil { + base.Fatalf("%v already has inline body", fn) + } + + r := importReaderFor(fn.Nname.Sym(), inlineImporter) + if r == nil { + base.Fatalf("missing import reader for %v", fn) + } + + if inimport { + base.Fatalf("recursive inimport") + } + inimport = true + r.doInline(fn) + inimport = false +} + +func importReaderFor(sym *types.Sym, importers map[*types.Sym]iimporterAndOffset) *importReader { + x, ok := importers[sym] + if !ok { + return nil + } + + return x.p.newReader(x.off, sym.Pkg) +} + +type intReader struct { + *bio.Reader + pkg *types.Pkg +} + +func (r *intReader) int64() int64 { + i, err := binary.ReadVarint(r.Reader) + if err != nil { + base.Errorf("import %q: read error: %v", r.pkg.Path, err) + base.ErrorExit() + } + return i +} + +func (r *intReader) uint64() uint64 { + i, err := binary.ReadUvarint(r.Reader) + if err != nil { + base.Errorf("import %q: read error: %v", r.pkg.Path, err) + base.ErrorExit() + } + return i +} + +func ReadImports(pkg *types.Pkg, in *bio.Reader) (fingerprint goobj.FingerprintType) { + ird := &intReader{in, pkg} + + version := ird.uint64() + if version != iexportVersion { + base.Errorf("import %q: unknown export format version %d", pkg.Path, version) + base.ErrorExit() + } + + sLen := ird.uint64() + dLen := ird.uint64() + + // Map string (and data) section into memory as a single large + // string. This reduces heap fragmentation and allows + // returning individual substrings very efficiently. + data, err := mapFile(in.File(), in.Offset(), int64(sLen+dLen)) + if err != nil { + base.Errorf("import %q: mapping input: %v", pkg.Path, err) + base.ErrorExit() + } + stringData := data[:sLen] + declData := data[sLen:] + + in.MustSeek(int64(sLen+dLen), os.SEEK_CUR) + + p := &iimporter{ + ipkg: pkg, + + pkgCache: map[uint64]*types.Pkg{}, + posBaseCache: map[uint64]*src.PosBase{}, + typCache: map[uint64]*types.Type{}, + + stringData: stringData, + declData: declData, + } + + for i, pt := range predeclared() { + p.typCache[uint64(i)] = pt + } + + // Declaration index. + for nPkgs := ird.uint64(); nPkgs > 0; nPkgs-- { + pkg := p.pkgAt(ird.uint64()) + pkgName := p.stringAt(ird.uint64()) + pkgHeight := int(ird.uint64()) + if pkg.Name == "" { + pkg.Name = pkgName + pkg.Height = pkgHeight + types.NumImport[pkgName]++ + + // TODO(mdempsky): This belongs somewhere else. + pkg.Lookup("_").Def = ir.BlankNode + } else { + if pkg.Name != pkgName { + base.Fatalf("conflicting package names %v and %v for path %q", pkg.Name, pkgName, pkg.Path) + } + if pkg.Height != pkgHeight { + base.Fatalf("conflicting package heights %v and %v for path %q", pkg.Height, pkgHeight, pkg.Path) + } + } + + for nSyms := ird.uint64(); nSyms > 0; nSyms-- { + s := pkg.Lookup(p.stringAt(ird.uint64())) + off := ird.uint64() + + if _, ok := DeclImporter[s]; !ok { + DeclImporter[s] = iimporterAndOffset{p, off} + } + } + } + + // Inline body index. + for nPkgs := ird.uint64(); nPkgs > 0; nPkgs-- { + pkg := p.pkgAt(ird.uint64()) + + for nSyms := ird.uint64(); nSyms > 0; nSyms-- { + s := pkg.Lookup(p.stringAt(ird.uint64())) + off := ird.uint64() + + if _, ok := inlineImporter[s]; !ok { + inlineImporter[s] = iimporterAndOffset{p, off} + } + } + } + + // Fingerprint. + _, err = io.ReadFull(in, fingerprint[:]) + if err != nil { + base.Errorf("import %s: error reading fingerprint", pkg.Path) + base.ErrorExit() + } + return fingerprint +} + +type iimporter struct { + ipkg *types.Pkg + + pkgCache map[uint64]*types.Pkg + posBaseCache map[uint64]*src.PosBase + typCache map[uint64]*types.Type + + stringData string + declData string +} + +func (p *iimporter) stringAt(off uint64) string { + var x [binary.MaxVarintLen64]byte + n := copy(x[:], p.stringData[off:]) + + slen, n := binary.Uvarint(x[:n]) + if n <= 0 { + base.Fatalf("varint failed") + } + spos := off + uint64(n) + return p.stringData[spos : spos+slen] +} + +func (p *iimporter) posBaseAt(off uint64) *src.PosBase { + if posBase, ok := p.posBaseCache[off]; ok { + return posBase + } + + file := p.stringAt(off) + posBase := src.NewFileBase(file, file) + p.posBaseCache[off] = posBase + return posBase +} + +func (p *iimporter) pkgAt(off uint64) *types.Pkg { + if pkg, ok := p.pkgCache[off]; ok { + return pkg + } + + pkg := p.ipkg + if pkgPath := p.stringAt(off); pkgPath != "" { + pkg = types.NewPkg(pkgPath, "") + } + p.pkgCache[off] = pkg + return pkg +} + +// An importReader keeps state for reading an individual imported +// object (declaration or inline body). +type importReader struct { + strings.Reader + p *iimporter + + currPkg *types.Pkg + prevBase *src.PosBase + prevLine int64 + prevColumn int64 + + // curfn is the current function we're importing into. + curfn *ir.Func + // Slice of all dcls for function, including any interior closures + allDcls []*ir.Name + allClosureVars []*ir.Name +} + +func (p *iimporter) newReader(off uint64, pkg *types.Pkg) *importReader { + r := &importReader{ + p: p, + currPkg: pkg, + } + // (*strings.Reader).Reset wasn't added until Go 1.7, and we + // need to build with Go 1.4. + r.Reader = *strings.NewReader(p.declData[off:]) + return r +} + +func (r *importReader) string() string { return r.p.stringAt(r.uint64()) } +func (r *importReader) posBase() *src.PosBase { return r.p.posBaseAt(r.uint64()) } +func (r *importReader) pkg() *types.Pkg { return r.p.pkgAt(r.uint64()) } + +func (r *importReader) setPkg() { + r.currPkg = r.pkg() +} + +func (r *importReader) doDecl(sym *types.Sym) *ir.Name { + tag := r.byte() + pos := r.pos() + + switch tag { + case 'A': + typ := r.typ() + + return importalias(r.p.ipkg, pos, sym, typ) + + case 'C': + typ := r.typ() + val := r.value(typ) + + n := importconst(r.p.ipkg, pos, sym, typ, val) + r.constExt(n) + return n + + case 'F': + typ := r.signature(nil) + + n := importfunc(r.p.ipkg, pos, sym, typ) + r.funcExt(n) + return n + + case 'T': + // Types can be recursive. We need to setup a stub + // declaration before recursing. + n := importtype(r.p.ipkg, pos, sym) + t := n.Type() + + // We also need to defer width calculations until + // after the underlying type has been assigned. + types.DeferCheckSize() + underlying := r.typ() + t.SetUnderlying(underlying) + types.ResumeCheckSize() + + if underlying.IsInterface() { + r.typeExt(t) + return n + } + + ms := make([]*types.Field, r.uint64()) + for i := range ms { + mpos := r.pos() + msym := r.selector() + recv := r.param() + mtyp := r.signature(recv) + + // MethodSym already marked m.Sym as a function. + m := ir.NewNameAt(mpos, ir.MethodSym(recv.Type, msym)) + m.Class = ir.PFUNC + m.SetType(mtyp) + + m.Func = ir.NewFunc(mpos) + m.Func.Nname = m + + f := types.NewField(mpos, msym, mtyp) + f.Nname = m + ms[i] = f + } + t.Methods().Set(ms) + + r.typeExt(t) + for _, m := range ms { + r.methExt(m) + } + return n + + case 'V': + typ := r.typ() + + n := importvar(r.p.ipkg, pos, sym, typ) + r.varExt(n) + return n + + default: + base.Fatalf("unexpected tag: %v", tag) + panic("unreachable") + } +} + +func (p *importReader) value(typ *types.Type) constant.Value { + switch constTypeOf(typ) { + case constant.Bool: + return constant.MakeBool(p.bool()) + case constant.String: + return constant.MakeString(p.string()) + case constant.Int: + var i big.Int + p.mpint(&i, typ) + return constant.Make(&i) + case constant.Float: + return p.float(typ) + case constant.Complex: + return makeComplex(p.float(typ), p.float(typ)) + } + + base.Fatalf("unexpected value type: %v", typ) + panic("unreachable") +} + +func (p *importReader) mpint(x *big.Int, typ *types.Type) { + signed, maxBytes := intSize(typ) + + maxSmall := 256 - maxBytes + if signed { + maxSmall = 256 - 2*maxBytes + } + if maxBytes == 1 { + maxSmall = 256 + } + + n, _ := p.ReadByte() + if uint(n) < maxSmall { + v := int64(n) + if signed { + v >>= 1 + if n&1 != 0 { + v = ^v + } + } + x.SetInt64(v) + return + } + + v := -n + if signed { + v = -(n &^ 1) >> 1 + } + if v < 1 || uint(v) > maxBytes { + base.Fatalf("weird decoding: %v, %v => %v", n, signed, v) + } + b := make([]byte, v) + p.Read(b) + x.SetBytes(b) + if signed && n&1 != 0 { + x.Neg(x) + } +} + +func (p *importReader) float(typ *types.Type) constant.Value { + var mant big.Int + p.mpint(&mant, typ) + var f big.Float + f.SetInt(&mant) + if f.Sign() != 0 { + f.SetMantExp(&f, int(p.int64())) + } + return constant.Make(&f) +} + +func (p *importReader) mprat(orig constant.Value) constant.Value { + if !p.bool() { + return orig + } + var rat big.Rat + rat.SetString(p.string()) + return constant.Make(&rat) +} + +func (r *importReader) ident(selector bool) *types.Sym { + name := r.string() + if name == "" { + return nil + } + pkg := r.currPkg + if selector && types.IsExported(name) { + pkg = types.LocalPkg + } + return pkg.Lookup(name) +} + +func (r *importReader) localIdent() *types.Sym { return r.ident(false) } +func (r *importReader) selector() *types.Sym { return r.ident(true) } + +func (r *importReader) qualifiedIdent() *ir.Ident { + name := r.string() + pkg := r.pkg() + sym := pkg.Lookup(name) + return ir.NewIdent(src.NoXPos, sym) +} + +func (r *importReader) pos() src.XPos { + delta := r.int64() + r.prevColumn += delta >> 1 + if delta&1 != 0 { + delta = r.int64() + r.prevLine += delta >> 1 + if delta&1 != 0 { + r.prevBase = r.posBase() + } + } + + if (r.prevBase == nil || r.prevBase.AbsFilename() == "") && r.prevLine == 0 && r.prevColumn == 0 { + // TODO(mdempsky): Remove once we reliably write + // position information for all nodes. + return src.NoXPos + } + + if r.prevBase == nil { + base.Fatalf("missing posbase") + } + pos := src.MakePos(r.prevBase, uint(r.prevLine), uint(r.prevColumn)) + return base.Ctxt.PosTable.XPos(pos) +} + +func (r *importReader) typ() *types.Type { + return r.p.typAt(r.uint64()) +} + +func (r *importReader) exoticType() *types.Type { + switch r.uint64() { + case exoticTypeNil: + return nil + case exoticTypeTuple: + funarg := types.Funarg(r.uint64()) + n := r.uint64() + fs := make([]*types.Field, n) + for i := range fs { + pos := r.pos() + var sym *types.Sym + switch r.uint64() { + case exoticTypeSymNil: + sym = nil + case exoticTypeSymNoPkg: + sym = types.NoPkg.Lookup(r.string()) + case exoticTypeSymWithPkg: + pkg := r.pkg() + sym = pkg.Lookup(r.string()) + default: + base.Fatalf("unknown symbol kind") + } + typ := r.typ() + f := types.NewField(pos, sym, typ) + fs[i] = f + } + t := types.NewStruct(types.NoPkg, fs) + t.StructType().Funarg = funarg + return t + case exoticTypeRecv: + var rcvr *types.Field + if r.bool() { // isFakeRecv + rcvr = fakeRecvField() + } else { + rcvr = r.exoticParam() + } + return r.exoticSignature(rcvr) + case exoticTypeRegular: + return r.typ() + default: + base.Fatalf("bad kind of call type") + return nil + } +} + +func (r *importReader) exoticSelector() *types.Sym { + name := r.string() + if name == "" { + return nil + } + pkg := r.currPkg + if types.IsExported(name) { + pkg = types.LocalPkg + } + if r.uint64() != 0 { + pkg = r.pkg() + } + return pkg.Lookup(name) +} + +func (r *importReader) exoticSignature(recv *types.Field) *types.Type { + var pkg *types.Pkg + if r.bool() { // hasPkg + pkg = r.pkg() + } + params := r.exoticParamList() + results := r.exoticParamList() + return types.NewSignature(pkg, recv, nil, params, results) +} + +func (r *importReader) exoticParamList() []*types.Field { + n := r.uint64() + fs := make([]*types.Field, n) + for i := range fs { + fs[i] = r.exoticParam() + } + return fs +} + +func (r *importReader) exoticParam() *types.Field { + pos := r.pos() + sym := r.exoticSym() + off := r.uint64() + typ := r.exoticType() + ddd := r.bool() + f := types.NewField(pos, sym, typ) + f.Offset = int64(off) + if sym != nil { + f.Nname = ir.NewNameAt(pos, sym) + } + f.SetIsDDD(ddd) + return f +} + +func (r *importReader) exoticField() *types.Field { + pos := r.pos() + sym := r.exoticSym() + off := r.uint64() + typ := r.exoticType() + note := r.string() + f := types.NewField(pos, sym, typ) + f.Offset = int64(off) + if sym != nil { + f.Nname = ir.NewNameAt(pos, sym) + } + f.Note = note + return f +} + +func (r *importReader) exoticSym() *types.Sym { + name := r.string() + if name == "" { + return nil + } + var pkg *types.Pkg + if types.IsExported(name) { + pkg = types.LocalPkg + } else { + pkg = r.pkg() + } + return pkg.Lookup(name) +} + +func (p *iimporter) typAt(off uint64) *types.Type { + t, ok := p.typCache[off] + if !ok { + if off < predeclReserved { + base.Fatalf("predeclared type missing from cache: %d", off) + } + t = p.newReader(off-predeclReserved, nil).typ1() + // Ensure size is calculated for imported types. Since CL 283313, the compiler + // does not compile the function immediately when it sees them. Instead, funtions + // are pushed to compile queue, then draining from the queue for compiling. + // During this process, the size calculation is disabled, so it is not safe for + // calculating size during SSA generation anymore. See issue #44732. + types.CheckSize(t) + p.typCache[off] = t + } + return t +} + +func (r *importReader) typ1() *types.Type { + switch k := r.kind(); k { + default: + base.Fatalf("unexpected kind tag in %q: %v", r.p.ipkg.Path, k) + return nil + + case definedType: + // We might be called from within doInline, in which + // case Sym.Def can point to declared parameters + // instead of the top-level types. Also, we don't + // support inlining functions with local defined + // types. Therefore, this must be a package-scope + // type. + n := expandDecl(r.qualifiedIdent()) + if n.Op() != ir.OTYPE { + base.Fatalf("expected OTYPE, got %v: %v, %v", n.Op(), n.Sym(), n) + } + return n.Type() + case pointerType: + return types.NewPtr(r.typ()) + case sliceType: + return types.NewSlice(r.typ()) + case arrayType: + n := r.uint64() + return types.NewArray(r.typ(), int64(n)) + case chanType: + dir := types.ChanDir(r.uint64()) + return types.NewChan(r.typ(), dir) + case mapType: + return types.NewMap(r.typ(), r.typ()) + + case signatureType: + r.setPkg() + return r.signature(nil) + + case structType: + r.setPkg() + + fs := make([]*types.Field, r.uint64()) + for i := range fs { + pos := r.pos() + sym := r.selector() + typ := r.typ() + emb := r.bool() + note := r.string() + + f := types.NewField(pos, sym, typ) + if emb { + f.Embedded = 1 + } + f.Note = note + fs[i] = f + } + + return types.NewStruct(r.currPkg, fs) + + case interfaceType: + r.setPkg() + + embeddeds := make([]*types.Field, r.uint64()) + for i := range embeddeds { + pos := r.pos() + typ := r.typ() + + embeddeds[i] = types.NewField(pos, nil, typ) + } + + methods := make([]*types.Field, r.uint64()) + for i := range methods { + pos := r.pos() + sym := r.selector() + typ := r.signature(fakeRecvField()) + + methods[i] = types.NewField(pos, sym, typ) + } + + t := types.NewInterface(r.currPkg, append(embeddeds, methods...)) + + // Ensure we expand the interface in the frontend (#25055). + types.CheckSize(t) + return t + } +} + +func (r *importReader) kind() itag { + return itag(r.uint64()) +} + +func (r *importReader) signature(recv *types.Field) *types.Type { + params := r.paramList() + results := r.paramList() + if n := len(params); n > 0 { + params[n-1].SetIsDDD(r.bool()) + } + return types.NewSignature(r.currPkg, recv, nil, params, results) +} + +func (r *importReader) paramList() []*types.Field { + fs := make([]*types.Field, r.uint64()) + for i := range fs { + fs[i] = r.param() + } + return fs +} + +func (r *importReader) param() *types.Field { + return types.NewField(r.pos(), r.localIdent(), r.typ()) +} + +func (r *importReader) bool() bool { + return r.uint64() != 0 +} + +func (r *importReader) int64() int64 { + n, err := binary.ReadVarint(r) + if err != nil { + base.Fatalf("readVarint: %v", err) + } + return n +} + +func (r *importReader) uint64() uint64 { + n, err := binary.ReadUvarint(r) + if err != nil { + base.Fatalf("readVarint: %v", err) + } + return n +} + +func (r *importReader) byte() byte { + x, err := r.ReadByte() + if err != nil { + base.Fatalf("declReader.ReadByte: %v", err) + } + return x +} + +// Compiler-specific extensions. + +func (r *importReader) constExt(n *ir.Name) { + switch n.Type() { + case types.UntypedFloat: + n.SetVal(r.mprat(n.Val())) + case types.UntypedComplex: + v := n.Val() + re := r.mprat(constant.Real(v)) + im := r.mprat(constant.Imag(v)) + n.SetVal(makeComplex(re, im)) + } +} + +func (r *importReader) varExt(n *ir.Name) { + r.linkname(n.Sym()) + r.symIdx(n.Sym()) +} + +func (r *importReader) funcExt(n *ir.Name) { + r.linkname(n.Sym()) + r.symIdx(n.Sym()) + + n.Func.ABI = obj.ABI(r.uint64()) + + n.SetPragma(ir.PragmaFlag(r.uint64())) + + // Escape analysis. + for _, fs := range &types.RecvsParams { + for _, f := range fs(n.Type()).FieldSlice() { + f.Note = r.string() + } + } + + // Inline body. + if u := r.uint64(); u > 0 { + n.Func.Inl = &ir.Inline{ + Cost: int32(u - 1), + } + n.Func.Endlineno = r.pos() + } +} + +func (r *importReader) methExt(m *types.Field) { + if r.bool() { + m.SetNointerface(true) + } + r.funcExt(m.Nname.(*ir.Name)) +} + +func (r *importReader) linkname(s *types.Sym) { + s.Linkname = r.string() +} + +func (r *importReader) symIdx(s *types.Sym) { + lsym := s.Linksym() + idx := int32(r.int64()) + if idx != -1 { + if s.Linkname != "" { + base.Fatalf("bad index for linknamed symbol: %v %d\n", lsym, idx) + } + lsym.SymIdx = idx + lsym.Set(obj.AttrIndexed, true) + } +} + +func (r *importReader) typeExt(t *types.Type) { + t.SetNotInHeap(r.bool()) + i, pi := r.int64(), r.int64() + if i != -1 && pi != -1 { + typeSymIdx[t] = [2]int64{i, pi} + } +} + +// Map imported type T to the index of type descriptor symbols of T and *T, +// so we can use index to reference the symbol. +var typeSymIdx = make(map[*types.Type][2]int64) + +func BaseTypeIndex(t *types.Type) int64 { + tbase := t + if t.IsPtr() && t.Sym() == nil && t.Elem().Sym() != nil { + tbase = t.Elem() + } + i, ok := typeSymIdx[tbase] + if !ok { + return -1 + } + if t != tbase { + return i[1] + } + return i[0] +} + +func (r *importReader) doInline(fn *ir.Func) { + if len(fn.Inl.Body) != 0 { + base.Fatalf("%v already has inline body", fn) + } + + //fmt.Printf("Importing %s\n", fn.Nname.Sym().Name) + r.funcBody(fn) + + importlist = append(importlist, fn) + + if base.Flag.E > 0 && base.Flag.LowerM > 2 { + if base.Flag.LowerM > 3 { + fmt.Printf("inl body for %v %v: %+v\n", fn, fn.Type(), ir.Nodes(fn.Inl.Body)) + } else { + fmt.Printf("inl body for %v %v: %v\n", fn, fn.Type(), ir.Nodes(fn.Inl.Body)) + } + } +} + +// ---------------------------------------------------------------------------- +// Inlined function bodies + +// Approach: Read nodes and use them to create/declare the same data structures +// as done originally by the (hidden) parser by closely following the parser's +// original code. In other words, "parsing" the import data (which happens to +// be encoded in binary rather textual form) is the best way at the moment to +// re-establish the syntax tree's invariants. At some future point we might be +// able to avoid this round-about way and create the rewritten nodes directly, +// possibly avoiding a lot of duplicate work (name resolution, type checking). +// +// Refined nodes (e.g., ODOTPTR as a refinement of OXDOT) are exported as their +// unrefined nodes (since this is what the importer uses). The respective case +// entries are unreachable in the importer. + +func (r *importReader) funcBody(fn *ir.Func) { + outerfn := r.curfn + r.curfn = fn + + // Import local declarations. + fn.Inl.Dcl = r.readFuncDcls(fn) + + // Import function body. + body := r.stmtList() + if body == nil { + // Make sure empty body is not interpreted as + // no inlineable body (see also parser.fnbody) + // (not doing so can cause significant performance + // degradation due to unnecessary calls to empty + // functions). + body = []ir.Node{} + } + if go117ExportTypes { + ir.VisitList(body, func(n ir.Node) { + n.SetTypecheck(1) + }) + } + fn.Inl.Body = body + + r.curfn = outerfn +} + +func (r *importReader) readNames(fn *ir.Func) []*ir.Name { + dcls := make([]*ir.Name, r.int64()) + for i := range dcls { + n := ir.NewDeclNameAt(r.pos(), ir.ONAME, r.localIdent()) + n.Class = ir.PAUTO // overwritten below for parameters/results + n.Curfn = fn + n.SetType(r.typ()) + dcls[i] = n + } + r.allDcls = append(r.allDcls, dcls...) + return dcls +} + +func (r *importReader) readFuncDcls(fn *ir.Func) []*ir.Name { + dcls := r.readNames(fn) + + // Fixup parameter classes and associate with their + // signature's type fields. + i := 0 + fix := func(f *types.Field, class ir.Class) { + if class == ir.PPARAM && (f.Sym == nil || f.Sym.Name == "_") { + return + } + n := dcls[i] + n.Class = class + f.Nname = n + i++ + } + + typ := fn.Type() + if recv := typ.Recv(); recv != nil { + fix(recv, ir.PPARAM) + } + for _, f := range typ.Params().FieldSlice() { + fix(f, ir.PPARAM) + } + for _, f := range typ.Results().FieldSlice() { + fix(f, ir.PPARAMOUT) + } + return dcls +} + +func (r *importReader) localName() *ir.Name { + i := r.int64() + if i == -1 { + return ir.BlankNode.(*ir.Name) + } + if i < 0 { + return r.allClosureVars[-i-2] + } + return r.allDcls[i] +} + +func (r *importReader) stmtList() []ir.Node { + var list []ir.Node + for { + n := r.node() + if n == nil { + break + } + // OBLOCK nodes are not written to the import data directly, + // but the handling of ODCL calls liststmt, which creates one. + // Inline them into the statement list. + if n.Op() == ir.OBLOCK { + n := n.(*ir.BlockStmt) + list = append(list, n.List...) + } else { + list = append(list, n) + } + + } + return list +} + +func (r *importReader) caseList(switchExpr ir.Node) []*ir.CaseClause { + namedTypeSwitch := isNamedTypeSwitch(switchExpr) + + cases := make([]*ir.CaseClause, r.uint64()) + for i := range cases { + cas := ir.NewCaseStmt(r.pos(), nil, nil) + cas.List = r.stmtList() + if namedTypeSwitch { + cas.Var = r.localName() + cas.Var.Defn = switchExpr + } + cas.Body = r.stmtList() + cases[i] = cas + } + return cases +} + +func (r *importReader) commList() []*ir.CommClause { + cases := make([]*ir.CommClause, r.uint64()) + for i := range cases { + cases[i] = ir.NewCommStmt(r.pos(), r.node(), r.stmtList()) + } + return cases +} + +func (r *importReader) exprList() []ir.Node { + var list []ir.Node + for { + n := r.expr() + if n == nil { + break + } + list = append(list, n) + } + return list +} + +func (r *importReader) expr() ir.Node { + n := r.node() + if n != nil && n.Op() == ir.OBLOCK { + n := n.(*ir.BlockStmt) + base.Fatalf("unexpected block node: %v", n) + } + return n +} + +// TODO(gri) split into expr and stmt +func (r *importReader) node() ir.Node { + op := r.op() + switch op { + // expressions + // case OPAREN: + // unreachable - unpacked by exporter + + case ir.ONIL: + pos := r.pos() + typ := r.typ() + + n := ir.NewNilExpr(pos) + n.SetType(typ) + return n + + case ir.OLITERAL: + pos := r.pos() + typ := r.typ() + + n := ir.NewBasicLit(pos, r.value(typ)) + n.SetType(typ) + return n + + case ir.ONONAME: + n := r.qualifiedIdent() + if go117ExportTypes { + n2 := Resolve(n) + typ := r.typ() + if n2.Type() == nil { + n2.SetType(typ) + } + return n2 + } + return n + + case ir.ONAME: + return r.localName() + + // case OPACK, ONONAME: + // unreachable - should have been resolved by typechecking + + case ir.OTYPE: + return ir.TypeNode(r.typ()) + + case ir.OTYPESW: + pos := r.pos() + var tag *ir.Ident + if s := r.localIdent(); s != nil { + tag = ir.NewIdent(pos, s) + } + return ir.NewTypeSwitchGuard(pos, tag, r.expr()) + + // case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC: + // unreachable - should have been resolved by typechecking + + case ir.OCLOSURE: + //println("Importing CLOSURE") + pos := r.pos() + typ := r.signature(nil) + + // All the remaining code below is similar to (*noder).funcLit(), but + // with Dcls and ClosureVars lists already set up + fn := ir.NewFunc(pos) + fn.SetIsHiddenClosure(true) + fn.Nname = ir.NewNameAt(pos, ir.BlankNode.Sym()) + fn.Nname.Func = fn + fn.Nname.Ntype = ir.TypeNode(typ) + fn.Nname.Defn = fn + fn.Nname.SetType(typ) + + cvars := make([]*ir.Name, r.int64()) + for i := range cvars { + cvars[i] = ir.CaptureName(r.pos(), fn, r.localName().Canonical()) + if go117ExportTypes { + if cvars[i].Type() != nil || cvars[i].Defn == nil { + base.Fatalf("bad import of closure variable") + } + // Closure variable should have Defn set, which is its captured + // variable, and it gets the same type as the captured variable. + cvars[i].SetType(cvars[i].Defn.Type()) + } + } + fn.ClosureVars = cvars + r.allClosureVars = append(r.allClosureVars, cvars...) + + fn.Inl = &ir.Inline{} + // Read in the Dcls and Body of the closure after temporarily + // setting r.curfn to fn. + r.funcBody(fn) + fn.Dcl = fn.Inl.Dcl + fn.Body = fn.Inl.Body + if len(fn.Body) == 0 { + // An empty closure must be represented as a single empty + // block statement, else it will be dropped. + fn.Body = []ir.Node{ir.NewBlockStmt(src.NoXPos, nil)} + } + fn.Inl = nil + + ir.FinishCaptureNames(pos, r.curfn, fn) + + clo := ir.NewClosureExpr(pos, fn) + fn.OClosure = clo + if go117ExportTypes { + clo.SetType(typ) + } + + return clo + + case ir.OSTRUCTLIT: + if go117ExportTypes { + pos := r.pos() + typ := r.typ() + list := r.fieldList() + n := ir.NewCompLitExpr(pos, ir.OSTRUCTLIT, nil, list) + n.SetType(typ) + return n + } + return ir.NewCompLitExpr(r.pos(), ir.OCOMPLIT, ir.TypeNode(r.typ()), r.fieldList()) + + case ir.OCOMPLIT: + return ir.NewCompLitExpr(r.pos(), ir.OCOMPLIT, ir.TypeNode(r.typ()), r.exprList()) + + case ir.OARRAYLIT, ir.OSLICELIT, ir.OMAPLIT: + if !go117ExportTypes { + // unreachable - mapped to OCOMPLIT by exporter + goto error + } + pos := r.pos() + typ := r.typ() + list := r.exprList() + n := ir.NewCompLitExpr(pos, op, ir.TypeNode(typ), list) + n.SetType(typ) + if op == ir.OSLICELIT { + n.Len = int64(r.uint64()) + } + return n + + case ir.OKEY: + return ir.NewKeyExpr(r.pos(), r.expr(), r.expr()) + + // case OSTRUCTKEY: + // unreachable - handled in case OSTRUCTLIT by elemList + + case ir.OXDOT: + // see parser.new_dotname + if go117ExportTypes { + base.Fatalf("shouldn't encounter XDOT in new importer") + } + return ir.NewSelectorExpr(r.pos(), ir.OXDOT, r.expr(), r.exoticSelector()) + + case ir.ODOT, ir.ODOTPTR, ir.ODOTINTER, ir.ODOTMETH, ir.OCALLPART, ir.OMETHEXPR: + if !go117ExportTypes { + // unreachable - mapped to case OXDOT by exporter + goto error + } + pos := r.pos() + expr := r.expr() + sel := r.exoticSelector() + n := ir.NewSelectorExpr(pos, op, expr, sel) + n.SetType(r.exoticType()) + switch op { + case ir.ODOT, ir.ODOTPTR, ir.ODOTINTER: + n.Selection = r.exoticField() + case ir.ODOTMETH, ir.OCALLPART, ir.OMETHEXPR: + // These require a Lookup to link to the correct declaration. + rcvrType := expr.Type() + typ := n.Type() + n.Selection = Lookdot(n, rcvrType, 1) + if op == ir.OCALLPART || op == ir.OMETHEXPR { + // Lookdot clobbers the opcode and type, undo that. + n.SetOp(op) + n.SetType(typ) + } + } + return n + + case ir.ODOTTYPE, ir.ODOTTYPE2: + n := ir.NewTypeAssertExpr(r.pos(), r.expr(), nil) + n.SetType(r.typ()) + if go117ExportTypes { + n.SetOp(op) + } + return n + + case ir.OINDEX, ir.OINDEXMAP: + n := ir.NewIndexExpr(r.pos(), r.expr(), r.expr()) + if go117ExportTypes { + n.SetOp(op) + n.SetType(r.typ()) + if op == ir.OINDEXMAP { + n.Assigned = r.bool() + } + } + return n + + case ir.OSLICE, ir.OSLICESTR, ir.OSLICEARR, ir.OSLICE3, ir.OSLICE3ARR: + pos, x := r.pos(), r.expr() + low, high := r.exprsOrNil() + var max ir.Node + if op.IsSlice3() { + max = r.expr() + } + n := ir.NewSliceExpr(pos, op, x, low, high, max) + if go117ExportTypes { + n.SetType(r.typ()) + } + return n + + case ir.OCONV, ir.OCONVIFACE, ir.OCONVNOP, ir.OBYTES2STR, ir.ORUNES2STR, ir.OSTR2BYTES, ir.OSTR2RUNES, ir.ORUNESTR, ir.OSLICE2ARRPTR: + if !go117ExportTypes && op != ir.OCONV { + // unreachable - mapped to OCONV case by exporter + goto error + } + return ir.NewConvExpr(r.pos(), op, r.typ(), r.expr()) + + case ir.OCOPY, ir.OCOMPLEX, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.OCAP, ir.OCLOSE, ir.ODELETE, ir.OLEN, ir.OMAKE, ir.ONEW, ir.OPANIC, ir.ORECOVER, ir.OPRINT, ir.OPRINTN, ir.OUNSAFEADD, ir.OUNSAFESLICE: + if go117ExportTypes { + switch op { + case ir.OCOPY, ir.OCOMPLEX, ir.OUNSAFEADD, ir.OUNSAFESLICE: + n := ir.NewBinaryExpr(r.pos(), op, r.expr(), r.expr()) + n.SetType(r.typ()) + return n + case ir.OREAL, ir.OIMAG, ir.OCAP, ir.OCLOSE, ir.OLEN, ir.ONEW, ir.OPANIC: + n := ir.NewUnaryExpr(r.pos(), op, r.expr()) + if op != ir.OPANIC { + n.SetType(r.typ()) + } + return n + case ir.OAPPEND, ir.ODELETE, ir.ORECOVER, ir.OPRINT, ir.OPRINTN: + n := ir.NewCallExpr(r.pos(), op, nil, r.exprList()) + if op == ir.OAPPEND { + n.IsDDD = r.bool() + } + if op == ir.OAPPEND || op == ir.ORECOVER { + n.SetType(r.typ()) + } + return n + } + // ir.OMAKE + goto error + } + n := builtinCall(r.pos(), op) + n.Args = r.exprList() + if op == ir.OAPPEND { + n.IsDDD = r.bool() + } + return n + + case ir.OCALL, ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER, ir.OGETG: + pos := r.pos() + init := r.stmtList() + n := ir.NewCallExpr(pos, ir.OCALL, r.expr(), r.exprList()) + if go117ExportTypes { + n.SetOp(op) + } + *n.PtrInit() = init + n.IsDDD = r.bool() + if go117ExportTypes { + n.SetType(r.exoticType()) + n.Use = ir.CallUse(r.uint64()) + } + return n + + case ir.OMAKEMAP, ir.OMAKECHAN, ir.OMAKESLICE: + if go117ExportTypes { + pos := r.pos() + typ := r.typ() + list := r.exprList() + var len_, cap_ ir.Node + if len(list) > 0 { + len_ = list[0] + } + if len(list) > 1 { + cap_ = list[1] + } + n := ir.NewMakeExpr(pos, op, len_, cap_) + n.SetType(typ) + return n + } + n := builtinCall(r.pos(), ir.OMAKE) + n.Args.Append(ir.TypeNode(r.typ())) + n.Args.Append(r.exprList()...) + return n + + // unary expressions + case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV: + n := ir.NewUnaryExpr(r.pos(), op, r.expr()) + if go117ExportTypes { + n.SetType(r.typ()) + } + return n + + case ir.OADDR, ir.OPTRLIT: + n := NodAddrAt(r.pos(), r.expr()) + if go117ExportTypes { + n.SetOp(op) + n.SetType(r.typ()) + } + return n + + case ir.ODEREF: + n := ir.NewStarExpr(r.pos(), r.expr()) + if go117ExportTypes { + n.SetType(r.typ()) + } + return n + + // binary expressions + case ir.OADD, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OEQ, ir.OGE, ir.OGT, ir.OLE, ir.OLT, + ir.OLSH, ir.OMOD, ir.OMUL, ir.ONE, ir.OOR, ir.ORSH, ir.OSUB, ir.OXOR: + n := ir.NewBinaryExpr(r.pos(), op, r.expr(), r.expr()) + if go117ExportTypes { + n.SetType(r.typ()) + } + return n + + case ir.OANDAND, ir.OOROR: + n := ir.NewLogicalExpr(r.pos(), op, r.expr(), r.expr()) + if go117ExportTypes { + n.SetType(r.typ()) + } + return n + + case ir.OSEND: + return ir.NewSendStmt(r.pos(), r.expr(), r.expr()) + + case ir.OADDSTR: + pos := r.pos() + list := r.exprList() + if go117ExportTypes { + n := ir.NewAddStringExpr(pos, list) + n.SetType(r.typ()) + return n + } + x := list[0] + for _, y := range list[1:] { + x = ir.NewBinaryExpr(pos, ir.OADD, x, y) + } + return x + + // -------------------------------------------------------------------- + // statements + case ir.ODCL: + var stmts ir.Nodes + n := r.localName() + stmts.Append(ir.NewDecl(n.Pos(), ir.ODCL, n)) + stmts.Append(ir.NewAssignStmt(n.Pos(), n, nil)) + return ir.NewBlockStmt(n.Pos(), stmts) + + // case OASWB: + // unreachable - never exported + + case ir.OAS: + return ir.NewAssignStmt(r.pos(), r.expr(), r.expr()) + + case ir.OASOP: + n := ir.NewAssignOpStmt(r.pos(), r.op(), r.expr(), nil) + if !r.bool() { + n.Y = ir.NewInt(1) + n.IncDec = true + } else { + n.Y = r.expr() + } + return n + + case ir.OAS2, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV: + if !go117ExportTypes && op != ir.OAS2 { + // unreachable - mapped to case OAS2 by exporter + goto error + } + return ir.NewAssignListStmt(r.pos(), op, r.exprList(), r.exprList()) + + case ir.ORETURN: + return ir.NewReturnStmt(r.pos(), r.exprList()) + + // case ORETJMP: + // unreachable - generated by compiler for trampolin routines (not exported) + + case ir.OGO, ir.ODEFER: + return ir.NewGoDeferStmt(r.pos(), op, r.expr()) + + case ir.OIF: + pos, init := r.pos(), r.stmtList() + n := ir.NewIfStmt(pos, r.expr(), r.stmtList(), r.stmtList()) + *n.PtrInit() = init + return n + + case ir.OFOR: + pos, init := r.pos(), r.stmtList() + cond, post := r.exprsOrNil() + n := ir.NewForStmt(pos, nil, cond, post, r.stmtList()) + *n.PtrInit() = init + return n + + case ir.ORANGE: + pos := r.pos() + k, v := r.exprsOrNil() + return ir.NewRangeStmt(pos, k, v, r.expr(), r.stmtList()) + + case ir.OSELECT: + pos := r.pos() + init := r.stmtList() + n := ir.NewSelectStmt(pos, r.commList()) + *n.PtrInit() = init + return n + + case ir.OSWITCH: + pos := r.pos() + init := r.stmtList() + x, _ := r.exprsOrNil() + n := ir.NewSwitchStmt(pos, x, r.caseList(x)) + *n.PtrInit() = init + return n + + // case OCASE: + // handled by caseList + + case ir.OFALL: + return ir.NewBranchStmt(r.pos(), ir.OFALL, nil) + + // case OEMPTY: + // unreachable - not emitted by exporter + + case ir.OBREAK, ir.OCONTINUE, ir.OGOTO: + pos := r.pos() + var sym *types.Sym + if label := r.string(); label != "" { + sym = Lookup(label) + } + return ir.NewBranchStmt(pos, op, sym) + + case ir.OLABEL: + return ir.NewLabelStmt(r.pos(), Lookup(r.string())) + + case ir.OEND: + return nil + + default: + base.Fatalf("cannot import %v (%d) node\n"+ + "\t==> please file an issue and assign to gri@", op, int(op)) + panic("unreachable") // satisfy compiler + } +error: + base.Fatalf("cannot import %v (%d) node\n"+ + "\t==> please file an issue and assign to khr@", op, int(op)) + panic("unreachable") // satisfy compiler +} + +func (r *importReader) op() ir.Op { + if debug && r.uint64() != magic { + base.Fatalf("import stream has desynchronized") + } + return ir.Op(r.uint64()) +} + +func (r *importReader) fieldList() []ir.Node { + list := make([]ir.Node, r.uint64()) + for i := range list { + x := ir.NewStructKeyExpr(r.pos(), r.selector(), r.expr()) + if go117ExportTypes { + x.Offset = int64(r.uint64()) + } + list[i] = x + } + return list +} + +func (r *importReader) exprsOrNil() (a, b ir.Node) { + ab := r.uint64() + if ab&1 != 0 { + a = r.expr() + } + if ab&2 != 0 { + b = r.node() + } + return +} + +func builtinCall(pos src.XPos, op ir.Op) *ir.CallExpr { + if go117ExportTypes { + // These should all be encoded as direct ops, not OCALL. + base.Fatalf("builtinCall should not be invoked when types are included in import/export") + } + return ir.NewCallExpr(pos, ir.OCALL, ir.NewIdent(base.Pos, types.BuiltinPkg.Lookup(ir.OpNames[op])), nil) +} diff --git a/src/cmd/compile/internal/gc/mapfile_mmap.go b/src/cmd/compile/internal/typecheck/mapfile_mmap.go similarity index 93% rename from src/cmd/compile/internal/gc/mapfile_mmap.go rename to src/cmd/compile/internal/typecheck/mapfile_mmap.go index 9483688d68034d254ff250c6c75ba1df02a2b949..298b385bcb0f762413c6cf6f7402f0817240d005 100644 --- a/src/cmd/compile/internal/gc/mapfile_mmap.go +++ b/src/cmd/compile/internal/typecheck/mapfile_mmap.go @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd // +build darwin dragonfly freebsd linux netbsd openbsd -package gc +package typecheck import ( "os" diff --git a/src/cmd/compile/internal/gc/mapfile_read.go b/src/cmd/compile/internal/typecheck/mapfile_read.go similarity index 83% rename from src/cmd/compile/internal/gc/mapfile_read.go rename to src/cmd/compile/internal/typecheck/mapfile_read.go index c6f68ed5df7a771939c164f67f7d130a5c1f043d..9637ab97abe458dc19e0ead21ed0759532fc7d47 100644 --- a/src/cmd/compile/internal/gc/mapfile_read.go +++ b/src/cmd/compile/internal/typecheck/mapfile_read.go @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd // +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd -package gc +package typecheck import ( "io" diff --git a/src/cmd/compile/internal/gc/mkbuiltin.go b/src/cmd/compile/internal/typecheck/mkbuiltin.go similarity index 78% rename from src/cmd/compile/internal/gc/mkbuiltin.go rename to src/cmd/compile/internal/typecheck/mkbuiltin.go index 63d2a12c079d6a3cd850406de94fedf696e63956..6dbd1869b3e11b6c2d73fbca37adf6de94b146be 100644 --- a/src/cmd/compile/internal/gc/mkbuiltin.go +++ b/src/cmd/compile/internal/typecheck/mkbuiltin.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore // Generate builtin.go from builtin/runtime.go. @@ -33,9 +34,12 @@ func main() { var b bytes.Buffer fmt.Fprintln(&b, "// Code generated by mkbuiltin.go. DO NOT EDIT.") fmt.Fprintln(&b) - fmt.Fprintln(&b, "package gc") + fmt.Fprintln(&b, "package typecheck") fmt.Fprintln(&b) - fmt.Fprintln(&b, `import "cmd/compile/internal/types"`) + fmt.Fprintln(&b, `import (`) + fmt.Fprintln(&b, ` "cmd/compile/internal/types"`) + fmt.Fprintln(&b, ` "cmd/internal/src"`) + fmt.Fprintln(&b, `)`) mkbuiltin(&b, "runtime") @@ -99,6 +103,21 @@ func mkbuiltin(w io.Writer, name string) { } fmt.Fprintln(w, "}") + fmt.Fprintln(w, ` +// Not inlining this function removes a significant chunk of init code. +//go:noinline +func newSig(params, results []*types.Field) *types.Type { + return types.NewSignature(types.NoPkg, nil, nil, params, results) +} + +func params(tlist ...*types.Type) []*types.Field { + flist := make([]*types.Field, len(tlist)) + for i, typ := range tlist { + flist[i] = types.NewField(src.NoXPos, nil, typ) + } + return flist +}`) + fmt.Fprintln(w) fmt.Fprintf(w, "func %sTypes() []*types.Type {\n", name) fmt.Fprintf(w, "var typs [%d]*types.Type\n", len(interner.typs)) @@ -140,16 +159,16 @@ func (i *typeInterner) mktype(t ast.Expr) string { case *ast.Ident: switch t.Name { case "byte": - return "types.Bytetype" + return "types.ByteType" case "rune": - return "types.Runetype" + return "types.RuneType" } - return fmt.Sprintf("types.Types[T%s]", strings.ToUpper(t.Name)) + return fmt.Sprintf("types.Types[types.T%s]", strings.ToUpper(t.Name)) case *ast.SelectorExpr: if t.X.(*ast.Ident).Name != "unsafe" || t.Sel.Name != "Pointer" { log.Fatalf("unhandled type: %#v", t) } - return "types.Types[TUNSAFEPTR]" + return "types.Types[types.TUNSAFEPTR]" case *ast.ArrayType: if t.Len == nil { @@ -166,18 +185,18 @@ func (i *typeInterner) mktype(t ast.Expr) string { } return fmt.Sprintf("types.NewChan(%s, %s)", i.subtype(t.Value), dir) case *ast.FuncType: - return fmt.Sprintf("functype(nil, %s, %s)", i.fields(t.Params, false), i.fields(t.Results, false)) + return fmt.Sprintf("newSig(%s, %s)", i.fields(t.Params, false), i.fields(t.Results, false)) case *ast.InterfaceType: if len(t.Methods.List) != 0 { log.Fatal("non-empty interfaces unsupported") } - return "types.Types[TINTER]" + return "types.Types[types.TINTER]" case *ast.MapType: return fmt.Sprintf("types.NewMap(%s, %s)", i.subtype(t.Key), i.subtype(t.Value)) case *ast.StarExpr: return fmt.Sprintf("types.NewPtr(%s)", i.subtype(t.X)) case *ast.StructType: - return fmt.Sprintf("tostruct(%s)", i.fields(t.Fields, true)) + return fmt.Sprintf("types.NewStruct(types.NoPkg, %s)", i.fields(t.Fields, true)) default: log.Fatalf("unhandled type: %#v", t) @@ -189,22 +208,27 @@ func (i *typeInterner) fields(fl *ast.FieldList, keepNames bool) string { if fl == nil || len(fl.List) == 0 { return "nil" } + var res []string for _, f := range fl.List { typ := i.subtype(f.Type) if len(f.Names) == 0 { - res = append(res, fmt.Sprintf("anonfield(%s)", typ)) + res = append(res, typ) } else { for _, name := range f.Names { if keepNames { - res = append(res, fmt.Sprintf("namedfield(%q, %s)", name.Name, typ)) + res = append(res, fmt.Sprintf("types.NewField(src.NoXPos, Lookup(%q), %s)", name.Name, typ)) } else { - res = append(res, fmt.Sprintf("anonfield(%s)", typ)) + res = append(res, typ) } } } } - return fmt.Sprintf("[]*Node{%s}", strings.Join(res, ", ")) + + if keepNames { + return fmt.Sprintf("[]*types.Field{%s}", strings.Join(res, ", ")) + } + return fmt.Sprintf("params(%s)", strings.Join(res, ", ")) } func intconst(e ast.Expr) int64 { diff --git a/src/cmd/compile/internal/typecheck/stmt.go b/src/cmd/compile/internal/typecheck/stmt.go new file mode 100644 index 0000000000000000000000000000000000000000..922a01bfbe9aa7d6dea6ab34e03e5955ef883105 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/stmt.go @@ -0,0 +1,681 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +func RangeExprType(t *types.Type) *types.Type { + if t.IsPtr() && t.Elem().IsArray() { + return t.Elem() + } + return t +} + +func typecheckrangeExpr(n *ir.RangeStmt) { + n.X = Expr(n.X) + if n.X.Type() == nil { + return + } + + t := RangeExprType(n.X.Type()) + // delicate little dance. see tcAssignList + if n.Key != nil && !ir.DeclaredBy(n.Key, n) { + n.Key = AssignExpr(n.Key) + } + if n.Value != nil && !ir.DeclaredBy(n.Value, n) { + n.Value = AssignExpr(n.Value) + } + + var tk, tv *types.Type + toomany := false + switch t.Kind() { + default: + base.ErrorfAt(n.Pos(), "cannot range over %L", n.X) + return + + case types.TARRAY, types.TSLICE: + tk = types.Types[types.TINT] + tv = t.Elem() + + case types.TMAP: + tk = t.Key() + tv = t.Elem() + + case types.TCHAN: + if !t.ChanDir().CanRecv() { + base.ErrorfAt(n.Pos(), "invalid operation: range %v (receive from send-only type %v)", n.X, n.X.Type()) + return + } + + tk = t.Elem() + tv = nil + if n.Value != nil { + toomany = true + } + + case types.TSTRING: + tk = types.Types[types.TINT] + tv = types.RuneType + } + + if toomany { + base.ErrorfAt(n.Pos(), "too many variables in range") + } + + do := func(nn ir.Node, t *types.Type) { + if nn != nil { + if ir.DeclaredBy(nn, n) { + nn.SetType(t) + } else if nn.Type() != nil { + if op, why := Assignop(t, nn.Type()); op == ir.OXXX { + base.ErrorfAt(n.Pos(), "cannot assign type %v to %L in range%s", t, nn, why) + } + } + checkassign(n, nn) + } + } + do(n.Key, tk) + do(n.Value, tv) +} + +// type check assignment. +// if this assignment is the definition of a var on the left side, +// fill in the var's type. +func tcAssign(n *ir.AssignStmt) { + if base.EnableTrace && base.Flag.LowerT { + defer tracePrint("tcAssign", n)(nil) + } + + if n.Y == nil { + n.X = AssignExpr(n.X) + return + } + + lhs, rhs := []ir.Node{n.X}, []ir.Node{n.Y} + assign(n, lhs, rhs) + n.X, n.Y = lhs[0], rhs[0] + + // TODO(mdempsky): This seems out of place. + if !ir.IsBlank(n.X) { + types.CheckSize(n.X.Type()) // ensure width is calculated for backend + } +} + +func tcAssignList(n *ir.AssignListStmt) { + if base.EnableTrace && base.Flag.LowerT { + defer tracePrint("tcAssignList", n)(nil) + } + + assign(n, n.Lhs, n.Rhs) +} + +func assign(stmt ir.Node, lhs, rhs []ir.Node) { + // delicate little dance. + // the definition of lhs may refer to this assignment + // as its definition, in which case it will call tcAssign. + // in that case, do not call typecheck back, or it will cycle. + // if the variable has a type (ntype) then typechecking + // will not look at defn, so it is okay (and desirable, + // so that the conversion below happens). + + checkLHS := func(i int, typ *types.Type) { + lhs[i] = Resolve(lhs[i]) + if n := lhs[i]; typ != nil && ir.DeclaredBy(n, stmt) && n.Name().Ntype == nil { + if typ.Kind() != types.TNIL { + n.SetType(defaultType(typ)) + } else { + base.Errorf("use of untyped nil") + } + } + if lhs[i].Typecheck() == 0 { + lhs[i] = AssignExpr(lhs[i]) + } + checkassign(stmt, lhs[i]) + } + + assignType := func(i int, typ *types.Type) { + checkLHS(i, typ) + if typ != nil { + checkassignto(typ, lhs[i]) + } + } + + cr := len(rhs) + if len(rhs) == 1 { + rhs[0] = typecheck(rhs[0], ctxExpr|ctxMultiOK) + if rtyp := rhs[0].Type(); rtyp != nil && rtyp.IsFuncArgStruct() { + cr = rtyp.NumFields() + } + } else { + Exprs(rhs) + } + + // x, ok = y +assignOK: + for len(lhs) == 2 && cr == 1 { + stmt := stmt.(*ir.AssignListStmt) + r := rhs[0] + + switch r.Op() { + case ir.OINDEXMAP: + stmt.SetOp(ir.OAS2MAPR) + case ir.ORECV: + stmt.SetOp(ir.OAS2RECV) + case ir.ODOTTYPE: + r := r.(*ir.TypeAssertExpr) + stmt.SetOp(ir.OAS2DOTTYPE) + r.SetOp(ir.ODOTTYPE2) + default: + break assignOK + } + + assignType(0, r.Type()) + assignType(1, types.UntypedBool) + return + } + + if len(lhs) != cr { + if r, ok := rhs[0].(*ir.CallExpr); ok && len(rhs) == 1 { + if r.Type() != nil { + base.ErrorfAt(stmt.Pos(), "assignment mismatch: %d variable%s but %v returns %d value%s", len(lhs), plural(len(lhs)), r.X, cr, plural(cr)) + } + } else { + base.ErrorfAt(stmt.Pos(), "assignment mismatch: %d variable%s but %v value%s", len(lhs), plural(len(lhs)), len(rhs), plural(len(rhs))) + } + + for i := range lhs { + checkLHS(i, nil) + } + return + } + + // x,y,z = f() + if cr > len(rhs) { + stmt := stmt.(*ir.AssignListStmt) + stmt.SetOp(ir.OAS2FUNC) + r := rhs[0].(*ir.CallExpr) + r.Use = ir.CallUseList + rtyp := r.Type() + + mismatched := false + failed := false + for i := range lhs { + result := rtyp.Field(i).Type + assignType(i, result) + + if lhs[i].Type() == nil || result == nil { + failed = true + } else if lhs[i] != ir.BlankNode && !types.Identical(lhs[i].Type(), result) { + mismatched = true + } + } + if mismatched && !failed { + rewriteMultiValueCall(stmt, r) + } + return + } + + for i, r := range rhs { + checkLHS(i, r.Type()) + if lhs[i].Type() != nil { + rhs[i] = AssignConv(r, lhs[i].Type(), "assignment") + } + } +} + +func plural(n int) string { + if n == 1 { + return "" + } + return "s" +} + +// tcFor typechecks an OFOR node. +func tcFor(n *ir.ForStmt) ir.Node { + Stmts(n.Init()) + n.Cond = Expr(n.Cond) + n.Cond = DefaultLit(n.Cond, nil) + if n.Cond != nil { + t := n.Cond.Type() + if t != nil && !t.IsBoolean() { + base.Errorf("non-bool %L used as for condition", n.Cond) + } + } + n.Post = Stmt(n.Post) + if n.Op() == ir.OFORUNTIL { + Stmts(n.Late) + } + Stmts(n.Body) + return n +} + +func tcGoDefer(n *ir.GoDeferStmt) { + what := "defer" + if n.Op() == ir.OGO { + what = "go" + } + + switch n.Call.Op() { + // ok + case ir.OCALLINTER, + ir.OCALLMETH, + ir.OCALLFUNC, + ir.OCLOSE, + ir.OCOPY, + ir.ODELETE, + ir.OPANIC, + ir.OPRINT, + ir.OPRINTN, + ir.ORECOVER: + return + + case ir.OAPPEND, + ir.OCAP, + ir.OCOMPLEX, + ir.OIMAG, + ir.OLEN, + ir.OMAKE, + ir.OMAKESLICE, + ir.OMAKECHAN, + ir.OMAKEMAP, + ir.ONEW, + ir.OREAL, + ir.OLITERAL: // conversion or unsafe.Alignof, Offsetof, Sizeof + if orig := ir.Orig(n.Call); orig.Op() == ir.OCONV { + break + } + base.ErrorfAt(n.Pos(), "%s discards result of %v", what, n.Call) + return + } + + // type is broken or missing, most likely a method call on a broken type + // we will warn about the broken type elsewhere. no need to emit a potentially confusing error + if n.Call.Type() == nil || n.Call.Type().Broke() { + return + } + + if !n.Diag() { + // The syntax made sure it was a call, so this must be + // a conversion. + n.SetDiag(true) + base.ErrorfAt(n.Pos(), "%s requires function call, not conversion", what) + } +} + +// tcIf typechecks an OIF node. +func tcIf(n *ir.IfStmt) ir.Node { + Stmts(n.Init()) + n.Cond = Expr(n.Cond) + n.Cond = DefaultLit(n.Cond, nil) + if n.Cond != nil { + t := n.Cond.Type() + if t != nil && !t.IsBoolean() { + base.Errorf("non-bool %L used as if condition", n.Cond) + } + } + Stmts(n.Body) + Stmts(n.Else) + return n +} + +// range +func tcRange(n *ir.RangeStmt) { + // Typechecking order is important here: + // 0. first typecheck range expression (slice/map/chan), + // it is evaluated only once and so logically it is not part of the loop. + // 1. typecheck produced values, + // this part can declare new vars and so it must be typechecked before body, + // because body can contain a closure that captures the vars. + // 2. decldepth++ to denote loop body. + // 3. typecheck body. + // 4. decldepth--. + typecheckrangeExpr(n) + + // second half of dance, the first half being typecheckrangeExpr + n.SetTypecheck(1) + if n.Key != nil && n.Key.Typecheck() == 0 { + n.Key = AssignExpr(n.Key) + } + if n.Value != nil && n.Value.Typecheck() == 0 { + n.Value = AssignExpr(n.Value) + } + + Stmts(n.Body) +} + +// tcReturn typechecks an ORETURN node. +func tcReturn(n *ir.ReturnStmt) ir.Node { + typecheckargs(n) + if ir.CurFunc == nil { + base.Errorf("return outside function") + n.SetType(nil) + return n + } + + if ir.HasNamedResults(ir.CurFunc) && len(n.Results) == 0 { + return n + } + typecheckaste(ir.ORETURN, nil, false, ir.CurFunc.Type().Results(), n.Results, func() string { return "return argument" }) + return n +} + +// select +func tcSelect(sel *ir.SelectStmt) { + var def *ir.CommClause + lno := ir.SetPos(sel) + Stmts(sel.Init()) + for _, ncase := range sel.Cases { + if ncase.Comm == nil { + // default + if def != nil { + base.ErrorfAt(ncase.Pos(), "multiple defaults in select (first at %v)", ir.Line(def)) + } else { + def = ncase + } + } else { + n := Stmt(ncase.Comm) + ncase.Comm = n + oselrecv2 := func(dst, recv ir.Node, def bool) { + n := ir.NewAssignListStmt(n.Pos(), ir.OSELRECV2, []ir.Node{dst, ir.BlankNode}, []ir.Node{recv}) + n.Def = def + n.SetTypecheck(1) + ncase.Comm = n + } + switch n.Op() { + default: + pos := n.Pos() + if n.Op() == ir.ONAME { + // We don't have the right position for ONAME nodes (see #15459 and + // others). Using ncase.Pos for now as it will provide the correct + // line number (assuming the expression follows the "case" keyword + // on the same line). This matches the approach before 1.10. + pos = ncase.Pos() + } + base.ErrorfAt(pos, "select case must be receive, send or assign recv") + + case ir.OAS: + // convert x = <-c into x, _ = <-c + // remove implicit conversions; the eventual assignment + // will reintroduce them. + n := n.(*ir.AssignStmt) + if r := n.Y; r.Op() == ir.OCONVNOP || r.Op() == ir.OCONVIFACE { + r := r.(*ir.ConvExpr) + if r.Implicit() { + n.Y = r.X + } + } + if n.Y.Op() != ir.ORECV { + base.ErrorfAt(n.Pos(), "select assignment must have receive on right hand side") + break + } + oselrecv2(n.X, n.Y, n.Def) + + case ir.OAS2RECV: + n := n.(*ir.AssignListStmt) + if n.Rhs[0].Op() != ir.ORECV { + base.ErrorfAt(n.Pos(), "select assignment must have receive on right hand side") + break + } + n.SetOp(ir.OSELRECV2) + + case ir.ORECV: + // convert <-c into _, _ = <-c + n := n.(*ir.UnaryExpr) + oselrecv2(ir.BlankNode, n, false) + + case ir.OSEND: + break + } + } + + Stmts(ncase.Body) + } + + base.Pos = lno +} + +// tcSend typechecks an OSEND node. +func tcSend(n *ir.SendStmt) ir.Node { + n.Chan = Expr(n.Chan) + n.Value = Expr(n.Value) + n.Chan = DefaultLit(n.Chan, nil) + t := n.Chan.Type() + if t == nil { + return n + } + if !t.IsChan() { + base.Errorf("invalid operation: %v (send to non-chan type %v)", n, t) + return n + } + + if !t.ChanDir().CanSend() { + base.Errorf("invalid operation: %v (send to receive-only type %v)", n, t) + return n + } + + n.Value = AssignConv(n.Value, t.Elem(), "send") + if n.Value.Type() == nil { + return n + } + return n +} + +// tcSwitch typechecks a switch statement. +func tcSwitch(n *ir.SwitchStmt) { + Stmts(n.Init()) + if n.Tag != nil && n.Tag.Op() == ir.OTYPESW { + tcSwitchType(n) + } else { + tcSwitchExpr(n) + } +} + +func tcSwitchExpr(n *ir.SwitchStmt) { + t := types.Types[types.TBOOL] + if n.Tag != nil { + n.Tag = Expr(n.Tag) + n.Tag = DefaultLit(n.Tag, nil) + t = n.Tag.Type() + } + + var nilonly string + if t != nil { + switch { + case t.IsMap(): + nilonly = "map" + case t.Kind() == types.TFUNC: + nilonly = "func" + case t.IsSlice(): + nilonly = "slice" + + case !types.IsComparable(t): + if t.IsStruct() { + base.ErrorfAt(n.Pos(), "cannot switch on %L (struct containing %v cannot be compared)", n.Tag, types.IncomparableField(t).Type) + } else { + base.ErrorfAt(n.Pos(), "cannot switch on %L", n.Tag) + } + t = nil + } + } + + var defCase ir.Node + var cs constSet + for _, ncase := range n.Cases { + ls := ncase.List + if len(ls) == 0 { // default: + if defCase != nil { + base.ErrorfAt(ncase.Pos(), "multiple defaults in switch (first at %v)", ir.Line(defCase)) + } else { + defCase = ncase + } + } + + for i := range ls { + ir.SetPos(ncase) + ls[i] = Expr(ls[i]) + ls[i] = DefaultLit(ls[i], t) + n1 := ls[i] + if t == nil || n1.Type() == nil { + continue + } + + if nilonly != "" && !ir.IsNil(n1) { + base.ErrorfAt(ncase.Pos(), "invalid case %v in switch (can only compare %s %v to nil)", n1, nilonly, n.Tag) + } else if t.IsInterface() && !n1.Type().IsInterface() && !types.IsComparable(n1.Type()) { + base.ErrorfAt(ncase.Pos(), "invalid case %L in switch (incomparable type)", n1) + } else { + op1, _ := Assignop(n1.Type(), t) + op2, _ := Assignop(t, n1.Type()) + if op1 == ir.OXXX && op2 == ir.OXXX { + if n.Tag != nil { + base.ErrorfAt(ncase.Pos(), "invalid case %v in switch on %v (mismatched types %v and %v)", n1, n.Tag, n1.Type(), t) + } else { + base.ErrorfAt(ncase.Pos(), "invalid case %v in switch (mismatched types %v and bool)", n1, n1.Type()) + } + } + } + + // Don't check for duplicate bools. Although the spec allows it, + // (1) the compiler hasn't checked it in the past, so compatibility mandates it, and + // (2) it would disallow useful things like + // case GOARCH == "arm" && GOARM == "5": + // case GOARCH == "arm": + // which would both evaluate to false for non-ARM compiles. + if !n1.Type().IsBoolean() { + cs.add(ncase.Pos(), n1, "case", "switch") + } + } + + Stmts(ncase.Body) + } +} + +func tcSwitchType(n *ir.SwitchStmt) { + guard := n.Tag.(*ir.TypeSwitchGuard) + guard.X = Expr(guard.X) + t := guard.X.Type() + if t != nil && !t.IsInterface() { + base.ErrorfAt(n.Pos(), "cannot type switch on non-interface value %L", guard.X) + t = nil + } + + // We don't actually declare the type switch's guarded + // declaration itself. So if there are no cases, we won't + // notice that it went unused. + if v := guard.Tag; v != nil && !ir.IsBlank(v) && len(n.Cases) == 0 { + base.ErrorfAt(v.Pos(), "%v declared but not used", v.Sym()) + } + + var defCase, nilCase ir.Node + var ts typeSet + for _, ncase := range n.Cases { + ls := ncase.List + if len(ls) == 0 { // default: + if defCase != nil { + base.ErrorfAt(ncase.Pos(), "multiple defaults in switch (first at %v)", ir.Line(defCase)) + } else { + defCase = ncase + } + } + + for i := range ls { + ls[i] = typecheck(ls[i], ctxExpr|ctxType) + n1 := ls[i] + if t == nil || n1.Type() == nil { + continue + } + + var missing, have *types.Field + var ptr int + if ir.IsNil(n1) { // case nil: + if nilCase != nil { + base.ErrorfAt(ncase.Pos(), "multiple nil cases in type switch (first at %v)", ir.Line(nilCase)) + } else { + nilCase = ncase + } + continue + } + if n1.Op() != ir.OTYPE { + base.ErrorfAt(ncase.Pos(), "%L is not a type", n1) + continue + } + if !n1.Type().IsInterface() && !implements(n1.Type(), t, &missing, &have, &ptr) && !missing.Broke() { + if have != nil && !have.Broke() { + base.ErrorfAt(ncase.Pos(), "impossible type switch case: %L cannot have dynamic type %v"+ + " (wrong type for %v method)\n\thave %v%S\n\twant %v%S", guard.X, n1.Type(), missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) + } else if ptr != 0 { + base.ErrorfAt(ncase.Pos(), "impossible type switch case: %L cannot have dynamic type %v"+ + " (%v method has pointer receiver)", guard.X, n1.Type(), missing.Sym) + } else { + base.ErrorfAt(ncase.Pos(), "impossible type switch case: %L cannot have dynamic type %v"+ + " (missing %v method)", guard.X, n1.Type(), missing.Sym) + } + continue + } + + ts.add(ncase.Pos(), n1.Type()) + } + + if ncase.Var != nil { + // Assign the clause variable's type. + vt := t + if len(ls) == 1 { + if ls[0].Op() == ir.OTYPE { + vt = ls[0].Type() + } else if !ir.IsNil(ls[0]) { + // Invalid single-type case; + // mark variable as broken. + vt = nil + } + } + + nvar := ncase.Var + nvar.SetType(vt) + if vt != nil { + nvar = AssignExpr(nvar).(*ir.Name) + } else { + // Clause variable is broken; prevent typechecking. + nvar.SetTypecheck(1) + nvar.SetWalkdef(1) + } + ncase.Var = nvar + } + + Stmts(ncase.Body) + } +} + +type typeSet struct { + m map[string][]typeSetEntry +} + +type typeSetEntry struct { + pos src.XPos + typ *types.Type +} + +func (s *typeSet) add(pos src.XPos, typ *types.Type) { + if s.m == nil { + s.m = make(map[string][]typeSetEntry) + } + + // LongString does not uniquely identify types, so we need to + // disambiguate collisions with types.Identical. + // TODO(mdempsky): Add a method that *is* unique. + ls := typ.LongString() + prevs := s.m[ls] + for _, prev := range prevs { + if types.Identical(typ, prev.typ) { + base.ErrorfAt(pos, "duplicate case %v in type switch\n\tprevious case at %s", typ, base.FmtPos(prev.pos)) + return + } + } + s.m[ls] = append(prevs, typeSetEntry{pos, typ}) +} diff --git a/src/cmd/compile/internal/typecheck/subr.go b/src/cmd/compile/internal/typecheck/subr.go new file mode 100644 index 0000000000000000000000000000000000000000..9ee7a94b1f24af5bea061042a8d471c8c4758b69 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/subr.go @@ -0,0 +1,876 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "fmt" + "sort" + "strconv" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +func AssignConv(n ir.Node, t *types.Type, context string) ir.Node { + return assignconvfn(n, t, func() string { return context }) +} + +// DotImportRefs maps idents introduced by importDot back to the +// ir.PkgName they were dot-imported through. +var DotImportRefs map[*ir.Ident]*ir.PkgName + +// LookupNum looks up the symbol starting with prefix and ending with +// the decimal n. If prefix is too long, LookupNum panics. +func LookupNum(prefix string, n int) *types.Sym { + var buf [20]byte // plenty long enough for all current users + copy(buf[:], prefix) + b := strconv.AppendInt(buf[:len(prefix)], int64(n), 10) + return types.LocalPkg.LookupBytes(b) +} + +// Given funarg struct list, return list of fn args. +func NewFuncParams(tl *types.Type, mustname bool) []*ir.Field { + var args []*ir.Field + gen := 0 + for _, t := range tl.Fields().Slice() { + s := t.Sym + if mustname && (s == nil || s.Name == "_") { + // invent a name so that we can refer to it in the trampoline + s = LookupNum(".anon", gen) + gen++ + } else if s != nil && s.Pkg != types.LocalPkg { + // TODO(mdempsky): Preserve original position, name, and package. + s = Lookup(s.Name) + } + a := ir.NewField(base.Pos, s, nil, t.Type) + a.Pos = t.Pos + a.IsDDD = t.IsDDD() + args = append(args, a) + } + + return args +} + +// newname returns a new ONAME Node associated with symbol s. +func NewName(s *types.Sym) *ir.Name { + n := ir.NewNameAt(base.Pos, s) + n.Curfn = ir.CurFunc + return n +} + +// NodAddr returns a node representing &n at base.Pos. +func NodAddr(n ir.Node) *ir.AddrExpr { + return NodAddrAt(base.Pos, n) +} + +// nodAddrPos returns a node representing &n at position pos. +func NodAddrAt(pos src.XPos, n ir.Node) *ir.AddrExpr { + n = markAddrOf(n) + return ir.NewAddrExpr(pos, n) +} + +func markAddrOf(n ir.Node) ir.Node { + if IncrementalAddrtaken { + // We can only do incremental addrtaken computation when it is ok + // to typecheck the argument of the OADDR. That's only safe after the + // main typecheck has completed. + // The argument to OADDR needs to be typechecked because &x[i] takes + // the address of x if x is an array, but not if x is a slice. + // Note: OuterValue doesn't work correctly until n is typechecked. + n = typecheck(n, ctxExpr) + if x := ir.OuterValue(n); x.Op() == ir.ONAME { + x.Name().SetAddrtaken(true) + } + } else { + // Remember that we built an OADDR without computing the Addrtaken bit for + // its argument. We'll do that later in bulk using computeAddrtaken. + DirtyAddrtaken = true + } + return n +} + +// If IncrementalAddrtaken is false, we do not compute Addrtaken for an OADDR Node +// when it is built. The Addrtaken bits are set in bulk by computeAddrtaken. +// If IncrementalAddrtaken is true, then when an OADDR Node is built the Addrtaken +// field of its argument is updated immediately. +var IncrementalAddrtaken = false + +// If DirtyAddrtaken is true, then there are OADDR whose corresponding arguments +// have not yet been marked as Addrtaken. +var DirtyAddrtaken = false + +func ComputeAddrtaken(top []ir.Node) { + for _, n := range top { + var doVisit func(n ir.Node) + doVisit = func(n ir.Node) { + if n.Op() == ir.OADDR { + if x := ir.OuterValue(n.(*ir.AddrExpr).X); x.Op() == ir.ONAME { + x.Name().SetAddrtaken(true) + if x.Name().IsClosureVar() { + // Mark the original variable as Addrtaken so that capturevars + // knows not to pass it by value. + x.Name().Defn.Name().SetAddrtaken(true) + } + } + } + if n.Op() == ir.OCLOSURE { + ir.VisitList(n.(*ir.ClosureExpr).Func.Body, doVisit) + } + } + ir.Visit(n, doVisit) + } +} + +func NodNil() ir.Node { + n := ir.NewNilExpr(base.Pos) + n.SetType(types.Types[types.TNIL]) + return n +} + +// AddImplicitDots finds missing fields in obj.field that +// will give the shortest unique addressing and +// modifies the tree with missing field names. +func AddImplicitDots(n *ir.SelectorExpr) *ir.SelectorExpr { + n.X = typecheck(n.X, ctxType|ctxExpr) + if n.X.Diag() { + n.SetDiag(true) + } + t := n.X.Type() + if t == nil { + return n + } + + if n.X.Op() == ir.OTYPE { + return n + } + + s := n.Sel + if s == nil { + return n + } + + switch path, ambig := dotpath(s, t, nil, false); { + case path != nil: + // rebuild elided dots + for c := len(path) - 1; c >= 0; c-- { + dot := ir.NewSelectorExpr(base.Pos, ir.ODOT, n.X, path[c].field.Sym) + dot.SetImplicit(true) + dot.SetType(path[c].field.Type) + n.X = dot + } + case ambig: + base.Errorf("ambiguous selector %v", n) + n.X = nil + } + + return n +} + +func CalcMethods(t *types.Type) { + if t == nil || t.AllMethods().Len() != 0 { + return + } + + // mark top-level method symbols + // so that expand1 doesn't consider them. + for _, f := range t.Methods().Slice() { + f.Sym.SetUniq(true) + } + + // generate all reachable methods + slist = slist[:0] + expand1(t, true) + + // check each method to be uniquely reachable + var ms []*types.Field + for i, sl := range slist { + slist[i].field = nil + sl.field.Sym.SetUniq(false) + + var f *types.Field + path, _ := dotpath(sl.field.Sym, t, &f, false) + if path == nil { + continue + } + + // dotpath may have dug out arbitrary fields, we only want methods. + if !f.IsMethod() { + continue + } + + // add it to the base type method list + f = f.Copy() + f.Embedded = 1 // needs a trampoline + for _, d := range path { + if d.field.Type.IsPtr() { + f.Embedded = 2 + break + } + } + ms = append(ms, f) + } + + for _, f := range t.Methods().Slice() { + f.Sym.SetUniq(false) + } + + ms = append(ms, t.Methods().Slice()...) + sort.Sort(types.MethodsByName(ms)) + t.SetAllMethods(ms) +} + +// adddot1 returns the number of fields or methods named s at depth d in Type t. +// If exactly one exists, it will be returned in *save (if save is not nil), +// and dotlist will contain the path of embedded fields traversed to find it, +// in reverse order. If none exist, more will indicate whether t contains any +// embedded fields at depth d, so callers can decide whether to retry at +// a greater depth. +func adddot1(s *types.Sym, t *types.Type, d int, save **types.Field, ignorecase bool) (c int, more bool) { + if t.Recur() { + return + } + t.SetRecur(true) + defer t.SetRecur(false) + + var u *types.Type + d-- + if d < 0 { + // We've reached our target depth. If t has any fields/methods + // named s, then we're done. Otherwise, we still need to check + // below for embedded fields. + c = lookdot0(s, t, save, ignorecase) + if c != 0 { + return c, false + } + } + + u = t + if u.IsPtr() { + u = u.Elem() + } + if !u.IsStruct() && !u.IsInterface() { + return c, false + } + + var fields *types.Fields + if u.IsStruct() { + fields = u.Fields() + } else { + fields = u.AllMethods() + } + for _, f := range fields.Slice() { + if f.Embedded == 0 || f.Sym == nil { + continue + } + if d < 0 { + // Found an embedded field at target depth. + return c, true + } + a, more1 := adddot1(s, f.Type, d, save, ignorecase) + if a != 0 && c == 0 { + dotlist[d].field = f + } + c += a + if more1 { + more = true + } + } + + return c, more +} + +// dotlist is used by adddot1 to record the path of embedded fields +// used to access a target field or method. +// Must be non-nil so that dotpath returns a non-nil slice even if d is zero. +var dotlist = make([]dlist, 10) + +// Convert node n for assignment to type t. +func assignconvfn(n ir.Node, t *types.Type, context func() string) ir.Node { + if n == nil || n.Type() == nil || n.Type().Broke() { + return n + } + + if t.Kind() == types.TBLANK && n.Type().Kind() == types.TNIL { + base.Errorf("use of untyped nil") + } + + n = convlit1(n, t, false, context) + if n.Type() == nil { + return n + } + if t.Kind() == types.TBLANK { + return n + } + + // Convert ideal bool from comparison to plain bool + // if the next step is non-bool (like interface{}). + if n.Type() == types.UntypedBool && !t.IsBoolean() { + if n.Op() == ir.ONAME || n.Op() == ir.OLITERAL { + r := ir.NewConvExpr(base.Pos, ir.OCONVNOP, nil, n) + r.SetType(types.Types[types.TBOOL]) + r.SetTypecheck(1) + r.SetImplicit(true) + n = r + } + } + + if types.Identical(n.Type(), t) { + return n + } + + op, why := Assignop(n.Type(), t) + if op == ir.OXXX { + base.Errorf("cannot use %L as type %v in %s%s", n, t, context(), why) + op = ir.OCONV + } + + r := ir.NewConvExpr(base.Pos, op, t, n) + r.SetTypecheck(1) + r.SetImplicit(true) + return r +} + +// Is type src assignment compatible to type dst? +// If so, return op code to use in conversion. +// If not, return OXXX. In this case, the string return parameter may +// hold a reason why. In all other cases, it'll be the empty string. +func Assignop(src, dst *types.Type) (ir.Op, string) { + if src == dst { + return ir.OCONVNOP, "" + } + if src == nil || dst == nil || src.Kind() == types.TFORW || dst.Kind() == types.TFORW || src.Underlying() == nil || dst.Underlying() == nil { + return ir.OXXX, "" + } + + // 1. src type is identical to dst. + if types.Identical(src, dst) { + return ir.OCONVNOP, "" + } + + // 2. src and dst have identical underlying types + // and either src or dst is not a named type or + // both are empty interface types. + // For assignable but different non-empty interface types, + // we want to recompute the itab. Recomputing the itab ensures + // that itabs are unique (thus an interface with a compile-time + // type I has an itab with interface type I). + if types.Identical(src.Underlying(), dst.Underlying()) { + if src.IsEmptyInterface() { + // Conversion between two empty interfaces + // requires no code. + return ir.OCONVNOP, "" + } + if (src.Sym() == nil || dst.Sym() == nil) && !src.IsInterface() { + // Conversion between two types, at least one unnamed, + // needs no conversion. The exception is nonempty interfaces + // which need to have their itab updated. + return ir.OCONVNOP, "" + } + } + + // 3. dst is an interface type and src implements dst. + if dst.IsInterface() && src.Kind() != types.TNIL { + var missing, have *types.Field + var ptr int + if implements(src, dst, &missing, &have, &ptr) { + // Call NeedITab/ITabAddr so that (src, dst) + // gets added to itabs early, which allows + // us to de-virtualize calls through this + // type/interface pair later. See CompileITabs in reflect.go + if types.IsDirectIface(src) && !dst.IsEmptyInterface() { + NeedITab(src, dst) + } + + return ir.OCONVIFACE, "" + } + + // we'll have complained about this method anyway, suppress spurious messages. + if have != nil && have.Sym == missing.Sym && (have.Type.Broke() || missing.Type.Broke()) { + return ir.OCONVIFACE, "" + } + + var why string + if isptrto(src, types.TINTER) { + why = fmt.Sprintf(":\n\t%v is pointer to interface, not interface", src) + } else if have != nil && have.Sym == missing.Sym && have.Nointerface() { + why = fmt.Sprintf(":\n\t%v does not implement %v (%v method is marked 'nointerface')", src, dst, missing.Sym) + } else if have != nil && have.Sym == missing.Sym { + why = fmt.Sprintf(":\n\t%v does not implement %v (wrong type for %v method)\n"+ + "\t\thave %v%S\n\t\twant %v%S", src, dst, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) + } else if ptr != 0 { + why = fmt.Sprintf(":\n\t%v does not implement %v (%v method has pointer receiver)", src, dst, missing.Sym) + } else if have != nil { + why = fmt.Sprintf(":\n\t%v does not implement %v (missing %v method)\n"+ + "\t\thave %v%S\n\t\twant %v%S", src, dst, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) + } else { + why = fmt.Sprintf(":\n\t%v does not implement %v (missing %v method)", src, dst, missing.Sym) + } + + return ir.OXXX, why + } + + if isptrto(dst, types.TINTER) { + why := fmt.Sprintf(":\n\t%v is pointer to interface, not interface", dst) + return ir.OXXX, why + } + + if src.IsInterface() && dst.Kind() != types.TBLANK { + var missing, have *types.Field + var ptr int + var why string + if implements(dst, src, &missing, &have, &ptr) { + why = ": need type assertion" + } + return ir.OXXX, why + } + + // 4. src is a bidirectional channel value, dst is a channel type, + // src and dst have identical element types, and + // either src or dst is not a named type. + if src.IsChan() && src.ChanDir() == types.Cboth && dst.IsChan() { + if types.Identical(src.Elem(), dst.Elem()) && (src.Sym() == nil || dst.Sym() == nil) { + return ir.OCONVNOP, "" + } + } + + // 5. src is the predeclared identifier nil and dst is a nillable type. + if src.Kind() == types.TNIL { + switch dst.Kind() { + case types.TPTR, + types.TFUNC, + types.TMAP, + types.TCHAN, + types.TINTER, + types.TSLICE: + return ir.OCONVNOP, "" + } + } + + // 6. rule about untyped constants - already converted by DefaultLit. + + // 7. Any typed value can be assigned to the blank identifier. + if dst.Kind() == types.TBLANK { + return ir.OCONVNOP, "" + } + + return ir.OXXX, "" +} + +// Can we convert a value of type src to a value of type dst? +// If so, return op code to use in conversion (maybe OCONVNOP). +// If not, return OXXX. In this case, the string return parameter may +// hold a reason why. In all other cases, it'll be the empty string. +// srcConstant indicates whether the value of type src is a constant. +func Convertop(srcConstant bool, src, dst *types.Type) (ir.Op, string) { + if src == dst { + return ir.OCONVNOP, "" + } + if src == nil || dst == nil { + return ir.OXXX, "" + } + + // Conversions from regular to go:notinheap are not allowed + // (unless it's unsafe.Pointer). These are runtime-specific + // rules. + // (a) Disallow (*T) to (*U) where T is go:notinheap but U isn't. + if src.IsPtr() && dst.IsPtr() && dst.Elem().NotInHeap() && !src.Elem().NotInHeap() { + why := fmt.Sprintf(":\n\t%v is incomplete (or unallocatable), but %v is not", dst.Elem(), src.Elem()) + return ir.OXXX, why + } + // (b) Disallow string to []T where T is go:notinheap. + if src.IsString() && dst.IsSlice() && dst.Elem().NotInHeap() && (dst.Elem().Kind() == types.ByteType.Kind() || dst.Elem().Kind() == types.RuneType.Kind()) { + why := fmt.Sprintf(":\n\t%v is incomplete (or unallocatable)", dst.Elem()) + return ir.OXXX, why + } + + // 1. src can be assigned to dst. + op, why := Assignop(src, dst) + if op != ir.OXXX { + return op, why + } + + // The rules for interfaces are no different in conversions + // than assignments. If interfaces are involved, stop now + // with the good message from assignop. + // Otherwise clear the error. + if src.IsInterface() || dst.IsInterface() { + return ir.OXXX, why + } + + // 2. Ignoring struct tags, src and dst have identical underlying types. + if types.IdenticalIgnoreTags(src.Underlying(), dst.Underlying()) { + return ir.OCONVNOP, "" + } + + // 3. src and dst are unnamed pointer types and, ignoring struct tags, + // their base types have identical underlying types. + if src.IsPtr() && dst.IsPtr() && src.Sym() == nil && dst.Sym() == nil { + if types.IdenticalIgnoreTags(src.Elem().Underlying(), dst.Elem().Underlying()) { + return ir.OCONVNOP, "" + } + } + + // 4. src and dst are both integer or floating point types. + if (src.IsInteger() || src.IsFloat()) && (dst.IsInteger() || dst.IsFloat()) { + if types.SimType[src.Kind()] == types.SimType[dst.Kind()] { + return ir.OCONVNOP, "" + } + return ir.OCONV, "" + } + + // 5. src and dst are both complex types. + if src.IsComplex() && dst.IsComplex() { + if types.SimType[src.Kind()] == types.SimType[dst.Kind()] { + return ir.OCONVNOP, "" + } + return ir.OCONV, "" + } + + // Special case for constant conversions: any numeric + // conversion is potentially okay. We'll validate further + // within evconst. See #38117. + if srcConstant && (src.IsInteger() || src.IsFloat() || src.IsComplex()) && (dst.IsInteger() || dst.IsFloat() || dst.IsComplex()) { + return ir.OCONV, "" + } + + // 6. src is an integer or has type []byte or []rune + // and dst is a string type. + if src.IsInteger() && dst.IsString() { + return ir.ORUNESTR, "" + } + + if src.IsSlice() && dst.IsString() { + if src.Elem().Kind() == types.ByteType.Kind() { + return ir.OBYTES2STR, "" + } + if src.Elem().Kind() == types.RuneType.Kind() { + return ir.ORUNES2STR, "" + } + } + + // 7. src is a string and dst is []byte or []rune. + // String to slice. + if src.IsString() && dst.IsSlice() { + if dst.Elem().Kind() == types.ByteType.Kind() { + return ir.OSTR2BYTES, "" + } + if dst.Elem().Kind() == types.RuneType.Kind() { + return ir.OSTR2RUNES, "" + } + } + + // 8. src is a pointer or uintptr and dst is unsafe.Pointer. + if (src.IsPtr() || src.IsUintptr()) && dst.IsUnsafePtr() { + return ir.OCONVNOP, "" + } + + // 9. src is unsafe.Pointer and dst is a pointer or uintptr. + if src.IsUnsafePtr() && (dst.IsPtr() || dst.IsUintptr()) { + return ir.OCONVNOP, "" + } + + // 10. src is map and dst is a pointer to corresponding hmap. + // This rule is needed for the implementation detail that + // go gc maps are implemented as a pointer to a hmap struct. + if src.Kind() == types.TMAP && dst.IsPtr() && + src.MapType().Hmap == dst.Elem() { + return ir.OCONVNOP, "" + } + + // 11. src is a slice and dst is a pointer-to-array. + // They must have same element type. + if src.IsSlice() && dst.IsPtr() && dst.Elem().IsArray() && + types.Identical(src.Elem(), dst.Elem().Elem()) { + if !types.AllowsGoVersion(curpkg(), 1, 17) { + return ir.OXXX, ":\n\tconversion of slices to array pointers only supported as of -lang=go1.17" + } + return ir.OSLICE2ARRPTR, "" + } + + return ir.OXXX, "" +} + +// Code to resolve elided DOTs in embedded types. + +// A dlist stores a pointer to a TFIELD Type embedded within +// a TSTRUCT or TINTER Type. +type dlist struct { + field *types.Field +} + +// dotpath computes the unique shortest explicit selector path to fully qualify +// a selection expression x.f, where x is of type t and f is the symbol s. +// If no such path exists, dotpath returns nil. +// If there are multiple shortest paths to the same depth, ambig is true. +func dotpath(s *types.Sym, t *types.Type, save **types.Field, ignorecase bool) (path []dlist, ambig bool) { + // The embedding of types within structs imposes a tree structure onto + // types: structs parent the types they embed, and types parent their + // fields or methods. Our goal here is to find the shortest path to + // a field or method named s in the subtree rooted at t. To accomplish + // that, we iteratively perform depth-first searches of increasing depth + // until we either find the named field/method or exhaust the tree. + for d := 0; ; d++ { + if d > len(dotlist) { + dotlist = append(dotlist, dlist{}) + } + if c, more := adddot1(s, t, d, save, ignorecase); c == 1 { + return dotlist[:d], false + } else if c > 1 { + return nil, true + } else if !more { + return nil, false + } + } +} + +func expand0(t *types.Type) { + u := t + if u.IsPtr() { + u = u.Elem() + } + + if u.IsInterface() { + for _, f := range u.AllMethods().Slice() { + if f.Sym.Uniq() { + continue + } + f.Sym.SetUniq(true) + slist = append(slist, symlink{field: f}) + } + + return + } + + u = types.ReceiverBaseType(t) + if u != nil { + for _, f := range u.Methods().Slice() { + if f.Sym.Uniq() { + continue + } + f.Sym.SetUniq(true) + slist = append(slist, symlink{field: f}) + } + } +} + +func expand1(t *types.Type, top bool) { + if t.Recur() { + return + } + t.SetRecur(true) + + if !top { + expand0(t) + } + + u := t + if u.IsPtr() { + u = u.Elem() + } + + if u.IsStruct() || u.IsInterface() { + var fields *types.Fields + if u.IsStruct() { + fields = u.Fields() + } else { + fields = u.AllMethods() + } + for _, f := range fields.Slice() { + if f.Embedded == 0 { + continue + } + if f.Sym == nil { + continue + } + expand1(f.Type, false) + } + } + + t.SetRecur(false) +} + +func ifacelookdot(s *types.Sym, t *types.Type, ignorecase bool) (m *types.Field, followptr bool) { + if t == nil { + return nil, false + } + + path, ambig := dotpath(s, t, &m, ignorecase) + if path == nil { + if ambig { + base.Errorf("%v.%v is ambiguous", t, s) + } + return nil, false + } + + for _, d := range path { + if d.field.Type.IsPtr() { + followptr = true + break + } + } + + if !m.IsMethod() { + base.Errorf("%v.%v is a field, not a method", t, s) + return nil, followptr + } + + return m, followptr +} + +func implements(t, iface *types.Type, m, samename **types.Field, ptr *int) bool { + t0 := t + if t == nil { + return false + } + + if t.IsInterface() { + i := 0 + tms := t.AllMethods().Slice() + for _, im := range iface.AllMethods().Slice() { + for i < len(tms) && tms[i].Sym != im.Sym { + i++ + } + if i == len(tms) { + *m = im + *samename = nil + *ptr = 0 + return false + } + tm := tms[i] + if !types.Identical(tm.Type, im.Type) { + *m = im + *samename = tm + *ptr = 0 + return false + } + } + + return true + } + + t = types.ReceiverBaseType(t) + var tms []*types.Field + if t != nil { + CalcMethods(t) + tms = t.AllMethods().Slice() + } + i := 0 + for _, im := range iface.AllMethods().Slice() { + if im.Broke() { + continue + } + for i < len(tms) && tms[i].Sym != im.Sym { + i++ + } + if i == len(tms) { + *m = im + *samename, _ = ifacelookdot(im.Sym, t, true) + *ptr = 0 + return false + } + tm := tms[i] + if tm.Nointerface() || !types.Identical(tm.Type, im.Type) { + *m = im + *samename = tm + *ptr = 0 + return false + } + followptr := tm.Embedded == 2 + + // if pointer receiver in method, + // the method does not exist for value types. + rcvr := tm.Type.Recv().Type + if rcvr.IsPtr() && !t0.IsPtr() && !followptr && !types.IsInterfaceMethod(tm.Type) { + if false && base.Flag.LowerR != 0 { + base.Errorf("interface pointer mismatch") + } + + *m = im + *samename = nil + *ptr = 1 + return false + } + } + + return true +} + +func isptrto(t *types.Type, et types.Kind) bool { + if t == nil { + return false + } + if !t.IsPtr() { + return false + } + t = t.Elem() + if t == nil { + return false + } + if t.Kind() != et { + return false + } + return true +} + +// lookdot0 returns the number of fields or methods named s associated +// with Type t. If exactly one exists, it will be returned in *save +// (if save is not nil). +func lookdot0(s *types.Sym, t *types.Type, save **types.Field, ignorecase bool) int { + u := t + if u.IsPtr() { + u = u.Elem() + } + + c := 0 + if u.IsStruct() || u.IsInterface() { + var fields *types.Fields + if u.IsStruct() { + fields = u.Fields() + } else { + fields = u.AllMethods() + } + for _, f := range fields.Slice() { + if f.Sym == s || (ignorecase && f.IsMethod() && strings.EqualFold(f.Sym.Name, s.Name)) { + if save != nil { + *save = f + } + c++ + } + } + } + + u = t + if t.Sym() != nil && t.IsPtr() && !t.Elem().IsPtr() { + // If t is a defined pointer type, then x.m is shorthand for (*x).m. + u = t.Elem() + } + u = types.ReceiverBaseType(u) + if u != nil { + for _, f := range u.Methods().Slice() { + if f.Embedded == 0 && (f.Sym == s || (ignorecase && strings.EqualFold(f.Sym.Name, s.Name))) { + if save != nil { + *save = f + } + c++ + } + } + } + + return c +} + +var slist []symlink + +// Code to help generate trampoline functions for methods on embedded +// types. These are approx the same as the corresponding AddImplicitDots +// routines except that they expect to be called with unique tasks and +// they return the actual methods. + +type symlink struct { + field *types.Field +} diff --git a/src/cmd/compile/internal/typecheck/syms.go b/src/cmd/compile/internal/typecheck/syms.go new file mode 100644 index 0000000000000000000000000000000000000000..f29af82db2cc25670a60ef4192fd5623a4ee4806 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/syms.go @@ -0,0 +1,103 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/src" +) + +func LookupRuntime(name string) *ir.Name { + s := ir.Pkgs.Runtime.Lookup(name) + if s == nil || s.Def == nil { + base.Fatalf("LookupRuntime: can't find runtime.%s", name) + } + return ir.AsNode(s.Def).(*ir.Name) +} + +// SubstArgTypes substitutes the given list of types for +// successive occurrences of the "any" placeholder in the +// type syntax expression n.Type. +// The result of SubstArgTypes MUST be assigned back to old, e.g. +// n.Left = SubstArgTypes(n.Left, t1, t2) +func SubstArgTypes(old *ir.Name, types_ ...*types.Type) *ir.Name { + for _, t := range types_ { + types.CalcSize(t) + } + n := ir.NewNameAt(old.Pos(), old.Sym()) + n.Class = old.Class + n.SetType(types.SubstAny(old.Type(), &types_)) + n.Func = old.Func + if len(types_) > 0 { + base.Fatalf("SubstArgTypes: too many argument types") + } + return n +} + +// AutoLabel generates a new Name node for use with +// an automatically generated label. +// prefix is a short mnemonic (e.g. ".s" for switch) +// to help with debugging. +// It should begin with "." to avoid conflicts with +// user labels. +func AutoLabel(prefix string) *types.Sym { + if prefix[0] != '.' { + base.Fatalf("autolabel prefix must start with '.', have %q", prefix) + } + fn := ir.CurFunc + if ir.CurFunc == nil { + base.Fatalf("autolabel outside function") + } + n := fn.Label + fn.Label++ + return LookupNum(prefix, int(n)) +} + +func Lookup(name string) *types.Sym { + return types.LocalPkg.Lookup(name) +} + +// InitRuntime loads the definitions for the low-level runtime functions, +// so that the compiler can generate calls to them, +// but does not make them visible to user code. +func InitRuntime() { + base.Timer.Start("fe", "loadsys") + types.Block = 1 + + typs := runtimeTypes() + for _, d := range &runtimeDecls { + sym := ir.Pkgs.Runtime.Lookup(d.name) + typ := typs[d.typ] + switch d.tag { + case funcTag: + importfunc(ir.Pkgs.Runtime, src.NoXPos, sym, typ) + case varTag: + importvar(ir.Pkgs.Runtime, src.NoXPos, sym, typ) + default: + base.Fatalf("unhandled declaration tag %v", d.tag) + } + } +} + +// LookupRuntimeFunc looks up Go function name in package runtime. This function +// must follow the internal calling convention. +func LookupRuntimeFunc(name string) *obj.LSym { + return LookupRuntimeABI(name, obj.ABIInternal) +} + +// LookupRuntimeVar looks up a variable (or assembly function) name in package +// runtime. If this is a function, it may have a special calling +// convention. +func LookupRuntimeVar(name string) *obj.LSym { + return LookupRuntimeABI(name, obj.ABI0) +} + +// LookupRuntimeABI looks up a name in package runtime using the given ABI. +func LookupRuntimeABI(name string, abi obj.ABI) *obj.LSym { + return base.PkgLinksym("runtime", name, abi) +} diff --git a/src/cmd/compile/internal/typecheck/target.go b/src/cmd/compile/internal/typecheck/target.go new file mode 100644 index 0000000000000000000000000000000000000000..018614d68bfc4f69c4e5260efa6f29d1416710e5 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/target.go @@ -0,0 +1,12 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run mkbuiltin.go + +package typecheck + +import "cmd/compile/internal/ir" + +// Target is the package being compiled. +var Target *ir.Package diff --git a/src/cmd/compile/internal/typecheck/type.go b/src/cmd/compile/internal/typecheck/type.go new file mode 100644 index 0000000000000000000000000000000000000000..af694c2d94a30fb4a5bf1a74e3afd1a48fddfaa8 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/type.go @@ -0,0 +1,188 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" +) + +// tcArrayType typechecks an OTARRAY node. +func tcArrayType(n *ir.ArrayType) ir.Node { + n.Elem = typecheckNtype(n.Elem) + if n.Elem.Type() == nil { + return n + } + if n.Len == nil { // [...]T + if !n.Diag() { + n.SetDiag(true) + base.Errorf("use of [...] array outside of array literal") + } + return n + } + n.Len = indexlit(Expr(n.Len)) + size := n.Len + if ir.ConstType(size) != constant.Int { + switch { + case size.Type() == nil: + // Error already reported elsewhere. + case size.Type().IsInteger() && size.Op() != ir.OLITERAL: + base.Errorf("non-constant array bound %v", size) + default: + base.Errorf("invalid array bound %v", size) + } + return n + } + + v := size.Val() + if ir.ConstOverflow(v, types.Types[types.TINT]) { + base.Errorf("array bound is too large") + return n + } + + if constant.Sign(v) < 0 { + base.Errorf("array bound must be non-negative") + return n + } + + bound, _ := constant.Int64Val(v) + t := types.NewArray(n.Elem.Type(), bound) + n.SetOTYPE(t) + types.CheckSize(t) + return n +} + +// tcChanType typechecks an OTCHAN node. +func tcChanType(n *ir.ChanType) ir.Node { + n.Elem = typecheckNtype(n.Elem) + l := n.Elem + if l.Type() == nil { + return n + } + if l.Type().NotInHeap() { + base.Errorf("chan of incomplete (or unallocatable) type not allowed") + } + n.SetOTYPE(types.NewChan(l.Type(), n.Dir)) + return n +} + +// tcFuncType typechecks an OTFUNC node. +func tcFuncType(n *ir.FuncType) ir.Node { + misc := func(f *types.Field, nf *ir.Field) { + f.SetIsDDD(nf.IsDDD) + if nf.Decl != nil { + nf.Decl.SetType(f.Type) + f.Nname = nf.Decl + } + } + + lno := base.Pos + + var recv *types.Field + if n.Recv != nil { + recv = tcField(n.Recv, misc) + } + + t := types.NewSignature(types.LocalPkg, recv, nil, tcFields(n.Params, misc), tcFields(n.Results, misc)) + checkdupfields("argument", t.Recvs().FieldSlice(), t.Params().FieldSlice(), t.Results().FieldSlice()) + + base.Pos = lno + + n.SetOTYPE(t) + return n +} + +// tcInterfaceType typechecks an OTINTER node. +func tcInterfaceType(n *ir.InterfaceType) ir.Node { + if len(n.Methods) == 0 { + n.SetOTYPE(types.Types[types.TINTER]) + return n + } + + lno := base.Pos + methods := tcFields(n.Methods, nil) + base.Pos = lno + + n.SetOTYPE(types.NewInterface(types.LocalPkg, methods)) + return n +} + +// tcMapType typechecks an OTMAP node. +func tcMapType(n *ir.MapType) ir.Node { + n.Key = typecheckNtype(n.Key) + n.Elem = typecheckNtype(n.Elem) + l := n.Key + r := n.Elem + if l.Type() == nil || r.Type() == nil { + return n + } + if l.Type().NotInHeap() { + base.Errorf("incomplete (or unallocatable) map key not allowed") + } + if r.Type().NotInHeap() { + base.Errorf("incomplete (or unallocatable) map value not allowed") + } + n.SetOTYPE(types.NewMap(l.Type(), r.Type())) + mapqueue = append(mapqueue, n) // check map keys when all types are settled + return n +} + +// tcSliceType typechecks an OTSLICE node. +func tcSliceType(n *ir.SliceType) ir.Node { + n.Elem = typecheckNtype(n.Elem) + if n.Elem.Type() == nil { + return n + } + t := types.NewSlice(n.Elem.Type()) + n.SetOTYPE(t) + types.CheckSize(t) + return n +} + +// tcStructType typechecks an OTSTRUCT node. +func tcStructType(n *ir.StructType) ir.Node { + lno := base.Pos + + fields := tcFields(n.Fields, func(f *types.Field, nf *ir.Field) { + if nf.Embedded { + checkembeddedtype(f.Type) + f.Embedded = 1 + } + f.Note = nf.Note + }) + checkdupfields("field", fields) + + base.Pos = lno + n.SetOTYPE(types.NewStruct(types.LocalPkg, fields)) + return n +} + +// tcField typechecks a generic Field. +// misc can be provided to handle specialized typechecking. +func tcField(n *ir.Field, misc func(*types.Field, *ir.Field)) *types.Field { + base.Pos = n.Pos + if n.Ntype != nil { + n.Type = typecheckNtype(n.Ntype).Type() + n.Ntype = nil + } + f := types.NewField(n.Pos, n.Sym, n.Type) + if misc != nil { + misc(f, n) + } + return f +} + +// tcFields typechecks a slice of generic Fields. +// misc can be provided to handle specialized typechecking. +func tcFields(l []*ir.Field, misc func(*types.Field, *ir.Field)) []*types.Field { + fields := make([]*types.Field, len(l)) + for i, n := range l { + fields[i] = tcField(n, misc) + } + return fields +} diff --git a/src/cmd/compile/internal/typecheck/typecheck.go b/src/cmd/compile/internal/typecheck/typecheck.go new file mode 100644 index 0000000000000000000000000000000000000000..359f66236969fb4e34867ba35ea9d56b9480f5ba --- /dev/null +++ b/src/cmd/compile/internal/typecheck/typecheck.go @@ -0,0 +1,2251 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "fmt" + "go/constant" + "go/token" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" +) + +// Function collecting autotmps generated during typechecking, +// to be included in the package-level init function. +var InitTodoFunc = ir.NewFunc(base.Pos) + +var inimport bool // set during import + +var TypecheckAllowed bool + +var ( + NeedITab = func(t, itype *types.Type) {} + NeedRuntimeType = func(*types.Type) {} +) + +func AssignExpr(n ir.Node) ir.Node { return typecheck(n, ctxExpr|ctxAssign) } +func Expr(n ir.Node) ir.Node { return typecheck(n, ctxExpr) } +func Stmt(n ir.Node) ir.Node { return typecheck(n, ctxStmt) } + +func Exprs(exprs []ir.Node) { typecheckslice(exprs, ctxExpr) } +func Stmts(stmts []ir.Node) { typecheckslice(stmts, ctxStmt) } + +func Call(call *ir.CallExpr) { + t := call.X.Type() + if t == nil { + panic("misuse of Call") + } + ctx := ctxStmt + if t.NumResults() > 0 { + ctx = ctxExpr | ctxMultiOK + } + if typecheck(call, ctx) != call { + panic("bad typecheck") + } +} + +func Callee(n ir.Node) ir.Node { + return typecheck(n, ctxExpr|ctxCallee) +} + +func FuncBody(n *ir.Func) { + ir.CurFunc = n + errorsBefore := base.Errors() + Stmts(n.Body) + CheckUnused(n) + CheckReturn(n) + if base.Errors() > errorsBefore { + n.Body = nil // type errors; do not compile + } +} + +var importlist []*ir.Func + +// AllImportedBodies reads in the bodies of all imported functions and typechecks +// them, if needed. +func AllImportedBodies() { + for _, n := range importlist { + if n.Inl != nil { + ImportedBody(n) + } + } +} + +var traceIndent []byte + +func tracePrint(title string, n ir.Node) func(np *ir.Node) { + indent := traceIndent + + // guard against nil + var pos, op string + var tc uint8 + if n != nil { + pos = base.FmtPos(n.Pos()) + op = n.Op().String() + tc = n.Typecheck() + } + + types.SkipSizeForTracing = true + defer func() { types.SkipSizeForTracing = false }() + fmt.Printf("%s: %s%s %p %s %v tc=%d\n", pos, indent, title, n, op, n, tc) + traceIndent = append(traceIndent, ". "...) + + return func(np *ir.Node) { + traceIndent = traceIndent[:len(traceIndent)-2] + + // if we have a result, use that + if np != nil { + n = *np + } + + // guard against nil + // use outer pos, op so we don't get empty pos/op if n == nil (nicer output) + var tc uint8 + var typ *types.Type + if n != nil { + pos = base.FmtPos(n.Pos()) + op = n.Op().String() + tc = n.Typecheck() + typ = n.Type() + } + + types.SkipSizeForTracing = true + defer func() { types.SkipSizeForTracing = false }() + fmt.Printf("%s: %s=> %p %s %v tc=%d type=%L\n", pos, indent, n, op, n, tc, typ) + } +} + +const ( + ctxStmt = 1 << iota // evaluated at statement level + ctxExpr // evaluated in value context + ctxType // evaluated in type context + ctxCallee // call-only expressions are ok + ctxMultiOK // multivalue function returns are ok + ctxAssign // assigning to expression +) + +// type checks the whole tree of an expression. +// calculates expression types. +// evaluates compile time constants. +// marks variables that escape the local frame. +// rewrites n.Op to be more specific in some cases. + +var typecheckdefstack []*ir.Name + +// Resolve ONONAME to definition, if any. +func Resolve(n ir.Node) (res ir.Node) { + if n == nil || n.Op() != ir.ONONAME { + return n + } + + // only trace if there's work to do + if base.EnableTrace && base.Flag.LowerT { + defer tracePrint("resolve", n)(&res) + } + + if sym := n.Sym(); sym.Pkg != types.LocalPkg { + // We might have an ir.Ident from oldname or importDot. + if id, ok := n.(*ir.Ident); ok { + if pkgName := DotImportRefs[id]; pkgName != nil { + pkgName.Used = true + } + } + + return expandDecl(n) + } + + r := ir.AsNode(n.Sym().Def) + if r == nil { + return n + } + + if r.Op() == ir.OIOTA { + if x := getIotaValue(); x >= 0 { + return ir.NewInt(x) + } + return n + } + + return r +} + +func typecheckslice(l []ir.Node, top int) { + for i := range l { + l[i] = typecheck(l[i], top) + } +} + +var _typekind = []string{ + types.TINT: "int", + types.TUINT: "uint", + types.TINT8: "int8", + types.TUINT8: "uint8", + types.TINT16: "int16", + types.TUINT16: "uint16", + types.TINT32: "int32", + types.TUINT32: "uint32", + types.TINT64: "int64", + types.TUINT64: "uint64", + types.TUINTPTR: "uintptr", + types.TCOMPLEX64: "complex64", + types.TCOMPLEX128: "complex128", + types.TFLOAT32: "float32", + types.TFLOAT64: "float64", + types.TBOOL: "bool", + types.TSTRING: "string", + types.TPTR: "pointer", + types.TUNSAFEPTR: "unsafe.Pointer", + types.TSTRUCT: "struct", + types.TINTER: "interface", + types.TCHAN: "chan", + types.TMAP: "map", + types.TARRAY: "array", + types.TSLICE: "slice", + types.TFUNC: "func", + types.TNIL: "nil", + types.TIDEAL: "untyped number", +} + +func typekind(t *types.Type) string { + if t.IsUntyped() { + return fmt.Sprintf("%v", t) + } + et := t.Kind() + if int(et) < len(_typekind) { + s := _typekind[et] + if s != "" { + return s + } + } + return fmt.Sprintf("etype=%d", et) +} + +func cycleFor(start ir.Node) []ir.Node { + // Find the start node in typecheck_tcstack. + // We know that it must exist because each time we mark + // a node with n.SetTypecheck(2) we push it on the stack, + // and each time we mark a node with n.SetTypecheck(2) we + // pop it from the stack. We hit a cycle when we encounter + // a node marked 2 in which case is must be on the stack. + i := len(typecheck_tcstack) - 1 + for i > 0 && typecheck_tcstack[i] != start { + i-- + } + + // collect all nodes with same Op + var cycle []ir.Node + for _, n := range typecheck_tcstack[i:] { + if n.Op() == start.Op() { + cycle = append(cycle, n) + } + } + + return cycle +} + +func cycleTrace(cycle []ir.Node) string { + var s string + for i, n := range cycle { + s += fmt.Sprintf("\n\t%v: %v uses %v", ir.Line(n), n, cycle[(i+1)%len(cycle)]) + } + return s +} + +var typecheck_tcstack []ir.Node + +func Func(fn *ir.Func) { + new := Stmt(fn) + if new != fn { + base.Fatalf("typecheck changed func") + } +} + +func typecheckNtype(n ir.Ntype) ir.Ntype { + return typecheck(n, ctxType).(ir.Ntype) +} + +// typecheck type checks node n. +// The result of typecheck MUST be assigned back to n, e.g. +// n.Left = typecheck(n.Left, top) +func typecheck(n ir.Node, top int) (res ir.Node) { + // cannot type check until all the source has been parsed + if !TypecheckAllowed { + base.Fatalf("early typecheck") + } + + if n == nil { + return nil + } + + // only trace if there's work to do + if base.EnableTrace && base.Flag.LowerT { + defer tracePrint("typecheck", n)(&res) + } + + lno := ir.SetPos(n) + + // Skip over parens. + for n.Op() == ir.OPAREN { + n = n.(*ir.ParenExpr).X + } + + // Resolve definition of name and value of iota lazily. + n = Resolve(n) + + // Skip typecheck if already done. + // But re-typecheck ONAME/OTYPE/OLITERAL/OPACK node in case context has changed. + if n.Typecheck() == 1 || n.Typecheck() == 3 { + switch n.Op() { + case ir.ONAME, ir.OTYPE, ir.OLITERAL, ir.OPACK: + break + + default: + base.Pos = lno + return n + } + } + + if n.Typecheck() == 2 { + // Typechecking loop. Trying printing a meaningful message, + // otherwise a stack trace of typechecking. + switch n.Op() { + // We can already diagnose variables used as types. + case ir.ONAME: + n := n.(*ir.Name) + if top&(ctxExpr|ctxType) == ctxType { + base.Errorf("%v is not a type", n) + } + + case ir.OTYPE: + // Only report a type cycle if we are expecting a type. + // Otherwise let other code report an error. + if top&ctxType == ctxType { + // A cycle containing only alias types is an error + // since it would expand indefinitely when aliases + // are substituted. + cycle := cycleFor(n) + for _, n1 := range cycle { + if n1.Name() != nil && !n1.Name().Alias() { + // Cycle is ok. But if n is an alias type and doesn't + // have a type yet, we have a recursive type declaration + // with aliases that we can't handle properly yet. + // Report an error rather than crashing later. + if n.Name() != nil && n.Name().Alias() && n.Type() == nil { + base.Pos = n.Pos() + base.Fatalf("cannot handle alias type declaration (issue #25838): %v", n) + } + base.Pos = lno + return n + } + } + base.ErrorfAt(n.Pos(), "invalid recursive type alias %v%s", n, cycleTrace(cycle)) + } + + case ir.OLITERAL: + if top&(ctxExpr|ctxType) == ctxType { + base.Errorf("%v is not a type", n) + break + } + base.ErrorfAt(n.Pos(), "constant definition loop%s", cycleTrace(cycleFor(n))) + } + + if base.Errors() == 0 { + var trace string + for i := len(typecheck_tcstack) - 1; i >= 0; i-- { + x := typecheck_tcstack[i] + trace += fmt.Sprintf("\n\t%v %v", ir.Line(x), x) + } + base.Errorf("typechecking loop involving %v%s", n, trace) + } + + base.Pos = lno + return n + } + + typecheck_tcstack = append(typecheck_tcstack, n) + + n.SetTypecheck(2) + n = typecheck1(n, top) + n.SetTypecheck(1) + + last := len(typecheck_tcstack) - 1 + typecheck_tcstack[last] = nil + typecheck_tcstack = typecheck_tcstack[:last] + + _, isExpr := n.(ir.Expr) + _, isStmt := n.(ir.Stmt) + isMulti := false + switch n.Op() { + case ir.OCALLFUNC, ir.OCALLINTER, ir.OCALLMETH: + n := n.(*ir.CallExpr) + if t := n.X.Type(); t != nil && t.Kind() == types.TFUNC { + nr := t.NumResults() + isMulti = nr > 1 + if nr == 0 { + isExpr = false + } + } + case ir.OAPPEND: + // Must be used (and not BinaryExpr/UnaryExpr). + isStmt = false + case ir.OCLOSE, ir.ODELETE, ir.OPANIC, ir.OPRINT, ir.OPRINTN, ir.OVARKILL, ir.OVARLIVE: + // Must not be used. + isExpr = false + isStmt = true + case ir.OCOPY, ir.ORECOVER, ir.ORECV: + // Can be used or not. + isStmt = true + } + + t := n.Type() + if t != nil && !t.IsFuncArgStruct() && n.Op() != ir.OTYPE { + switch t.Kind() { + case types.TFUNC, // might have TANY; wait until it's called + types.TANY, types.TFORW, types.TIDEAL, types.TNIL, types.TBLANK: + break + + default: + types.CheckSize(t) + } + } + if t != nil { + n = EvalConst(n) + t = n.Type() + } + + // TODO(rsc): Lots of the complexity here is because typecheck can + // see OTYPE, ONAME, and OLITERAL nodes multiple times. + // Once we make the IR a proper tree, we should be able to simplify + // this code a bit, especially the final case. + switch { + case top&(ctxStmt|ctxExpr) == ctxExpr && !isExpr && n.Op() != ir.OTYPE && !isMulti: + if !n.Diag() { + base.Errorf("%v used as value", n) + n.SetDiag(true) + } + if t != nil { + n.SetType(nil) + } + + case top&ctxType == 0 && n.Op() == ir.OTYPE && t != nil: + if !n.Type().Broke() { + base.Errorf("type %v is not an expression", n.Type()) + n.SetDiag(true) + } + + case top&(ctxStmt|ctxExpr) == ctxStmt && !isStmt && t != nil: + if !n.Diag() { + base.Errorf("%v evaluated but not used", n) + n.SetDiag(true) + } + n.SetType(nil) + + case top&(ctxType|ctxExpr) == ctxType && n.Op() != ir.OTYPE && n.Op() != ir.ONONAME && (t != nil || n.Op() == ir.ONAME): + base.Errorf("%v is not a type", n) + if t != nil { + if n.Op() == ir.ONAME { + t.SetBroke(true) + } else { + n.SetType(nil) + } + } + + } + + base.Pos = lno + return n +} + +// indexlit implements typechecking of untyped values as +// array/slice indexes. It is almost equivalent to DefaultLit +// but also accepts untyped numeric values representable as +// value of type int (see also checkmake for comparison). +// The result of indexlit MUST be assigned back to n, e.g. +// n.Left = indexlit(n.Left) +func indexlit(n ir.Node) ir.Node { + if n != nil && n.Type() != nil && n.Type().Kind() == types.TIDEAL { + return DefaultLit(n, types.Types[types.TINT]) + } + return n +} + +// typecheck1 should ONLY be called from typecheck. +func typecheck1(n ir.Node, top int) ir.Node { + if n, ok := n.(*ir.Name); ok { + typecheckdef(n) + } + + switch n.Op() { + default: + ir.Dump("typecheck", n) + base.Fatalf("typecheck %v", n.Op()) + panic("unreachable") + + case ir.OLITERAL: + if n.Sym() == nil && n.Type() == nil { + if !n.Diag() { + base.Fatalf("literal missing type: %v", n) + } + } + return n + + case ir.ONIL: + return n + + // names + case ir.ONONAME: + if !n.Diag() { + // Note: adderrorname looks for this string and + // adds context about the outer expression + base.ErrorfAt(n.Pos(), "undefined: %v", n.Sym()) + n.SetDiag(true) + } + n.SetType(nil) + return n + + case ir.ONAME: + n := n.(*ir.Name) + if n.BuiltinOp != 0 { + if top&ctxCallee == 0 { + base.Errorf("use of builtin %v not in function call", n.Sym()) + n.SetType(nil) + return n + } + return n + } + if top&ctxAssign == 0 { + // not a write to the variable + if ir.IsBlank(n) { + base.Errorf("cannot use _ as value") + n.SetType(nil) + return n + } + n.SetUsed(true) + } + return n + + case ir.OLINKSYMOFFSET: + // type already set + return n + + case ir.OPACK: + n := n.(*ir.PkgName) + base.Errorf("use of package %v without selector", n.Sym()) + n.SetDiag(true) + return n + + // types (ODEREF is with exprs) + case ir.OTYPE: + return n + + case ir.OTSLICE: + n := n.(*ir.SliceType) + return tcSliceType(n) + + case ir.OTARRAY: + n := n.(*ir.ArrayType) + return tcArrayType(n) + + case ir.OTMAP: + n := n.(*ir.MapType) + return tcMapType(n) + + case ir.OTCHAN: + n := n.(*ir.ChanType) + return tcChanType(n) + + case ir.OTSTRUCT: + n := n.(*ir.StructType) + return tcStructType(n) + + case ir.OTINTER: + n := n.(*ir.InterfaceType) + return tcInterfaceType(n) + + case ir.OTFUNC: + n := n.(*ir.FuncType) + return tcFuncType(n) + // type or expr + case ir.ODEREF: + n := n.(*ir.StarExpr) + return tcStar(n, top) + + // x op= y + case ir.OASOP: + n := n.(*ir.AssignOpStmt) + n.X, n.Y = Expr(n.X), Expr(n.Y) + checkassign(n, n.X) + if n.IncDec && !okforarith[n.X.Type().Kind()] { + base.Errorf("invalid operation: %v (non-numeric type %v)", n, n.X.Type()) + return n + } + switch n.AsOp { + case ir.OLSH, ir.ORSH: + n.X, n.Y, _ = tcShift(n, n.X, n.Y) + case ir.OADD, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OMOD, ir.OMUL, ir.OOR, ir.OSUB, ir.OXOR: + n.X, n.Y, _ = tcArith(n, n.AsOp, n.X, n.Y) + default: + base.Fatalf("invalid assign op: %v", n.AsOp) + } + return n + + // logical operators + case ir.OANDAND, ir.OOROR: + n := n.(*ir.LogicalExpr) + n.X, n.Y = Expr(n.X), Expr(n.Y) + if n.X.Type() == nil || n.Y.Type() == nil { + n.SetType(nil) + return n + } + // For "x == x && len(s)", it's better to report that "len(s)" (type int) + // can't be used with "&&" than to report that "x == x" (type untyped bool) + // can't be converted to int (see issue #41500). + if !n.X.Type().IsBoolean() { + base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, n.Op(), typekind(n.X.Type())) + n.SetType(nil) + return n + } + if !n.Y.Type().IsBoolean() { + base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, n.Op(), typekind(n.Y.Type())) + n.SetType(nil) + return n + } + l, r, t := tcArith(n, n.Op(), n.X, n.Y) + n.X, n.Y = l, r + n.SetType(t) + return n + + // shift operators + case ir.OLSH, ir.ORSH: + n := n.(*ir.BinaryExpr) + n.X, n.Y = Expr(n.X), Expr(n.Y) + l, r, t := tcShift(n, n.X, n.Y) + n.X, n.Y = l, r + n.SetType(t) + return n + + // comparison operators + case ir.OEQ, ir.OGE, ir.OGT, ir.OLE, ir.OLT, ir.ONE: + n := n.(*ir.BinaryExpr) + n.X, n.Y = Expr(n.X), Expr(n.Y) + l, r, t := tcArith(n, n.Op(), n.X, n.Y) + if t != nil { + n.X, n.Y = l, r + n.SetType(types.UntypedBool) + if con := EvalConst(n); con.Op() == ir.OLITERAL { + return con + } + n.X, n.Y = defaultlit2(l, r, true) + } + return n + + // binary operators + case ir.OADD, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OMOD, ir.OMUL, ir.OOR, ir.OSUB, ir.OXOR: + n := n.(*ir.BinaryExpr) + n.X, n.Y = Expr(n.X), Expr(n.Y) + l, r, t := tcArith(n, n.Op(), n.X, n.Y) + if t != nil && t.Kind() == types.TSTRING && n.Op() == ir.OADD { + // create or update OADDSTR node with list of strings in x + y + z + (w + v) + ... + var add *ir.AddStringExpr + if l.Op() == ir.OADDSTR { + add = l.(*ir.AddStringExpr) + add.SetPos(n.Pos()) + } else { + add = ir.NewAddStringExpr(n.Pos(), []ir.Node{l}) + } + if r.Op() == ir.OADDSTR { + r := r.(*ir.AddStringExpr) + add.List.Append(r.List.Take()...) + } else { + add.List.Append(r) + } + add.SetType(t) + return add + } + n.X, n.Y = l, r + n.SetType(t) + return n + + case ir.OBITNOT, ir.ONEG, ir.ONOT, ir.OPLUS: + n := n.(*ir.UnaryExpr) + return tcUnaryArith(n) + + // exprs + case ir.OADDR: + n := n.(*ir.AddrExpr) + return tcAddr(n) + + case ir.OCOMPLIT: + return tcCompLit(n.(*ir.CompLitExpr)) + + case ir.OXDOT, ir.ODOT: + n := n.(*ir.SelectorExpr) + return tcDot(n, top) + + case ir.ODOTTYPE: + n := n.(*ir.TypeAssertExpr) + return tcDotType(n) + + case ir.OINDEX: + n := n.(*ir.IndexExpr) + return tcIndex(n) + + case ir.ORECV: + n := n.(*ir.UnaryExpr) + return tcRecv(n) + + case ir.OSEND: + n := n.(*ir.SendStmt) + return tcSend(n) + + case ir.OSLICEHEADER: + n := n.(*ir.SliceHeaderExpr) + return tcSliceHeader(n) + + case ir.OMAKESLICECOPY: + n := n.(*ir.MakeExpr) + return tcMakeSliceCopy(n) + + case ir.OSLICE, ir.OSLICE3: + n := n.(*ir.SliceExpr) + return tcSlice(n) + + // call and call like + case ir.OCALL: + n := n.(*ir.CallExpr) + return tcCall(n, top) + + case ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF: + n := n.(*ir.UnaryExpr) + n.SetType(types.Types[types.TUINTPTR]) + return n + + case ir.OCAP, ir.OLEN: + n := n.(*ir.UnaryExpr) + return tcLenCap(n) + + case ir.OREAL, ir.OIMAG: + n := n.(*ir.UnaryExpr) + return tcRealImag(n) + + case ir.OCOMPLEX: + n := n.(*ir.BinaryExpr) + return tcComplex(n) + + case ir.OCLOSE: + n := n.(*ir.UnaryExpr) + return tcClose(n) + + case ir.ODELETE: + n := n.(*ir.CallExpr) + return tcDelete(n) + + case ir.OAPPEND: + n := n.(*ir.CallExpr) + return tcAppend(n) + + case ir.OCOPY: + n := n.(*ir.BinaryExpr) + return tcCopy(n) + + case ir.OCONV: + n := n.(*ir.ConvExpr) + return tcConv(n) + + case ir.OMAKE: + n := n.(*ir.CallExpr) + return tcMake(n) + + case ir.ONEW: + n := n.(*ir.UnaryExpr) + return tcNew(n) + + case ir.OPRINT, ir.OPRINTN: + n := n.(*ir.CallExpr) + return tcPrint(n) + + case ir.OPANIC: + n := n.(*ir.UnaryExpr) + return tcPanic(n) + + case ir.ORECOVER: + n := n.(*ir.CallExpr) + return tcRecover(n) + + case ir.OUNSAFEADD: + n := n.(*ir.BinaryExpr) + return tcUnsafeAdd(n) + + case ir.OUNSAFESLICE: + n := n.(*ir.BinaryExpr) + return tcUnsafeSlice(n) + + case ir.OCLOSURE: + n := n.(*ir.ClosureExpr) + tcClosure(n, top) + if n.Type() == nil { + return n + } + return n + + case ir.OITAB: + n := n.(*ir.UnaryExpr) + return tcITab(n) + + case ir.OIDATA: + // Whoever creates the OIDATA node must know a priori the concrete type at that moment, + // usually by just having checked the OITAB. + n := n.(*ir.UnaryExpr) + base.Fatalf("cannot typecheck interface data %v", n) + panic("unreachable") + + case ir.OSPTR: + n := n.(*ir.UnaryExpr) + return tcSPtr(n) + + case ir.OCFUNC: + n := n.(*ir.UnaryExpr) + n.X = Expr(n.X) + n.SetType(types.Types[types.TUINTPTR]) + return n + + case ir.OCONVNOP: + n := n.(*ir.ConvExpr) + n.X = Expr(n.X) + return n + + // statements + case ir.OAS: + n := n.(*ir.AssignStmt) + tcAssign(n) + + // Code that creates temps does not bother to set defn, so do it here. + if n.X.Op() == ir.ONAME && ir.IsAutoTmp(n.X) { + n.X.Name().Defn = n + } + return n + + case ir.OAS2: + tcAssignList(n.(*ir.AssignListStmt)) + return n + + case ir.OBREAK, + ir.OCONTINUE, + ir.ODCL, + ir.OGOTO, + ir.OFALL, + ir.OVARKILL, + ir.OVARLIVE: + return n + + case ir.OBLOCK: + n := n.(*ir.BlockStmt) + Stmts(n.List) + return n + + case ir.OLABEL: + if n.Sym().IsBlank() { + // Empty identifier is valid but useless. + // Eliminate now to simplify life later. + // See issues 7538, 11589, 11593. + n = ir.NewBlockStmt(n.Pos(), nil) + } + return n + + case ir.ODEFER, ir.OGO: + n := n.(*ir.GoDeferStmt) + n.Call = typecheck(n.Call, ctxStmt|ctxExpr) + if !n.Call.Diag() { + tcGoDefer(n) + } + return n + + case ir.OFOR, ir.OFORUNTIL: + n := n.(*ir.ForStmt) + return tcFor(n) + + case ir.OIF: + n := n.(*ir.IfStmt) + return tcIf(n) + + case ir.ORETURN: + n := n.(*ir.ReturnStmt) + return tcReturn(n) + + case ir.OTAILCALL: + n := n.(*ir.TailCallStmt) + return n + + case ir.OSELECT: + tcSelect(n.(*ir.SelectStmt)) + return n + + case ir.OSWITCH: + tcSwitch(n.(*ir.SwitchStmt)) + return n + + case ir.ORANGE: + tcRange(n.(*ir.RangeStmt)) + return n + + case ir.OTYPESW: + n := n.(*ir.TypeSwitchGuard) + base.Errorf("use of .(type) outside type switch") + n.SetDiag(true) + return n + + case ir.ODCLFUNC: + tcFunc(n.(*ir.Func)) + return n + + case ir.ODCLCONST: + n := n.(*ir.Decl) + n.X = Expr(n.X).(*ir.Name) + return n + + case ir.ODCLTYPE: + n := n.(*ir.Decl) + n.X = typecheck(n.X, ctxType).(*ir.Name) + types.CheckSize(n.X.Type()) + return n + } + + // No return n here! + // Individual cases can type-assert n, introducing a new one. + // Each must execute its own return n. +} + +func typecheckargs(n ir.InitNode) { + var list []ir.Node + switch n := n.(type) { + default: + base.Fatalf("typecheckargs %+v", n.Op()) + case *ir.CallExpr: + list = n.Args + if n.IsDDD { + Exprs(list) + return + } + case *ir.ReturnStmt: + list = n.Results + } + if len(list) != 1 { + Exprs(list) + return + } + + typecheckslice(list, ctxExpr|ctxMultiOK) + t := list[0].Type() + if t == nil || !t.IsFuncArgStruct() { + return + } + + // Save n as n.Orig for fmt.go. + if ir.Orig(n) == n { + n.(ir.OrigNode).SetOrig(ir.SepCopy(n)) + } + + // Rewrite f(g()) into t1, t2, ... = g(); f(t1, t2, ...). + rewriteMultiValueCall(n, list[0]) +} + +// rewriteMultiValueCall rewrites multi-valued f() to use temporaries, +// so the backend wouldn't need to worry about tuple-valued expressions. +func rewriteMultiValueCall(n ir.InitNode, call ir.Node) { + // If we're outside of function context, then this call will + // be executed during the generated init function. However, + // init.go hasn't yet created it. Instead, associate the + // temporary variables with InitTodoFunc for now, and init.go + // will reassociate them later when it's appropriate. + static := ir.CurFunc == nil + if static { + ir.CurFunc = InitTodoFunc + } + + as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, []ir.Node{call}) + results := call.Type().FieldSlice() + list := make([]ir.Node, len(results)) + for i, result := range results { + tmp := Temp(result.Type) + as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, tmp)) + as.Lhs.Append(tmp) + list[i] = tmp + } + if static { + ir.CurFunc = nil + } + + n.PtrInit().Append(Stmt(as)) + + switch n := n.(type) { + default: + base.Fatalf("rewriteMultiValueCall %+v", n.Op()) + case *ir.CallExpr: + n.Args = list + case *ir.ReturnStmt: + n.Results = list + case *ir.AssignListStmt: + if n.Op() != ir.OAS2FUNC { + base.Fatalf("rewriteMultiValueCall: invalid op %v", n.Op()) + } + as.SetOp(ir.OAS2FUNC) + n.SetOp(ir.OAS2) + n.Rhs = make([]ir.Node, len(list)) + for i, tmp := range list { + n.Rhs[i] = AssignConv(tmp, n.Lhs[i].Type(), "assignment") + } + } +} + +func checksliceindex(l ir.Node, r ir.Node, tp *types.Type) bool { + t := r.Type() + if t == nil { + return false + } + if !t.IsInteger() { + base.Errorf("invalid slice index %v (type %v)", r, t) + return false + } + + if r.Op() == ir.OLITERAL { + x := r.Val() + if constant.Sign(x) < 0 { + base.Errorf("invalid slice index %v (index must be non-negative)", r) + return false + } else if tp != nil && tp.NumElem() >= 0 && constant.Compare(x, token.GTR, constant.MakeInt64(tp.NumElem())) { + base.Errorf("invalid slice index %v (out of bounds for %d-element array)", r, tp.NumElem()) + return false + } else if ir.IsConst(l, constant.String) && constant.Compare(x, token.GTR, constant.MakeInt64(int64(len(ir.StringVal(l))))) { + base.Errorf("invalid slice index %v (out of bounds for %d-byte string)", r, len(ir.StringVal(l))) + return false + } else if ir.ConstOverflow(x, types.Types[types.TINT]) { + base.Errorf("invalid slice index %v (index too large)", r) + return false + } + } + + return true +} + +func checksliceconst(lo ir.Node, hi ir.Node) bool { + if lo != nil && hi != nil && lo.Op() == ir.OLITERAL && hi.Op() == ir.OLITERAL && constant.Compare(lo.Val(), token.GTR, hi.Val()) { + base.Errorf("invalid slice index: %v > %v", lo, hi) + return false + } + + return true +} + +// The result of implicitstar MUST be assigned back to n, e.g. +// n.Left = implicitstar(n.Left) +func implicitstar(n ir.Node) ir.Node { + // insert implicit * if needed for fixed array + t := n.Type() + if t == nil || !t.IsPtr() { + return n + } + t = t.Elem() + if t == nil { + return n + } + if !t.IsArray() { + return n + } + star := ir.NewStarExpr(base.Pos, n) + star.SetImplicit(true) + return Expr(star) +} + +func needOneArg(n *ir.CallExpr, f string, args ...interface{}) (ir.Node, bool) { + if len(n.Args) == 0 { + p := fmt.Sprintf(f, args...) + base.Errorf("missing argument to %s: %v", p, n) + return nil, false + } + + if len(n.Args) > 1 { + p := fmt.Sprintf(f, args...) + base.Errorf("too many arguments to %s: %v", p, n) + return n.Args[0], false + } + + return n.Args[0], true +} + +func needTwoArgs(n *ir.CallExpr) (ir.Node, ir.Node, bool) { + if len(n.Args) != 2 { + if len(n.Args) < 2 { + base.Errorf("not enough arguments in call to %v", n) + } else { + base.Errorf("too many arguments in call to %v", n) + } + return nil, nil, false + } + return n.Args[0], n.Args[1], true +} + +// Lookdot1 looks up the specified method s in the list fs of methods, returning +// the matching field or nil. If dostrcmp is 0, it matches the symbols. If +// dostrcmp is 1, it matches by name exactly. If dostrcmp is 2, it matches names +// with case folding. +func Lookdot1(errnode ir.Node, s *types.Sym, t *types.Type, fs *types.Fields, dostrcmp int) *types.Field { + var r *types.Field + for _, f := range fs.Slice() { + if dostrcmp != 0 && f.Sym.Name == s.Name { + return f + } + if dostrcmp == 2 && strings.EqualFold(f.Sym.Name, s.Name) { + return f + } + if f.Sym != s { + continue + } + if r != nil { + if errnode != nil { + base.Errorf("ambiguous selector %v", errnode) + } else if t.IsPtr() { + base.Errorf("ambiguous selector (%v).%v", t, s) + } else { + base.Errorf("ambiguous selector %v.%v", t, s) + } + break + } + + r = f + } + + return r +} + +// typecheckMethodExpr checks selector expressions (ODOT) where the +// base expression is a type expression (OTYPE). +func typecheckMethodExpr(n *ir.SelectorExpr) (res ir.Node) { + if base.EnableTrace && base.Flag.LowerT { + defer tracePrint("typecheckMethodExpr", n)(&res) + } + + t := n.X.Type() + + // Compute the method set for t. + var ms *types.Fields + if t.IsInterface() { + ms = t.AllMethods() + } else { + mt := types.ReceiverBaseType(t) + if mt == nil { + base.Errorf("%v undefined (type %v has no method %v)", n, t, n.Sel) + n.SetType(nil) + return n + } + CalcMethods(mt) + ms = mt.AllMethods() + + // The method expression T.m requires a wrapper when T + // is different from m's declared receiver type. We + // normally generate these wrappers while writing out + // runtime type descriptors, which is always done for + // types declared at package scope. However, we need + // to make sure to generate wrappers for anonymous + // receiver types too. + if mt.Sym() == nil { + NeedRuntimeType(t) + } + } + + s := n.Sel + m := Lookdot1(n, s, t, ms, 0) + if m == nil { + if Lookdot1(n, s, t, ms, 1) != nil { + base.Errorf("%v undefined (cannot refer to unexported method %v)", n, s) + } else if _, ambig := dotpath(s, t, nil, false); ambig { + base.Errorf("%v undefined (ambiguous selector)", n) // method or field + } else { + base.Errorf("%v undefined (type %v has no method %v)", n, t, s) + } + n.SetType(nil) + return n + } + + if !types.IsMethodApplicable(t, m) { + base.Errorf("invalid method expression %v (needs pointer receiver: (*%v).%S)", n, t, s) + n.SetType(nil) + return n + } + + n.SetOp(ir.OMETHEXPR) + n.Selection = m + n.SetType(NewMethodType(m.Type, n.X.Type())) + return n +} + +func derefall(t *types.Type) *types.Type { + for t != nil && t.IsPtr() { + t = t.Elem() + } + return t +} + +// Lookdot looks up field or method n.Sel in the type t and returns the matching +// field. It transforms the op of node n to ODOTINTER or ODOTMETH, if appropriate. +// It also may add a StarExpr node to n.X as needed for access to non-pointer +// methods. If dostrcmp is 0, it matches the field/method with the exact symbol +// as n.Sel (appropriate for exported fields). If dostrcmp is 1, it matches by name +// exactly. If dostrcmp is 2, it matches names with case folding. +func Lookdot(n *ir.SelectorExpr, t *types.Type, dostrcmp int) *types.Field { + s := n.Sel + + types.CalcSize(t) + var f1 *types.Field + if t.IsStruct() { + f1 = Lookdot1(n, s, t, t.Fields(), dostrcmp) + } else if t.IsInterface() { + f1 = Lookdot1(n, s, t, t.AllMethods(), dostrcmp) + } + + var f2 *types.Field + if n.X.Type() == t || n.X.Type().Sym() == nil { + mt := types.ReceiverBaseType(t) + if mt != nil { + f2 = Lookdot1(n, s, mt, mt.Methods(), dostrcmp) + } + } + + if f1 != nil { + if dostrcmp > 1 || f1.Broke() { + // Already in the process of diagnosing an error. + return f1 + } + if f2 != nil { + base.Errorf("%v is both field and method", n.Sel) + } + if f1.Offset == types.BADWIDTH { + base.Fatalf("Lookdot badwidth t=%v, f1=%v@%p", t, f1, f1) + } + n.Selection = f1 + n.SetType(f1.Type) + if t.IsInterface() { + if n.X.Type().IsPtr() { + star := ir.NewStarExpr(base.Pos, n.X) + star.SetImplicit(true) + n.X = Expr(star) + } + + n.SetOp(ir.ODOTINTER) + } + return f1 + } + + if f2 != nil { + if dostrcmp > 1 { + // Already in the process of diagnosing an error. + return f2 + } + orig := n.X + tt := n.X.Type() + types.CalcSize(tt) + rcvr := f2.Type.Recv().Type + if !types.Identical(rcvr, tt) { + if rcvr.IsPtr() && types.Identical(rcvr.Elem(), tt) { + checklvalue(n.X, "call pointer method on") + addr := NodAddr(n.X) + addr.SetImplicit(true) + n.X = typecheck(addr, ctxType|ctxExpr) + } else if tt.IsPtr() && (!rcvr.IsPtr() || rcvr.IsPtr() && rcvr.Elem().NotInHeap()) && types.Identical(tt.Elem(), rcvr) { + star := ir.NewStarExpr(base.Pos, n.X) + star.SetImplicit(true) + n.X = typecheck(star, ctxType|ctxExpr) + } else if tt.IsPtr() && tt.Elem().IsPtr() && types.Identical(derefall(tt), derefall(rcvr)) { + base.Errorf("calling method %v with receiver %L requires explicit dereference", n.Sel, n.X) + for tt.IsPtr() { + // Stop one level early for method with pointer receiver. + if rcvr.IsPtr() && !tt.Elem().IsPtr() { + break + } + star := ir.NewStarExpr(base.Pos, n.X) + star.SetImplicit(true) + n.X = typecheck(star, ctxType|ctxExpr) + tt = tt.Elem() + } + } else { + base.Fatalf("method mismatch: %v for %v", rcvr, tt) + } + } + + // Check that we haven't implicitly dereferenced any defined pointer types. + for x := n.X; ; { + var inner ir.Node + implicit := false + switch x := x.(type) { + case *ir.AddrExpr: + inner, implicit = x.X, x.Implicit() + case *ir.SelectorExpr: + inner, implicit = x.X, x.Implicit() + case *ir.StarExpr: + inner, implicit = x.X, x.Implicit() + } + if !implicit { + break + } + if inner.Type().Sym() != nil && (x.Op() == ir.ODEREF || x.Op() == ir.ODOTPTR) { + // Found an implicit dereference of a defined pointer type. + // Restore n.X for better error message. + n.X = orig + return nil + } + x = inner + } + + n.Selection = f2 + n.SetType(f2.Type) + n.SetOp(ir.ODOTMETH) + + return f2 + } + + return nil +} + +func nokeys(l ir.Nodes) bool { + for _, n := range l { + if n.Op() == ir.OKEY || n.Op() == ir.OSTRUCTKEY { + return false + } + } + return true +} + +func hasddd(t *types.Type) bool { + for _, tl := range t.Fields().Slice() { + if tl.IsDDD() { + return true + } + } + + return false +} + +// typecheck assignment: type list = expression list +func typecheckaste(op ir.Op, call ir.Node, isddd bool, tstruct *types.Type, nl ir.Nodes, desc func() string) { + var t *types.Type + var i int + + lno := base.Pos + defer func() { base.Pos = lno }() + + if tstruct.Broke() { + return + } + + var n ir.Node + if len(nl) == 1 { + n = nl[0] + } + + n1 := tstruct.NumFields() + n2 := len(nl) + if !hasddd(tstruct) { + if isddd { + goto invalidddd + } + if n2 > n1 { + goto toomany + } + if n2 < n1 { + goto notenough + } + } else { + if !isddd { + if n2 < n1-1 { + goto notenough + } + } else { + if n2 > n1 { + goto toomany + } + if n2 < n1 { + goto notenough + } + } + } + + i = 0 + for _, tl := range tstruct.Fields().Slice() { + t = tl.Type + if tl.IsDDD() { + if isddd { + if i >= len(nl) { + goto notenough + } + if len(nl)-i > 1 { + goto toomany + } + n = nl[i] + ir.SetPos(n) + if n.Type() != nil { + nl[i] = assignconvfn(n, t, desc) + } + return + } + + // TODO(mdempsky): Make into ... call with implicit slice. + for ; i < len(nl); i++ { + n = nl[i] + ir.SetPos(n) + if n.Type() != nil { + nl[i] = assignconvfn(n, t.Elem(), desc) + } + } + return + } + + if i >= len(nl) { + goto notenough + } + n = nl[i] + ir.SetPos(n) + if n.Type() != nil { + nl[i] = assignconvfn(n, t, desc) + } + i++ + } + + if i < len(nl) { + goto toomany + } + +invalidddd: + if isddd { + if call != nil { + base.Errorf("invalid use of ... in call to %v", call) + } else { + base.Errorf("invalid use of ... in %v", op) + } + } + return + +notenough: + if n == nil || (!n.Diag() && n.Type() != nil) { + details := errorDetails(nl, tstruct, isddd) + if call != nil { + // call is the expression being called, not the overall call. + // Method expressions have the form T.M, and the compiler has + // rewritten those to ONAME nodes but left T in Left. + if call.Op() == ir.OMETHEXPR { + call := call.(*ir.SelectorExpr) + base.Errorf("not enough arguments in call to method expression %v%s", call, details) + } else { + base.Errorf("not enough arguments in call to %v%s", call, details) + } + } else { + base.Errorf("not enough arguments to %v%s", op, details) + } + if n != nil { + n.SetDiag(true) + } + } + return + +toomany: + details := errorDetails(nl, tstruct, isddd) + if call != nil { + base.Errorf("too many arguments in call to %v%s", call, details) + } else { + base.Errorf("too many arguments to %v%s", op, details) + } +} + +func errorDetails(nl ir.Nodes, tstruct *types.Type, isddd bool) string { + // Suppress any return message signatures if: + // + // (1) We don't know any type at a call site (see #19012). + // (2) Any node has an unknown type. + // (3) Invalid type for variadic parameter (see #46957). + if tstruct == nil { + return "" // case 1 + } + + if isddd && !nl[len(nl)-1].Type().IsSlice() { + return "" // case 3 + } + + for _, n := range nl { + if n.Type() == nil { + return "" // case 2 + } + } + return fmt.Sprintf("\n\thave %s\n\twant %v", fmtSignature(nl, isddd), tstruct) +} + +// sigrepr is a type's representation to the outside world, +// in string representations of return signatures +// e.g in error messages about wrong arguments to return. +func sigrepr(t *types.Type, isddd bool) string { + switch t { + case types.UntypedString: + return "string" + case types.UntypedBool: + return "bool" + } + + if t.Kind() == types.TIDEAL { + // "untyped number" is not commonly used + // outside of the compiler, so let's use "number". + // TODO(mdempsky): Revisit this. + return "number" + } + + // Turn []T... argument to ...T for clearer error message. + if isddd { + if !t.IsSlice() { + base.Fatalf("bad type for ... argument: %v", t) + } + return "..." + t.Elem().String() + } + return t.String() +} + +// sigerr returns the signature of the types at the call or return. +func fmtSignature(nl ir.Nodes, isddd bool) string { + if len(nl) < 1 { + return "()" + } + + var typeStrings []string + for i, n := range nl { + isdddArg := isddd && i == len(nl)-1 + typeStrings = append(typeStrings, sigrepr(n.Type(), isdddArg)) + } + + return fmt.Sprintf("(%s)", strings.Join(typeStrings, ", ")) +} + +// type check composite +func fielddup(name string, hash map[string]bool) { + if hash[name] { + base.Errorf("duplicate field name in struct literal: %s", name) + return + } + hash[name] = true +} + +// iscomptype reports whether type t is a composite literal type. +func iscomptype(t *types.Type) bool { + switch t.Kind() { + case types.TARRAY, types.TSLICE, types.TSTRUCT, types.TMAP: + return true + default: + return false + } +} + +// pushtype adds elided type information for composite literals if +// appropriate, and returns the resulting expression. +func pushtype(nn ir.Node, t *types.Type) ir.Node { + if nn == nil || nn.Op() != ir.OCOMPLIT { + return nn + } + n := nn.(*ir.CompLitExpr) + if n.Ntype != nil { + return n + } + + switch { + case iscomptype(t): + // For T, return T{...}. + n.Ntype = ir.TypeNode(t) + + case t.IsPtr() && iscomptype(t.Elem()): + // For *T, return &T{...}. + n.Ntype = ir.TypeNode(t.Elem()) + + addr := NodAddrAt(n.Pos(), n) + addr.SetImplicit(true) + return addr + } + return n +} + +// typecheckarraylit type-checks a sequence of slice/array literal elements. +func typecheckarraylit(elemType *types.Type, bound int64, elts []ir.Node, ctx string) int64 { + // If there are key/value pairs, create a map to keep seen + // keys so we can check for duplicate indices. + var indices map[int64]bool + for _, elt := range elts { + if elt.Op() == ir.OKEY { + indices = make(map[int64]bool) + break + } + } + + var key, length int64 + for i, elt := range elts { + ir.SetPos(elt) + r := elts[i] + var kv *ir.KeyExpr + if elt.Op() == ir.OKEY { + elt := elt.(*ir.KeyExpr) + elt.Key = Expr(elt.Key) + key = IndexConst(elt.Key) + if key < 0 { + if !elt.Key.Diag() { + if key == -2 { + base.Errorf("index too large") + } else { + base.Errorf("index must be non-negative integer constant") + } + elt.Key.SetDiag(true) + } + key = -(1 << 30) // stay negative for a while + } + kv = elt + r = elt.Value + } + + r = pushtype(r, elemType) + r = Expr(r) + r = AssignConv(r, elemType, ctx) + if kv != nil { + kv.Value = r + } else { + elts[i] = r + } + + if key >= 0 { + if indices != nil { + if indices[key] { + base.Errorf("duplicate index in %s: %d", ctx, key) + } else { + indices[key] = true + } + } + + if bound >= 0 && key >= bound { + base.Errorf("array index %d out of bounds [0:%d]", key, bound) + bound = -1 + } + } + + key++ + if key > length { + length = key + } + } + + return length +} + +// visible reports whether sym is exported or locally defined. +func visible(sym *types.Sym) bool { + return sym != nil && (types.IsExported(sym.Name) || sym.Pkg == types.LocalPkg) +} + +// nonexported reports whether sym is an unexported field. +func nonexported(sym *types.Sym) bool { + return sym != nil && !types.IsExported(sym.Name) +} + +func checklvalue(n ir.Node, verb string) { + if !ir.IsAddressable(n) { + base.Errorf("cannot %s %v", verb, n) + } +} + +func checkassign(stmt ir.Node, n ir.Node) { + // have already complained about n being invalid + if n.Type() == nil { + if base.Errors() == 0 { + base.Fatalf("expected an error about %v", n) + } + return + } + + if ir.IsAddressable(n) { + return + } + if n.Op() == ir.OINDEXMAP { + n := n.(*ir.IndexExpr) + n.Assigned = true + return + } + + defer n.SetType(nil) + if n.Diag() { + return + } + switch { + case n.Op() == ir.ODOT && n.(*ir.SelectorExpr).X.Op() == ir.OINDEXMAP: + base.Errorf("cannot assign to struct field %v in map", n) + case (n.Op() == ir.OINDEX && n.(*ir.IndexExpr).X.Type().IsString()) || n.Op() == ir.OSLICESTR: + base.Errorf("cannot assign to %v (strings are immutable)", n) + case n.Op() == ir.OLITERAL && n.Sym() != nil && ir.IsConstNode(n): + base.Errorf("cannot assign to %v (declared const)", n) + default: + base.Errorf("cannot assign to %v", n) + } +} + +func checkassignto(src *types.Type, dst ir.Node) { + // TODO(mdempsky): Handle all untyped types correctly. + if src == types.UntypedBool && dst.Type().IsBoolean() { + return + } + + if op, why := Assignop(src, dst.Type()); op == ir.OXXX { + base.Errorf("cannot assign %v to %L in multiple assignment%s", src, dst, why) + return + } +} + +// The result of stringtoruneslit MUST be assigned back to n, e.g. +// n.Left = stringtoruneslit(n.Left) +func stringtoruneslit(n *ir.ConvExpr) ir.Node { + if n.X.Op() != ir.OLITERAL || n.X.Val().Kind() != constant.String { + base.Fatalf("stringtoarraylit %v", n) + } + + var l []ir.Node + i := 0 + for _, r := range ir.StringVal(n.X) { + l = append(l, ir.NewKeyExpr(base.Pos, ir.NewInt(int64(i)), ir.NewInt(int64(r)))) + i++ + } + + nn := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(n.Type()), nil) + nn.List = l + return Expr(nn) +} + +var mapqueue []*ir.MapType + +func CheckMapKeys() { + for _, n := range mapqueue { + k := n.Type().MapType().Key + if !k.Broke() && !types.IsComparable(k) { + base.ErrorfAt(n.Pos(), "invalid map key type %v", k) + } + } + mapqueue = nil +} + +// TypeGen tracks the number of function-scoped defined types that +// have been declared. It's used to generate unique linker symbols for +// their runtime type descriptors. +var TypeGen int32 + +func typecheckdeftype(n *ir.Name) { + if base.EnableTrace && base.Flag.LowerT { + defer tracePrint("typecheckdeftype", n)(nil) + } + + t := types.NewNamed(n) + if n.Curfn != nil { + TypeGen++ + t.Vargen = TypeGen + } + + if n.Pragma()&ir.NotInHeap != 0 { + t.SetNotInHeap(true) + } + + n.SetType(t) + n.SetTypecheck(1) + n.SetWalkdef(1) + + types.DeferCheckSize() + errorsBefore := base.Errors() + n.Ntype = typecheckNtype(n.Ntype) + if underlying := n.Ntype.Type(); underlying != nil { + t.SetUnderlying(underlying) + } else { + n.SetDiag(true) + n.SetType(nil) + } + if t.Kind() == types.TFORW && base.Errors() > errorsBefore { + // Something went wrong during type-checking, + // but it was reported. Silence future errors. + t.SetBroke(true) + } + types.ResumeCheckSize() +} + +func typecheckdef(n *ir.Name) { + if base.EnableTrace && base.Flag.LowerT { + defer tracePrint("typecheckdef", n)(nil) + } + + if n.Walkdef() == 1 { + return + } + + if n.Type() != nil { // builtin + // Mark as Walkdef so that if n.SetType(nil) is called later, we + // won't try walking again. + if got := n.Walkdef(); got != 0 { + base.Fatalf("unexpected walkdef: %v", got) + } + n.SetWalkdef(1) + return + } + + lno := ir.SetPos(n) + typecheckdefstack = append(typecheckdefstack, n) + if n.Walkdef() == 2 { + base.FlushErrors() + fmt.Printf("typecheckdef loop:") + for i := len(typecheckdefstack) - 1; i >= 0; i-- { + n := typecheckdefstack[i] + fmt.Printf(" %v", n.Sym()) + } + fmt.Printf("\n") + base.Fatalf("typecheckdef loop") + } + + n.SetWalkdef(2) + + switch n.Op() { + default: + base.Fatalf("typecheckdef %v", n.Op()) + + case ir.OLITERAL: + if n.Ntype != nil { + n.Ntype = typecheckNtype(n.Ntype) + n.SetType(n.Ntype.Type()) + n.Ntype = nil + if n.Type() == nil { + n.SetDiag(true) + goto ret + } + } + + e := n.Defn + n.Defn = nil + if e == nil { + ir.Dump("typecheckdef nil defn", n) + base.ErrorfAt(n.Pos(), "xxx") + } + + e = Expr(e) + if e.Type() == nil { + goto ret + } + if !ir.IsConstNode(e) { + if !e.Diag() { + if e.Op() == ir.ONIL { + base.ErrorfAt(n.Pos(), "const initializer cannot be nil") + } else { + base.ErrorfAt(n.Pos(), "const initializer %v is not a constant", e) + } + e.SetDiag(true) + } + goto ret + } + + t := n.Type() + if t != nil { + if !ir.OKForConst[t.Kind()] { + base.ErrorfAt(n.Pos(), "invalid constant type %v", t) + goto ret + } + + if !e.Type().IsUntyped() && !types.Identical(t, e.Type()) { + base.ErrorfAt(n.Pos(), "cannot use %L as type %v in const initializer", e, t) + goto ret + } + + e = convlit(e, t) + } + + n.SetType(e.Type()) + if n.Type() != nil { + n.SetVal(e.Val()) + } + + case ir.ONAME: + if n.Ntype != nil { + n.Ntype = typecheckNtype(n.Ntype) + n.SetType(n.Ntype.Type()) + if n.Type() == nil { + n.SetDiag(true) + goto ret + } + } + + if n.Type() != nil { + break + } + if n.Defn == nil { + if n.BuiltinOp != 0 { // like OPRINTN + break + } + if base.Errors() > 0 { + // Can have undefined variables in x := foo + // that make x have an n.name.Defn == nil. + // If there are other errors anyway, don't + // bother adding to the noise. + break + } + + base.Fatalf("var without type, init: %v", n.Sym()) + } + + if n.Defn.Op() == ir.ONAME { + n.Defn = Expr(n.Defn) + n.SetType(n.Defn.Type()) + break + } + + n.Defn = Stmt(n.Defn) // fills in n.Type + + case ir.OTYPE: + if n.Alias() { + // Type alias declaration: Simply use the rhs type - no need + // to create a new type. + // If we have a syntax error, name.Ntype may be nil. + if n.Ntype != nil { + n.Ntype = typecheckNtype(n.Ntype) + n.SetType(n.Ntype.Type()) + if n.Type() == nil { + n.SetDiag(true) + goto ret + } + // For package-level type aliases, set n.Sym.Def so we can identify + // it as a type alias during export. See also #31959. + if n.Curfn == nil { + n.Sym().Def = n.Ntype + } + } + break + } + + // regular type declaration + typecheckdeftype(n) + } + +ret: + if n.Op() != ir.OLITERAL && n.Type() != nil && n.Type().IsUntyped() { + base.Fatalf("got %v for %v", n.Type(), n) + } + last := len(typecheckdefstack) - 1 + if typecheckdefstack[last] != n { + base.Fatalf("typecheckdefstack mismatch") + } + typecheckdefstack[last] = nil + typecheckdefstack = typecheckdefstack[:last] + + base.Pos = lno + n.SetWalkdef(1) +} + +func checkmake(t *types.Type, arg string, np *ir.Node) bool { + n := *np + if !n.Type().IsInteger() && n.Type().Kind() != types.TIDEAL { + base.Errorf("non-integer %s argument in make(%v) - %v", arg, t, n.Type()) + return false + } + + // Do range checks for constants before DefaultLit + // to avoid redundant "constant NNN overflows int" errors. + if n.Op() == ir.OLITERAL { + v := toint(n.Val()) + if constant.Sign(v) < 0 { + base.Errorf("negative %s argument in make(%v)", arg, t) + return false + } + if ir.ConstOverflow(v, types.Types[types.TINT]) { + base.Errorf("%s argument too large in make(%v)", arg, t) + return false + } + } + + // DefaultLit is necessary for non-constants too: n might be 1.1< 0 { + if x := typecheckdefstack[i-1]; x.Op() == ir.OLITERAL { + return x.Iota() + } + } + + if ir.CurFunc != nil && ir.CurFunc.Iota >= 0 { + return ir.CurFunc.Iota + } + + return -1 +} + +// curpkg returns the current package, based on Curfn. +func curpkg() *types.Pkg { + fn := ir.CurFunc + if fn == nil { + // Initialization expressions for package-scope variables. + return types.LocalPkg + } + return fnpkg(fn.Nname) +} + +func Conv(n ir.Node, t *types.Type) ir.Node { + if types.Identical(n.Type(), t) { + return n + } + n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n) + n.SetType(t) + n = Expr(n) + return n +} + +// ConvNop converts node n to type t using the OCONVNOP op +// and typechecks the result with ctxExpr. +func ConvNop(n ir.Node, t *types.Type) ir.Node { + if types.Identical(n.Type(), t) { + return n + } + n = ir.NewConvExpr(base.Pos, ir.OCONVNOP, nil, n) + n.SetType(t) + n = Expr(n) + return n +} diff --git a/src/cmd/compile/internal/typecheck/universe.go b/src/cmd/compile/internal/typecheck/universe.go new file mode 100644 index 0000000000000000000000000000000000000000..de185ab94471d640c834341e2a38432ff43acc09 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/universe.go @@ -0,0 +1,359 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +var ( + okfor [ir.OEND][]bool + iscmp [ir.OEND]bool +) + +var ( + okforeq [types.NTYPE]bool + okforadd [types.NTYPE]bool + okforand [types.NTYPE]bool + okfornone [types.NTYPE]bool + okforbool [types.NTYPE]bool + okforcap [types.NTYPE]bool + okforlen [types.NTYPE]bool + okforarith [types.NTYPE]bool +) + +var basicTypes = [...]struct { + name string + etype types.Kind +}{ + {"int8", types.TINT8}, + {"int16", types.TINT16}, + {"int32", types.TINT32}, + {"int64", types.TINT64}, + {"uint8", types.TUINT8}, + {"uint16", types.TUINT16}, + {"uint32", types.TUINT32}, + {"uint64", types.TUINT64}, + {"float32", types.TFLOAT32}, + {"float64", types.TFLOAT64}, + {"complex64", types.TCOMPLEX64}, + {"complex128", types.TCOMPLEX128}, + {"bool", types.TBOOL}, + {"string", types.TSTRING}, +} + +var typedefs = [...]struct { + name string + etype types.Kind + sameas32 types.Kind + sameas64 types.Kind +}{ + {"int", types.TINT, types.TINT32, types.TINT64}, + {"uint", types.TUINT, types.TUINT32, types.TUINT64}, + {"uintptr", types.TUINTPTR, types.TUINT32, types.TUINT64}, +} + +var builtinFuncs = [...]struct { + name string + op ir.Op +}{ + {"append", ir.OAPPEND}, + {"cap", ir.OCAP}, + {"close", ir.OCLOSE}, + {"complex", ir.OCOMPLEX}, + {"copy", ir.OCOPY}, + {"delete", ir.ODELETE}, + {"imag", ir.OIMAG}, + {"len", ir.OLEN}, + {"make", ir.OMAKE}, + {"new", ir.ONEW}, + {"panic", ir.OPANIC}, + {"print", ir.OPRINT}, + {"println", ir.OPRINTN}, + {"real", ir.OREAL}, + {"recover", ir.ORECOVER}, +} + +var unsafeFuncs = [...]struct { + name string + op ir.Op +}{ + {"Add", ir.OUNSAFEADD}, + {"Alignof", ir.OALIGNOF}, + {"Offsetof", ir.OOFFSETOF}, + {"Sizeof", ir.OSIZEOF}, + {"Slice", ir.OUNSAFESLICE}, +} + +// InitUniverse initializes the universe block. +func InitUniverse() { + if types.PtrSize == 0 { + base.Fatalf("typeinit before betypeinit") + } + + types.SlicePtrOffset = 0 + types.SliceLenOffset = types.Rnd(types.SlicePtrOffset+int64(types.PtrSize), int64(types.PtrSize)) + types.SliceCapOffset = types.Rnd(types.SliceLenOffset+int64(types.PtrSize), int64(types.PtrSize)) + types.SliceSize = types.Rnd(types.SliceCapOffset+int64(types.PtrSize), int64(types.PtrSize)) + + // string is same as slice wo the cap + types.StringSize = types.Rnd(types.SliceLenOffset+int64(types.PtrSize), int64(types.PtrSize)) + + for et := types.Kind(0); et < types.NTYPE; et++ { + types.SimType[et] = et + } + + types.Types[types.TANY] = types.New(types.TANY) + types.Types[types.TINTER] = types.NewInterface(types.LocalPkg, nil) + + defBasic := func(kind types.Kind, pkg *types.Pkg, name string) *types.Type { + sym := pkg.Lookup(name) + n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, sym) + t := types.NewBasic(kind, n) + n.SetType(t) + sym.Def = n + if kind != types.TANY { + types.CalcSize(t) + } + return t + } + + for _, s := range &basicTypes { + types.Types[s.etype] = defBasic(s.etype, types.BuiltinPkg, s.name) + } + + for _, s := range &typedefs { + sameas := s.sameas32 + if types.PtrSize == 8 { + sameas = s.sameas64 + } + types.SimType[s.etype] = sameas + + types.Types[s.etype] = defBasic(s.etype, types.BuiltinPkg, s.name) + } + + // We create separate byte and rune types for better error messages + // rather than just creating type alias *types.Sym's for the uint8 and + // int32 types. Hence, (bytetype|runtype).Sym.isAlias() is false. + // TODO(gri) Should we get rid of this special case (at the cost + // of less informative error messages involving bytes and runes)? + // (Alternatively, we could introduce an OTALIAS node representing + // type aliases, albeit at the cost of having to deal with it everywhere). + types.ByteType = defBasic(types.TUINT8, types.BuiltinPkg, "byte") + types.RuneType = defBasic(types.TINT32, types.BuiltinPkg, "rune") + + // error type + s := types.BuiltinPkg.Lookup("error") + n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, s) + types.ErrorType = types.NewNamed(n) + types.ErrorType.SetUnderlying(makeErrorInterface()) + n.SetType(types.ErrorType) + s.Def = n + types.CalcSize(types.ErrorType) + + types.Types[types.TUNSAFEPTR] = defBasic(types.TUNSAFEPTR, ir.Pkgs.Unsafe, "Pointer") + + // simple aliases + types.SimType[types.TMAP] = types.TPTR + types.SimType[types.TCHAN] = types.TPTR + types.SimType[types.TFUNC] = types.TPTR + types.SimType[types.TUNSAFEPTR] = types.TPTR + + for _, s := range &builtinFuncs { + s2 := types.BuiltinPkg.Lookup(s.name) + def := NewName(s2) + def.BuiltinOp = s.op + s2.Def = def + } + + for _, s := range &unsafeFuncs { + s2 := ir.Pkgs.Unsafe.Lookup(s.name) + def := NewName(s2) + def.BuiltinOp = s.op + s2.Def = def + } + + s = types.BuiltinPkg.Lookup("true") + s.Def = ir.NewConstAt(src.NoXPos, s, types.UntypedBool, constant.MakeBool(true)) + + s = types.BuiltinPkg.Lookup("false") + s.Def = ir.NewConstAt(src.NoXPos, s, types.UntypedBool, constant.MakeBool(false)) + + s = Lookup("_") + types.BlankSym = s + s.Block = -100 + s.Def = NewName(s) + types.Types[types.TBLANK] = types.New(types.TBLANK) + ir.AsNode(s.Def).SetType(types.Types[types.TBLANK]) + ir.BlankNode = ir.AsNode(s.Def) + ir.BlankNode.SetTypecheck(1) + + s = types.BuiltinPkg.Lookup("_") + s.Block = -100 + s.Def = NewName(s) + types.Types[types.TBLANK] = types.New(types.TBLANK) + ir.AsNode(s.Def).SetType(types.Types[types.TBLANK]) + + types.Types[types.TNIL] = types.New(types.TNIL) + s = types.BuiltinPkg.Lookup("nil") + nnil := NodNil() + nnil.(*ir.NilExpr).SetSym(s) + s.Def = nnil + + s = types.BuiltinPkg.Lookup("iota") + s.Def = ir.NewIota(base.Pos, s) + + for et := types.TINT8; et <= types.TUINT64; et++ { + types.IsInt[et] = true + } + types.IsInt[types.TINT] = true + types.IsInt[types.TUINT] = true + types.IsInt[types.TUINTPTR] = true + + types.IsFloat[types.TFLOAT32] = true + types.IsFloat[types.TFLOAT64] = true + + types.IsComplex[types.TCOMPLEX64] = true + types.IsComplex[types.TCOMPLEX128] = true + + // initialize okfor + for et := types.Kind(0); et < types.NTYPE; et++ { + if types.IsInt[et] || et == types.TIDEAL { + okforeq[et] = true + types.IsOrdered[et] = true + okforarith[et] = true + okforadd[et] = true + okforand[et] = true + ir.OKForConst[et] = true + types.IsSimple[et] = true + } + + if types.IsFloat[et] { + okforeq[et] = true + types.IsOrdered[et] = true + okforadd[et] = true + okforarith[et] = true + ir.OKForConst[et] = true + types.IsSimple[et] = true + } + + if types.IsComplex[et] { + okforeq[et] = true + okforadd[et] = true + okforarith[et] = true + ir.OKForConst[et] = true + types.IsSimple[et] = true + } + } + + types.IsSimple[types.TBOOL] = true + + okforadd[types.TSTRING] = true + + okforbool[types.TBOOL] = true + + okforcap[types.TARRAY] = true + okforcap[types.TCHAN] = true + okforcap[types.TSLICE] = true + + ir.OKForConst[types.TBOOL] = true + ir.OKForConst[types.TSTRING] = true + + okforlen[types.TARRAY] = true + okforlen[types.TCHAN] = true + okforlen[types.TMAP] = true + okforlen[types.TSLICE] = true + okforlen[types.TSTRING] = true + + okforeq[types.TPTR] = true + okforeq[types.TUNSAFEPTR] = true + okforeq[types.TINTER] = true + okforeq[types.TCHAN] = true + okforeq[types.TSTRING] = true + okforeq[types.TBOOL] = true + okforeq[types.TMAP] = true // nil only; refined in typecheck + okforeq[types.TFUNC] = true // nil only; refined in typecheck + okforeq[types.TSLICE] = true // nil only; refined in typecheck + okforeq[types.TARRAY] = true // only if element type is comparable; refined in typecheck + okforeq[types.TSTRUCT] = true // only if all struct fields are comparable; refined in typecheck + + types.IsOrdered[types.TSTRING] = true + + for i := range okfor { + okfor[i] = okfornone[:] + } + + // binary + okfor[ir.OADD] = okforadd[:] + okfor[ir.OAND] = okforand[:] + okfor[ir.OANDAND] = okforbool[:] + okfor[ir.OANDNOT] = okforand[:] + okfor[ir.ODIV] = okforarith[:] + okfor[ir.OEQ] = okforeq[:] + okfor[ir.OGE] = types.IsOrdered[:] + okfor[ir.OGT] = types.IsOrdered[:] + okfor[ir.OLE] = types.IsOrdered[:] + okfor[ir.OLT] = types.IsOrdered[:] + okfor[ir.OMOD] = okforand[:] + okfor[ir.OMUL] = okforarith[:] + okfor[ir.ONE] = okforeq[:] + okfor[ir.OOR] = okforand[:] + okfor[ir.OOROR] = okforbool[:] + okfor[ir.OSUB] = okforarith[:] + okfor[ir.OXOR] = okforand[:] + okfor[ir.OLSH] = okforand[:] + okfor[ir.ORSH] = okforand[:] + + // unary + okfor[ir.OBITNOT] = okforand[:] + okfor[ir.ONEG] = okforarith[:] + okfor[ir.ONOT] = okforbool[:] + okfor[ir.OPLUS] = okforarith[:] + + // special + okfor[ir.OCAP] = okforcap[:] + okfor[ir.OLEN] = okforlen[:] + + // comparison + iscmp[ir.OLT] = true + iscmp[ir.OGT] = true + iscmp[ir.OGE] = true + iscmp[ir.OLE] = true + iscmp[ir.OEQ] = true + iscmp[ir.ONE] = true +} + +func makeErrorInterface() *types.Type { + sig := types.NewSignature(types.NoPkg, fakeRecvField(), nil, nil, []*types.Field{ + types.NewField(src.NoXPos, nil, types.Types[types.TSTRING]), + }) + method := types.NewField(src.NoXPos, Lookup("Error"), sig) + return types.NewInterface(types.NoPkg, []*types.Field{method}) +} + +// DeclareUniverse makes the universe block visible within the current package. +func DeclareUniverse() { + // Operationally, this is similar to a dot import of builtinpkg, except + // that we silently skip symbols that are already declared in the + // package block rather than emitting a redeclared symbol error. + + for _, s := range types.BuiltinPkg.Syms { + if s.Def == nil { + continue + } + s1 := Lookup(s.Name) + if s1.Def != nil { + continue + } + + s1.Def = s.Def + s1.Block = s.Block + } +} diff --git a/src/cmd/compile/internal/types/alg.go b/src/cmd/compile/internal/types/alg.go new file mode 100644 index 0000000000000000000000000000000000000000..2c2700f345730e2b0a99d472aedc5962953ca78a --- /dev/null +++ b/src/cmd/compile/internal/types/alg.go @@ -0,0 +1,173 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +import "cmd/compile/internal/base" + +// AlgKind describes the kind of algorithms used for comparing and +// hashing a Type. +type AlgKind int + +//go:generate stringer -type AlgKind -trimprefix A alg.go + +const ( + // These values are known by runtime. + ANOEQ AlgKind = iota + AMEM0 + AMEM8 + AMEM16 + AMEM32 + AMEM64 + AMEM128 + ASTRING + AINTER + ANILINTER + AFLOAT32 + AFLOAT64 + ACPLX64 + ACPLX128 + + // Type can be compared/hashed as regular memory. + AMEM AlgKind = 100 + + // Type needs special comparison/hashing functions. + ASPECIAL AlgKind = -1 +) + +// AlgType returns the AlgKind used for comparing and hashing Type t. +// If it returns ANOEQ, it also returns the component type of t that +// makes it incomparable. +func AlgType(t *Type) (AlgKind, *Type) { + if t.Broke() { + return AMEM, nil + } + if t.Noalg() { + return ANOEQ, t + } + + switch t.Kind() { + case TANY, TFORW: + // will be defined later. + return ANOEQ, t + + case TINT8, TUINT8, TINT16, TUINT16, + TINT32, TUINT32, TINT64, TUINT64, + TINT, TUINT, TUINTPTR, + TBOOL, TPTR, + TCHAN, TUNSAFEPTR: + return AMEM, nil + + case TFUNC, TMAP: + return ANOEQ, t + + case TFLOAT32: + return AFLOAT32, nil + + case TFLOAT64: + return AFLOAT64, nil + + case TCOMPLEX64: + return ACPLX64, nil + + case TCOMPLEX128: + return ACPLX128, nil + + case TSTRING: + return ASTRING, nil + + case TINTER: + if t.IsEmptyInterface() { + return ANILINTER, nil + } + return AINTER, nil + + case TSLICE: + return ANOEQ, t + + case TARRAY: + a, bad := AlgType(t.Elem()) + switch a { + case AMEM: + return AMEM, nil + case ANOEQ: + return ANOEQ, bad + } + + switch t.NumElem() { + case 0: + // We checked above that the element type is comparable. + return AMEM, nil + case 1: + // Single-element array is same as its lone element. + return a, nil + } + + return ASPECIAL, nil + + case TSTRUCT: + fields := t.FieldSlice() + + // One-field struct is same as that one field alone. + if len(fields) == 1 && !fields[0].Sym.IsBlank() { + return AlgType(fields[0].Type) + } + + ret := AMEM + for i, f := range fields { + // All fields must be comparable. + a, bad := AlgType(f.Type) + if a == ANOEQ { + return ANOEQ, bad + } + + // Blank fields, padded fields, fields with non-memory + // equality need special compare. + if a != AMEM || f.Sym.IsBlank() || IsPaddedField(t, i) { + ret = ASPECIAL + } + } + + return ret, nil + } + + base.Fatalf("AlgType: unexpected type %v", t) + return 0, nil +} + +// TypeHasNoAlg reports whether t does not have any associated hash/eq +// algorithms because t, or some component of t, is marked Noalg. +func TypeHasNoAlg(t *Type) bool { + a, bad := AlgType(t) + return a == ANOEQ && bad.Noalg() +} + +// IsComparable reports whether t is a comparable type. +func IsComparable(t *Type) bool { + a, _ := AlgType(t) + return a != ANOEQ +} + +// IncomparableField returns an incomparable Field of struct Type t, if any. +func IncomparableField(t *Type) *Field { + for _, f := range t.FieldSlice() { + if !IsComparable(f.Type) { + return f + } + } + return nil +} + +// IsPaddedField reports whether the i'th field of struct type t is followed +// by padding. +func IsPaddedField(t *Type, i int) bool { + if !t.IsStruct() { + base.Fatalf("IsPaddedField called non-struct %v", t) + } + end := t.Width + if i+1 < t.NumFields() { + end = t.Field(i + 1).Offset + } + return t.Field(i).End() != end +} diff --git a/src/cmd/compile/internal/gc/algkind_string.go b/src/cmd/compile/internal/types/algkind_string.go similarity index 91% rename from src/cmd/compile/internal/gc/algkind_string.go rename to src/cmd/compile/internal/types/algkind_string.go index 52b53999561704cfa86869efdf922ef4876ed803..a1b518e4dde260d08c5fa36aeeb72c599ed5e7e5 100644 --- a/src/cmd/compile/internal/gc/algkind_string.go +++ b/src/cmd/compile/internal/types/algkind_string.go @@ -1,6 +1,6 @@ -// Code generated by "stringer -type AlgKind -trimprefix A"; DO NOT EDIT. +// Code generated by "stringer -type AlgKind -trimprefix A alg.go"; DO NOT EDIT. -package gc +package types import "strconv" diff --git a/src/cmd/compile/internal/types/etype_string.go b/src/cmd/compile/internal/types/etype_string.go deleted file mode 100644 index 14fd5b71df801ef27ef8a2b0be2ea4f4bbd0a750..0000000000000000000000000000000000000000 --- a/src/cmd/compile/internal/types/etype_string.go +++ /dev/null @@ -1,60 +0,0 @@ -// Code generated by "stringer -type EType -trimprefix T"; DO NOT EDIT. - -package types - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[Txxx-0] - _ = x[TINT8-1] - _ = x[TUINT8-2] - _ = x[TINT16-3] - _ = x[TUINT16-4] - _ = x[TINT32-5] - _ = x[TUINT32-6] - _ = x[TINT64-7] - _ = x[TUINT64-8] - _ = x[TINT-9] - _ = x[TUINT-10] - _ = x[TUINTPTR-11] - _ = x[TCOMPLEX64-12] - _ = x[TCOMPLEX128-13] - _ = x[TFLOAT32-14] - _ = x[TFLOAT64-15] - _ = x[TBOOL-16] - _ = x[TPTR-17] - _ = x[TFUNC-18] - _ = x[TSLICE-19] - _ = x[TARRAY-20] - _ = x[TSTRUCT-21] - _ = x[TCHAN-22] - _ = x[TMAP-23] - _ = x[TINTER-24] - _ = x[TFORW-25] - _ = x[TANY-26] - _ = x[TSTRING-27] - _ = x[TUNSAFEPTR-28] - _ = x[TIDEAL-29] - _ = x[TNIL-30] - _ = x[TBLANK-31] - _ = x[TFUNCARGS-32] - _ = x[TCHANARGS-33] - _ = x[TSSA-34] - _ = x[TTUPLE-35] - _ = x[TRESULTS-36] - _ = x[NTYPE-37] -} - -const _EType_name = "xxxINT8UINT8INT16UINT16INT32UINT32INT64UINT64INTUINTUINTPTRCOMPLEX64COMPLEX128FLOAT32FLOAT64BOOLPTRFUNCSLICEARRAYSTRUCTCHANMAPINTERFORWANYSTRINGUNSAFEPTRIDEALNILBLANKFUNCARGSCHANARGSSSATUPLERESULTSNTYPE" - -var _EType_index = [...]uint8{0, 3, 7, 12, 17, 23, 28, 34, 39, 45, 48, 52, 59, 68, 78, 85, 92, 96, 99, 103, 108, 113, 119, 123, 126, 131, 135, 138, 144, 153, 158, 161, 166, 174, 182, 185, 190, 197, 202} - -func (i EType) String() string { - if i >= EType(len(_EType_index)-1) { - return "EType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _EType_name[_EType_index[i]:_EType_index[i+1]] -} diff --git a/src/cmd/compile/internal/types/fmt.go b/src/cmd/compile/internal/types/fmt.go new file mode 100644 index 0000000000000000000000000000000000000000..8b988952a78d2c689714ddfe2d61260fcdb75e19 --- /dev/null +++ b/src/cmd/compile/internal/types/fmt.go @@ -0,0 +1,679 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +import ( + "bytes" + "crypto/md5" + "encoding/binary" + "fmt" + "go/constant" + "strconv" + "strings" + "sync" + + "cmd/compile/internal/base" +) + +// BuiltinPkg is a fake package that declares the universe block. +var BuiltinPkg *Pkg + +// LocalPkg is the package being compiled. +var LocalPkg *Pkg + +// BlankSym is the blank (_) symbol. +var BlankSym *Sym + +// OrigSym returns the original symbol written by the user. +func OrigSym(s *Sym) *Sym { + if s == nil { + return nil + } + + if len(s.Name) > 1 && s.Name[0] == '~' { + switch s.Name[1] { + case 'r': // originally an unnamed result + return nil + case 'b': // originally the blank identifier _ + // TODO(mdempsky): Does s.Pkg matter here? + return BlankSym + } + return s + } + + if strings.HasPrefix(s.Name, ".anon") { + // originally an unnamed or _ name (see subr.go: NewFuncParams) + return nil + } + + return s +} + +// numImport tracks how often a package with a given name is imported. +// It is used to provide a better error message (by using the package +// path to disambiguate) if a package that appears multiple times with +// the same name appears in an error message. +var NumImport = make(map[string]int) + +// fmtMode represents the kind of printing being done. +// The default is regular Go syntax (fmtGo). +// fmtDebug is like fmtGo but for debugging dumps and prints the type kind too. +// fmtTypeID and fmtTypeIDName are for generating various unique representations +// of types used in hashes and the linker. +type fmtMode int + +const ( + fmtGo fmtMode = iota + fmtDebug + fmtTypeID + fmtTypeIDName +) + +// Sym + +// Format implements formatting for a Sym. +// The valid formats are: +// +// %v Go syntax: Name for symbols in the local package, PkgName.Name for imported symbols. +// %+v Debug syntax: always include PkgName. prefix even for local names. +// %S Short syntax: Name only, no matter what. +// +func (s *Sym) Format(f fmt.State, verb rune) { + mode := fmtGo + switch verb { + case 'v', 'S': + if verb == 'v' && f.Flag('+') { + mode = fmtDebug + } + fmt.Fprint(f, sconv(s, verb, mode)) + + default: + fmt.Fprintf(f, "%%!%c(*types.Sym=%p)", verb, s) + } +} + +func (s *Sym) String() string { + return sconv(s, 0, fmtGo) +} + +// See #16897 for details about performance implications +// before changing the implementation of sconv. +func sconv(s *Sym, verb rune, mode fmtMode) string { + if verb == 'L' { + panic("linksymfmt") + } + + if s == nil { + return "" + } + + q := pkgqual(s.Pkg, verb, mode) + if q == "" { + return s.Name + } + + buf := fmtBufferPool.Get().(*bytes.Buffer) + buf.Reset() + defer fmtBufferPool.Put(buf) + + buf.WriteString(q) + buf.WriteByte('.') + buf.WriteString(s.Name) + return InternString(buf.Bytes()) +} + +func sconv2(b *bytes.Buffer, s *Sym, verb rune, mode fmtMode) { + if verb == 'L' { + panic("linksymfmt") + } + if s == nil { + b.WriteString("") + return + } + + symfmt(b, s, verb, mode) +} + +func symfmt(b *bytes.Buffer, s *Sym, verb rune, mode fmtMode) { + if q := pkgqual(s.Pkg, verb, mode); q != "" { + b.WriteString(q) + b.WriteByte('.') + } + b.WriteString(s.Name) +} + +// pkgqual returns the qualifier that should be used for printing +// symbols from the given package in the given mode. +// If it returns the empty string, no qualification is needed. +func pkgqual(pkg *Pkg, verb rune, mode fmtMode) string { + if verb != 'S' { + switch mode { + case fmtGo: // This is for the user + if pkg == BuiltinPkg || pkg == LocalPkg { + return "" + } + + // If the name was used by multiple packages, display the full path, + if pkg.Name != "" && NumImport[pkg.Name] > 1 { + return strconv.Quote(pkg.Path) + } + return pkg.Name + + case fmtDebug: + return pkg.Name + + case fmtTypeIDName: + // dcommontype, typehash + return pkg.Name + + case fmtTypeID: + // (methodsym), typesym, weaksym + return pkg.Prefix + } + } + + return "" +} + +// Type + +var BasicTypeNames = []string{ + TINT: "int", + TUINT: "uint", + TINT8: "int8", + TUINT8: "uint8", + TINT16: "int16", + TUINT16: "uint16", + TINT32: "int32", + TUINT32: "uint32", + TINT64: "int64", + TUINT64: "uint64", + TUINTPTR: "uintptr", + TFLOAT32: "float32", + TFLOAT64: "float64", + TCOMPLEX64: "complex64", + TCOMPLEX128: "complex128", + TBOOL: "bool", + TANY: "any", + TSTRING: "string", + TNIL: "nil", + TIDEAL: "untyped number", + TBLANK: "blank", +} + +var fmtBufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +// Format implements formatting for a Type. +// The valid formats are: +// +// %v Go syntax +// %+v Debug syntax: Go syntax with a KIND- prefix for all but builtins. +// %L Go syntax for underlying type if t is named +// %S short Go syntax: drop leading "func" in function type +// %-S special case for method receiver symbol +// +func (t *Type) Format(s fmt.State, verb rune) { + mode := fmtGo + switch verb { + case 'v', 'S', 'L': + if verb == 'v' && s.Flag('+') { // %+v is debug format + mode = fmtDebug + } + if verb == 'S' && s.Flag('-') { // %-S is special case for receiver - short typeid format + mode = fmtTypeID + } + fmt.Fprint(s, tconv(t, verb, mode)) + default: + fmt.Fprintf(s, "%%!%c(*Type=%p)", verb, t) + } +} + +// String returns the Go syntax for the type t. +func (t *Type) String() string { + return tconv(t, 0, fmtGo) +} + +// ShortString generates a short description of t. +// It is used in autogenerated method names, reflection, +// and itab names. +func (t *Type) ShortString() string { + return tconv(t, 0, fmtTypeID) +} + +// LongString generates a complete description of t. +// It is useful for reflection, +// or when a unique fingerprint or hash of a type is required. +func (t *Type) LongString() string { + return tconv(t, 0, fmtTypeIDName) +} + +func tconv(t *Type, verb rune, mode fmtMode) string { + buf := fmtBufferPool.Get().(*bytes.Buffer) + buf.Reset() + defer fmtBufferPool.Put(buf) + + tconv2(buf, t, verb, mode, nil) + return InternString(buf.Bytes()) +} + +// tconv2 writes a string representation of t to b. +// flag and mode control exactly what is printed. +// Any types x that are already in the visited map get printed as @%d where %d=visited[x]. +// See #16897 before changing the implementation of tconv. +func tconv2(b *bytes.Buffer, t *Type, verb rune, mode fmtMode, visited map[*Type]int) { + if off, ok := visited[t]; ok { + // We've seen this type before, so we're trying to print it recursively. + // Print a reference to it instead. + fmt.Fprintf(b, "@%d", off) + return + } + if t == nil { + b.WriteString("") + return + } + if t.Kind() == TSSA { + b.WriteString(t.Extra.(string)) + return + } + if t.Kind() == TTUPLE { + b.WriteString(t.FieldType(0).String()) + b.WriteByte(',') + b.WriteString(t.FieldType(1).String()) + return + } + + if t.Kind() == TRESULTS { + tys := t.Extra.(*Results).Types + for i, et := range tys { + if i > 0 { + b.WriteByte(',') + } + b.WriteString(et.String()) + } + return + } + + if t == ByteType || t == RuneType { + // in %-T mode collapse rune and byte with their originals. + switch mode { + case fmtTypeIDName, fmtTypeID: + t = Types[t.Kind()] + default: + sconv2(b, t.Sym(), 'S', mode) + return + } + } + if t == ErrorType { + b.WriteString("error") + return + } + + // Unless the 'L' flag was specified, if the type has a name, just print that name. + if verb != 'L' && t.Sym() != nil && t != Types[t.Kind()] { + switch mode { + case fmtTypeID, fmtTypeIDName: + if verb == 'S' { + if t.Vargen != 0 { + sconv2(b, t.Sym(), 'S', mode) + fmt.Fprintf(b, "·%d", t.Vargen) + return + } + sconv2(b, t.Sym(), 'S', mode) + return + } + + if mode == fmtTypeIDName { + sconv2(b, t.Sym(), 'v', fmtTypeIDName) + return + } + + if t.Sym().Pkg == LocalPkg && t.Vargen != 0 { + sconv2(b, t.Sym(), 'v', mode) + fmt.Fprintf(b, "·%d", t.Vargen) + return + } + } + + sconv2(b, t.Sym(), 'v', mode) + return + } + + if int(t.Kind()) < len(BasicTypeNames) && BasicTypeNames[t.Kind()] != "" { + var name string + switch t { + case UntypedBool: + name = "untyped bool" + case UntypedString: + name = "untyped string" + case UntypedInt: + name = "untyped int" + case UntypedRune: + name = "untyped rune" + case UntypedFloat: + name = "untyped float" + case UntypedComplex: + name = "untyped complex" + default: + name = BasicTypeNames[t.Kind()] + } + b.WriteString(name) + return + } + + if mode == fmtDebug { + b.WriteString(t.Kind().String()) + b.WriteByte('-') + tconv2(b, t, 'v', fmtGo, visited) + return + } + + // At this point, we might call tconv2 recursively. Add the current type to the visited list so we don't + // try to print it recursively. + // We record the offset in the result buffer where the type's text starts. This offset serves as a reference + // point for any later references to the same type. + // Note that we remove the type from the visited map as soon as the recursive call is done. + // This prevents encoding types like map[*int]*int as map[*int]@4. (That encoding would work, + // but I'd like to use the @ notation only when strictly necessary.) + if visited == nil { + visited = map[*Type]int{} + } + visited[t] = b.Len() + defer delete(visited, t) + + switch t.Kind() { + case TPTR: + b.WriteByte('*') + switch mode { + case fmtTypeID, fmtTypeIDName: + if verb == 'S' { + tconv2(b, t.Elem(), 'S', mode, visited) + return + } + } + tconv2(b, t.Elem(), 'v', mode, visited) + + case TARRAY: + b.WriteByte('[') + b.WriteString(strconv.FormatInt(t.NumElem(), 10)) + b.WriteByte(']') + tconv2(b, t.Elem(), 0, mode, visited) + + case TSLICE: + b.WriteString("[]") + tconv2(b, t.Elem(), 0, mode, visited) + + case TCHAN: + switch t.ChanDir() { + case Crecv: + b.WriteString("<-chan ") + tconv2(b, t.Elem(), 0, mode, visited) + case Csend: + b.WriteString("chan<- ") + tconv2(b, t.Elem(), 0, mode, visited) + default: + b.WriteString("chan ") + if t.Elem() != nil && t.Elem().IsChan() && t.Elem().Sym() == nil && t.Elem().ChanDir() == Crecv { + b.WriteByte('(') + tconv2(b, t.Elem(), 0, mode, visited) + b.WriteByte(')') + } else { + tconv2(b, t.Elem(), 0, mode, visited) + } + } + + case TMAP: + b.WriteString("map[") + tconv2(b, t.Key(), 0, mode, visited) + b.WriteByte(']') + tconv2(b, t.Elem(), 0, mode, visited) + + case TINTER: + if t.IsEmptyInterface() { + b.WriteString("interface {}") + break + } + b.WriteString("interface {") + for i, f := range t.AllMethods().Slice() { + if i != 0 { + b.WriteByte(';') + } + b.WriteByte(' ') + switch { + case f.Sym == nil: + // Check first that a symbol is defined for this type. + // Wrong interface definitions may have types lacking a symbol. + break + case IsExported(f.Sym.Name): + sconv2(b, f.Sym, 'S', mode) + default: + if mode != fmtTypeIDName { + mode = fmtTypeID + } + sconv2(b, f.Sym, 'v', mode) + } + tconv2(b, f.Type, 'S', mode, visited) + } + if t.AllMethods().Len() != 0 { + b.WriteByte(' ') + } + b.WriteByte('}') + + case TFUNC: + if verb == 'S' { + // no leading func + } else { + if t.Recv() != nil { + b.WriteString("method") + tconv2(b, t.Recvs(), 0, mode, visited) + b.WriteByte(' ') + } + b.WriteString("func") + } + if t.NumTParams() > 0 { + tconv2(b, t.TParams(), 0, mode, visited) + } + tconv2(b, t.Params(), 0, mode, visited) + + switch t.NumResults() { + case 0: + // nothing to do + + case 1: + b.WriteByte(' ') + tconv2(b, t.Results().Field(0).Type, 0, mode, visited) // struct->field->field's type + + default: + b.WriteByte(' ') + tconv2(b, t.Results(), 0, mode, visited) + } + + case TSTRUCT: + if m := t.StructType().Map; m != nil { + mt := m.MapType() + // Format the bucket struct for map[x]y as map.bucket[x]y. + // This avoids a recursive print that generates very long names. + switch t { + case mt.Bucket: + b.WriteString("map.bucket[") + case mt.Hmap: + b.WriteString("map.hdr[") + case mt.Hiter: + b.WriteString("map.iter[") + default: + base.Fatalf("unknown internal map type") + } + tconv2(b, m.Key(), 0, mode, visited) + b.WriteByte(']') + tconv2(b, m.Elem(), 0, mode, visited) + break + } + + if funarg := t.StructType().Funarg; funarg != FunargNone { + open, close := '(', ')' + if funarg == FunargTparams { + open, close = '[', ']' + } + b.WriteByte(byte(open)) + fieldVerb := 'v' + switch mode { + case fmtTypeID, fmtTypeIDName, fmtGo: + // no argument names on function signature, and no "noescape"/"nosplit" tags + fieldVerb = 'S' + } + for i, f := range t.Fields().Slice() { + if i != 0 { + b.WriteString(", ") + } + fldconv(b, f, fieldVerb, mode, visited, funarg) + } + b.WriteByte(byte(close)) + } else { + b.WriteString("struct {") + for i, f := range t.Fields().Slice() { + if i != 0 { + b.WriteByte(';') + } + b.WriteByte(' ') + fldconv(b, f, 'L', mode, visited, funarg) + } + if t.NumFields() != 0 { + b.WriteByte(' ') + } + b.WriteByte('}') + } + + case TFORW: + b.WriteString("undefined") + if t.Sym() != nil { + b.WriteByte(' ') + sconv2(b, t.Sym(), 'v', mode) + } + + case TUNSAFEPTR: + b.WriteString("unsafe.Pointer") + + case TTYPEPARAM: + if t.Sym() != nil { + sconv2(b, t.Sym(), 'v', mode) + } else { + b.WriteString("tp") + // Print out the pointer value for now to disambiguate type params + b.WriteString(fmt.Sprintf("%p", t)) + } + + case Txxx: + b.WriteString("Txxx") + + default: + // Don't know how to handle - fall back to detailed prints + b.WriteString(t.Kind().String()) + b.WriteString(" <") + sconv2(b, t.Sym(), 'v', mode) + b.WriteString(">") + + } +} + +func fldconv(b *bytes.Buffer, f *Field, verb rune, mode fmtMode, visited map[*Type]int, funarg Funarg) { + if f == nil { + b.WriteString("") + return + } + + var name string + if verb != 'S' { + s := f.Sym + + // Take the name from the original. + if mode == fmtGo { + s = OrigSym(s) + } + + if s != nil && f.Embedded == 0 { + if funarg != FunargNone { + name = fmt.Sprint(f.Nname) + } else if verb == 'L' { + name = s.Name + if name == ".F" { + name = "F" // Hack for toolstash -cmp. + } + if !IsExported(name) && mode != fmtTypeIDName { + name = sconv(s, 0, mode) // qualify non-exported names (used on structs, not on funarg) + } + } else { + name = sconv(s, 0, mode) + } + } + } + + if name != "" { + b.WriteString(name) + b.WriteString(" ") + } + + if f.IsDDD() { + var et *Type + if f.Type != nil { + et = f.Type.Elem() + } + b.WriteString("...") + tconv2(b, et, 0, mode, visited) + } else { + tconv2(b, f.Type, 0, mode, visited) + } + + if verb != 'S' && funarg == FunargNone && f.Note != "" { + b.WriteString(" ") + b.WriteString(strconv.Quote(f.Note)) + } +} + +// Val + +func FmtConst(v constant.Value, sharp bool) string { + if !sharp && v.Kind() == constant.Complex { + real, imag := constant.Real(v), constant.Imag(v) + + var re string + sre := constant.Sign(real) + if sre != 0 { + re = real.String() + } + + var im string + sim := constant.Sign(imag) + if sim != 0 { + im = imag.String() + } + + switch { + case sre == 0 && sim == 0: + return "0" + case sre == 0: + return im + "i" + case sim == 0: + return re + case sim < 0: + return fmt.Sprintf("(%s%si)", re, im) + default: + return fmt.Sprintf("(%s+%si)", re, im) + } + } + + return v.String() +} + +// TypeHash computes a hash value for type t to use in type switch statements. +func TypeHash(t *Type) uint32 { + p := t.LongString() + + // Using MD5 is overkill, but reduces accidental collisions. + h := md5.Sum([]byte(p)) + return binary.LittleEndian.Uint32(h[:4]) +} diff --git a/src/cmd/compile/internal/types/goversion.go b/src/cmd/compile/internal/types/goversion.go new file mode 100644 index 0000000000000000000000000000000000000000..1a324aa42fdb70de258ce6abfbc12dabf2505bca --- /dev/null +++ b/src/cmd/compile/internal/types/goversion.go @@ -0,0 +1,94 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +import ( + "fmt" + "internal/goversion" + "log" + "regexp" + "strconv" + + "cmd/compile/internal/base" +) + +// A lang is a language version broken into major and minor numbers. +type lang struct { + major, minor int +} + +// langWant is the desired language version set by the -lang flag. +// If the -lang flag is not set, this is the zero value, meaning that +// any language version is supported. +var langWant lang + +// AllowsGoVersion reports whether a particular package +// is allowed to use Go version major.minor. +// We assume the imported packages have all been checked, +// so we only have to check the local package against the -lang flag. +func AllowsGoVersion(pkg *Pkg, major, minor int) bool { + if pkg == nil { + // TODO(mdempsky): Set Pkg for local types earlier. + pkg = LocalPkg + } + if pkg != LocalPkg { + // Assume imported packages passed type-checking. + return true + } + if langWant.major == 0 && langWant.minor == 0 { + return true + } + return langWant.major > major || (langWant.major == major && langWant.minor >= minor) +} + +// ParseLangFlag verifies that the -lang flag holds a valid value, and +// exits if not. It initializes data used by langSupported. +func ParseLangFlag() { + if base.Flag.Lang == "" { + return + } + + var err error + langWant, err = parseLang(base.Flag.Lang) + if err != nil { + log.Fatalf("invalid value %q for -lang: %v", base.Flag.Lang, err) + } + + if def := currentLang(); base.Flag.Lang != def { + defVers, err := parseLang(def) + if err != nil { + log.Fatalf("internal error parsing default lang %q: %v", def, err) + } + if langWant.major > defVers.major || (langWant.major == defVers.major && langWant.minor > defVers.minor) { + log.Fatalf("invalid value %q for -lang: max known version is %q", base.Flag.Lang, def) + } + } +} + +// parseLang parses a -lang option into a langVer. +func parseLang(s string) (lang, error) { + matches := goVersionRE.FindStringSubmatch(s) + if matches == nil { + return lang{}, fmt.Errorf(`should be something like "go1.12"`) + } + major, err := strconv.Atoi(matches[1]) + if err != nil { + return lang{}, err + } + minor, err := strconv.Atoi(matches[2]) + if err != nil { + return lang{}, err + } + return lang{major: major, minor: minor}, nil +} + +// currentLang returns the current language version. +func currentLang() string { + return fmt.Sprintf("go1.%d", goversion.Version) +} + +// goVersionRE is a regular expression that matches the valid +// arguments to the -lang flag. +var goVersionRE = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`) diff --git a/src/cmd/compile/internal/types/identity.go b/src/cmd/compile/internal/types/identity.go index a77f514df96bef11730a806da97d251435135e44..dde9f5185687ad6869d0961f85928df97e8a133b 100644 --- a/src/cmd/compile/internal/types/identity.go +++ b/src/cmd/compile/internal/types/identity.go @@ -25,17 +25,17 @@ func identical(t1, t2 *Type, cmpTags bool, assumedEqual map[typePair]struct{}) b if t1 == t2 { return true } - if t1 == nil || t2 == nil || t1.Etype != t2.Etype || t1.Broke() || t2.Broke() { + if t1 == nil || t2 == nil || t1.kind != t2.kind || t1.Broke() || t2.Broke() { return false } - if t1.Sym != nil || t2.Sym != nil { + if t1.sym != nil || t2.sym != nil { // Special case: we keep byte/uint8 and rune/int32 // separate for error messages. Treat them as equal. - switch t1.Etype { + switch t1.kind { case TUINT8: - return (t1 == Types[TUINT8] || t1 == Bytetype) && (t2 == Types[TUINT8] || t2 == Bytetype) + return (t1 == Types[TUINT8] || t1 == ByteType) && (t2 == Types[TUINT8] || t2 == ByteType) case TINT32: - return (t1 == Types[TINT32] || t1 == Runetype) && (t2 == Types[TINT32] || t2 == Runetype) + return (t1 == Types[TINT32] || t1 == RuneType) && (t2 == Types[TINT32] || t2 == RuneType) default: return false } @@ -52,7 +52,7 @@ func identical(t1, t2 *Type, cmpTags bool, assumedEqual map[typePair]struct{}) b } assumedEqual[typePair{t1, t2}] = struct{}{} - switch t1.Etype { + switch t1.kind { case TIDEAL: // Historically, cmd/compile used a single "untyped // number" type, so all untyped number types were @@ -61,11 +61,11 @@ func identical(t1, t2 *Type, cmpTags bool, assumedEqual map[typePair]struct{}) b return true case TINTER: - if t1.NumFields() != t2.NumFields() { + if t1.AllMethods().Len() != t2.AllMethods().Len() { return false } - for i, f1 := range t1.FieldSlice() { - f2 := t2.Field(i) + for i, f1 := range t1.AllMethods().Slice() { + f2 := t2.AllMethods().Index(i) if f1.Sym != f2.Sym || !identical(f1.Type, f2.Type, cmpTags, assumedEqual) { return false } diff --git a/src/cmd/compile/internal/types/kind_string.go b/src/cmd/compile/internal/types/kind_string.go new file mode 100644 index 0000000000000000000000000000000000000000..ae24a58b9219d483b710e69af65ce892bf7759e8 --- /dev/null +++ b/src/cmd/compile/internal/types/kind_string.go @@ -0,0 +1,61 @@ +// Code generated by "stringer -type Kind -trimprefix T type.go"; DO NOT EDIT. + +package types + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Txxx-0] + _ = x[TINT8-1] + _ = x[TUINT8-2] + _ = x[TINT16-3] + _ = x[TUINT16-4] + _ = x[TINT32-5] + _ = x[TUINT32-6] + _ = x[TINT64-7] + _ = x[TUINT64-8] + _ = x[TINT-9] + _ = x[TUINT-10] + _ = x[TUINTPTR-11] + _ = x[TCOMPLEX64-12] + _ = x[TCOMPLEX128-13] + _ = x[TFLOAT32-14] + _ = x[TFLOAT64-15] + _ = x[TBOOL-16] + _ = x[TPTR-17] + _ = x[TFUNC-18] + _ = x[TSLICE-19] + _ = x[TARRAY-20] + _ = x[TSTRUCT-21] + _ = x[TCHAN-22] + _ = x[TMAP-23] + _ = x[TINTER-24] + _ = x[TFORW-25] + _ = x[TANY-26] + _ = x[TSTRING-27] + _ = x[TUNSAFEPTR-28] + _ = x[TTYPEPARAM-29] + _ = x[TIDEAL-30] + _ = x[TNIL-31] + _ = x[TBLANK-32] + _ = x[TFUNCARGS-33] + _ = x[TCHANARGS-34] + _ = x[TSSA-35] + _ = x[TTUPLE-36] + _ = x[TRESULTS-37] + _ = x[NTYPE-38] +} + +const _Kind_name = "xxxINT8UINT8INT16UINT16INT32UINT32INT64UINT64INTUINTUINTPTRCOMPLEX64COMPLEX128FLOAT32FLOAT64BOOLPTRFUNCSLICEARRAYSTRUCTCHANMAPINTERFORWANYSTRINGUNSAFEPTRTYPEPARAMIDEALNILBLANKFUNCARGSCHANARGSSSATUPLERESULTSNTYPE" + +var _Kind_index = [...]uint8{0, 3, 7, 12, 17, 23, 28, 34, 39, 45, 48, 52, 59, 68, 78, 85, 92, 96, 99, 103, 108, 113, 119, 123, 126, 131, 135, 138, 144, 153, 162, 167, 170, 175, 183, 191, 194, 199, 206, 211} + +func (i Kind) String() string { + if i >= Kind(len(_Kind_index)-1) { + return "Kind(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Kind_name[_Kind_index[i]:_Kind_index[i+1]] +} diff --git a/src/cmd/compile/internal/types/pkg.go b/src/cmd/compile/internal/types/pkg.go index bcc67895095812f79dd3482684f3c80bb0c28894..a6d2e2007b0424d4927ef5cda15fb67d94d27186 100644 --- a/src/cmd/compile/internal/types/pkg.go +++ b/src/cmd/compile/internal/types/pkg.go @@ -31,8 +31,7 @@ type Pkg struct { // height of their imported packages. Height int - Imported bool // export data of this package was parsed - Direct bool // imported directly + Direct bool // imported directly } // NewPkg returns a new Pkg for the given package path and name. @@ -84,9 +83,6 @@ func (pkg *Pkg) Lookup(name string) *Sym { return s } -// List of .inittask entries in imported packages, in source code order. -var InitSyms []*Sym - // LookupOK looks up name in pkg and reports whether it previously existed. func (pkg *Pkg) LookupOK(name string) (s *Sym, existed bool) { // TODO(gri) remove this check in favor of specialized lookup @@ -101,9 +97,6 @@ func (pkg *Pkg) LookupOK(name string) (s *Sym, existed bool) { Name: name, Pkg: pkg, } - if name == ".inittask" { - InitSyms = append(InitSyms, s) - } pkg.Syms[name] = s return s, false } @@ -144,3 +137,7 @@ func CleanroomDo(f func()) { f() pkgMap = saved } + +func IsDotAlias(sym *Sym) bool { + return sym.Def != nil && sym.Def.Sym() != sym +} diff --git a/src/cmd/compile/internal/types/scope.go b/src/cmd/compile/internal/types/scope.go index 40d3d86ef11a4168db0f5f7c0221a1e94353ed88..d7c454f3795e416ce1af5049cfb029f1e5b2a687 100644 --- a/src/cmd/compile/internal/types/scope.go +++ b/src/cmd/compile/internal/types/scope.go @@ -4,18 +4,21 @@ package types -import "cmd/internal/src" +import ( + "cmd/compile/internal/base" + "cmd/internal/src" +) // Declaration stack & operations var blockgen int32 = 1 // max block number -var Block int32 // current block number +var Block int32 = 1 // current block number // A dsym stores a symbol's shadowed declaration so that it can be // restored once the block scope ends. type dsym struct { sym *Sym // sym == nil indicates stack mark - def *Node + def Object block int32 lastlineno src.XPos // last declaration for diagnostic } @@ -56,7 +59,7 @@ func Popdcl() { d.sym = nil d.def = nil } - Fatalf("popdcl: no stack mark") + base.Fatalf("popdcl: no stack mark") } // Markdcl records the start of a new block scope for declarations. @@ -69,7 +72,7 @@ func Markdcl() { Block = blockgen } -func IsDclstackValid() bool { +func isDclstackValid() bool { for _, d := range dclstack { if d.sym == nil { return false @@ -79,19 +82,20 @@ func IsDclstackValid() bool { } // PkgDef returns the definition associated with s at package scope. -func (s *Sym) PkgDef() *Node { +func (s *Sym) PkgDef() Object { return *s.pkgDefPtr() } // SetPkgDef sets the definition associated with s at package scope. -func (s *Sym) SetPkgDef(n *Node) { +func (s *Sym) SetPkgDef(n Object) { *s.pkgDefPtr() = n } -func (s *Sym) pkgDefPtr() **Node { +func (s *Sym) pkgDefPtr() *Object { // Look for outermost saved declaration, which must be the // package scope definition, if present. - for _, d := range dclstack { + for i := range dclstack { + d := &dclstack[i] if s == d.sym { return &d.def } @@ -101,3 +105,9 @@ func (s *Sym) pkgDefPtr() **Node { // function scope. return &s.Def } + +func CheckDclstack() { + if !isDclstackValid() { + base.Fatalf("mark left on the dclstack") + } +} diff --git a/src/cmd/compile/internal/gc/align.go b/src/cmd/compile/internal/types/size.go similarity index 50% rename from src/cmd/compile/internal/gc/align.go rename to src/cmd/compile/internal/types/size.go index a3a0c8fce822e43226bbaa70a17d5d2e5ee42ccd..f0e695ab964ac532dbeb3ad2bfdcae2e1e8479c2 100644 --- a/src/cmd/compile/internal/gc/align.go +++ b/src/cmd/compile/internal/types/size.go @@ -2,18 +2,64 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gc +package types import ( "bytes" - "cmd/compile/internal/types" "fmt" "sort" + + "cmd/compile/internal/base" + "cmd/internal/src" +) + +var PtrSize int + +var RegSize int + +// Slices in the runtime are represented by three components: +// +// type slice struct { +// ptr unsafe.Pointer +// len int +// cap int +// } +// +// Strings in the runtime are represented by two components: +// +// type string struct { +// ptr unsafe.Pointer +// len int +// } +// +// These variables are the offsets of fields and sizes of these structs. +var ( + SlicePtrOffset int64 + SliceLenOffset int64 + SliceCapOffset int64 + + SliceSize int64 + StringSize int64 ) -// sizeCalculationDisabled indicates whether it is safe -// to calculate Types' widths and alignments. See dowidth. -var sizeCalculationDisabled bool +var SkipSizeForTracing bool + +// typePos returns the position associated with t. +// This is where t was declared or where it appeared as a type expression. +func typePos(t *Type) src.XPos { + if pos := t.Pos(); pos.IsKnown() { + return pos + } + base.Fatalf("bad type: %v", t) + panic("unreachable") +} + +// MaxWidth is the maximum size of a value on the target architecture. +var MaxWidth int64 + +// CalcSizeDisabled indicates whether it is safe +// to calculate Types' widths and alignments. See CalcSize. +var CalcSizeDisabled bool // machine size and rounding alignment is dictated around // the size of a pointer, set in betypeinit (see ../amd64/galign.go). @@ -21,25 +67,25 @@ var defercalc int func Rnd(o int64, r int64) int64 { if r < 1 || r > 8 || r&(r-1) != 0 { - Fatalf("rnd %d", r) + base.Fatalf("rnd %d", r) } return (o + r - 1) &^ (r - 1) } // expandiface computes the method set for interface type t by // expanding embedded interfaces. -func expandiface(t *types.Type) { - seen := make(map[*types.Sym]*types.Field) - var methods []*types.Field +func expandiface(t *Type) { + seen := make(map[*Sym]*Field) + var methods []*Field - addMethod := func(m *types.Field, explicit bool) { + addMethod := func(m *Field, explicit bool) { switch prev := seen[m.Sym]; { case prev == nil: seen[m.Sym] = m - case langSupported(1, 14, t.Pkg()) && !explicit && types.Identical(m.Type, prev.Type): + case AllowsGoVersion(t.Pkg(), 1, 14) && !explicit && Identical(m.Type, prev.Type): return default: - yyerrorl(m.Pos, "duplicate method %s", m.Sym.Name) + base.ErrorfAt(m.Pos, "duplicate method %s", m.Sym.Name) } methods = append(methods, m) } @@ -49,17 +95,17 @@ func expandiface(t *types.Type) { continue } - checkwidth(m.Type) + CheckSize(m.Type) addMethod(m, true) } for _, m := range t.Methods().Slice() { - if m.Sym != nil { + if m.Sym != nil || m.Type == nil { continue } if !m.Type.IsInterface() { - yyerrorl(m.Pos, "interface contains embedded non-interface %v", m.Type) + base.ErrorfAt(m.Pos, "interface contains embedded non-interface %v", m.Type) m.SetBroke(true) t.SetBroke(true) // Add to fields so that error messages @@ -73,31 +119,28 @@ func expandiface(t *types.Type) { // Embedded interface: duplicate all methods // (including broken ones, if any) and add to t's // method set. - for _, t1 := range m.Type.Fields().Slice() { - f := types.NewField() - f.Pos = m.Pos // preserve embedding position - f.Sym = t1.Sym - f.Type = t1.Type - f.SetBroke(t1.Broke()) + for _, t1 := range m.Type.AllMethods().Slice() { + // Use m.Pos rather than t1.Pos to preserve embedding position. + f := NewField(m.Pos, t1.Sym, t1.Type) addMethod(f, false) } } - sort.Sort(methcmp(methods)) + sort.Sort(MethodsByName(methods)) - if int64(len(methods)) >= thearch.MAXWIDTH/int64(Widthptr) { - yyerrorl(typePos(t), "interface too large") + if int64(len(methods)) >= MaxWidth/int64(PtrSize) { + base.ErrorfAt(typePos(t), "interface too large") } for i, m := range methods { - m.Offset = int64(i) * int64(Widthptr) + m.Offset = int64(i) * int64(PtrSize) } - // Access fields directly to avoid recursively calling dowidth - // within Type.Fields(). - t.Extra.(*types.Interface).Fields.Set(methods) + t.SetAllMethods(methods) } -func widstruct(errtype *types.Type, t *types.Type, o int64, flag int) int64 { +func calcStructOffset(errtype *Type, t *Type, o int64, flag int) int64 { + // flag is 0 (receiver), 1 (actual struct), or RegSize (in/out parameters) + isStruct := flag == 1 starto := o maxalign := int32(flag) if maxalign < 1 { @@ -111,46 +154,33 @@ func widstruct(errtype *types.Type, t *types.Type, o int64, flag int) int64 { continue } - dowidth(f.Type) + CalcSize(f.Type) if int32(f.Type.Align) > maxalign { maxalign = int32(f.Type.Align) } if f.Type.Align > 0 { o = Rnd(o, int64(f.Type.Align)) } - f.Offset = o - if n := asNode(f.Nname); n != nil { - // addrescapes has similar code to update these offsets. - // Usually addrescapes runs after widstruct, - // in which case we could drop this, - // but function closure functions are the exception. - // NOTE(rsc): This comment may be stale. - // It's possible the ordering has changed and this is - // now the common case. I'm not sure. - if n.Name.Param.Stackcopy != nil { - n.Name.Param.Stackcopy.Xoffset = o - n.Xoffset = 0 - } else { - n.Xoffset = o - } + if isStruct { // For receiver/args/results, do not set, it depends on ABI + f.Offset = o } w := f.Type.Width if w < 0 { - Fatalf("invalid width %d", f.Type.Width) + base.Fatalf("invalid width %d", f.Type.Width) } if w == 0 { lastzero = o } o += w - maxwidth := thearch.MAXWIDTH + maxwidth := MaxWidth // On 32-bit systems, reflect tables impose an additional constraint // that each field start offset must fit in 31 bits. if maxwidth < 1<<32 { maxwidth = 1<<31 - 1 } if o >= maxwidth { - yyerrorl(typePos(errtype), "type %L too large", errtype) + base.ErrorfAt(typePos(errtype), "type %L too large", errtype) o = 8 // small but nonzero } } @@ -182,15 +212,22 @@ func widstruct(errtype *types.Type, t *types.Type, o int64, flag int) int64 { // path points to a slice used for tracking the sequence of types // visited. Using a pointer to a slice allows the slice capacity to // grow and limit reallocations. -func findTypeLoop(t *types.Type, path *[]*types.Type) bool { +func findTypeLoop(t *Type, path *[]*Type) bool { // We implement a simple DFS loop-finding algorithm. This // could be faster, but type cycles are rare. - if t.Sym != nil { + if t.Sym() != nil { // Declared type. Check for loops and otherwise // recurse on the type expression used in the type // declaration. + // Type imported from package, so it can't be part of + // a type loop (otherwise that package should have + // failed to compile). + if t.Sym().Pkg != LocalPkg { + return false + } + for i, x := range *path { if x == t { *path = (*path)[i:] @@ -199,14 +236,14 @@ func findTypeLoop(t *types.Type, path *[]*types.Type) bool { } *path = append(*path, t) - if p := asNode(t.Nod).Name.Param; p != nil && findTypeLoop(p.Ntype.Type, path) { + if findTypeLoop(t.Obj().(TypeObject).TypeDefn(), path) { return true } *path = (*path)[:len(*path)-1] } else { // Anonymous type. Recurse on contained types. - switch t.Etype { + switch t.Kind() { case TARRAY: if findTypeLoop(t.Elem(), path) { return true @@ -231,14 +268,14 @@ func findTypeLoop(t *types.Type, path *[]*types.Type) bool { return false } -func reportTypeLoop(t *types.Type) { +func reportTypeLoop(t *Type) { if t.Broke() { return } - var l []*types.Type + var l []*Type if !findTypeLoop(t, &l) { - Fatalf("failed to find type loop for: %v", t) + base.Fatalf("failed to find type loop for: %v", t) } // Rotate loop so that the earliest type declaration is first. @@ -253,25 +290,26 @@ func reportTypeLoop(t *types.Type) { var msg bytes.Buffer fmt.Fprintf(&msg, "invalid recursive type %v\n", l[0]) for _, t := range l { - fmt.Fprintf(&msg, "\t%v: %v refers to\n", linestr(typePos(t)), t) + fmt.Fprintf(&msg, "\t%v: %v refers to\n", base.FmtPos(typePos(t)), t) t.SetBroke(true) } - fmt.Fprintf(&msg, "\t%v: %v", linestr(typePos(l[0])), l[0]) - yyerrorl(typePos(l[0]), msg.String()) + fmt.Fprintf(&msg, "\t%v: %v", base.FmtPos(typePos(l[0])), l[0]) + base.ErrorfAt(typePos(l[0]), msg.String()) } -// dowidth calculates and stores the size and alignment for t. -// If sizeCalculationDisabled is set, and the size/alignment +// CalcSize calculates and stores the size and alignment for t. +// If CalcSizeDisabled is set, and the size/alignment // have not already been calculated, it calls Fatal. // This is used to prevent data races in the back end. -func dowidth(t *types.Type) { - // Calling dowidth when typecheck tracing enabled is not safe. +func CalcSize(t *Type) { + // Calling CalcSize when typecheck tracing enabled is not safe. // See issue #33658. - if enableTrace && skipDowidthForTracing { + if base.EnableTrace && SkipSizeForTracing { return } - if Widthptr == 0 { - Fatalf("dowidth without betypeinit") + if PtrSize == 0 { + // Assume this is a test. + return } if t == nil { @@ -289,13 +327,13 @@ func dowidth(t *types.Type) { return } - if sizeCalculationDisabled { + if CalcSizeDisabled { if t.Broke() { // break infinite recursion from Fatal call below return } t.SetBroke(true) - Fatalf("width not calculated: %v", t) + base.Fatalf("width not calculated: %v", t) } // break infinite recursion if the broken recursive type @@ -304,33 +342,33 @@ func dowidth(t *types.Type) { return } - // defer checkwidth calls until after we're done - defercheckwidth() + // defer CheckSize calls until after we're done + DeferCheckSize() - lno := lineno - if asNode(t.Nod) != nil { - lineno = asNode(t.Nod).Pos + lno := base.Pos + if pos := t.Pos(); pos.IsKnown() { + base.Pos = pos } t.Width = -2 t.Align = 0 // 0 means use t.Width, below - et := t.Etype + et := t.Kind() switch et { case TFUNC, TCHAN, TMAP, TSTRING: break - // simtype == 0 during bootstrap + // SimType == 0 during bootstrap default: - if simtype[t.Etype] != 0 { - et = simtype[t.Etype] + if SimType[t.Kind()] != 0 { + et = SimType[t.Kind()] } } var w int64 switch et { default: - Fatalf("dowidth: unknown type: %v", t) + base.Fatalf("CalcSize: unknown type: %v", t) // compiler-specific stuff case TINT8, TUINT8, TBOOL: @@ -345,7 +383,7 @@ func dowidth(t *types.Type) { case TINT64, TUINT64, TFLOAT64: w = 8 - t.Align = uint8(Widthreg) + t.Align = uint8(RegSize) case TCOMPLEX64: w = 8 @@ -353,68 +391,68 @@ func dowidth(t *types.Type) { case TCOMPLEX128: w = 16 - t.Align = uint8(Widthreg) + t.Align = uint8(RegSize) case TPTR: - w = int64(Widthptr) - checkwidth(t.Elem()) + w = int64(PtrSize) + CheckSize(t.Elem()) case TUNSAFEPTR: - w = int64(Widthptr) + w = int64(PtrSize) case TINTER: // implemented as 2 pointers - w = 2 * int64(Widthptr) - t.Align = uint8(Widthptr) + w = 2 * int64(PtrSize) + t.Align = uint8(PtrSize) expandiface(t) case TCHAN: // implemented as pointer - w = int64(Widthptr) + w = int64(PtrSize) - checkwidth(t.Elem()) + CheckSize(t.Elem()) // make fake type to check later to // trigger channel argument check. - t1 := types.NewChanArgs(t) - checkwidth(t1) + t1 := NewChanArgs(t) + CheckSize(t1) case TCHANARGS: t1 := t.ChanArgs() - dowidth(t1) // just in case + CalcSize(t1) // just in case if t1.Elem().Width >= 1<<16 { - yyerrorl(typePos(t1), "channel element type too large (>64kB)") + base.ErrorfAt(typePos(t1), "channel element type too large (>64kB)") } w = 1 // anything will do case TMAP: // implemented as pointer - w = int64(Widthptr) - checkwidth(t.Elem()) - checkwidth(t.Key()) + w = int64(PtrSize) + CheckSize(t.Elem()) + CheckSize(t.Key()) case TFORW: // should have been filled in reportTypeLoop(t) w = 1 // anything will do case TANY: - // dummy type; should be replaced before use. - Fatalf("dowidth any") + // not a real type; should be replaced before use. + base.Fatalf("CalcSize any") case TSTRING: - if sizeofString == 0 { - Fatalf("early dowidth string") + if StringSize == 0 { + base.Fatalf("early CalcSize string") } - w = sizeofString - t.Align = uint8(Widthptr) + w = StringSize + t.Align = uint8(PtrSize) case TARRAY: if t.Elem() == nil { break } - dowidth(t.Elem()) + CalcSize(t.Elem()) if t.Elem().Width != 0 { - cap := (uint64(thearch.MAXWIDTH) - 1) / uint64(t.Elem().Width) + cap := (uint64(MaxWidth) - 1) / uint64(t.Elem().Width) if uint64(t.NumElem()) > cap { - yyerrorl(typePos(t), "type %L larger than address space", t) + base.ErrorfAt(typePos(t), "type %L larger than address space", t) } } w = t.NumElem() * t.Elem().Width @@ -424,55 +462,67 @@ func dowidth(t *types.Type) { if t.Elem() == nil { break } - w = sizeofSlice - checkwidth(t.Elem()) - t.Align = uint8(Widthptr) + w = SliceSize + CheckSize(t.Elem()) + t.Align = uint8(PtrSize) case TSTRUCT: if t.IsFuncArgStruct() { - Fatalf("dowidth fn struct %v", t) + base.Fatalf("CalcSize fn struct %v", t) } - w = widstruct(t, t, 0, 1) + w = calcStructOffset(t, t, 0, 1) // make fake type to check later to // trigger function argument computation. case TFUNC: - t1 := types.NewFuncArgs(t) - checkwidth(t1) - w = int64(Widthptr) // width of func type is pointer + t1 := NewFuncArgs(t) + CheckSize(t1) + w = int64(PtrSize) // width of func type is pointer // function is 3 cated structures; // compute their widths as side-effect. case TFUNCARGS: t1 := t.FuncArgs() - w = widstruct(t1, t1.Recvs(), 0, 0) - w = widstruct(t1, t1.Params(), w, Widthreg) - w = widstruct(t1, t1.Results(), w, Widthreg) - t1.Extra.(*types.Func).Argwid = w - if w%int64(Widthreg) != 0 { - Warn("bad type %v %d\n", t1, w) + w = calcStructOffset(t1, t1.Recvs(), 0, 0) + w = calcStructOffset(t1, t1.Params(), w, RegSize) + w = calcStructOffset(t1, t1.Results(), w, RegSize) + t1.Extra.(*Func).Argwid = w + if w%int64(RegSize) != 0 { + base.Warn("bad type %v %d\n", t1, w) } t.Align = 1 + + case TTYPEPARAM: + // TODO(danscales) - remove when we eliminate the need + // to do CalcSize in noder2 (which shouldn't be needed in the noder) + w = int64(PtrSize) } - if Widthptr == 4 && w != int64(int32(w)) { - yyerrorl(typePos(t), "type %v too large", t) + if PtrSize == 4 && w != int64(int32(w)) { + base.ErrorfAt(typePos(t), "type %v too large", t) } t.Width = w if t.Align == 0 { if w == 0 || w > 8 || w&(w-1) != 0 { - Fatalf("invalid alignment for %v", t) + base.Fatalf("invalid alignment for %v", t) } t.Align = uint8(w) } - lineno = lno + base.Pos = lno - resumecheckwidth() + ResumeCheckSize() } -// when a type's width should be known, we call checkwidth +// CalcStructSize calculates the size of s, +// filling in s.Width and s.Align, +// even if size calculation is otherwise disabled. +func CalcStructSize(s *Type) { + s.Width = calcStructOffset(s, s, 0, 1) // sets align +} + +// when a type's width should be known, we call CheckSize // to compute it. during a declaration like // // type T *struct { next T } @@ -481,16 +531,16 @@ func dowidth(t *types.Type) { // until after T has been initialized to be a pointer to that struct. // similarly, during import processing structs may be used // before their definition. in those situations, calling -// defercheckwidth() stops width calculations until -// resumecheckwidth() is called, at which point all the -// checkwidths that were deferred are executed. -// dowidth should only be called when the type's size -// is needed immediately. checkwidth makes sure the +// DeferCheckSize() stops width calculations until +// ResumeCheckSize() is called, at which point all the +// CalcSizes that were deferred are executed. +// CalcSize should only be called when the type's size +// is needed immediately. CheckSize makes sure the // size is evaluated eventually. -var deferredTypeStack []*types.Type +var deferredTypeStack []*Type -func checkwidth(t *types.Type) { +func CheckSize(t *Type) { if t == nil { return } @@ -498,11 +548,11 @@ func checkwidth(t *types.Type) { // function arg structs should not be checked // outside of the enclosing function. if t.IsFuncArgStruct() { - Fatalf("checkwidth %v", t) + base.Fatalf("CheckSize %v", t) } if defercalc == 0 { - dowidth(t) + CalcSize(t) return } @@ -513,19 +563,70 @@ func checkwidth(t *types.Type) { } } -func defercheckwidth() { +func DeferCheckSize() { defercalc++ } -func resumecheckwidth() { +func ResumeCheckSize() { if defercalc == 1 { for len(deferredTypeStack) > 0 { t := deferredTypeStack[len(deferredTypeStack)-1] deferredTypeStack = deferredTypeStack[:len(deferredTypeStack)-1] t.SetDeferwidth(false) - dowidth(t) + CalcSize(t) } } defercalc-- } + +// PtrDataSize returns the length in bytes of the prefix of t +// containing pointer data. Anything after this offset is scalar data. +func PtrDataSize(t *Type) int64 { + if !t.HasPointers() { + return 0 + } + + switch t.Kind() { + case TPTR, + TUNSAFEPTR, + TFUNC, + TCHAN, + TMAP: + return int64(PtrSize) + + case TSTRING: + // struct { byte *str; intgo len; } + return int64(PtrSize) + + case TINTER: + // struct { Itab *tab; void *data; } or + // struct { Type *type; void *data; } + // Note: see comment in typebits.Set + return 2 * int64(PtrSize) + + case TSLICE: + // struct { byte *array; uintgo len; uintgo cap; } + return int64(PtrSize) + + case TARRAY: + // haspointers already eliminated t.NumElem() == 0. + return (t.NumElem()-1)*t.Elem().Width + PtrDataSize(t.Elem()) + + case TSTRUCT: + // Find the last field that has pointers. + var lastPtrField *Field + fs := t.Fields().Slice() + for i := len(fs) - 1; i >= 0; i-- { + if fs[i].Type.HasPointers() { + lastPtrField = fs[i] + break + } + } + return lastPtrField.Offset + PtrDataSize(lastPtrField.Type) + + default: + base.Fatalf("PtrDataSize: unexpected type, %v", t) + return 0 + } +} diff --git a/src/cmd/compile/internal/types/sizeof_test.go b/src/cmd/compile/internal/types/sizeof_test.go index ea947d8f417c5f08fb5150144330a083521f7145..702893874214874080dfa1a148048303279050d9 100644 --- a/src/cmd/compile/internal/types/sizeof_test.go +++ b/src/cmd/compile/internal/types/sizeof_test.go @@ -20,13 +20,13 @@ func TestSizeof(t *testing.T) { _32bit uintptr // size on 32bit platforms _64bit uintptr // size on 64bit platforms }{ - {Sym{}, 52, 88}, - {Type{}, 52, 88}, + {Sym{}, 44, 72}, + {Type{}, 60, 104}, {Map{}, 20, 40}, {Forward{}, 20, 32}, - {Func{}, 32, 56}, + {Func{}, 28, 48}, {Struct{}, 16, 32}, - {Interface{}, 8, 16}, + {Interface{}, 4, 8}, {Chan{}, 8, 16}, {Array{}, 12, 16}, {FuncArgs{}, 4, 8}, diff --git a/src/cmd/compile/internal/types/sort.go b/src/cmd/compile/internal/types/sort.go new file mode 100644 index 0000000000000000000000000000000000000000..dc59b064153282cd0808a0fd3d2b4d51cd98680b --- /dev/null +++ b/src/cmd/compile/internal/types/sort.go @@ -0,0 +1,14 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +// MethodsByName sorts methods by symbol. +type MethodsByName []*Field + +func (x MethodsByName) Len() int { return len(x) } + +func (x MethodsByName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x MethodsByName) Less(i, j int) bool { return x[i].Sym.Less(x[j].Sym) } diff --git a/src/cmd/compile/internal/types/sym.go b/src/cmd/compile/internal/types/sym.go index 07bce4d5cdbb81a3d4a18c0f4b135c54d4ea1610..534cf7e2376d726e9d3cb3568de4b4d7c5ea9125 100644 --- a/src/cmd/compile/internal/types/sym.go +++ b/src/cmd/compile/internal/types/sym.go @@ -5,6 +5,7 @@ package types import ( + "cmd/compile/internal/base" "cmd/internal/obj" "cmd/internal/src" "unicode" @@ -26,20 +27,21 @@ import ( // NOTE: In practice, things can be messier than the description above // for various reasons (historical, convenience). type Sym struct { - Importdef *Pkg // where imported definition was found - Linkname string // link name + Linkname string // link name Pkg *Pkg Name string // object name - // saved and restored by dcopy - Def *Node // definition: ONAME OTYPE OPACK or OLITERAL + // Def, Block, and Lastlineno are saved and restored by Pushdcl/Popdcl. + + // The unique ONAME, OTYPE, OPACK, or OLITERAL node that this symbol is + // bound to within the current scope. (Most parts of the compiler should + // prefer passing the Node directly, rather than relying on this field.) + Def Object Block int32 // blocknumber to catch redeclaration Lastlineno src.XPos // last declaration for diagnostic - flags bitset8 - Label *Node // corresponding label (ephemeral) - Origpkg *Pkg // original package for . import + flags bitset8 } const ( @@ -47,7 +49,7 @@ const ( symUniq symSiggen // type symbol has been generated symAsm // on asmlist, for writing to -asmhdr - symFunc // function symbol; uses internal ABI + symFunc // function symbol ) func (sym *Sym) OnExportList() bool { return sym.flags&symOnExportList != 0 } @@ -66,32 +68,30 @@ func (sym *Sym) IsBlank() bool { return sym != nil && sym.Name == "_" } -func (sym *Sym) LinksymName() string { - if sym.IsBlank() { - return "_" - } - if sym.Linkname != "" { - return sym.Linkname +// Deprecated: This method should not be used directly. Instead, use a +// higher-level abstraction that directly returns the linker symbol +// for a named object. For example, reflectdata.TypeLinksym(t) instead +// of reflectdata.TypeSym(t).Linksym(). +func (sym *Sym) Linksym() *obj.LSym { + abi := obj.ABI0 + if sym.Func() { + abi = obj.ABIInternal } - return sym.Pkg.Prefix + "." + sym.Name + return sym.LinksymABI(abi) } -func (sym *Sym) Linksym() *obj.LSym { +// Deprecated: This method should not be used directly. Instead, use a +// higher-level abstraction that directly returns the linker symbol +// for a named object. For example, (*ir.Name).LinksymABI(abi) instead +// of (*ir.Name).Sym().LinksymABI(abi). +func (sym *Sym) LinksymABI(abi obj.ABI) *obj.LSym { if sym == nil { - return nil - } - initPkg := func(r *obj.LSym) { - if sym.Linkname != "" { - r.Pkg = "_" - } else { - r.Pkg = sym.Pkg.Prefix - } + base.Fatalf("nil symbol") } - if sym.Func() { - // This is a function symbol. Mark it as "internal ABI". - return Ctxt.LookupABIInit(sym.LinksymName(), obj.ABIInternal, initPkg) + if sym.Linkname != "" { + return base.Linkname(sym.Linkname, abi) } - return Ctxt.LookupInit(sym.LinksymName(), initPkg) + return base.PkgLinksym(sym.Pkg.Prefix, sym.Name, abi) } // Less reports whether symbol a is ordered before symbol b. diff --git a/src/cmd/compile/internal/types/type.go b/src/cmd/compile/internal/types/type.go index 023ab9af88aee048f29fd88b5596e515360f0fd2..1a9aa6916a2faca5a12a999226cea51cef0c4234 100644 --- a/src/cmd/compile/internal/types/type.go +++ b/src/cmd/compile/internal/types/type.go @@ -5,23 +5,40 @@ package types import ( - "cmd/internal/obj" + "cmd/compile/internal/base" "cmd/internal/src" "fmt" + "sync" ) -// Dummy Node so we can refer to *Node without actually -// having a gc.Node. Necessary to break import cycles. -// TODO(gri) try to eliminate soon -type Node struct{ _ int } +// Object represents an ir.Node, but without needing to import cmd/compile/internal/ir, +// which would cause an import cycle. The uses in other packages must type assert +// values of type Object to ir.Node or a more specific type. +type Object interface { + Pos() src.XPos + Sym() *Sym + Type() *Type +} + +// A TypeObject is an Object representing a named type. +type TypeObject interface { + Object + TypeDefn() *Type // for "type T Defn", returns Defn +} + +// A VarObject is an Object representing a function argument, variable, or struct field. +type VarObject interface { + Object + RecordFrameOffset(int64) // save frame offset +} -//go:generate stringer -type EType -trimprefix T +//go:generate stringer -type Kind -trimprefix T type.go -// EType describes a kind of type. -type EType uint8 +// Kind describes a kind of type. +type Kind uint8 const ( - Txxx EType = iota + Txxx Kind = iota TINT8 TUINT8 @@ -55,6 +72,7 @@ const ( TANY TSTRING TUNSAFEPTR + TTYPEPARAM // pseudo-types for literals TIDEAL // untyped numeric constants @@ -90,7 +108,7 @@ const ( // Types stores pointers to predeclared named types. // // It also stores pointers to several special types: -// - Types[TANY] is the placeholder "any" type recognized by substArgTypes. +// - Types[TANY] is the placeholder "any" type recognized by SubstArgTypes. // - Types[TBLANK] represents the blank variable's type. // - Types[TNIL] represents the predeclared "nil" value's type. // - Types[TUNSAFEPTR] is package unsafe's Pointer type. @@ -98,15 +116,15 @@ var Types [NTYPE]*Type var ( // Predeclared alias types. Kept separate for better error messages. - Bytetype *Type - Runetype *Type + ByteType *Type + RuneType *Type // Predeclared error interface type. - Errortype *Type + ErrorType *Type // Types to represent untyped string and boolean constants. - UntypedString *Type - UntypedBool *Type + UntypedString = New(TSTRING) + UntypedBool = New(TBOOL) // Types to represent untyped numeric constants. UntypedInt = New(TIDEAL) @@ -133,38 +151,54 @@ type Type struct { // TARRAY: *Array // TSLICE: Slice // TSSA: string + // TTYPEPARAM: *Interface (though we may not need to store/use the Interface info) Extra interface{} // Width is the width of this Type in bytes. Width int64 // valid if Align > 0 - methods Fields + // list of base methods (excluding embedding) + methods Fields + // list of all methods (including embedding) allMethods Fields - Nod *Node // canonical OTYPE node - Orig *Type // original type (type literal or predefined type) + // canonical OTYPE node for a named type (should be an ir.Name node with same sym) + nod Object + // the underlying type (type literal or predeclared type) for a defined type + underlying *Type // Cache of composite types, with this type being the element type. - Cache struct { + cache struct { ptr *Type // *T, or nil slice *Type // []T, or nil } - Sym *Sym // symbol containing name, for named types + sym *Sym // symbol containing name, for named types Vargen int32 // unique name for OTYPE/ONAME - Etype EType // kind of type + kind Kind // kind of type Align uint8 // the required alignment of this type, in bytes (0 means Width and Align have not yet been computed) flags bitset8 + + // For defined (named) generic types, a pointer to the list of type params + // (in order) of this type that need to be instantiated. For + // fully-instantiated generic types, this is the targs used to instantiate + // them (which are used when generating the corresponding instantiated + // methods). rparams is only set for named types that are generic or are + // fully-instantiated from a generic type, and is otherwise set to nil. + rparams *[]*Type } +func (*Type) CanBeAnSSAAux() {} + const ( typeNotInHeap = 1 << iota // type cannot be heap allocated typeBroke // broken type definition typeNoalg // suppress hash and eq algorithm generation typeDeferwidth // width computation has been deferred and type is on deferredTypeStack typeRecur + typeHasTParam // there is a typeparam somewhere in the type (generic function or type) ) func (t *Type) NotInHeap() bool { return t.flags&typeNotInHeap != 0 } @@ -172,12 +206,73 @@ func (t *Type) Broke() bool { return t.flags&typeBroke != 0 } func (t *Type) Noalg() bool { return t.flags&typeNoalg != 0 } func (t *Type) Deferwidth() bool { return t.flags&typeDeferwidth != 0 } func (t *Type) Recur() bool { return t.flags&typeRecur != 0 } +func (t *Type) HasTParam() bool { return t.flags&typeHasTParam != 0 } func (t *Type) SetNotInHeap(b bool) { t.flags.set(typeNotInHeap, b) } func (t *Type) SetBroke(b bool) { t.flags.set(typeBroke, b) } func (t *Type) SetNoalg(b bool) { t.flags.set(typeNoalg, b) } func (t *Type) SetDeferwidth(b bool) { t.flags.set(typeDeferwidth, b) } func (t *Type) SetRecur(b bool) { t.flags.set(typeRecur, b) } +func (t *Type) SetHasTParam(b bool) { t.flags.set(typeHasTParam, b) } + +// Kind returns the kind of type t. +func (t *Type) Kind() Kind { return t.kind } + +// Sym returns the name of type t. +func (t *Type) Sym() *Sym { return t.sym } +func (t *Type) SetSym(sym *Sym) { t.sym = sym } + +// Underlying returns the underlying type of type t. +func (t *Type) Underlying() *Type { return t.underlying } + +// SetNod associates t with syntax node n. +func (t *Type) SetNod(n Object) { + // t.nod can be non-nil already + // in the case of shared *Types, like []byte or interface{}. + if t.nod == nil { + t.nod = n + } +} + +// Pos returns a position associated with t, if any. +// This should only be used for diagnostics. +func (t *Type) Pos() src.XPos { + if t.nod != nil { + return t.nod.Pos() + } + return src.NoXPos +} + +func (t *Type) RParams() []*Type { + if t.rparams == nil { + return nil + } + return *t.rparams +} + +func (t *Type) SetRParams(rparams []*Type) { + if len(rparams) == 0 { + base.Fatalf("Setting nil or zero-length rparams") + } + t.rparams = &rparams + if t.HasTParam() { + return + } + // HasTParam should be set if any rparam is or has a type param. This is + // to handle the case of a generic type which doesn't reference any of its + // type params (e.g. most commonly, an empty struct). + for _, rparam := range rparams { + if rparam.HasTParam() { + t.SetHasTParam(true) + break + } + } +} + +// NoPkg is a nil *Pkg value for clarity. +// It's intended for use when constructing types that aren't exported +// and thus don't need to be associated with any package. +var NoPkg *Pkg = nil // Pkg returns the package that t appeared in. // @@ -186,7 +281,7 @@ func (t *Type) SetRecur(b bool) { t.flags.set(typeRecur, b) } // cmd/compile itself, but we need to track it because it's exposed by // the go/types API. func (t *Type) Pkg() *Pkg { - switch t.Etype { + switch t.kind { case TFUNC: return t.Extra.(*Func).pkg case TSTRUCT: @@ -194,25 +289,11 @@ func (t *Type) Pkg() *Pkg { case TINTER: return t.Extra.(*Interface).pkg default: - Fatalf("Pkg: unexpected kind: %v", t) + base.Fatalf("Pkg: unexpected kind: %v", t) return nil } } -// SetPkg sets the package that t appeared in. -func (t *Type) SetPkg(pkg *Pkg) { - switch t.Etype { - case TFUNC: - t.Extra.(*Func).pkg = pkg - case TSTRUCT: - t.Extra.(*Struct).pkg = pkg - case TINTER: - t.Extra.(*Interface).pkg = pkg - default: - Fatalf("Pkg: unexpected kind: %v", t) - } -} - // Map contains Type fields specific to maps. type Map struct { Key *Type // Key type @@ -246,16 +327,14 @@ type Func struct { Receiver *Type // function receiver Results *Type // function results Params *Type // function params + TParams *Type // type params of receiver (if method) or function - Nname *Node - pkg *Pkg + pkg *Pkg // Argwid is the total width of the function receiver, params, and results. // It gets calculated via a temporary TFUNCARGS type. // Note that TFUNC's Width is Widthptr. Argwid int64 - - Outnamed bool } // FuncType returns t's extra func-specific fields. @@ -284,6 +363,7 @@ const ( FunargRcvr // receiver FunargParams // input parameters FunargResults // output results + FunargTparams // type params ) // StructType returns t's extra struct-specific fields. @@ -294,8 +374,7 @@ func (t *Type) StructType() *Struct { // Interface contains Type fields specific to interface types. type Interface struct { - Fields Fields - pkg *Pkg + pkg *Pkg } // Ptr contains Type fields specific to pointer types. @@ -347,8 +426,11 @@ type Slice struct { Elem *Type // element type } -// A Field represents a field in a struct or a method in an interface or -// associated with a named type. +// A Field is a (Sym, Type) pairing along with some other information, and, +// depending on the context, is used to represent: +// - a field in a struct +// - a method in an interface or associated with a named type +// - a function parameter type Field struct { flags bitset8 @@ -361,10 +443,11 @@ type Field struct { // For fields that represent function parameters, Nname points // to the associated ONAME Node. - Nname *Node + Nname Object // Offset in bytes of this field or method within its enclosing struct - // or interface Type. + // or interface Type. Exception: if field is function receiver, arg or + // result, then this is BOGUS_FUNARG_OFFSET; types does not know the Abi. Offset int64 } @@ -389,7 +472,7 @@ func (f *Field) End() int64 { // IsMethod reports whether f represents a method rather than a struct field. func (f *Field) IsMethod() bool { - return f.Type.Etype == TFUNC && f.Type.Recv() != nil + return f.Type.kind == TFUNC && f.Type.Recv() != nil } // Fields is a pointer to a slice of *Field. @@ -444,14 +527,14 @@ func (f *Fields) Append(s ...*Field) { } // New returns a new Type of the specified kind. -func New(et EType) *Type { +func New(et Kind) *Type { t := &Type{ - Etype: et, + kind: et, Width: BADWIDTH, } - t.Orig = t + t.underlying = t // TODO(josharian): lazily initialize some of these? - switch t.Etype { + switch t.kind { case TMAP: t.Extra = new(Map) case TFORW: @@ -474,6 +557,8 @@ func New(et EType) *Type { t.Extra = new(Tuple) case TRESULTS: t.Extra = new(Results) + case TTYPEPARAM: + t.Extra = new(Interface) } return t } @@ -481,26 +566,32 @@ func New(et EType) *Type { // NewArray returns a new fixed-length array Type. func NewArray(elem *Type, bound int64) *Type { if bound < 0 { - Fatalf("NewArray: invalid bound %v", bound) + base.Fatalf("NewArray: invalid bound %v", bound) } t := New(TARRAY) t.Extra = &Array{Elem: elem, Bound: bound} t.SetNotInHeap(elem.NotInHeap()) + if elem.HasTParam() { + t.SetHasTParam(true) + } return t } // NewSlice returns the slice Type with element type elem. func NewSlice(elem *Type) *Type { - if t := elem.Cache.slice; t != nil { + if t := elem.cache.slice; t != nil { if t.Elem() != elem { - Fatalf("elem mismatch") + base.Fatalf("elem mismatch") } return t } t := New(TSLICE) t.Extra = Slice{Elem: elem} - elem.Cache.slice = t + elem.cache.slice = t + if elem.HasTParam() { + t.SetHasTParam(true) + } return t } @@ -510,6 +601,9 @@ func NewChan(elem *Type, dir ChanDir) *Type { ct := t.ChanType() ct.Elem = elem ct.Dir = dir + if elem.HasTParam() { + t.SetHasTParam(true) + } return t } @@ -517,15 +611,25 @@ func NewTuple(t1, t2 *Type) *Type { t := New(TTUPLE) t.Extra.(*Tuple).first = t1 t.Extra.(*Tuple).second = t2 + if t1.HasTParam() || t2.HasTParam() { + t.SetHasTParam(true) + } return t } -func NewResults(types []*Type) *Type { +func newResults(types []*Type) *Type { t := New(TRESULTS) t.Extra.(*Results).Types = types return t } +func NewResults(types []*Type) *Type { + if len(types) == 1 && types[0] == TypeMem { + return TypeResultMem + } + return newResults(types) +} + func newSSA(name string) *Type { t := New(TSSA) t.Extra = name @@ -538,6 +642,9 @@ func NewMap(k, v *Type) *Type { mt := t.MapType() mt.Key = k mt.Elem = v + if k.HasTParam() || v.HasTParam() { + t.SetHasTParam(true) + } return t } @@ -549,22 +656,31 @@ var NewPtrCacheEnabled = true // NewPtr returns the pointer type pointing to t. func NewPtr(elem *Type) *Type { if elem == nil { - Fatalf("NewPtr: pointer to elem Type is nil") + base.Fatalf("NewPtr: pointer to elem Type is nil") } - if t := elem.Cache.ptr; t != nil { + if t := elem.cache.ptr; t != nil { if t.Elem() != elem { - Fatalf("NewPtr: elem mismatch") + base.Fatalf("NewPtr: elem mismatch") + } + if elem.HasTParam() { + // Extra check when reusing the cache, since the elem + // might have still been undetermined (i.e. a TFORW type) + // when this entry was cached. + t.SetHasTParam(true) } return t } t := New(TPTR) t.Extra = Ptr{Elem: elem} - t.Width = int64(Widthptr) - t.Align = uint8(Widthptr) + t.Width = int64(PtrSize) + t.Align = uint8(PtrSize) if NewPtrCacheEnabled { - elem.Cache.ptr = t + elem.cache.ptr = t + } + if elem.HasTParam() { + t.SetHasTParam(true) } return t } @@ -583,10 +699,17 @@ func NewFuncArgs(f *Type) *Type { return t } -func NewField() *Field { - return &Field{ +func NewField(pos src.XPos, sym *Sym, typ *Type) *Field { + f := &Field{ + Pos: pos, + Sym: sym, + Type: typ, Offset: BADWIDTH, } + if typ == nil { + f.SetBroke(true) + } + return f } // SubstAny walks t, replacing instances of "any" with successive @@ -596,13 +719,13 @@ func SubstAny(t *Type, types *[]*Type) *Type { return nil } - switch t.Etype { + switch t.kind { default: // Leave the type unchanged. case TANY: if len(*types) == 0 { - Fatalf("substArgTypes: not enough argument types") + base.Fatalf("SubstArgTypes: not enough argument types") } t = (*types)[0] *types = (*types)[1:] @@ -680,7 +803,7 @@ func (t *Type) copy() *Type { } nt := *t // copy any *T Extra fields, to avoid aliasing - switch t.Etype { + switch t.kind { case TMAP: x := *t.Extra.(*Map) nt.Extra = &x @@ -703,11 +826,11 @@ func (t *Type) copy() *Type { x := *t.Extra.(*Array) nt.Extra = &x case TTUPLE, TSSA, TRESULTS: - Fatalf("ssa types cannot be copied") + base.Fatalf("ssa types cannot be copied") } // TODO(mdempsky): Find out why this is necessary and explain. - if t.Orig == t { - nt.Orig = &nt + if t.underlying == t { + nt.underlying = &nt } return &nt } @@ -717,17 +840,19 @@ func (f *Field) Copy() *Field { return &nf } -func (t *Type) wantEtype(et EType) { - if t.Etype != et { - Fatalf("want %v, but have %v", et, t) +func (t *Type) wantEtype(et Kind) { + if t.kind != et { + base.Fatalf("want %v, but have %v", et, t) } } func (t *Type) Recvs() *Type { return t.FuncType().Receiver } +func (t *Type) TParams() *Type { return t.FuncType().TParams } func (t *Type) Params() *Type { return t.FuncType().Params } func (t *Type) Results() *Type { return t.FuncType().Results } func (t *Type) NumRecvs() int { return t.FuncType().Receiver.NumFields() } +func (t *Type) NumTParams() int { return t.FuncType().TParams.NumFields() } func (t *Type) NumParams() int { return t.FuncType().Params.NumFields() } func (t *Type) NumResults() int { return t.FuncType().Results.NumFields() } @@ -772,7 +897,7 @@ func (t *Type) Key() *Type { // Elem returns the type of elements of t. // Usable with pointers, channels, arrays, slices, and maps. func (t *Type) Elem() *Type { - switch t.Etype { + switch t.kind { case TPTR: return t.Extra.(Ptr).Elem case TARRAY: @@ -784,7 +909,7 @@ func (t *Type) Elem() *Type { case TMAP: return t.Extra.(*Map).Elem } - Fatalf("Type.Elem %s", t.Etype) + base.Fatalf("Type.Elem %s", t.kind) return nil } @@ -800,65 +925,54 @@ func (t *Type) FuncArgs() *Type { return t.Extra.(FuncArgs).T } -// Nname returns the associated function's nname. -func (t *Type) Nname() *Node { - switch t.Etype { - case TFUNC: - return t.Extra.(*Func).Nname - } - Fatalf("Type.Nname %v %v", t.Etype, t) - return nil -} - -// Nname sets the associated function's nname. -func (t *Type) SetNname(n *Node) { - switch t.Etype { - case TFUNC: - t.Extra.(*Func).Nname = n - default: - Fatalf("Type.SetNname %v %v", t.Etype, t) - } -} - // IsFuncArgStruct reports whether t is a struct representing function parameters. func (t *Type) IsFuncArgStruct() bool { - return t.Etype == TSTRUCT && t.Extra.(*Struct).Funarg != FunargNone + return t.kind == TSTRUCT && t.Extra.(*Struct).Funarg != FunargNone } +// Methods returns a pointer to the base methods (excluding embedding) for type t. +// These can either be concrete methods (for non-interface types) or interface +// methods (for interface types). func (t *Type) Methods() *Fields { - // TODO(mdempsky): Validate t? return &t.methods } +// AllMethods returns a pointer to all the methods (including embedding) for type t. +// For an interface type, this is the set of methods that are typically iterated over. func (t *Type) AllMethods() *Fields { - // TODO(mdempsky): Validate t? + if t.kind == TINTER { + // Calculate the full method set of an interface type on the fly + // now, if not done yet. + CalcSize(t) + } return &t.allMethods } +// SetAllMethods sets the set of all methods (including embedding) for type t. +// Use this method instead of t.AllMethods().Set(), which might call CalcSize() on +// an uninitialized interface type. +func (t *Type) SetAllMethods(fs []*Field) { + t.allMethods.Set(fs) +} + +// Fields returns the fields of struct type t. func (t *Type) Fields() *Fields { - switch t.Etype { - case TSTRUCT: - return &t.Extra.(*Struct).fields - case TINTER: - Dowidth(t) - return &t.Extra.(*Interface).Fields - } - Fatalf("Fields: type %v does not have fields", t) - return nil + t.wantEtype(TSTRUCT) + return &t.Extra.(*Struct).fields } -// Field returns the i'th field/method of struct/interface type t. +// Field returns the i'th field of struct type t. func (t *Type) Field(i int) *Field { return t.Fields().Slice()[i] } -// FieldSlice returns a slice of containing all fields/methods of -// struct/interface type t. +// FieldSlice returns a slice of containing all fields of +// a struct type t. func (t *Type) FieldSlice() []*Field { return t.Fields().Slice() } -// SetFields sets struct/interface type t's fields/methods to fields. +// SetFields sets struct type t's fields to fields. func (t *Type) SetFields(fields []*Field) { // If we've calculated the width of t before, // then some other type such as a function signature @@ -867,7 +981,7 @@ func (t *Type) SetFields(fields []*Field) { // enforce that SetFields cannot be called once // t's width has been calculated. if t.WidthCalculated() { - Fatalf("SetFields of %v: width previously calculated", t) + base.Fatalf("SetFields of %v: width previously calculated", t) } t.wantEtype(TSTRUCT) for _, f := range fields { @@ -884,6 +998,7 @@ func (t *Type) SetFields(fields []*Field) { t.Fields().Set(fields) } +// SetInterface sets the base methods of an interface type t. func (t *Type) SetInterface(methods []*Field) { t.wantEtype(TINTER) t.Methods().Set(methods) @@ -901,23 +1016,23 @@ func (t *Type) ArgWidth() int64 { } func (t *Type) Size() int64 { - if t.Etype == TSSA { + if t.kind == TSSA { if t == TypeInt128 { return 16 } return 0 } - Dowidth(t) + CalcSize(t) return t.Width } func (t *Type) Alignment() int64 { - Dowidth(t) + CalcSize(t) return int64(t.Align) } func (t *Type) SimpleString() string { - return t.Etype.String() + return t.kind.String() } // Cmp is a comparison between values a and b. @@ -1001,31 +1116,31 @@ func (t *Type) cmp(x *Type) Cmp { return CMPgt } - if t.Etype != x.Etype { - return cmpForNe(t.Etype < x.Etype) + if t.kind != x.kind { + return cmpForNe(t.kind < x.kind) } - if t.Sym != nil || x.Sym != nil { + if t.sym != nil || x.sym != nil { // Special case: we keep byte and uint8 separate // for error messages. Treat them as equal. - switch t.Etype { + switch t.kind { case TUINT8: - if (t == Types[TUINT8] || t == Bytetype) && (x == Types[TUINT8] || x == Bytetype) { + if (t == Types[TUINT8] || t == ByteType) && (x == Types[TUINT8] || x == ByteType) { return CMPeq } case TINT32: - if (t == Types[Runetype.Etype] || t == Runetype) && (x == Types[Runetype.Etype] || x == Runetype) { + if (t == Types[RuneType.kind] || t == RuneType) && (x == Types[RuneType.kind] || x == RuneType) { return CMPeq } } } - if c := t.Sym.cmpsym(x.Sym); c != CMPeq { + if c := t.sym.cmpsym(x.sym); c != CMPeq { return c } - if x.Sym != nil { + if x.sym != nil { // Syms non-nil, if vargens match then equal. if t.Vargen != x.Vargen { return cmpForNe(t.Vargen < x.Vargen) @@ -1034,7 +1149,7 @@ func (t *Type) cmp(x *Type) Cmp { } // both syms nil, look at structure below. - switch t.Etype { + switch t.kind { case TBOOL, TFLOAT32, TFLOAT64, TCOMPLEX64, TCOMPLEX128, TUNSAFEPTR, TUINTPTR, TINT8, TINT16, TINT32, TINT64, TINT, TUINT8, TUINT16, TUINT32, TUINT64, TUINT: return CMPeq @@ -1134,8 +1249,8 @@ func (t *Type) cmp(x *Type) Cmp { return CMPeq case TINTER: - tfs := t.FieldSlice() - xfs := x.FieldSlice() + tfs := t.AllMethods().Slice() + xfs := x.AllMethods().Slice() for i := 0; i < len(tfs) && i < len(xfs); i++ { t1, x1 := tfs[i], xfs[i] if c := t1.Sym.cmpsym(x1.Sym); c != CMPeq { @@ -1191,15 +1306,15 @@ func (t *Type) cmp(x *Type) Cmp { } // IsKind reports whether t is a Type of the specified kind. -func (t *Type) IsKind(et EType) bool { - return t != nil && t.Etype == et +func (t *Type) IsKind(et Kind) bool { + return t != nil && t.kind == et } func (t *Type) IsBoolean() bool { - return t.Etype == TBOOL + return t.kind == TBOOL } -var unsignedEType = [...]EType{ +var unsignedEType = [...]Kind{ TINT8: TUINT8, TUINT8: TUINT8, TINT16: TUINT16, @@ -1216,54 +1331,62 @@ var unsignedEType = [...]EType{ // ToUnsigned returns the unsigned equivalent of integer type t. func (t *Type) ToUnsigned() *Type { if !t.IsInteger() { - Fatalf("unsignedType(%v)", t) + base.Fatalf("unsignedType(%v)", t) } - return Types[unsignedEType[t.Etype]] + return Types[unsignedEType[t.kind]] } func (t *Type) IsInteger() bool { - switch t.Etype { + switch t.kind { case TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64, TUINT64, TINT, TUINT, TUINTPTR: return true } - return false + return t == UntypedInt || t == UntypedRune } func (t *Type) IsSigned() bool { - switch t.Etype { + switch t.kind { case TINT8, TINT16, TINT32, TINT64, TINT: return true } return false } +func (t *Type) IsUnsigned() bool { + switch t.kind { + case TUINT8, TUINT16, TUINT32, TUINT64, TUINT, TUINTPTR: + return true + } + return false +} + func (t *Type) IsFloat() bool { - return t.Etype == TFLOAT32 || t.Etype == TFLOAT64 + return t.kind == TFLOAT32 || t.kind == TFLOAT64 || t == UntypedFloat } func (t *Type) IsComplex() bool { - return t.Etype == TCOMPLEX64 || t.Etype == TCOMPLEX128 + return t.kind == TCOMPLEX64 || t.kind == TCOMPLEX128 || t == UntypedComplex } // IsPtr reports whether t is a regular Go pointer type. // This does not include unsafe.Pointer. func (t *Type) IsPtr() bool { - return t.Etype == TPTR + return t.kind == TPTR } // IsPtrElem reports whether t is the element of a pointer (to t). func (t *Type) IsPtrElem() bool { - return t.Cache.ptr != nil + return t.cache.ptr != nil } // IsUnsafePtr reports whether t is an unsafe pointer. func (t *Type) IsUnsafePtr() bool { - return t.Etype == TUNSAFEPTR + return t.kind == TUNSAFEPTR } // IsUintptr reports whether t is an uintptr. func (t *Type) IsUintptr() bool { - return t.Etype == TUINTPTR + return t.kind == TUINTPTR } // IsPtrShaped reports whether t is represented by a single machine pointer. @@ -1272,50 +1395,64 @@ func (t *Type) IsUintptr() bool { // that consist of a single pointer shaped type. // TODO(mdempsky): Should it? See golang.org/issue/15028. func (t *Type) IsPtrShaped() bool { - return t.Etype == TPTR || t.Etype == TUNSAFEPTR || - t.Etype == TMAP || t.Etype == TCHAN || t.Etype == TFUNC + return t.kind == TPTR || t.kind == TUNSAFEPTR || + t.kind == TMAP || t.kind == TCHAN || t.kind == TFUNC } // HasNil reports whether the set of values determined by t includes nil. func (t *Type) HasNil() bool { - switch t.Etype { - case TCHAN, TFUNC, TINTER, TMAP, TPTR, TSLICE, TUNSAFEPTR: + switch t.kind { + case TCHAN, TFUNC, TINTER, TMAP, TNIL, TPTR, TSLICE, TUNSAFEPTR: return true } return false } func (t *Type) IsString() bool { - return t.Etype == TSTRING + return t.kind == TSTRING } func (t *Type) IsMap() bool { - return t.Etype == TMAP + return t.kind == TMAP } func (t *Type) IsChan() bool { - return t.Etype == TCHAN + return t.kind == TCHAN } func (t *Type) IsSlice() bool { - return t.Etype == TSLICE + return t.kind == TSLICE } func (t *Type) IsArray() bool { - return t.Etype == TARRAY + return t.kind == TARRAY } func (t *Type) IsStruct() bool { - return t.Etype == TSTRUCT + return t.kind == TSTRUCT } func (t *Type) IsInterface() bool { - return t.Etype == TINTER + return t.kind == TINTER } // IsEmptyInterface reports whether t is an empty interface type. func (t *Type) IsEmptyInterface() bool { - return t.IsInterface() && t.NumFields() == 0 + return t.IsInterface() && t.AllMethods().Len() == 0 +} + +// IsScalar reports whether 't' is a scalar Go type, e.g. +// bool/int/float/complex. Note that struct and array types consisting +// of a single scalar element are not considered scalar, likewise +// pointer types are also not considered scalar. +func (t *Type) IsScalar() bool { + switch t.kind { + case TBOOL, TINT8, TUINT8, TINT16, TUINT16, TINT32, + TUINT32, TINT64, TUINT64, TINT, TUINT, + TUINTPTR, TCOMPLEX64, TCOMPLEX128, TFLOAT32, TFLOAT64: + return true + } + return false } func (t *Type) PtrTo() *Type { @@ -1323,10 +1460,13 @@ func (t *Type) PtrTo() *Type { } func (t *Type) NumFields() int { + if t.kind == TRESULTS { + return len(t.Extra.(*Results).Types) + } return t.Fields().Len() } func (t *Type) FieldType(i int) *Type { - if t.Etype == TTUPLE { + if t.kind == TTUPLE { switch i { case 0: return t.Extra.(*Tuple).first @@ -1336,7 +1476,7 @@ func (t *Type) FieldType(i int) *Type { panic("bad tuple index") } } - if t.Etype == TRESULTS { + if t.kind == TRESULTS { return t.Extra.(*Results).Types[i] } return t.Field(i).Type @@ -1367,10 +1507,10 @@ const ( // (and their comprised elements) are excluded from the count. // struct { x, y [3]int } has six components; [10]struct{ x, y string } has twenty. func (t *Type) NumComponents(countBlank componentsIncludeBlankFields) int64 { - switch t.Etype { + switch t.kind { case TSTRUCT: if t.IsFuncArgStruct() { - Fatalf("NumComponents func arg struct") + base.Fatalf("NumComponents func arg struct") } var n int64 for _, f := range t.FieldSlice() { @@ -1390,10 +1530,10 @@ func (t *Type) NumComponents(countBlank componentsIncludeBlankFields) int64 { // if there is exactly one. Otherwise, it returns nil. // Components are counted as in NumComponents, including blank fields. func (t *Type) SoleComponent() *Type { - switch t.Etype { + switch t.kind { case TSTRUCT: if t.IsFuncArgStruct() { - Fatalf("SoleComponent func arg struct") + base.Fatalf("SoleComponent func arg struct") } if t.NumFields() != 1 { return nil @@ -1416,10 +1556,10 @@ func (t *Type) ChanDir() ChanDir { } func (t *Type) IsMemory() bool { - if t == TypeMem || t.Etype == TTUPLE && t.Extra.(*Tuple).second == TypeMem { + if t == TypeMem || t.kind == TTUPLE && t.Extra.(*Tuple).second == TypeMem { return true } - if t.Etype == TRESULTS { + if t.kind == TRESULTS { if types := t.Extra.(*Results).Types; len(types) > 0 && types[len(types)-1] == TypeMem { return true } @@ -1428,8 +1568,8 @@ func (t *Type) IsMemory() bool { } func (t *Type) IsFlags() bool { return t == TypeFlags } func (t *Type) IsVoid() bool { return t == TypeVoid } -func (t *Type) IsTuple() bool { return t.Etype == TTUPLE } -func (t *Type) IsResults() bool { return t.Etype == TRESULTS } +func (t *Type) IsTuple() bool { return t.kind == TTUPLE } +func (t *Type) IsResults() bool { return t.kind == TRESULTS } // IsUntyped reports whether t is an untyped type. func (t *Type) IsUntyped() bool { @@ -1439,7 +1579,7 @@ func (t *Type) IsUntyped() bool { if t == UntypedString || t == UntypedBool { return true } - switch t.Etype { + switch t.kind { case TNIL, TIDEAL: return true } @@ -1449,7 +1589,7 @@ func (t *Type) IsUntyped() bool { // HasPointers reports whether t contains a heap pointer. // Note that this function ignores pointers to go:notinheap types. func (t *Type) HasPointers() bool { - switch t.Etype { + switch t.kind { case TINT, TUINT, TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64, TUINT64, TUINTPTR, TFLOAT32, TFLOAT64, TCOMPLEX64, TCOMPLEX128, TBOOL, TSSA: return false @@ -1488,10 +1628,6 @@ func (t *Type) HasPointers() bool { return true } -func (t *Type) Symbol() *obj.LSym { - return TypeLinkSym(t) -} - // Tie returns 'T' if t is a concrete type, // 'I' if t is an interface type, and 'E' if t is an empty interface type. // It is used to build calls to the conv* and assert* runtime routines. @@ -1517,9 +1653,394 @@ func FakeRecvType() *Type { var ( // TSSA types. HasPointers assumes these are pointer-free. - TypeInvalid = newSSA("invalid") - TypeMem = newSSA("mem") - TypeFlags = newSSA("flags") - TypeVoid = newSSA("void") - TypeInt128 = newSSA("int128") + TypeInvalid = newSSA("invalid") + TypeMem = newSSA("mem") + TypeFlags = newSSA("flags") + TypeVoid = newSSA("void") + TypeInt128 = newSSA("int128") + TypeResultMem = newResults([]*Type{TypeMem}) +) + +// NewNamed returns a new named type for the given type name. obj should be an +// ir.Name. The new type is incomplete (marked as TFORW kind), and the underlying +// type should be set later via SetUnderlying(). References to the type are +// maintained until the type is filled in, so those references can be updated when +// the type is complete. +func NewNamed(obj Object) *Type { + t := New(TFORW) + t.sym = obj.Sym() + t.nod = obj + return t +} + +// Obj returns the canonical type name node for a named type t, nil for an unnamed type. +func (t *Type) Obj() Object { + if t.sym != nil { + return t.nod + } + return nil +} + +// SetUnderlying sets the underlying type. SetUnderlying automatically updates any +// types that were waiting for this type to be completed. +func (t *Type) SetUnderlying(underlying *Type) { + if underlying.kind == TFORW { + // This type isn't computed yet; when it is, update n. + underlying.ForwardType().Copyto = append(underlying.ForwardType().Copyto, t) + return + } + + ft := t.ForwardType() + + // TODO(mdempsky): Fix Type rekinding. + t.kind = underlying.kind + t.Extra = underlying.Extra + t.Width = underlying.Width + t.Align = underlying.Align + t.underlying = underlying.underlying + + if underlying.NotInHeap() { + t.SetNotInHeap(true) + } + if underlying.Broke() { + t.SetBroke(true) + } + if underlying.HasTParam() { + t.SetHasTParam(true) + } + + // spec: "The declared type does not inherit any methods bound + // to the existing type, but the method set of an interface + // type [...] remains unchanged." + if t.IsInterface() { + t.methods = underlying.methods + t.allMethods = underlying.allMethods + } + + // Update types waiting on this type. + for _, w := range ft.Copyto { + w.SetUnderlying(t) + } + + // Double-check use of type as embedded type. + if ft.Embedlineno.IsKnown() { + if t.IsPtr() || t.IsUnsafePtr() { + base.ErrorfAt(ft.Embedlineno, "embedded type cannot be a pointer") + } + } +} + +func fieldsHasTParam(fields []*Field) bool { + for _, f := range fields { + if f.Type != nil && f.Type.HasTParam() { + return true + } + } + return false +} + +// NewBasic returns a new basic type of the given kind. +func NewBasic(kind Kind, obj Object) *Type { + t := New(kind) + t.sym = obj.Sym() + t.nod = obj + return t +} + +// NewInterface returns a new interface for the given methods and +// embedded types. Embedded types are specified as fields with no Sym. +func NewInterface(pkg *Pkg, methods []*Field) *Type { + t := New(TINTER) + t.SetInterface(methods) + for _, f := range methods { + // f.Type could be nil for a broken interface declaration + if f.Type != nil && f.Type.HasTParam() { + t.SetHasTParam(true) + break + } + } + if anyBroke(methods) { + t.SetBroke(true) + } + t.Extra.(*Interface).pkg = pkg + return t +} + +// NewTypeParam returns a new type param. +func NewTypeParam(pkg *Pkg) *Type { + t := New(TTYPEPARAM) + t.Extra.(*Interface).pkg = pkg + t.SetHasTParam(true) + return t +} + +const BOGUS_FUNARG_OFFSET = -1000000000 + +func unzeroFieldOffsets(f []*Field) { + for i := range f { + f[i].Offset = BOGUS_FUNARG_OFFSET // This will cause an explosion if it is not corrected + } +} + +// NewSignature returns a new function type for the given receiver, +// parameters, results, and type parameters, any of which may be nil. +func NewSignature(pkg *Pkg, recv *Field, tparams, params, results []*Field) *Type { + var recvs []*Field + if recv != nil { + recvs = []*Field{recv} + } + + t := New(TFUNC) + ft := t.FuncType() + + funargs := func(fields []*Field, funarg Funarg) *Type { + s := NewStruct(NoPkg, fields) + s.StructType().Funarg = funarg + if s.Broke() { + t.SetBroke(true) + } + return s + } + + if recv != nil { + recv.Offset = BOGUS_FUNARG_OFFSET + } + unzeroFieldOffsets(params) + unzeroFieldOffsets(results) + ft.Receiver = funargs(recvs, FunargRcvr) + // TODO(danscales): just use nil here (save memory) if no tparams + ft.TParams = funargs(tparams, FunargTparams) + ft.Params = funargs(params, FunargParams) + ft.Results = funargs(results, FunargResults) + ft.pkg = pkg + if len(tparams) > 0 || fieldsHasTParam(recvs) || fieldsHasTParam(params) || + fieldsHasTParam(results) { + t.SetHasTParam(true) + } + + return t +} + +// NewStruct returns a new struct with the given fields. +func NewStruct(pkg *Pkg, fields []*Field) *Type { + t := New(TSTRUCT) + t.SetFields(fields) + if anyBroke(fields) { + t.SetBroke(true) + } + t.Extra.(*Struct).pkg = pkg + if fieldsHasTParam(fields) { + t.SetHasTParam(true) + } + return t +} + +func anyBroke(fields []*Field) bool { + for _, f := range fields { + if f.Broke() { + return true + } + } + return false +} + +var ( + IsInt [NTYPE]bool + IsFloat [NTYPE]bool + IsComplex [NTYPE]bool + IsSimple [NTYPE]bool +) + +var IsOrdered [NTYPE]bool + +// IsReflexive reports whether t has a reflexive equality operator. +// That is, if x==x for all x of type t. +func IsReflexive(t *Type) bool { + switch t.Kind() { + case TBOOL, + TINT, + TUINT, + TINT8, + TUINT8, + TINT16, + TUINT16, + TINT32, + TUINT32, + TINT64, + TUINT64, + TUINTPTR, + TPTR, + TUNSAFEPTR, + TSTRING, + TCHAN: + return true + + case TFLOAT32, + TFLOAT64, + TCOMPLEX64, + TCOMPLEX128, + TINTER: + return false + + case TARRAY: + return IsReflexive(t.Elem()) + + case TSTRUCT: + for _, t1 := range t.Fields().Slice() { + if !IsReflexive(t1.Type) { + return false + } + } + return true + + default: + base.Fatalf("bad type for map key: %v", t) + return false + } +} + +// Can this type be stored directly in an interface word? +// Yes, if the representation is a single pointer. +func IsDirectIface(t *Type) bool { + if t.Broke() { + return false + } + + switch t.Kind() { + case TPTR: + // Pointers to notinheap types must be stored indirectly. See issue 42076. + return !t.Elem().NotInHeap() + case TCHAN, + TMAP, + TFUNC, + TUNSAFEPTR: + return true + + case TARRAY: + // Array of 1 direct iface type can be direct. + return t.NumElem() == 1 && IsDirectIface(t.Elem()) + + case TSTRUCT: + // Struct with 1 field of direct iface type can be direct. + return t.NumFields() == 1 && IsDirectIface(t.Field(0).Type) + } + + return false +} + +// IsInterfaceMethod reports whether (field) m is +// an interface method. Such methods have the +// special receiver type types.FakeRecvType(). +func IsInterfaceMethod(f *Type) bool { + return f.Recv().Type == FakeRecvType() +} + +// IsMethodApplicable reports whether method m can be called on a +// value of type t. This is necessary because we compute a single +// method set for both T and *T, but some *T methods are not +// applicable to T receivers. +func IsMethodApplicable(t *Type, m *Field) bool { + return t.IsPtr() || !m.Type.Recv().Type.IsPtr() || IsInterfaceMethod(m.Type) || m.Embedded == 2 +} + +// IsRuntimePkg reports whether p is package runtime. +func IsRuntimePkg(p *Pkg) bool { + if base.Flag.CompilingRuntime && p == LocalPkg { + return true + } + return p.Path == "runtime" +} + +// IsReflectPkg reports whether p is package reflect. +func IsReflectPkg(p *Pkg) bool { + if p == LocalPkg { + return base.Ctxt.Pkgpath == "reflect" + } + return p.Path == "reflect" +} + +// ReceiverBaseType returns the underlying type, if any, +// that owns methods with receiver parameter t. +// The result is either a named type or an anonymous struct. +func ReceiverBaseType(t *Type) *Type { + if t == nil { + return nil + } + + // Strip away pointer if it's there. + if t.IsPtr() { + if t.Sym() != nil { + return nil + } + t = t.Elem() + if t == nil { + return nil + } + } + + // Must be a named type or anonymous struct. + if t.Sym() == nil && !t.IsStruct() { + return nil + } + + // Check types. + if IsSimple[t.Kind()] { + return t + } + switch t.Kind() { + case TARRAY, TCHAN, TFUNC, TMAP, TSLICE, TSTRING, TSTRUCT: + return t + } + return nil +} + +func FloatForComplex(t *Type) *Type { + switch t.Kind() { + case TCOMPLEX64: + return Types[TFLOAT32] + case TCOMPLEX128: + return Types[TFLOAT64] + } + base.Fatalf("unexpected type: %v", t) + return nil +} + +func ComplexForFloat(t *Type) *Type { + switch t.Kind() { + case TFLOAT32: + return Types[TCOMPLEX64] + case TFLOAT64: + return Types[TCOMPLEX128] + } + base.Fatalf("unexpected type: %v", t) + return nil +} + +func TypeSym(t *Type) *Sym { + return TypeSymLookup(TypeSymName(t)) +} + +func TypeSymLookup(name string) *Sym { + typepkgmu.Lock() + s := typepkg.Lookup(name) + typepkgmu.Unlock() + return s +} + +func TypeSymName(t *Type) string { + name := t.ShortString() + // Use a separate symbol name for Noalg types for #17752. + if TypeHasNoAlg(t) { + name = "noalg." + name + } + return name +} + +// Fake package for runtime type info (headers) +// Don't access directly, use typeLookup below. +var ( + typepkgmu sync.Mutex // protects typepkg lookups + typepkg = NewPkg("type", "type") ) + +var SimType [NTYPE]Kind diff --git a/src/cmd/compile/internal/types/utils.go b/src/cmd/compile/internal/types/utils.go index e8b107381805450e39c2d9d5d71db65381b1649b..f9f629ca3ea6cf5bd878387b6cab1a7892e1685b 100644 --- a/src/cmd/compile/internal/types/utils.go +++ b/src/cmd/compile/internal/types/utils.go @@ -4,64 +4,8 @@ package types -import ( - "cmd/internal/obj" - "fmt" -) - const BADWIDTH = -1000000000 -// The following variables must be initialized early by the frontend. -// They are here to break import cycles. -// TODO(gri) eliminate these dependencies. -var ( - Widthptr int - Dowidth func(*Type) - Fatalf func(string, ...interface{}) - Sconv func(*Sym, int, int) string // orig: func sconv(s *Sym, flag FmtFlag, mode fmtMode) string - Tconv func(*Type, int, int) string // orig: func tconv(t *Type, flag FmtFlag, mode fmtMode) string - FormatSym func(*Sym, fmt.State, rune, int) // orig: func symFormat(sym *Sym, s fmt.State, verb rune, mode fmtMode) - FormatType func(*Type, fmt.State, rune, int) // orig: func typeFormat(t *Type, s fmt.State, verb rune, mode fmtMode) - TypeLinkSym func(*Type) *obj.LSym - Ctxt *obj.Link - - FmtLeft int - FmtUnsigned int - FErr int -) - -func (s *Sym) String() string { - return Sconv(s, 0, FErr) -} - -func (sym *Sym) Format(s fmt.State, verb rune) { - FormatSym(sym, s, verb, FErr) -} - -func (t *Type) String() string { - // The implementation of tconv (including typefmt and fldconv) - // must handle recursive types correctly. - return Tconv(t, 0, FErr) -} - -// ShortString generates a short description of t. -// It is used in autogenerated method names, reflection, -// and itab names. -func (t *Type) ShortString() string { - return Tconv(t, FmtLeft, FErr) -} - -// LongString generates a complete description of t. -// It is useful for reflection, -// or when a unique fingerprint or hash of a type is required. -func (t *Type) LongString() string { - return Tconv(t, FmtLeft|FmtUnsigned, FErr) -} - -func (t *Type) Format(s fmt.State, verb rune) { - FormatType(t, s, verb, FErr) -} - type bitset8 uint8 func (f *bitset8) set(mask uint8, b bool) { diff --git a/src/cmd/compile/internal/types2/api.go b/src/cmd/compile/internal/types2/api.go new file mode 100644 index 0000000000000000000000000000000000000000..2939dcc0bdf27356ae1c5a99abfd44c836004060 --- /dev/null +++ b/src/cmd/compile/internal/types2/api.go @@ -0,0 +1,434 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package types declares the data types and implements +// the algorithms for type-checking of Go packages. Use +// Config.Check to invoke the type checker for a package. +// Alternatively, create a new type checker with NewChecker +// and invoke it incrementally by calling Checker.Files. +// +// Type-checking consists of several interdependent phases: +// +// Name resolution maps each identifier (syntax.Name) in the program to the +// language object (Object) it denotes. +// Use Info.{Defs,Uses,Implicits} for the results of name resolution. +// +// Constant folding computes the exact constant value (constant.Value) +// for every expression (syntax.Expr) that is a compile-time constant. +// Use Info.Types[expr].Value for the results of constant folding. +// +// Type inference computes the type (Type) of every expression (syntax.Expr) +// and checks for compliance with the language specification. +// Use Info.Types[expr].Type for the results of type inference. +// +package types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "go/constant" +) + +// An Error describes a type-checking error; it implements the error interface. +// A "soft" error is an error that still permits a valid interpretation of a +// package (such as "unused variable"); "hard" errors may lead to unpredictable +// behavior if ignored. +type Error struct { + Pos syntax.Pos // error position + Msg string // default error message, user-friendly + Full string // full error message, for debugging (may contain internal details) + Soft bool // if set, error is "soft" +} + +// Error returns an error string formatted as follows: +// filename:line:column: message +func (err Error) Error() string { + return fmt.Sprintf("%s: %s", err.Pos, err.Msg) +} + +// FullError returns an error string like Error, buy it may contain +// type-checker internal details such as subscript indices for type +// parameters and more. Useful for debugging. +func (err Error) FullError() string { + return fmt.Sprintf("%s: %s", err.Pos, err.Full) +} + +// An Importer resolves import paths to Packages. +// +// CAUTION: This interface does not support the import of locally +// vendored packages. See https://golang.org/s/go15vendor. +// If possible, external implementations should implement ImporterFrom. +type Importer interface { + // Import returns the imported package for the given import path. + // The semantics is like for ImporterFrom.ImportFrom except that + // dir and mode are ignored (since they are not present). + Import(path string) (*Package, error) +} + +// ImportMode is reserved for future use. +type ImportMode int + +// An ImporterFrom resolves import paths to packages; it +// supports vendoring per https://golang.org/s/go15vendor. +// Use go/importer to obtain an ImporterFrom implementation. +type ImporterFrom interface { + // Importer is present for backward-compatibility. Calling + // Import(path) is the same as calling ImportFrom(path, "", 0); + // i.e., locally vendored packages may not be found. + // The types package does not call Import if an ImporterFrom + // is present. + Importer + + // ImportFrom returns the imported package for the given import + // path when imported by a package file located in dir. + // If the import failed, besides returning an error, ImportFrom + // is encouraged to cache and return a package anyway, if one + // was created. This will reduce package inconsistencies and + // follow-on type checker errors due to the missing package. + // The mode value must be 0; it is reserved for future use. + // Two calls to ImportFrom with the same path and dir must + // return the same package. + ImportFrom(path, dir string, mode ImportMode) (*Package, error) +} + +// A Config specifies the configuration for type checking. +// The zero value for Config is a ready-to-use default configuration. +type Config struct { + // GoVersion describes the accepted Go language version. The string + // must follow the format "go%d.%d" (e.g. "go1.12") or ist must be + // empty; an empty string indicates the latest language version. + // If the format is invalid, invoking the type checker will cause a + // panic. + GoVersion string + + // If IgnoreFuncBodies is set, function bodies are not + // type-checked. + IgnoreFuncBodies bool + + // If FakeImportC is set, `import "C"` (for packages requiring Cgo) + // declares an empty "C" package and errors are omitted for qualified + // identifiers referring to package C (which won't find an object). + // This feature is intended for the standard library cmd/api tool. + // + // Caution: Effects may be unpredictable due to follow-on errors. + // Do not use casually! + FakeImportC bool + + // If IgnoreLabels is set, correct label use is not checked. + // TODO(gri) Consolidate label checking and remove this flag. + IgnoreLabels bool + + // If CompilerErrorMessages is set, errors are reported using + // cmd/compile error strings to match $GOROOT/test errors. + // TODO(gri) Consolidate error messages and remove this flag. + CompilerErrorMessages bool + + // If go115UsesCgo is set, the type checker expects the + // _cgo_gotypes.go file generated by running cmd/cgo to be + // provided as a package source file. Qualified identifiers + // referring to package C will be resolved to cgo-provided + // declarations within _cgo_gotypes.go. + // + // It is an error to set both FakeImportC and go115UsesCgo. + go115UsesCgo bool + + // If Trace is set, a debug trace is printed to stdout. + Trace bool + + // If Error != nil, it is called with each error found + // during type checking; err has dynamic type Error. + // Secondary errors (for instance, to enumerate all types + // involved in an invalid recursive type declaration) have + // error strings that start with a '\t' character. + // If Error == nil, type-checking stops with the first + // error found. + Error func(err error) + + // An importer is used to import packages referred to from + // import declarations. + // If the installed importer implements ImporterFrom, the type + // checker calls ImportFrom instead of Import. + // The type checker reports an error if an importer is needed + // but none was installed. + Importer Importer + + // If Sizes != nil, it provides the sizing functions for package unsafe. + // Otherwise SizesFor("gc", "amd64") is used instead. + Sizes Sizes + + // If DisableUnusedImportCheck is set, packages are not checked + // for unused imports. + DisableUnusedImportCheck bool +} + +func srcimporter_setUsesCgo(conf *Config) { + conf.go115UsesCgo = true +} + +// Info holds result type information for a type-checked package. +// Only the information for which a map is provided is collected. +// If the package has type errors, the collected information may +// be incomplete. +type Info struct { + // Types maps expressions to their types, and for constant + // expressions, also their values. Invalid expressions are + // omitted. + // + // For (possibly parenthesized) identifiers denoting built-in + // functions, the recorded signatures are call-site specific: + // if the call result is not a constant, the recorded type is + // an argument-specific signature. Otherwise, the recorded type + // is invalid. + // + // The Types map does not record the type of every identifier, + // only those that appear where an arbitrary expression is + // permitted. For instance, the identifier f in a selector + // expression x.f is found only in the Selections map, the + // identifier z in a variable declaration 'var z int' is found + // only in the Defs map, and identifiers denoting packages in + // qualified identifiers are collected in the Uses map. + Types map[syntax.Expr]TypeAndValue + + // Inferred maps calls of parameterized functions that use + // type inference to the inferred type arguments and signature + // of the function called. The recorded "call" expression may be + // an *ast.CallExpr (as in f(x)), or an *ast.IndexExpr (s in f[T]). + Inferred map[syntax.Expr]Inferred + + // Defs maps identifiers to the objects they define (including + // package names, dots "." of dot-imports, and blank "_" identifiers). + // For identifiers that do not denote objects (e.g., the package name + // in package clauses, or symbolic variables t in t := x.(type) of + // type switch headers), the corresponding objects are nil. + // + // For an embedded field, Defs returns the field *Var it defines. + // + // Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos() + Defs map[*syntax.Name]Object + + // Uses maps identifiers to the objects they denote. + // + // For an embedded field, Uses returns the *TypeName it denotes. + // + // Invariant: Uses[id].Pos() != id.Pos() + Uses map[*syntax.Name]Object + + // Implicits maps nodes to their implicitly declared objects, if any. + // The following node and object types may appear: + // + // node declared object + // + // *syntax.ImportDecl *PkgName for imports without renames + // *syntax.CaseClause type-specific *Var for each type switch case clause (incl. default) + // *syntax.Field anonymous parameter *Var (incl. unnamed results) + // + Implicits map[syntax.Node]Object + + // Selections maps selector expressions (excluding qualified identifiers) + // to their corresponding selections. + Selections map[*syntax.SelectorExpr]*Selection + + // Scopes maps syntax.Nodes to the scopes they define. Package scopes are not + // associated with a specific node but with all files belonging to a package. + // Thus, the package scope can be found in the type-checked Package object. + // Scopes nest, with the Universe scope being the outermost scope, enclosing + // the package scope, which contains (one or more) files scopes, which enclose + // function scopes which in turn enclose statement and function literal scopes. + // Note that even though package-level functions are declared in the package + // scope, the function scopes are embedded in the file scope of the file + // containing the function declaration. + // + // The following node types may appear in Scopes: + // + // *syntax.File + // *syntax.FuncType + // *syntax.BlockStmt + // *syntax.IfStmt + // *syntax.SwitchStmt + // *syntax.CaseClause + // *syntax.CommClause + // *syntax.ForStmt + // + Scopes map[syntax.Node]*Scope + + // InitOrder is the list of package-level initializers in the order in which + // they must be executed. Initializers referring to variables related by an + // initialization dependency appear in topological order, the others appear + // in source order. Variables without an initialization expression do not + // appear in this list. + InitOrder []*Initializer +} + +// TypeOf returns the type of expression e, or nil if not found. +// Precondition: the Types, Uses and Defs maps are populated. +// +func (info *Info) TypeOf(e syntax.Expr) Type { + if t, ok := info.Types[e]; ok { + return t.Type + } + if id, _ := e.(*syntax.Name); id != nil { + if obj := info.ObjectOf(id); obj != nil { + return obj.Type() + } + } + return nil +} + +// ObjectOf returns the object denoted by the specified id, +// or nil if not found. +// +// If id is an embedded struct field, ObjectOf returns the field (*Var) +// it defines, not the type (*TypeName) it uses. +// +// Precondition: the Uses and Defs maps are populated. +// +func (info *Info) ObjectOf(id *syntax.Name) Object { + if obj := info.Defs[id]; obj != nil { + return obj + } + return info.Uses[id] +} + +// TypeAndValue reports the type and value (for constants) +// of the corresponding expression. +type TypeAndValue struct { + mode operandMode + Type Type + Value constant.Value +} + +// IsVoid reports whether the corresponding expression +// is a function call without results. +func (tv TypeAndValue) IsVoid() bool { + return tv.mode == novalue +} + +// IsType reports whether the corresponding expression specifies a type. +func (tv TypeAndValue) IsType() bool { + return tv.mode == typexpr +} + +// IsBuiltin reports whether the corresponding expression denotes +// a (possibly parenthesized) built-in function. +func (tv TypeAndValue) IsBuiltin() bool { + return tv.mode == builtin +} + +// IsValue reports whether the corresponding expression is a value. +// Builtins are not considered values. Constant values have a non- +// nil Value. +func (tv TypeAndValue) IsValue() bool { + switch tv.mode { + case constant_, variable, mapindex, value, nilvalue, commaok, commaerr: + return true + } + return false +} + +// IsNil reports whether the corresponding expression denotes the +// predeclared value nil. Depending on context, it may have been +// given a type different from UntypedNil. +func (tv TypeAndValue) IsNil() bool { + return tv.mode == nilvalue +} + +// Addressable reports whether the corresponding expression +// is addressable (https://golang.org/ref/spec#Address_operators). +func (tv TypeAndValue) Addressable() bool { + return tv.mode == variable +} + +// Assignable reports whether the corresponding expression +// is assignable to (provided a value of the right type). +func (tv TypeAndValue) Assignable() bool { + return tv.mode == variable || tv.mode == mapindex +} + +// HasOk reports whether the corresponding expression may be +// used on the rhs of a comma-ok assignment. +func (tv TypeAndValue) HasOk() bool { + return tv.mode == commaok || tv.mode == mapindex +} + +// Inferred reports the inferred type arguments and signature +// for a parameterized function call that uses type inference. +type Inferred struct { + Targs []Type + Sig *Signature +} + +// An Initializer describes a package-level variable, or a list of variables in case +// of a multi-valued initialization expression, and the corresponding initialization +// expression. +type Initializer struct { + Lhs []*Var // var Lhs = Rhs + Rhs syntax.Expr +} + +func (init *Initializer) String() string { + var buf bytes.Buffer + for i, lhs := range init.Lhs { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(lhs.Name()) + } + buf.WriteString(" = ") + syntax.Fprint(&buf, init.Rhs, syntax.ShortForm) + return buf.String() +} + +// Check type-checks a package and returns the resulting package object and +// the first error if any. Additionally, if info != nil, Check populates each +// of the non-nil maps in the Info struct. +// +// The package is marked as complete if no errors occurred, otherwise it is +// incomplete. See Config.Error for controlling behavior in the presence of +// errors. +// +// The package is specified by a list of *syntax.Files and corresponding +// file set, and the package path the package is identified with. +// The clean path must not be empty or dot ("."). +func (conf *Config) Check(path string, files []*syntax.File, info *Info) (*Package, error) { + pkg := NewPackage(path, "") + return pkg, NewChecker(conf, pkg, info).Files(files) +} + +// AssertableTo reports whether a value of type V can be asserted to have type T. +func AssertableTo(V *Interface, T Type) bool { + m, _ := (*Checker)(nil).assertableTo(V, T) + return m == nil +} + +// AssignableTo reports whether a value of type V is assignable to a variable of type T. +func AssignableTo(V, T Type) bool { + x := operand{mode: value, typ: V} + ok, _ := x.assignableTo(nil, T, nil) // check not needed for non-constant x + return ok +} + +// ConvertibleTo reports whether a value of type V is convertible to a value of type T. +func ConvertibleTo(V, T Type) bool { + x := operand{mode: value, typ: V} + return x.convertibleTo(nil, T) // check not needed for non-constant x +} + +// Implements reports whether type V implements interface T. +func Implements(V Type, T *Interface) bool { + f, _ := MissingMethod(V, T, true) + return f == nil +} + +// Identical reports whether x and y are identical types. +// Receivers of Signature types are ignored. +func Identical(x, y Type) bool { + return (*Checker)(nil).identical(x, y) +} + +// IdenticalIgnoreTags reports whether x and y are identical types if tags are ignored. +// Receivers of Signature types are ignored. +func IdenticalIgnoreTags(x, y Type) bool { + return (*Checker)(nil).identicalIgnoreTags(x, y) +} diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go new file mode 100644 index 0000000000000000000000000000000000000000..873390c1e9280d6b961ad02592e52e16f39096c3 --- /dev/null +++ b/src/cmd/compile/internal/types2/api_test.go @@ -0,0 +1,1849 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2_test + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "internal/testenv" + "reflect" + "regexp" + "strings" + "testing" + + . "cmd/compile/internal/types2" +) + +func unimplemented() { + panic("unimplemented") +} + +// genericPkg is a source prefix for packages that contain generic code. +const genericPkg = "package generic_" + +// brokenPkg is a source prefix for packages that are not expected to parse +// or type-check cleanly. They are always parsed assuming that they contain +// generic code. +const brokenPkg = "package broken_" + +func parseSrc(path, src string) (*syntax.File, error) { + var mode syntax.Mode + if strings.HasPrefix(src, genericPkg) || strings.HasPrefix(src, brokenPkg) { + mode = syntax.AllowGenerics + } + errh := func(error) {} // dummy error handler so that parsing continues in presence of errors + return syntax.Parse(syntax.NewFileBase(path), strings.NewReader(src), errh, nil, mode) +} + +func pkgFor(path, source string, info *Info) (*Package, error) { + f, err := parseSrc(path, source) + if err != nil { + return nil, err + } + conf := Config{Importer: defaultImporter()} + return conf.Check(f.PkgName.Value, []*syntax.File{f}, info) +} + +func mustTypecheck(t *testing.T, path, source string, info *Info) string { + pkg, err := pkgFor(path, source, info) + if err != nil { + name := path + if pkg != nil { + name = "package " + pkg.Name() + } + t.Fatalf("%s: didn't type-check (%s)", name, err) + } + return pkg.Name() +} + +func mayTypecheck(t *testing.T, path, source string, info *Info) (string, error) { + f, err := parseSrc(path, source) + if f == nil { // ignore errors unless f is nil + t.Fatalf("%s: unable to parse: %s", path, err) + } + conf := Config{ + Error: func(err error) {}, + Importer: defaultImporter(), + } + pkg, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, info) + return pkg.Name(), err +} + +func TestValuesInfo(t *testing.T) { + var tests = []struct { + src string + expr string // constant expression + typ string // constant type + val string // constant value + }{ + {`package a0; const _ = false`, `false`, `untyped bool`, `false`}, + {`package a1; const _ = 0`, `0`, `untyped int`, `0`}, + {`package a2; const _ = 'A'`, `'A'`, `untyped rune`, `65`}, + {`package a3; const _ = 0.`, `0.`, `untyped float`, `0`}, + {`package a4; const _ = 0i`, `0i`, `untyped complex`, `(0 + 0i)`}, + {`package a5; const _ = "foo"`, `"foo"`, `untyped string`, `"foo"`}, + + {`package b0; var _ = false`, `false`, `bool`, `false`}, + {`package b1; var _ = 0`, `0`, `int`, `0`}, + {`package b2; var _ = 'A'`, `'A'`, `rune`, `65`}, + {`package b3; var _ = 0.`, `0.`, `float64`, `0`}, + {`package b4; var _ = 0i`, `0i`, `complex128`, `(0 + 0i)`}, + {`package b5; var _ = "foo"`, `"foo"`, `string`, `"foo"`}, + + {`package c0a; var _ = bool(false)`, `false`, `bool`, `false`}, + {`package c0b; var _ = bool(false)`, `bool(false)`, `bool`, `false`}, + {`package c0c; type T bool; var _ = T(false)`, `T(false)`, `c0c.T`, `false`}, + + {`package c1a; var _ = int(0)`, `0`, `int`, `0`}, + {`package c1b; var _ = int(0)`, `int(0)`, `int`, `0`}, + {`package c1c; type T int; var _ = T(0)`, `T(0)`, `c1c.T`, `0`}, + + {`package c2a; var _ = rune('A')`, `'A'`, `rune`, `65`}, + {`package c2b; var _ = rune('A')`, `rune('A')`, `rune`, `65`}, + {`package c2c; type T rune; var _ = T('A')`, `T('A')`, `c2c.T`, `65`}, + + {`package c3a; var _ = float32(0.)`, `0.`, `float32`, `0`}, + {`package c3b; var _ = float32(0.)`, `float32(0.)`, `float32`, `0`}, + {`package c3c; type T float32; var _ = T(0.)`, `T(0.)`, `c3c.T`, `0`}, + + {`package c4a; var _ = complex64(0i)`, `0i`, `complex64`, `(0 + 0i)`}, + {`package c4b; var _ = complex64(0i)`, `complex64(0i)`, `complex64`, `(0 + 0i)`}, + {`package c4c; type T complex64; var _ = T(0i)`, `T(0i)`, `c4c.T`, `(0 + 0i)`}, + + {`package c5a; var _ = string("foo")`, `"foo"`, `string`, `"foo"`}, + {`package c5b; var _ = string("foo")`, `string("foo")`, `string`, `"foo"`}, + {`package c5c; type T string; var _ = T("foo")`, `T("foo")`, `c5c.T`, `"foo"`}, + {`package c5d; var _ = string(65)`, `65`, `untyped int`, `65`}, + {`package c5e; var _ = string('A')`, `'A'`, `untyped rune`, `65`}, + {`package c5f; type T string; var _ = T('A')`, `'A'`, `untyped rune`, `65`}, + {`package c5g; var s uint; var _ = string(1 << s)`, `1 << s`, `untyped int`, ``}, + + {`package d0; var _ = []byte("foo")`, `"foo"`, `string`, `"foo"`}, + {`package d1; var _ = []byte(string("foo"))`, `"foo"`, `string`, `"foo"`}, + {`package d2; var _ = []byte(string("foo"))`, `string("foo")`, `string`, `"foo"`}, + {`package d3; type T []byte; var _ = T("foo")`, `"foo"`, `string`, `"foo"`}, + + {`package e0; const _ = float32( 1e-200)`, `float32(1e-200)`, `float32`, `0`}, + {`package e1; const _ = float32(-1e-200)`, `float32(-1e-200)`, `float32`, `0`}, + {`package e2; const _ = float64( 1e-2000)`, `float64(1e-2000)`, `float64`, `0`}, + {`package e3; const _ = float64(-1e-2000)`, `float64(-1e-2000)`, `float64`, `0`}, + {`package e4; const _ = complex64( 1e-200)`, `complex64(1e-200)`, `complex64`, `(0 + 0i)`}, + {`package e5; const _ = complex64(-1e-200)`, `complex64(-1e-200)`, `complex64`, `(0 + 0i)`}, + {`package e6; const _ = complex128( 1e-2000)`, `complex128(1e-2000)`, `complex128`, `(0 + 0i)`}, + {`package e7; const _ = complex128(-1e-2000)`, `complex128(-1e-2000)`, `complex128`, `(0 + 0i)`}, + + {`package f0 ; var _ float32 = 1e-200`, `1e-200`, `float32`, `0`}, + {`package f1 ; var _ float32 = -1e-200`, `-1e-200`, `float32`, `0`}, + {`package f2a; var _ float64 = 1e-2000`, `1e-2000`, `float64`, `0`}, + {`package f3a; var _ float64 = -1e-2000`, `-1e-2000`, `float64`, `0`}, + {`package f2b; var _ = 1e-2000`, `1e-2000`, `float64`, `0`}, + {`package f3b; var _ = -1e-2000`, `-1e-2000`, `float64`, `0`}, + {`package f4 ; var _ complex64 = 1e-200 `, `1e-200`, `complex64`, `(0 + 0i)`}, + {`package f5 ; var _ complex64 = -1e-200 `, `-1e-200`, `complex64`, `(0 + 0i)`}, + {`package f6a; var _ complex128 = 1e-2000i`, `1e-2000i`, `complex128`, `(0 + 0i)`}, + {`package f7a; var _ complex128 = -1e-2000i`, `-1e-2000i`, `complex128`, `(0 + 0i)`}, + {`package f6b; var _ = 1e-2000i`, `1e-2000i`, `complex128`, `(0 + 0i)`}, + {`package f7b; var _ = -1e-2000i`, `-1e-2000i`, `complex128`, `(0 + 0i)`}, + + {`package g0; const (a = len([iota]int{}); b; c); const _ = c`, `c`, `int`, `2`}, // issue #22341 + } + + for _, test := range tests { + info := Info{ + Types: make(map[syntax.Expr]TypeAndValue), + } + name := mustTypecheck(t, "ValuesInfo", test.src, &info) + + // look for expression + var expr syntax.Expr + for e := range info.Types { + if syntax.String(e) == test.expr { + expr = e + break + } + } + if expr == nil { + t.Errorf("package %s: no expression found for %s", name, test.expr) + continue + } + tv := info.Types[expr] + + // check that type is correct + if got := tv.Type.String(); got != test.typ { + t.Errorf("package %s: got type %s; want %s", name, got, test.typ) + continue + } + + // if we have a constant, check that value is correct + if tv.Value != nil { + if got := tv.Value.ExactString(); got != test.val { + t.Errorf("package %s: got value %s; want %s", name, got, test.val) + } + } else { + if test.val != "" { + t.Errorf("package %s: no constant found; want %s", name, test.val) + } + } + } +} + +func TestTypesInfo(t *testing.T) { + var tests = []struct { + src string + expr string // expression + typ string // value type + }{ + // single-valued expressions of untyped constants + {`package b0; var x interface{} = false`, `false`, `bool`}, + {`package b1; var x interface{} = 0`, `0`, `int`}, + {`package b2; var x interface{} = 0.`, `0.`, `float64`}, + {`package b3; var x interface{} = 0i`, `0i`, `complex128`}, + {`package b4; var x interface{} = "foo"`, `"foo"`, `string`}, + + // uses of nil + {`package n0; var _ *int = nil`, `nil`, `*int`}, + {`package n1; var _ func() = nil`, `nil`, `func()`}, + {`package n2; var _ []byte = nil`, `nil`, `[]byte`}, + {`package n3; var _ map[int]int = nil`, `nil`, `map[int]int`}, + {`package n4; var _ chan int = nil`, `nil`, `chan int`}, + {`package n5a; var _ interface{} = (*int)(nil)`, `nil`, `*int`}, + {`package n5b; var _ interface{m()} = nil`, `nil`, `interface{m()}`}, + {`package n6; import "unsafe"; var _ unsafe.Pointer = nil`, `nil`, `unsafe.Pointer`}, + + {`package n10; var (x *int; _ = x == nil)`, `nil`, `*int`}, + {`package n11; var (x func(); _ = x == nil)`, `nil`, `func()`}, + {`package n12; var (x []byte; _ = x == nil)`, `nil`, `[]byte`}, + {`package n13; var (x map[int]int; _ = x == nil)`, `nil`, `map[int]int`}, + {`package n14; var (x chan int; _ = x == nil)`, `nil`, `chan int`}, + {`package n15a; var (x interface{}; _ = x == (*int)(nil))`, `nil`, `*int`}, + {`package n15b; var (x interface{m()}; _ = x == nil)`, `nil`, `interface{m()}`}, + {`package n15; import "unsafe"; var (x unsafe.Pointer; _ = x == nil)`, `nil`, `unsafe.Pointer`}, + + {`package n20; var _ = (*int)(nil)`, `nil`, `*int`}, + {`package n21; var _ = (func())(nil)`, `nil`, `func()`}, + {`package n22; var _ = ([]byte)(nil)`, `nil`, `[]byte`}, + {`package n23; var _ = (map[int]int)(nil)`, `nil`, `map[int]int`}, + {`package n24; var _ = (chan int)(nil)`, `nil`, `chan int`}, + {`package n25a; var _ = (interface{})((*int)(nil))`, `nil`, `*int`}, + {`package n25b; var _ = (interface{m()})(nil)`, `nil`, `interface{m()}`}, + {`package n26; import "unsafe"; var _ = unsafe.Pointer(nil)`, `nil`, `unsafe.Pointer`}, + + {`package n30; func f(*int) { f(nil) }`, `nil`, `*int`}, + {`package n31; func f(func()) { f(nil) }`, `nil`, `func()`}, + {`package n32; func f([]byte) { f(nil) }`, `nil`, `[]byte`}, + {`package n33; func f(map[int]int) { f(nil) }`, `nil`, `map[int]int`}, + {`package n34; func f(chan int) { f(nil) }`, `nil`, `chan int`}, + {`package n35a; func f(interface{}) { f((*int)(nil)) }`, `nil`, `*int`}, + {`package n35b; func f(interface{m()}) { f(nil) }`, `nil`, `interface{m()}`}, + {`package n35; import "unsafe"; func f(unsafe.Pointer) { f(nil) }`, `nil`, `unsafe.Pointer`}, + + // comma-ok expressions + {`package p0; var x interface{}; var _, _ = x.(int)`, + `x.(int)`, + `(int, bool)`, + }, + {`package p1; var x interface{}; func _() { _, _ = x.(int) }`, + `x.(int)`, + `(int, bool)`, + }, + {`package p2a; type mybool bool; var m map[string]complex128; var b mybool; func _() { _, b = m["foo"] }`, + `m["foo"]`, + `(complex128, p2a.mybool)`, + }, + {`package p2b; var m map[string]complex128; var b bool; func _() { _, b = m["foo"] }`, + `m["foo"]`, + `(complex128, bool)`, + }, + {`package p3; var c chan string; var _, _ = <-c`, + `<-c`, + `(string, bool)`, + }, + + // issue 6796 + {`package issue6796_a; var x interface{}; var _, _ = (x.(int))`, + `x.(int)`, + `(int, bool)`, + }, + {`package issue6796_b; var c chan string; var _, _ = (<-c)`, + `(<-c)`, + `(string, bool)`, + }, + {`package issue6796_c; var c chan string; var _, _ = (<-c)`, + `<-c`, + `(string, bool)`, + }, + {`package issue6796_d; var c chan string; var _, _ = ((<-c))`, + `(<-c)`, + `(string, bool)`, + }, + {`package issue6796_e; func f(c chan string) { _, _ = ((<-c)) }`, + `(<-c)`, + `(string, bool)`, + }, + + // issue 7060 + {`package issue7060_a; var ( m map[int]string; x, ok = m[0] )`, + `m[0]`, + `(string, bool)`, + }, + {`package issue7060_b; var ( m map[int]string; x, ok interface{} = m[0] )`, + `m[0]`, + `(string, bool)`, + }, + {`package issue7060_c; func f(x interface{}, ok bool, m map[int]string) { x, ok = m[0] }`, + `m[0]`, + `(string, bool)`, + }, + {`package issue7060_d; var ( ch chan string; x, ok = <-ch )`, + `<-ch`, + `(string, bool)`, + }, + {`package issue7060_e; var ( ch chan string; x, ok interface{} = <-ch )`, + `<-ch`, + `(string, bool)`, + }, + {`package issue7060_f; func f(x interface{}, ok bool, ch chan string) { x, ok = <-ch }`, + `<-ch`, + `(string, bool)`, + }, + + // issue 28277 + {`package issue28277_a; func f(...int)`, + `...int`, + `[]int`, + }, + {`package issue28277_b; func f(a, b int, c ...[]struct{})`, + `...[]struct{}`, + `[][]struct{}`, + }, + + // tests for broken code that doesn't parse or type-check + {brokenPkg + `x0; func _() { var x struct {f string}; x.f := 0 }`, `x.f`, `string`}, + {brokenPkg + `x1; func _() { var z string; type x struct {f string}; y := &x{q: z}}`, `z`, `string`}, + {brokenPkg + `x2; func _() { var a, b string; type x struct {f string}; z := &x{f: a, f: b,}}`, `b`, `string`}, + {brokenPkg + `x3; var x = panic("");`, `panic`, `func(interface{})`}, + {`package x4; func _() { panic("") }`, `panic`, `func(interface{})`}, + {brokenPkg + `x5; func _() { var x map[string][...]int; x = map[string][...]int{"": {1,2,3}} }`, `x`, `map[string]invalid type`}, + + // parameterized functions + {genericPkg + `p0; func f[T any](T); var _ = f[int]`, `f`, `func[T₁ interface{}](T₁)`}, + {genericPkg + `p1; func f[T any](T); var _ = f[int]`, `f[int]`, `func(int)`}, + {genericPkg + `p2; func f[T any](T); func _() { f(42) }`, `f`, `func[T₁ interface{}](T₁)`}, + {genericPkg + `p3; func f[T any](T); func _() { f(42) }`, `f(42)`, `()`}, + + // type parameters + {genericPkg + `t0; type t[] int; var _ t`, `t`, `generic_t0.t`}, // t[] is a syntax error that is ignored in this test in favor of t + {genericPkg + `t1; type t[P any] int; var _ t[int]`, `t`, `generic_t1.t[P₁ interface{}]`}, + {genericPkg + `t2; type t[P interface{}] int; var _ t[int]`, `t`, `generic_t2.t[P₁ interface{}]`}, + {genericPkg + `t3; type t[P, Q interface{}] int; var _ t[int, int]`, `t`, `generic_t3.t[P₁, Q₂ interface{}]`}, + {brokenPkg + `t4; type t[P, Q interface{ m() }] int; var _ t[int, int]`, `t`, `broken_t4.t[P₁, Q₂ interface{m()}]`}, + + // instantiated types must be sanitized + {genericPkg + `g0; type t[P any] int; var x struct{ f t[int] }; var _ = x.f`, `x.f`, `generic_g0.t[int]`}, + + // issue 45096 + {genericPkg + `issue45096; func _[T interface{ type int8, int16, int32 }](x T) { _ = x < 0 }`, `0`, `T₁`}, + } + + for _, test := range tests { + ResetId() // avoid renumbering of type parameter ids when adding tests + info := Info{Types: make(map[syntax.Expr]TypeAndValue)} + var name string + if strings.HasPrefix(test.src, brokenPkg) { + var err error + name, err = mayTypecheck(t, "TypesInfo", test.src, &info) + if err == nil { + t.Errorf("package %s: expected to fail but passed", name) + continue + } + } else { + name = mustTypecheck(t, "TypesInfo", test.src, &info) + } + + // look for expression type + var typ Type + for e, tv := range info.Types { + if syntax.String(e) == test.expr { + typ = tv.Type + break + } + } + if typ == nil { + t.Errorf("package %s: no type found for %s", name, test.expr) + continue + } + + // check that type is correct + if got := typ.String(); got != test.typ { + t.Errorf("package %s: got %s; want %s", name, got, test.typ) + } + } +} + +func TestInferredInfo(t *testing.T) { + var tests = []struct { + src string + fun string + targs []string + sig string + }{ + {genericPkg + `p0; func f[T any](T); func _() { f(42) }`, + `f`, + []string{`int`}, + `func(int)`, + }, + {genericPkg + `p1; func f[T any](T) T; func _() { f('@') }`, + `f`, + []string{`rune`}, + `func(rune) rune`, + }, + {genericPkg + `p2; func f[T any](...T) T; func _() { f(0i) }`, + `f`, + []string{`complex128`}, + `func(...complex128) complex128`, + }, + {genericPkg + `p3; func f[A, B, C any](A, *B, []C); func _() { f(1.2, new(string), []byte{}) }`, + `f`, + []string{`float64`, `string`, `byte`}, + `func(float64, *string, []byte)`, + }, + {genericPkg + `p4; func f[A, B any](A, *B, ...[]B); func _() { f(1.2, new(byte)) }`, + `f`, + []string{`float64`, `byte`}, + `func(float64, *byte, ...[]byte)`, + }, + + // we don't know how to translate these but we can type-check them + {genericPkg + `q0; type T struct{}; func (T) m[P any](P); func _(x T) { x.m(42) }`, + `x.m`, + []string{`int`}, + `func(int)`, + }, + {genericPkg + `q1; type T struct{}; func (T) m[P any](P) P; func _(x T) { x.m(42) }`, + `x.m`, + []string{`int`}, + `func(int) int`, + }, + {genericPkg + `q2; type T struct{}; func (T) m[P any](...P) P; func _(x T) { x.m(42) }`, + `x.m`, + []string{`int`}, + `func(...int) int`, + }, + {genericPkg + `q3; type T struct{}; func (T) m[A, B, C any](A, *B, []C); func _(x T) { x.m(1.2, new(string), []byte{}) }`, + `x.m`, + []string{`float64`, `string`, `byte`}, + `func(float64, *string, []byte)`, + }, + {genericPkg + `q4; type T struct{}; func (T) m[A, B any](A, *B, ...[]B); func _(x T) { x.m(1.2, new(byte)) }`, + `x.m`, + []string{`float64`, `byte`}, + `func(float64, *byte, ...[]byte)`, + }, + + {genericPkg + `r0; type T[P any] struct{}; func (_ T[P]) m[Q any](Q); func _[P any](x T[P]) { x.m(42) }`, + `x.m`, + []string{`int`}, + `func(int)`, + }, + // TODO(gri) record method type parameters in syntax.FuncType so we can check this + // {genericPkg + `r1; type T interface{ m[P any](P) }; func _(x T) { x.m(4.2) }`, + // `x.m`, + // []string{`float64`}, + // `func(float64)`, + // }, + + {genericPkg + `s1; func f[T any, P interface{type *T}](x T); func _(x string) { f(x) }`, + `f`, + []string{`string`, `*string`}, + `func(x string)`, + }, + {genericPkg + `s2; func f[T any, P interface{type *T}](x []T); func _(x []int) { f(x) }`, + `f`, + []string{`int`, `*int`}, + `func(x []int)`, + }, + {genericPkg + `s3; type C[T any] interface{type chan<- T}; func f[T any, P C[T]](x []T); func _(x []int) { f(x) }`, + `f`, + []string{`int`, `chan<- int`}, + `func(x []int)`, + }, + {genericPkg + `s4; type C[T any] interface{type chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T); func _(x []int) { f(x) }`, + `f`, + []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, + `func(x []int)`, + }, + + {genericPkg + `t1; func f[T any, P interface{type *T}]() T; func _() { _ = f[string] }`, + `f`, + []string{`string`, `*string`}, + `func() string`, + }, + {genericPkg + `t2; type C[T any] interface{type chan<- T}; func f[T any, P C[T]]() []T; func _() { _ = f[int] }`, + `f`, + []string{`int`, `chan<- int`}, + `func() []int`, + }, + {genericPkg + `t3; type C[T any] interface{type chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T; func _() { _ = f[int] }`, + `f`, + []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, + `func() []int`, + }, + } + + for _, test := range tests { + info := Info{Inferred: make(map[syntax.Expr]Inferred)} + name, err := mayTypecheck(t, "InferredInfo", test.src, &info) + if err != nil { + t.Errorf("package %s: %v", name, err) + continue + } + + // look for inferred type arguments and signature + var targs []Type + var sig *Signature + for call, inf := range info.Inferred { + var fun syntax.Expr + switch x := call.(type) { + case *syntax.CallExpr: + fun = x.Fun + case *syntax.IndexExpr: + fun = x.X + default: + panic(fmt.Sprintf("unexpected call expression type %T", call)) + } + if syntax.String(fun) == test.fun { + targs = inf.Targs + sig = inf.Sig + break + } + } + if targs == nil { + t.Errorf("package %s: no inferred information found for %s", name, test.fun) + continue + } + + // check that type arguments are correct + if len(targs) != len(test.targs) { + t.Errorf("package %s: got %d type arguments; want %d", name, len(targs), len(test.targs)) + continue + } + for i, targ := range targs { + if got := targ.String(); got != test.targs[i] { + t.Errorf("package %s, %d. type argument: got %s; want %s", name, i, got, test.targs[i]) + continue + } + } + + // check that signature is correct + if got := sig.String(); got != test.sig { + t.Errorf("package %s: got %s; want %s", name, got, test.sig) + } + } +} + +func TestDefsInfo(t *testing.T) { + var tests = []struct { + src string + obj string + want string + }{ + {`package p0; const x = 42`, `x`, `const p0.x untyped int`}, + {`package p1; const x int = 42`, `x`, `const p1.x int`}, + {`package p2; var x int`, `x`, `var p2.x int`}, + {`package p3; type x int`, `x`, `type p3.x int`}, + {`package p4; func f()`, `f`, `func p4.f()`}, + {`package p5; func f() int { x, _ := 1, 2; return x }`, `_`, `var _ int`}, + + // generic types must be sanitized + // (need to use sufficiently nested types to provoke unexpanded types) + {genericPkg + `g0; type t[P any] P; const x = t[int](42)`, `x`, `const generic_g0.x generic_g0.t[int]`}, + {genericPkg + `g1; type t[P any] P; var x = t[int](42)`, `x`, `var generic_g1.x generic_g1.t[int]`}, + {genericPkg + `g2; type t[P any] P; type x struct{ f t[int] }`, `x`, `type generic_g2.x struct{f generic_g2.t[int]}`}, + {genericPkg + `g3; type t[P any] P; func f(x struct{ f t[string] }); var g = f`, `g`, `var generic_g3.g func(x struct{f generic_g3.t[string]})`}, + } + + for _, test := range tests { + info := Info{ + Defs: make(map[*syntax.Name]Object), + } + name := mustTypecheck(t, "DefsInfo", test.src, &info) + + // find object + var def Object + for id, obj := range info.Defs { + if id.Value == test.obj { + def = obj + break + } + } + if def == nil { + t.Errorf("package %s: %s not found", name, test.obj) + continue + } + + if got := def.String(); got != test.want { + t.Errorf("package %s: got %s; want %s", name, got, test.want) + } + } +} + +func TestUsesInfo(t *testing.T) { + var tests = []struct { + src string + obj string + want string + }{ + {`package p0; func _() { _ = x }; const x = 42`, `x`, `const p0.x untyped int`}, + {`package p1; func _() { _ = x }; const x int = 42`, `x`, `const p1.x int`}, + {`package p2; func _() { _ = x }; var x int`, `x`, `var p2.x int`}, + {`package p3; func _() { type _ x }; type x int`, `x`, `type p3.x int`}, + {`package p4; func _() { _ = f }; func f()`, `f`, `func p4.f()`}, + + // generic types must be sanitized + // (need to use sufficiently nested types to provoke unexpanded types) + {genericPkg + `g0; func _() { _ = x }; type t[P any] P; const x = t[int](42)`, `x`, `const generic_g0.x generic_g0.t[int]`}, + {genericPkg + `g1; func _() { _ = x }; type t[P any] P; var x = t[int](42)`, `x`, `var generic_g1.x generic_g1.t[int]`}, + {genericPkg + `g2; func _() { type _ x }; type t[P any] P; type x struct{ f t[int] }`, `x`, `type generic_g2.x struct{f generic_g2.t[int]}`}, + {genericPkg + `g3; func _() { _ = f }; type t[P any] P; func f(x struct{ f t[string] })`, `f`, `func generic_g3.f(x struct{f generic_g3.t[string]})`}, + } + + for _, test := range tests { + info := Info{ + Uses: make(map[*syntax.Name]Object), + } + name := mustTypecheck(t, "UsesInfo", test.src, &info) + + // find object + var use Object + for id, obj := range info.Uses { + if id.Value == test.obj { + use = obj + break + } + } + if use == nil { + t.Errorf("package %s: %s not found", name, test.obj) + continue + } + + if got := use.String(); got != test.want { + t.Errorf("package %s: got %s; want %s", name, got, test.want) + } + } +} + +func TestImplicitsInfo(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests = []struct { + src string + want string + }{ + {`package p2; import . "fmt"; var _ = Println`, ""}, // no Implicits entry + {`package p0; import local "fmt"; var _ = local.Println`, ""}, // no Implicits entry + {`package p1; import "fmt"; var _ = fmt.Println`, "importSpec: package fmt"}, + + {`package p3; func f(x interface{}) { switch x.(type) { case int: } }`, ""}, // no Implicits entry + {`package p4; func f(x interface{}) { switch t := x.(type) { case int: _ = t } }`, "caseClause: var t int"}, + {`package p5; func f(x interface{}) { switch t := x.(type) { case int, uint: _ = t } }`, "caseClause: var t interface{}"}, + {`package p6; func f(x interface{}) { switch t := x.(type) { default: _ = t } }`, "caseClause: var t interface{}"}, + + {`package p7; func f(x int) {}`, ""}, // no Implicits entry + {`package p8; func f(int) {}`, "field: var int"}, + {`package p9; func f() (complex64) { return 0 }`, "field: var complex64"}, + {`package p10; type T struct{}; func (*T) f() {}`, "field: var *p10.T"}, + } + + for _, test := range tests { + info := Info{ + Implicits: make(map[syntax.Node]Object), + } + name := mustTypecheck(t, "ImplicitsInfo", test.src, &info) + + // the test cases expect at most one Implicits entry + if len(info.Implicits) > 1 { + t.Errorf("package %s: %d Implicits entries found", name, len(info.Implicits)) + continue + } + + // extract Implicits entry, if any + var got string + for n, obj := range info.Implicits { + switch x := n.(type) { + case *syntax.ImportDecl: + got = "importSpec" + case *syntax.CaseClause: + got = "caseClause" + case *syntax.Field: + got = "field" + default: + t.Fatalf("package %s: unexpected %T", name, x) + } + got += ": " + obj.String() + } + + // verify entry + if got != test.want { + t.Errorf("package %s: got %q; want %q", name, got, test.want) + } + } +} + +func predString(tv TypeAndValue) string { + var buf bytes.Buffer + pred := func(b bool, s string) { + if b { + if buf.Len() > 0 { + buf.WriteString(", ") + } + buf.WriteString(s) + } + } + + pred(tv.IsVoid(), "void") + pred(tv.IsType(), "type") + pred(tv.IsBuiltin(), "builtin") + pred(tv.IsValue() && tv.Value != nil, "const") + pred(tv.IsValue() && tv.Value == nil, "value") + pred(tv.IsNil(), "nil") + pred(tv.Addressable(), "addressable") + pred(tv.Assignable(), "assignable") + pred(tv.HasOk(), "hasOk") + + if buf.Len() == 0 { + return "invalid" + } + return buf.String() +} + +func TestPredicatesInfo(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests = []struct { + src string + expr string + pred string + }{ + // void + {`package n0; func f() { f() }`, `f()`, `void`}, + + // types + {`package t0; type _ int`, `int`, `type`}, + {`package t1; type _ []int`, `[]int`, `type`}, + {`package t2; type _ func()`, `func()`, `type`}, + {`package t3; type _ func(int)`, `int`, `type`}, + {`package t3; type _ func(...int)`, `...int`, `type`}, + + // built-ins + {`package b0; var _ = len("")`, `len`, `builtin`}, + {`package b1; var _ = (len)("")`, `(len)`, `builtin`}, + + // constants + {`package c0; var _ = 42`, `42`, `const`}, + {`package c1; var _ = "foo" + "bar"`, `"foo" + "bar"`, `const`}, + {`package c2; const (i = 1i; _ = i)`, `i`, `const`}, + + // values + {`package v0; var (a, b int; _ = a + b)`, `a + b`, `value`}, + {`package v1; var _ = &[]int{1}`, `[]int{…}`, `value`}, + {`package v2; var _ = func(){}`, `func() {}`, `value`}, + {`package v4; func f() { _ = f }`, `f`, `value`}, + {`package v3; var _ *int = nil`, `nil`, `value, nil`}, + {`package v3; var _ *int = (nil)`, `(nil)`, `value, nil`}, + + // addressable (and thus assignable) operands + {`package a0; var (x int; _ = x)`, `x`, `value, addressable, assignable`}, + {`package a1; var (p *int; _ = *p)`, `*p`, `value, addressable, assignable`}, + {`package a2; var (s []int; _ = s[0])`, `s[0]`, `value, addressable, assignable`}, + {`package a3; var (s struct{f int}; _ = s.f)`, `s.f`, `value, addressable, assignable`}, + {`package a4; var (a [10]int; _ = a[0])`, `a[0]`, `value, addressable, assignable`}, + {`package a5; func _(x int) { _ = x }`, `x`, `value, addressable, assignable`}, + {`package a6; func _()(x int) { _ = x; return }`, `x`, `value, addressable, assignable`}, + {`package a7; type T int; func (x T) _() { _ = x }`, `x`, `value, addressable, assignable`}, + // composite literals are not addressable + + // assignable but not addressable values + {`package s0; var (m map[int]int; _ = m[0])`, `m[0]`, `value, assignable, hasOk`}, + {`package s1; var (m map[int]int; _, _ = m[0])`, `m[0]`, `value, assignable, hasOk`}, + + // hasOk expressions + {`package k0; var (ch chan int; _ = <-ch)`, `<-ch`, `value, hasOk`}, + {`package k1; var (ch chan int; _, _ = <-ch)`, `<-ch`, `value, hasOk`}, + + // missing entries + // - package names are collected in the Uses map + // - identifiers being declared are collected in the Defs map + {`package m0; import "os"; func _() { _ = os.Stdout }`, `os`, ``}, + {`package m1; import p "os"; func _() { _ = p.Stdout }`, `p`, ``}, + {`package m2; const c = 0`, `c`, ``}, + {`package m3; type T int`, `T`, ``}, + {`package m4; var v int`, `v`, ``}, + {`package m5; func f() {}`, `f`, ``}, + {`package m6; func _(x int) {}`, `x`, ``}, + {`package m6; func _()(x int) { return }`, `x`, ``}, + {`package m6; type T int; func (x T) _() {}`, `x`, ``}, + } + + for _, test := range tests { + info := Info{Types: make(map[syntax.Expr]TypeAndValue)} + name := mustTypecheck(t, "PredicatesInfo", test.src, &info) + + // look for expression predicates + got := "" + for e, tv := range info.Types { + //println(name, syntax.String(e)) + if syntax.String(e) == test.expr { + got = predString(tv) + break + } + } + + if got != test.pred { + t.Errorf("package %s: got %s; want %s", name, got, test.pred) + } + } +} + +func TestScopesInfo(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests = []struct { + src string + scopes []string // list of scope descriptors of the form kind:varlist + }{ + {`package p0`, []string{ + "file:", + }}, + {`package p1; import ( "fmt"; m "math"; _ "os" ); var ( _ = fmt.Println; _ = m.Pi )`, []string{ + "file:fmt m", + }}, + {`package p2; func _() {}`, []string{ + "file:", "func:", + }}, + {`package p3; func _(x, y int) {}`, []string{ + "file:", "func:x y", + }}, + {`package p4; func _(x, y int) { x, z := 1, 2; _ = z }`, []string{ + "file:", "func:x y z", // redeclaration of x + }}, + {`package p5; func _(x, y int) (u, _ int) { return }`, []string{ + "file:", "func:u x y", + }}, + {`package p6; func _() { { var x int; _ = x } }`, []string{ + "file:", "func:", "block:x", + }}, + {`package p7; func _() { if true {} }`, []string{ + "file:", "func:", "if:", "block:", + }}, + {`package p8; func _() { if x := 0; x < 0 { y := x; _ = y } }`, []string{ + "file:", "func:", "if:x", "block:y", + }}, + {`package p9; func _() { switch x := 0; x {} }`, []string{ + "file:", "func:", "switch:x", + }}, + {`package p10; func _() { switch x := 0; x { case 1: y := x; _ = y; default: }}`, []string{ + "file:", "func:", "switch:x", "case:y", "case:", + }}, + {`package p11; func _(t interface{}) { switch t.(type) {} }`, []string{ + "file:", "func:t", "switch:", + }}, + {`package p12; func _(t interface{}) { switch t := t; t.(type) {} }`, []string{ + "file:", "func:t", "switch:t", + }}, + {`package p13; func _(t interface{}) { switch x := t.(type) { case int: _ = x } }`, []string{ + "file:", "func:t", "switch:", "case:x", // x implicitly declared + }}, + {`package p14; func _() { select{} }`, []string{ + "file:", "func:", + }}, + {`package p15; func _(c chan int) { select{ case <-c: } }`, []string{ + "file:", "func:c", "comm:", + }}, + {`package p16; func _(c chan int) { select{ case i := <-c: x := i; _ = x} }`, []string{ + "file:", "func:c", "comm:i x", + }}, + {`package p17; func _() { for{} }`, []string{ + "file:", "func:", "for:", "block:", + }}, + {`package p18; func _(n int) { for i := 0; i < n; i++ { _ = i } }`, []string{ + "file:", "func:n", "for:i", "block:", + }}, + {`package p19; func _(a []int) { for i := range a { _ = i} }`, []string{ + "file:", "func:a", "for:i", "block:", + }}, + {`package p20; var s int; func _(a []int) { for i, x := range a { s += x; _ = i } }`, []string{ + "file:", "func:a", "for:i x", "block:", + }}, + } + + for _, test := range tests { + info := Info{Scopes: make(map[syntax.Node]*Scope)} + name := mustTypecheck(t, "ScopesInfo", test.src, &info) + + // number of scopes must match + if len(info.Scopes) != len(test.scopes) { + t.Errorf("package %s: got %d scopes; want %d", name, len(info.Scopes), len(test.scopes)) + } + + // scope descriptions must match + for node, scope := range info.Scopes { + var kind string + switch node.(type) { + case *syntax.File: + kind = "file" + case *syntax.FuncType: + kind = "func" + case *syntax.BlockStmt: + kind = "block" + case *syntax.IfStmt: + kind = "if" + case *syntax.SwitchStmt: + kind = "switch" + case *syntax.SelectStmt: + kind = "select" + case *syntax.CaseClause: + kind = "case" + case *syntax.CommClause: + kind = "comm" + case *syntax.ForStmt: + kind = "for" + default: + kind = fmt.Sprintf("%T", node) + } + + // look for matching scope description + desc := kind + ":" + strings.Join(scope.Names(), " ") + found := false + for _, d := range test.scopes { + if desc == d { + found = true + break + } + } + if !found { + t.Errorf("package %s: no matching scope found for %s", name, desc) + } + } + } +} + +func TestInitOrderInfo(t *testing.T) { + var tests = []struct { + src string + inits []string + }{ + {`package p0; var (x = 1; y = x)`, []string{ + "x = 1", "y = x", + }}, + {`package p1; var (a = 1; b = 2; c = 3)`, []string{ + "a = 1", "b = 2", "c = 3", + }}, + {`package p2; var (a, b, c = 1, 2, 3)`, []string{ + "a = 1", "b = 2", "c = 3", + }}, + {`package p3; var _ = f(); func f() int { return 1 }`, []string{ + "_ = f()", // blank var + }}, + {`package p4; var (a = 0; x = y; y = z; z = 0)`, []string{ + "a = 0", "z = 0", "y = z", "x = y", + }}, + {`package p5; var (a, _ = m[0]; m map[int]string)`, []string{ + "a, _ = m[0]", // blank var + }}, + {`package p6; var a, b = f(); func f() (_, _ int) { return z, z }; var z = 0`, []string{ + "z = 0", "a, b = f()", + }}, + {`package p7; var (a = func() int { return b }(); b = 1)`, []string{ + "b = 1", "a = func() int {…}()", + }}, + {`package p8; var (a, b = func() (_, _ int) { return c, c }(); c = 1)`, []string{ + "c = 1", "a, b = func() (_, _ int) {…}()", + }}, + {`package p9; type T struct{}; func (T) m() int { _ = y; return 0 }; var x, y = T.m, 1`, []string{ + "y = 1", "x = T.m", + }}, + {`package p10; var (d = c + b; a = 0; b = 0; c = 0)`, []string{ + "a = 0", "b = 0", "c = 0", "d = c + b", + }}, + {`package p11; var (a = e + c; b = d + c; c = 0; d = 0; e = 0)`, []string{ + "c = 0", "d = 0", "b = d + c", "e = 0", "a = e + c", + }}, + // emit an initializer for n:1 initializations only once (not for each node + // on the lhs which may appear in different order in the dependency graph) + {`package p12; var (a = x; b = 0; x, y = m[0]; m map[int]int)`, []string{ + "b = 0", "x, y = m[0]", "a = x", + }}, + // test case from spec section on package initialization + {`package p12 + + var ( + a = c + b + b = f() + c = f() + d = 3 + ) + + func f() int { + d++ + return d + }`, []string{ + "d = 3", "b = f()", "c = f()", "a = c + b", + }}, + // test case for issue 7131 + {`package main + + var counter int + func next() int { counter++; return counter } + + var _ = makeOrder() + func makeOrder() []int { return []int{f, b, d, e, c, a} } + + var a = next() + var b, c = next(), next() + var d, e, f = next(), next(), next() + `, []string{ + "a = next()", "b = next()", "c = next()", "d = next()", "e = next()", "f = next()", "_ = makeOrder()", + }}, + // test case for issue 10709 + {`package p13 + + var ( + v = t.m() + t = makeT(0) + ) + + type T struct{} + + func (T) m() int { return 0 } + + func makeT(n int) T { + if n > 0 { + return makeT(n-1) + } + return T{} + }`, []string{ + "t = makeT(0)", "v = t.m()", + }}, + // test case for issue 10709: same as test before, but variable decls swapped + {`package p14 + + var ( + t = makeT(0) + v = t.m() + ) + + type T struct{} + + func (T) m() int { return 0 } + + func makeT(n int) T { + if n > 0 { + return makeT(n-1) + } + return T{} + }`, []string{ + "t = makeT(0)", "v = t.m()", + }}, + // another candidate possibly causing problems with issue 10709 + {`package p15 + + var y1 = f1() + + func f1() int { return g1() } + func g1() int { f1(); return x1 } + + var x1 = 0 + + var y2 = f2() + + func f2() int { return g2() } + func g2() int { return x2 } + + var x2 = 0`, []string{ + "x1 = 0", "y1 = f1()", "x2 = 0", "y2 = f2()", + }}, + } + + for _, test := range tests { + info := Info{} + name := mustTypecheck(t, "InitOrderInfo", test.src, &info) + + // number of initializers must match + if len(info.InitOrder) != len(test.inits) { + t.Errorf("package %s: got %d initializers; want %d", name, len(info.InitOrder), len(test.inits)) + continue + } + + // initializers must match + for i, want := range test.inits { + got := info.InitOrder[i].String() + if got != want { + t.Errorf("package %s, init %d: got %s; want %s", name, i, got, want) + continue + } + } + } +} + +func TestMultiFileInitOrder(t *testing.T) { + mustParse := func(src string) *syntax.File { + f, err := parseSrc("main", src) + if err != nil { + t.Fatal(err) + } + return f + } + + fileA := mustParse(`package main; var a = 1`) + fileB := mustParse(`package main; var b = 2`) + + // The initialization order must not depend on the parse + // order of the files, only on the presentation order to + // the type-checker. + for _, test := range []struct { + files []*syntax.File + want string + }{ + {[]*syntax.File{fileA, fileB}, "[a = 1 b = 2]"}, + {[]*syntax.File{fileB, fileA}, "[b = 2 a = 1]"}, + } { + var info Info + if _, err := new(Config).Check("main", test.files, &info); err != nil { + t.Fatal(err) + } + if got := fmt.Sprint(info.InitOrder); got != test.want { + t.Fatalf("got %s; want %s", got, test.want) + } + } +} + +func TestFiles(t *testing.T) { + var sources = []string{ + "package p; type T struct{}; func (T) m1() {}", + "package p; func (T) m2() {}; var x interface{ m1(); m2() } = T{}", + "package p; func (T) m3() {}; var y interface{ m1(); m2(); m3() } = T{}", + "package p", + } + + var conf Config + pkg := NewPackage("p", "p") + var info Info + check := NewChecker(&conf, pkg, &info) + + for i, src := range sources { + filename := fmt.Sprintf("sources%d", i) + f, err := parseSrc(filename, src) + if err != nil { + t.Fatal(err) + } + if err := check.Files([]*syntax.File{f}); err != nil { + t.Error(err) + } + } + + // check InitOrder is [x y] + var vars []string + for _, init := range info.InitOrder { + for _, v := range init.Lhs { + vars = append(vars, v.Name()) + } + } + if got, want := fmt.Sprint(vars), "[x y]"; got != want { + t.Errorf("InitOrder == %s, want %s", got, want) + } +} + +type testImporter map[string]*Package + +func (m testImporter) Import(path string) (*Package, error) { + if pkg := m[path]; pkg != nil { + return pkg, nil + } + return nil, fmt.Errorf("package %q not found", path) +} + +func TestSelection(t *testing.T) { + t.Skip("requires fixes around source positions") + + selections := make(map[*syntax.SelectorExpr]*Selection) + + imports := make(testImporter) + conf := Config{Importer: imports} + makePkg := func(path, src string) { + f, err := parseSrc(path+".go", src) + if err != nil { + t.Fatal(err) + } + pkg, err := conf.Check(path, []*syntax.File{f}, &Info{Selections: selections}) + if err != nil { + t.Fatal(err) + } + imports[path] = pkg + } + + const libSrc = ` +package lib +type T float64 +const C T = 3 +var V T +func F() {} +func (T) M() {} +` + const mainSrc = ` +package main +import "lib" + +type A struct { + *B + C +} + +type B struct { + b int +} + +func (B) f(int) + +type C struct { + c int +} + +func (C) g() +func (*C) h() + +func main() { + // qualified identifiers + var _ lib.T + _ = lib.C + _ = lib.F + _ = lib.V + _ = lib.T.M + + // fields + _ = A{}.B + _ = new(A).B + + _ = A{}.C + _ = new(A).C + + _ = A{}.b + _ = new(A).b + + _ = A{}.c + _ = new(A).c + + // methods + _ = A{}.f + _ = new(A).f + _ = A{}.g + _ = new(A).g + _ = new(A).h + + _ = B{}.f + _ = new(B).f + + _ = C{}.g + _ = new(C).g + _ = new(C).h + + // method expressions + _ = A.f + _ = (*A).f + _ = B.f + _ = (*B).f +}` + + wantOut := map[string][2]string{ + "lib.T.M": {"method expr (lib.T) M(lib.T)", ".[0]"}, + + "A{}.B": {"field (main.A) B *main.B", ".[0]"}, + "new(A).B": {"field (*main.A) B *main.B", "->[0]"}, + "A{}.C": {"field (main.A) C main.C", ".[1]"}, + "new(A).C": {"field (*main.A) C main.C", "->[1]"}, + "A{}.b": {"field (main.A) b int", "->[0 0]"}, + "new(A).b": {"field (*main.A) b int", "->[0 0]"}, + "A{}.c": {"field (main.A) c int", ".[1 0]"}, + "new(A).c": {"field (*main.A) c int", "->[1 0]"}, + + "A{}.f": {"method (main.A) f(int)", "->[0 0]"}, + "new(A).f": {"method (*main.A) f(int)", "->[0 0]"}, + "A{}.g": {"method (main.A) g()", ".[1 0]"}, + "new(A).g": {"method (*main.A) g()", "->[1 0]"}, + "new(A).h": {"method (*main.A) h()", "->[1 1]"}, // TODO(gri) should this report .[1 1] ? + "B{}.f": {"method (main.B) f(int)", ".[0]"}, + "new(B).f": {"method (*main.B) f(int)", "->[0]"}, + "C{}.g": {"method (main.C) g()", ".[0]"}, + "new(C).g": {"method (*main.C) g()", "->[0]"}, + "new(C).h": {"method (*main.C) h()", "->[1]"}, // TODO(gri) should this report .[1] ? + + "A.f": {"method expr (main.A) f(main.A, int)", "->[0 0]"}, + "(*A).f": {"method expr (*main.A) f(*main.A, int)", "->[0 0]"}, + "B.f": {"method expr (main.B) f(main.B, int)", ".[0]"}, + "(*B).f": {"method expr (*main.B) f(*main.B, int)", "->[0]"}, + } + + makePkg("lib", libSrc) + makePkg("main", mainSrc) + + for e, sel := range selections { + _ = sel.String() // assertion: must not panic + + unimplemented() + _ = e + // start := fset.Position(e.Pos()).Offset + // end := fset.Position(e.End()).Offset + // syntax := mainSrc[start:end] // (all SelectorExprs are in main, not lib) + + direct := "." + if sel.Indirect() { + direct = "->" + } + got := [2]string{ + sel.String(), + fmt.Sprintf("%s%v", direct, sel.Index()), + } + unimplemented() + _ = got + // want := wantOut[syntax] + // if want != got { + // t.Errorf("%s: got %q; want %q", syntax, got, want) + // } + // delete(wantOut, syntax) + + // We must explicitly assert properties of the + // Signature's receiver since it doesn't participate + // in Identical() or String(). + sig, _ := sel.Type().(*Signature) + if sel.Kind() == MethodVal { + got := sig.Recv().Type() + want := sel.Recv() + if !Identical(got, want) { + unimplemented() + // t.Errorf("%s: Recv() = %s, want %s", syntax, got, want) + } + } else if sig != nil && sig.Recv() != nil { + t.Errorf("%s: signature has receiver %s", sig, sig.Recv().Type()) + } + } + // Assert that all wantOut entries were used exactly once. + for syntax := range wantOut { + t.Errorf("no syntax.Selection found with syntax %q", syntax) + } +} + +func TestIssue8518(t *testing.T) { + imports := make(testImporter) + conf := Config{ + Error: func(err error) { t.Log(err) }, // don't exit after first error + Importer: imports, + } + makePkg := func(path, src string) { + f, err := parseSrc(path, src) + if err != nil { + t.Fatal(err) + } + pkg, _ := conf.Check(path, []*syntax.File{f}, nil) // errors logged via conf.Error + imports[path] = pkg + } + + const libSrc = ` +package a +import "missing" +const C1 = foo +const C2 = missing.C +` + + const mainSrc = ` +package main +import "a" +var _ = a.C1 +var _ = a.C2 +` + + makePkg("a", libSrc) + makePkg("main", mainSrc) // don't crash when type-checking this package +} + +func TestLookupFieldOrMethod(t *testing.T) { + // Test cases assume a lookup of the form a.f or x.f, where a stands for an + // addressable value, and x for a non-addressable value (even though a variable + // for ease of test case writing). + var tests = []struct { + src string + found bool + index []int + indirect bool + }{ + // field lookups + {"var x T; type T struct{}", false, nil, false}, + {"var x T; type T struct{ f int }", true, []int{0}, false}, + {"var x T; type T struct{ a, b, f, c int }", true, []int{2}, false}, + + // method lookups + {"var a T; type T struct{}; func (T) f() {}", true, []int{0}, false}, + {"var a *T; type T struct{}; func (T) f() {}", true, []int{0}, true}, + {"var a T; type T struct{}; func (*T) f() {}", true, []int{0}, false}, + {"var a *T; type T struct{}; func (*T) f() {}", true, []int{0}, true}, // TODO(gri) should this report indirect = false? + + // collisions + {"type ( E1 struct{ f int }; E2 struct{ f int }; x struct{ E1; *E2 })", false, []int{1, 0}, false}, + {"type ( E1 struct{ f int }; E2 struct{}; x struct{ E1; *E2 }); func (E2) f() {}", false, []int{1, 0}, false}, + + // outside methodset + // (*T).f method exists, but value of type T is not addressable + {"var x T; type T struct{}; func (*T) f() {}", false, nil, true}, + } + + for _, test := range tests { + pkg, err := pkgFor("test", "package p;"+test.src, nil) + if err != nil { + t.Errorf("%s: incorrect test case: %s", test.src, err) + continue + } + + obj := pkg.Scope().Lookup("a") + if obj == nil { + if obj = pkg.Scope().Lookup("x"); obj == nil { + t.Errorf("%s: incorrect test case - no object a or x", test.src) + continue + } + } + + f, index, indirect := LookupFieldOrMethod(obj.Type(), obj.Name() == "a", pkg, "f") + if (f != nil) != test.found { + if f == nil { + t.Errorf("%s: got no object; want one", test.src) + } else { + t.Errorf("%s: got object = %v; want none", test.src, f) + } + } + if !sameSlice(index, test.index) { + t.Errorf("%s: got index = %v; want %v", test.src, index, test.index) + } + if indirect != test.indirect { + t.Errorf("%s: got indirect = %v; want %v", test.src, indirect, test.indirect) + } + } +} + +func sameSlice(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i, x := range a { + if x != b[i] { + return false + } + } + return true +} + +// TestScopeLookupParent ensures that (*Scope).LookupParent returns +// the correct result at various positions within the source. +func TestScopeLookupParent(t *testing.T) { + imports := make(testImporter) + conf := Config{Importer: imports} + var info Info + makePkg := func(path, src string) { + f, err := parseSrc(path, src) + if err != nil { + t.Fatal(err) + } + imports[path], err = conf.Check(path, []*syntax.File{f}, &info) + if err != nil { + t.Fatal(err) + } + } + + makePkg("lib", "package lib; var X int") + // Each /*name=kind:line*/ comment makes the test look up the + // name at that point and checks that it resolves to a decl of + // the specified kind and line number. "undef" means undefined. + mainSrc := ` +/*lib=pkgname:5*/ /*X=var:1*/ /*Pi=const:8*/ /*T=typename:9*/ /*Y=var:10*/ /*F=func:12*/ +package main + +import "lib" +import . "lib" + +const Pi = 3.1415 +type T struct{} +var Y, _ = lib.X, X + +func F(){ + const pi, e = 3.1415, /*pi=undef*/ 2.71828 /*pi=const:13*/ /*e=const:13*/ + type /*t=undef*/ t /*t=typename:14*/ *t + print(Y) /*Y=var:10*/ + x, Y := Y, /*x=undef*/ /*Y=var:10*/ Pi /*x=var:16*/ /*Y=var:16*/ ; _ = x; _ = Y + var F = /*F=func:12*/ F /*F=var:17*/ ; _ = F + + var a []int + for i, x := range /*i=undef*/ /*x=var:16*/ a /*i=var:20*/ /*x=var:20*/ { _ = i; _ = x } + + var i interface{} + switch y := i.(type) { /*y=undef*/ + case /*y=undef*/ int /*y=var:23*/ : + case float32, /*y=undef*/ float64 /*y=var:23*/ : + default /*y=var:23*/: + println(y) + } + /*y=undef*/ + + switch int := i.(type) { + case /*int=typename:0*/ int /*int=var:31*/ : + println(int) + default /*int=var:31*/ : + } +} +/*main=undef*/ +` + + info.Uses = make(map[*syntax.Name]Object) + makePkg("main", mainSrc) + mainScope := imports["main"].Scope() + + rx := regexp.MustCompile(`^/\*(\w*)=([\w:]*)\*/$`) + + base := syntax.NewFileBase("main") + syntax.CommentsDo(strings.NewReader(mainSrc), func(line, col uint, text string) { + pos := syntax.MakePos(base, line, col) + + // Syntax errors are not comments. + if text[0] != '/' { + t.Errorf("%s: %s", pos, text) + return + } + + // Parse the assertion in the comment. + m := rx.FindStringSubmatch(text) + if m == nil { + t.Errorf("%s: bad comment: %s", pos, text) + return + } + name, want := m[1], m[2] + + // Look up the name in the innermost enclosing scope. + inner := mainScope.Innermost(pos) + if inner == nil { + t.Errorf("%s: at %s: can't find innermost scope", pos, text) + return + } + got := "undef" + if _, obj := inner.LookupParent(name, pos); obj != nil { + kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types2.")) + got = fmt.Sprintf("%s:%d", kind, obj.Pos().Line()) + } + if got != want { + t.Errorf("%s: at %s: %s resolved to %s, want %s", pos, text, name, got, want) + } + }) + + // Check that for each referring identifier, + // a lookup of its name on the innermost + // enclosing scope returns the correct object. + + for id, wantObj := range info.Uses { + inner := mainScope.Innermost(id.Pos()) + if inner == nil { + t.Errorf("%s: can't find innermost scope enclosing %q", id.Pos(), id.Value) + continue + } + + // Exclude selectors and qualified identifiers---lexical + // refs only. (Ideally, we'd see if the AST parent is a + // SelectorExpr, but that requires PathEnclosingInterval + // from golang.org/x/tools/go/ast/astutil.) + if id.Value == "X" { + continue + } + + _, gotObj := inner.LookupParent(id.Value, id.Pos()) + if gotObj != wantObj { + t.Errorf("%s: got %v, want %v", id.Pos(), gotObj, wantObj) + continue + } + } +} + +func TestConvertibleTo(t *testing.T) { + for _, test := range []struct { + v, t Type + want bool + }{ + {Typ[Int], Typ[Int], true}, + {Typ[Int], Typ[Float32], true}, + {newDefined(Typ[Int]), Typ[Int], true}, + {newDefined(new(Struct)), new(Struct), true}, + {newDefined(Typ[Int]), new(Struct), false}, + {Typ[UntypedInt], Typ[Int], true}, + {NewSlice(Typ[Int]), NewPointer(NewArray(Typ[Int], 10)), true}, + {NewSlice(Typ[Int]), NewArray(Typ[Int], 10), false}, + {NewSlice(Typ[Int]), NewPointer(NewArray(Typ[Uint], 10)), false}, + // Untyped string values are not permitted by the spec, so the below + // behavior is undefined. + {Typ[UntypedString], Typ[String], true}, + } { + if got := ConvertibleTo(test.v, test.t); got != test.want { + t.Errorf("ConvertibleTo(%v, %v) = %t, want %t", test.v, test.t, got, test.want) + } + } +} + +func TestAssignableTo(t *testing.T) { + for _, test := range []struct { + v, t Type + want bool + }{ + {Typ[Int], Typ[Int], true}, + {Typ[Int], Typ[Float32], false}, + {newDefined(Typ[Int]), Typ[Int], false}, + {newDefined(new(Struct)), new(Struct), true}, + {Typ[UntypedBool], Typ[Bool], true}, + {Typ[UntypedString], Typ[Bool], false}, + // Neither untyped string nor untyped numeric assignments arise during + // normal type checking, so the below behavior is technically undefined by + // the spec. + {Typ[UntypedString], Typ[String], true}, + {Typ[UntypedInt], Typ[Int], true}, + } { + if got := AssignableTo(test.v, test.t); got != test.want { + t.Errorf("AssignableTo(%v, %v) = %t, want %t", test.v, test.t, got, test.want) + } + } +} + +func TestIdentical_issue15173(t *testing.T) { + // Identical should allow nil arguments and be symmetric. + for _, test := range []struct { + x, y Type + want bool + }{ + {Typ[Int], Typ[Int], true}, + {Typ[Int], nil, false}, + {nil, Typ[Int], false}, + {nil, nil, true}, + } { + if got := Identical(test.x, test.y); got != test.want { + t.Errorf("Identical(%v, %v) = %t", test.x, test.y, got) + } + } +} + +func TestIssue15305(t *testing.T) { + const src = "package p; func f() int16; var _ = f(undef)" + f, err := parseSrc("issue15305.go", src) + if err != nil { + t.Fatal(err) + } + conf := Config{ + Error: func(err error) {}, // allow errors + } + info := &Info{ + Types: make(map[syntax.Expr]TypeAndValue), + } + conf.Check("p", []*syntax.File{f}, info) // ignore result + for e, tv := range info.Types { + if _, ok := e.(*syntax.CallExpr); ok { + if tv.Type != Typ[Int16] { + t.Errorf("CallExpr has type %v, want int16", tv.Type) + } + return + } + } + t.Errorf("CallExpr has no type") +} + +// TestCompositeLitTypes verifies that Info.Types registers the correct +// types for composite literal expressions and composite literal type +// expressions. +func TestCompositeLitTypes(t *testing.T) { + for _, test := range []struct { + lit, typ string + }{ + {`[16]byte{}`, `[16]byte`}, + {`[...]byte{}`, `[0]byte`}, // test for issue #14092 + {`[...]int{1, 2, 3}`, `[3]int`}, // test for issue #14092 + {`[...]int{90: 0, 98: 1, 2}`, `[100]int`}, // test for issue #14092 + {`[]int{}`, `[]int`}, + {`map[string]bool{"foo": true}`, `map[string]bool`}, + {`struct{}{}`, `struct{}`}, + {`struct{x, y int; z complex128}{}`, `struct{x int; y int; z complex128}`}, + } { + f, err := parseSrc(test.lit, "package p; var _ = "+test.lit) + if err != nil { + t.Fatalf("%s: %v", test.lit, err) + } + + info := &Info{ + Types: make(map[syntax.Expr]TypeAndValue), + } + if _, err = new(Config).Check("p", []*syntax.File{f}, info); err != nil { + t.Fatalf("%s: %v", test.lit, err) + } + + cmptype := func(x syntax.Expr, want string) { + tv, ok := info.Types[x] + if !ok { + t.Errorf("%s: no Types entry found", test.lit) + return + } + if tv.Type == nil { + t.Errorf("%s: type is nil", test.lit) + return + } + if got := tv.Type.String(); got != want { + t.Errorf("%s: got %v, want %s", test.lit, got, want) + } + } + + // test type of composite literal expression + rhs := f.DeclList[0].(*syntax.VarDecl).Values + cmptype(rhs, test.typ) + + // test type of composite literal type expression + cmptype(rhs.(*syntax.CompositeLit).Type, test.typ) + } +} + +// TestObjectParents verifies that objects have parent scopes or not +// as specified by the Object interface. +func TestObjectParents(t *testing.T) { + const src = ` +package p + +const C = 0 + +type T1 struct { + a, b int + T2 +} + +type T2 interface { + im1() + im2() +} + +func (T1) m1() {} +func (*T1) m2() {} + +func f(x int) { y := x; print(y) } +` + + f, err := parseSrc("src", src) + if err != nil { + t.Fatal(err) + } + + info := &Info{ + Defs: make(map[*syntax.Name]Object), + } + if _, err = new(Config).Check("p", []*syntax.File{f}, info); err != nil { + t.Fatal(err) + } + + for ident, obj := range info.Defs { + if obj == nil { + // only package names and implicit vars have a nil object + // (in this test we only need to handle the package name) + if ident.Value != "p" { + t.Errorf("%v has nil object", ident) + } + continue + } + + // struct fields, type-associated and interface methods + // have no parent scope + wantParent := true + switch obj := obj.(type) { + case *Var: + if obj.IsField() { + wantParent = false + } + case *Func: + if obj.Type().(*Signature).Recv() != nil { // method + wantParent = false + } + } + + gotParent := obj.Parent() != nil + switch { + case gotParent && !wantParent: + t.Errorf("%v: want no parent, got %s", ident, obj.Parent()) + case !gotParent && wantParent: + t.Errorf("%v: no parent found", ident) + } + } +} + +// TestFailedImport tests that we don't get follow-on errors +// elsewhere in a package due to failing to import a package. +func TestFailedImport(t *testing.T) { + testenv.MustHaveGoBuild(t) + + const src = ` +package p + +import foo "go/types/thisdirectorymustnotexistotherwisethistestmayfail/foo" // should only see an error here + +const c = foo.C +type T = foo.T +var v T = c +func f(x T) T { return foo.F(x) } +` + f, err := parseSrc("src", src) + if err != nil { + t.Fatal(err) + } + files := []*syntax.File{f} + + // type-check using all possible importers + for _, compiler := range []string{"gc", "gccgo", "source"} { + errcount := 0 + conf := Config{ + Error: func(err error) { + // we should only see the import error + if errcount > 0 || !strings.Contains(err.Error(), "could not import") { + t.Errorf("for %s importer, got unexpected error: %v", compiler, err) + } + errcount++ + }, + //Importer: importer.For(compiler, nil), + } + + info := &Info{ + Uses: make(map[*syntax.Name]Object), + } + pkg, _ := conf.Check("p", files, info) + if pkg == nil { + t.Errorf("for %s importer, type-checking failed to return a package", compiler) + continue + } + + imports := pkg.Imports() + if len(imports) != 1 { + t.Errorf("for %s importer, got %d imports, want 1", compiler, len(imports)) + continue + } + imp := imports[0] + if imp.Name() != "foo" { + t.Errorf(`for %s importer, got %q, want "foo"`, compiler, imp.Name()) + continue + } + + // verify that all uses of foo refer to the imported package foo (imp) + for ident, obj := range info.Uses { + if ident.Value == "foo" { + if obj, ok := obj.(*PkgName); ok { + if obj.Imported() != imp { + t.Errorf("%s resolved to %v; want %v", ident.Value, obj.Imported(), imp) + } + } else { + t.Errorf("%s resolved to %v; want package name", ident.Value, obj) + } + } + } + } +} diff --git a/src/cmd/compile/internal/types2/assignments.go b/src/cmd/compile/internal/types2/assignments.go new file mode 100644 index 0000000000000000000000000000000000000000..583118c8b235c5e6ac6a419e97add3e402c1cd47 --- /dev/null +++ b/src/cmd/compile/internal/types2/assignments.go @@ -0,0 +1,407 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements initialization and assignment checks. + +package types2 + +import "cmd/compile/internal/syntax" + +// assignment reports whether x can be assigned to a variable of type T, +// if necessary by attempting to convert untyped values to the appropriate +// type. context describes the context in which the assignment takes place. +// Use T == nil to indicate assignment to an untyped blank identifier. +// x.mode is set to invalid if the assignment failed. +func (check *Checker) assignment(x *operand, T Type, context string) { + check.singleValue(x) + + switch x.mode { + case invalid: + return // error reported before + case constant_, variable, mapindex, value, nilvalue, commaok, commaerr: + // ok + default: + // we may get here because of other problems (issue #39634, crash 12) + check.errorf(x, "cannot assign %s to %s in %s", x, T, context) + return + } + + if isUntyped(x.typ) { + target := T + // spec: "If an untyped constant is assigned to a variable of interface + // type or the blank identifier, the constant is first converted to type + // bool, rune, int, float64, complex128 or string respectively, depending + // on whether the value is a boolean, rune, integer, floating-point, + // complex, or string constant." + if x.isNil() { + if T == nil { + check.errorf(x, "use of untyped nil in %s", context) + x.mode = invalid + return + } + } else if T == nil || IsInterface(T) { + target = Default(x.typ) + } + newType, val, code := check.implicitTypeAndValue(x, target) + if code != 0 { + msg := check.sprintf("cannot use %s as %s value in %s", x, target, context) + switch code { + case _TruncatedFloat: + msg += " (truncated)" + case _NumericOverflow: + msg += " (overflows)" + } + check.error(x, msg) + x.mode = invalid + return + } + if val != nil { + x.val = val + check.updateExprVal(x.expr, val) + } + if newType != x.typ { + x.typ = newType + check.updateExprType(x.expr, newType, false) + } + } + // x.typ is typed + + // A generic (non-instantiated) function value cannot be assigned to a variable. + if sig := asSignature(x.typ); sig != nil && len(sig.tparams) > 0 { + check.errorf(x, "cannot use generic function %s without instantiation in %s", x, context) + } + + // spec: "If a left-hand side is the blank identifier, any typed or + // non-constant value except for the predeclared identifier nil may + // be assigned to it." + if T == nil { + return + } + + reason := "" + if ok, _ := x.assignableTo(check, T, &reason); !ok { + if check.conf.CompilerErrorMessages { + check.errorf(x, "incompatible type: cannot use %s as %s value", x, T) + } else { + if reason != "" { + check.errorf(x, "cannot use %s as %s value in %s: %s", x, T, context, reason) + } else { + check.errorf(x, "cannot use %s as %s value in %s", x, T, context) + } + } + x.mode = invalid + } +} + +func (check *Checker) initConst(lhs *Const, x *operand) { + if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] { + if lhs.typ == nil { + lhs.typ = Typ[Invalid] + } + return + } + + // rhs must be a constant + if x.mode != constant_ { + check.errorf(x, "%s is not constant", x) + if lhs.typ == nil { + lhs.typ = Typ[Invalid] + } + return + } + assert(isConstType(x.typ)) + + // If the lhs doesn't have a type yet, use the type of x. + if lhs.typ == nil { + lhs.typ = x.typ + } + + check.assignment(x, lhs.typ, "constant declaration") + if x.mode == invalid { + return + } + + lhs.val = x.val +} + +func (check *Checker) initVar(lhs *Var, x *operand, context string) Type { + if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] { + if lhs.typ == nil { + lhs.typ = Typ[Invalid] + } + // Note: This was reverted in go/types (https://golang.org/cl/292751). + // TODO(gri): decide what to do (also affects test/run.go exclusion list) + lhs.used = true // avoid follow-on "declared but not used" errors + return nil + } + + // If the lhs doesn't have a type yet, use the type of x. + if lhs.typ == nil { + typ := x.typ + if isUntyped(typ) { + // convert untyped types to default types + if typ == Typ[UntypedNil] { + check.errorf(x, "use of untyped nil in %s", context) + lhs.typ = Typ[Invalid] + return nil + } + typ = Default(typ) + } + lhs.typ = typ + } + + check.assignment(x, lhs.typ, context) + if x.mode == invalid { + return nil + } + + return x.typ +} + +func (check *Checker) assignVar(lhs syntax.Expr, x *operand) Type { + if x.mode == invalid || x.typ == Typ[Invalid] { + check.useLHS(lhs) + return nil + } + + // Determine if the lhs is a (possibly parenthesized) identifier. + ident, _ := unparen(lhs).(*syntax.Name) + + // Don't evaluate lhs if it is the blank identifier. + if ident != nil && ident.Value == "_" { + check.recordDef(ident, nil) + check.assignment(x, nil, "assignment to _ identifier") + if x.mode == invalid { + return nil + } + return x.typ + } + + // If the lhs is an identifier denoting a variable v, this assignment + // is not a 'use' of v. Remember current value of v.used and restore + // after evaluating the lhs via check.expr. + var v *Var + var v_used bool + if ident != nil { + if obj := check.lookup(ident.Value); obj != nil { + // It's ok to mark non-local variables, but ignore variables + // from other packages to avoid potential race conditions with + // dot-imported variables. + if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg { + v = w + v_used = v.used + } + } + } + + var z operand + check.expr(&z, lhs) + if v != nil { + v.used = v_used // restore v.used + } + + if z.mode == invalid || z.typ == Typ[Invalid] { + return nil + } + + // spec: "Each left-hand side operand must be addressable, a map index + // expression, or the blank identifier. Operands may be parenthesized." + switch z.mode { + case invalid: + return nil + case variable, mapindex: + // ok + case nilvalue: + check.error(&z, "cannot assign to nil") // default would print "untyped nil" + return nil + default: + if sel, ok := z.expr.(*syntax.SelectorExpr); ok { + var op operand + check.expr(&op, sel.X) + if op.mode == mapindex { + check.errorf(&z, "cannot assign to struct field %s in map", syntax.String(z.expr)) + return nil + } + } + check.errorf(&z, "cannot assign to %s", &z) + return nil + } + + check.assignment(x, z.typ, "assignment") + if x.mode == invalid { + return nil + } + + return x.typ +} + +// If returnPos is valid, initVars is called to type-check the assignment of +// return expressions, and returnPos is the position of the return statement. +func (check *Checker) initVars(lhs []*Var, orig_rhs []syntax.Expr, returnPos syntax.Pos) { + rhs, commaOk := check.exprList(orig_rhs, len(lhs) == 2 && !returnPos.IsKnown()) + + if len(lhs) != len(rhs) { + // invalidate lhs + for _, obj := range lhs { + if obj.typ == nil { + obj.typ = Typ[Invalid] + } + } + // don't report an error if we already reported one + for _, x := range rhs { + if x.mode == invalid { + return + } + } + if returnPos.IsKnown() { + check.errorf(returnPos, "wrong number of return values (want %d, got %d)", len(lhs), len(rhs)) + return + } + check.errorf(rhs[0], "cannot initialize %d variables with %d values", len(lhs), len(rhs)) + return + } + + context := "assignment" + if returnPos.IsKnown() { + context = "return statement" + } + + if commaOk { + var a [2]Type + for i := range a { + a[i] = check.initVar(lhs[i], rhs[i], context) + } + check.recordCommaOkTypes(orig_rhs[0], a) + return + } + + for i, lhs := range lhs { + check.initVar(lhs, rhs[i], context) + } +} + +func (check *Checker) assignVars(lhs, orig_rhs []syntax.Expr) { + rhs, commaOk := check.exprList(orig_rhs, len(lhs) == 2) + + if len(lhs) != len(rhs) { + check.useLHS(lhs...) + // don't report an error if we already reported one + for _, x := range rhs { + if x.mode == invalid { + return + } + } + check.errorf(rhs[0], "cannot assign %d values to %d variables", len(rhs), len(lhs)) + return + } + + if commaOk { + var a [2]Type + for i := range a { + a[i] = check.assignVar(lhs[i], rhs[i]) + } + check.recordCommaOkTypes(orig_rhs[0], a) + return + } + + for i, lhs := range lhs { + check.assignVar(lhs, rhs[i]) + } +} + +// unpack unpacks a *syntax.ListExpr into a list of syntax.Expr. +// Helper introduced for the go/types -> types2 port. +// TODO(gri) Should find a more efficient solution that doesn't +// require introduction of a new slice for simple +// expressions. +func unpackExpr(x syntax.Expr) []syntax.Expr { + if x, _ := x.(*syntax.ListExpr); x != nil { + return x.ElemList + } + if x != nil { + return []syntax.Expr{x} + } + return nil +} + +func (check *Checker) shortVarDecl(pos syntax.Pos, lhs, rhs []syntax.Expr) { + top := len(check.delayed) + scope := check.scope + + // collect lhs variables + seen := make(map[string]bool, len(lhs)) + lhsVars := make([]*Var, len(lhs)) + newVars := make([]*Var, 0, len(lhs)) + hasErr := false + for i, lhs := range lhs { + ident, _ := lhs.(*syntax.Name) + if ident == nil { + check.useLHS(lhs) + check.errorf(lhs, "non-name %s on left side of :=", lhs) + hasErr = true + continue + } + + name := ident.Value + if name != "_" { + if seen[name] { + check.errorf(lhs, "%s repeated on left side of :=", lhs) + hasErr = true + continue + } + seen[name] = true + } + + // Use the correct obj if the ident is redeclared. The + // variable's scope starts after the declaration; so we + // must use Scope.Lookup here and call Scope.Insert + // (via check.declare) later. + if alt := scope.Lookup(name); alt != nil { + check.recordUse(ident, alt) + // redeclared object must be a variable + if obj, _ := alt.(*Var); obj != nil { + lhsVars[i] = obj + } else { + check.errorf(lhs, "cannot assign to %s", lhs) + hasErr = true + } + continue + } + + // declare new variable + obj := NewVar(ident.Pos(), check.pkg, name, nil) + lhsVars[i] = obj + if name != "_" { + newVars = append(newVars, obj) + } + check.recordDef(ident, obj) + } + + // create dummy variables where the lhs is invalid + for i, obj := range lhsVars { + if obj == nil { + lhsVars[i] = NewVar(lhs[i].Pos(), check.pkg, "_", nil) + } + } + + check.initVars(lhsVars, rhs, nopos) + + // process function literals in rhs expressions before scope changes + check.processDelayed(top) + + if len(newVars) == 0 && !hasErr { + check.softErrorf(pos, "no new variables on left side of :=") + return + } + + // declare new variables + // spec: "The scope of a constant or variable identifier declared inside + // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl + // for short variable declarations) and ends at the end of the innermost + // containing block." + scopePos := syntax.EndPos(rhs[len(rhs)-1]) + for _, obj := range newVars { + check.declare(scope, nil, obj, scopePos) // id = nil: recordDef already called + } +} diff --git a/src/cmd/compile/internal/types2/builtins.go b/src/cmd/compile/internal/types2/builtins.go new file mode 100644 index 0000000000000000000000000000000000000000..f90e06f226791ddb7630c8b5c6d79788378bf486 --- /dev/null +++ b/src/cmd/compile/internal/types2/builtins.go @@ -0,0 +1,827 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements typechecking of builtin function calls. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "go/constant" + "go/token" +) + +// builtin type-checks a call to the built-in specified by id and +// reports whether the call is valid, with *x holding the result; +// but x.expr is not set. If the call is invalid, the result is +// false, and *x is undefined. +// +func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (_ bool) { + // append is the only built-in that permits the use of ... for the last argument + bin := predeclaredFuncs[id] + if call.HasDots && id != _Append { + //check.errorf(call.Ellipsis, invalidOp + "invalid use of ... with built-in %s", bin.name) + check.errorf(call, invalidOp+"invalid use of ... with built-in %s", bin.name) + check.use(call.ArgList...) + return + } + + // For len(x) and cap(x) we need to know if x contains any function calls or + // receive operations. Save/restore current setting and set hasCallOrRecv to + // false for the evaluation of x so that we can check it afterwards. + // Note: We must do this _before_ calling exprList because exprList evaluates + // all arguments. + if id == _Len || id == _Cap { + defer func(b bool) { + check.hasCallOrRecv = b + }(check.hasCallOrRecv) + check.hasCallOrRecv = false + } + + // determine actual arguments + var arg func(*operand, int) // TODO(gri) remove use of arg getter in favor of using xlist directly + nargs := len(call.ArgList) + switch id { + default: + // make argument getter + xlist, _ := check.exprList(call.ArgList, false) + arg = func(x *operand, i int) { *x = *xlist[i]; x.typ = expand(x.typ) } + nargs = len(xlist) + // evaluate first argument, if present + if nargs > 0 { + arg(x, 0) + if x.mode == invalid { + return + } + } + case _Make, _New, _Offsetof, _Trace: + // arguments require special handling + } + + // check argument count + { + msg := "" + if nargs < bin.nargs { + msg = "not enough" + } else if !bin.variadic && nargs > bin.nargs { + msg = "too many" + } + if msg != "" { + check.errorf(call, invalidOp+"%s arguments for %v (expected %d, found %d)", msg, call, bin.nargs, nargs) + return + } + } + + switch id { + case _Append: + // append(s S, x ...T) S, where T is the element type of S + // spec: "The variadic function append appends zero or more values x to s of type + // S, which must be a slice type, and returns the resulting slice, also of type S. + // The values x are passed to a parameter of type ...T where T is the element type + // of S and the respective parameter passing rules apply." + S := x.typ + var T Type + if s := asSlice(S); s != nil { + T = s.elem + } else { + check.errorf(x, invalidArg+"%s is not a slice", x) + return + } + + // remember arguments that have been evaluated already + alist := []operand{*x} + + // spec: "As a special case, append also accepts a first argument assignable + // to type []byte with a second argument of string type followed by ... . + // This form appends the bytes of the string. + if nargs == 2 && call.HasDots { + if ok, _ := x.assignableTo(check, NewSlice(universeByte), nil); ok { + arg(x, 1) + if x.mode == invalid { + return + } + if isString(x.typ) { + if check.Types != nil { + sig := makeSig(S, S, x.typ) + sig.variadic = true + check.recordBuiltinType(call.Fun, sig) + } + x.mode = value + x.typ = S + break + } + alist = append(alist, *x) + // fallthrough + } + } + + // check general case by creating custom signature + sig := makeSig(S, S, NewSlice(T)) // []T required for variadic signature + sig.variadic = true + var xlist []*operand + // convert []operand to []*operand + for i := range alist { + xlist = append(xlist, &alist[i]) + } + for i := len(alist); i < nargs; i++ { + var x operand + arg(&x, i) + xlist = append(xlist, &x) + } + check.arguments(call, sig, nil, xlist) // discard result (we know the result type) + // ok to continue even if check.arguments reported errors + + x.mode = value + x.typ = S + if check.Types != nil { + check.recordBuiltinType(call.Fun, sig) + } + + case _Cap, _Len: + // cap(x) + // len(x) + mode := invalid + var typ Type + var val constant.Value + switch typ = implicitArrayDeref(optype(x.typ)); t := typ.(type) { + case *Basic: + if isString(t) && id == _Len { + if x.mode == constant_ { + mode = constant_ + val = constant.MakeInt64(int64(len(constant.StringVal(x.val)))) + } else { + mode = value + } + } + + case *Array: + mode = value + // spec: "The expressions len(s) and cap(s) are constants + // if the type of s is an array or pointer to an array and + // the expression s does not contain channel receives or + // function calls; in this case s is not evaluated." + if !check.hasCallOrRecv { + mode = constant_ + if t.len >= 0 { + val = constant.MakeInt64(t.len) + } else { + val = constant.MakeUnknown() + } + } + + case *Slice, *Chan: + mode = value + + case *Map: + if id == _Len { + mode = value + } + + case *Sum: + if t.is(func(t Type) bool { + switch t := under(t).(type) { + case *Basic: + if isString(t) && id == _Len { + return true + } + case *Array, *Slice, *Chan: + return true + case *Map: + if id == _Len { + return true + } + } + return false + }) { + mode = value + } + } + + if mode == invalid && typ != Typ[Invalid] { + check.errorf(x, invalidArg+"%s for %s", x, bin.name) + return + } + + x.mode = mode + x.typ = Typ[Int] + x.val = val + if check.Types != nil && mode != constant_ { + check.recordBuiltinType(call.Fun, makeSig(x.typ, typ)) + } + + case _Close: + // close(c) + c := asChan(x.typ) + if c == nil { + check.errorf(x, invalidArg+"%s is not a channel", x) + return + } + if c.dir == RecvOnly { + check.errorf(x, invalidArg+"%s must not be a receive-only channel", x) + return + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, c)) + } + + case _Complex: + // complex(x, y floatT) complexT + var y operand + arg(&y, 1) + if y.mode == invalid { + return + } + + // convert or check untyped arguments + d := 0 + if isUntyped(x.typ) { + d |= 1 + } + if isUntyped(y.typ) { + d |= 2 + } + switch d { + case 0: + // x and y are typed => nothing to do + case 1: + // only x is untyped => convert to type of y + check.convertUntyped(x, y.typ) + case 2: + // only y is untyped => convert to type of x + check.convertUntyped(&y, x.typ) + case 3: + // x and y are untyped => + // 1) if both are constants, convert them to untyped + // floating-point numbers if possible, + // 2) if one of them is not constant (possible because + // it contains a shift that is yet untyped), convert + // both of them to float64 since they must have the + // same type to succeed (this will result in an error + // because shifts of floats are not permitted) + if x.mode == constant_ && y.mode == constant_ { + toFloat := func(x *operand) { + if isNumeric(x.typ) && constant.Sign(constant.Imag(x.val)) == 0 { + x.typ = Typ[UntypedFloat] + } + } + toFloat(x) + toFloat(&y) + } else { + check.convertUntyped(x, Typ[Float64]) + check.convertUntyped(&y, Typ[Float64]) + // x and y should be invalid now, but be conservative + // and check below + } + } + if x.mode == invalid || y.mode == invalid { + return + } + + // both argument types must be identical + if !check.identical(x.typ, y.typ) { + check.errorf(x, invalidOp+"%v (mismatched types %s and %s)", call, x.typ, y.typ) + return + } + + // the argument types must be of floating-point type + f := func(x Type) Type { + if t := asBasic(x); t != nil { + switch t.kind { + case Float32: + return Typ[Complex64] + case Float64: + return Typ[Complex128] + case UntypedFloat: + return Typ[UntypedComplex] + } + } + return nil + } + resTyp := check.applyTypeFunc(f, x.typ) + if resTyp == nil { + check.errorf(x, invalidArg+"arguments have type %s, expected floating-point", x.typ) + return + } + + // if both arguments are constants, the result is a constant + if x.mode == constant_ && y.mode == constant_ { + x.val = constant.BinaryOp(constant.ToFloat(x.val), token.ADD, constant.MakeImag(constant.ToFloat(y.val))) + } else { + x.mode = value + } + + if check.Types != nil && x.mode != constant_ { + check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ, x.typ)) + } + + x.typ = resTyp + + case _Copy: + // copy(x, y []T) int + var dst Type + if t := asSlice(x.typ); t != nil { + dst = t.elem + } + + var y operand + arg(&y, 1) + if y.mode == invalid { + return + } + var src Type + switch t := optype(y.typ).(type) { + case *Basic: + if isString(y.typ) { + src = universeByte + } + case *Slice: + src = t.elem + } + + if dst == nil || src == nil { + check.errorf(x, invalidArg+"copy expects slice arguments; found %s and %s", x, &y) + return + } + + if !check.identical(dst, src) { + check.errorf(x, invalidArg+"arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src) + return + } + + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(Typ[Int], x.typ, y.typ)) + } + x.mode = value + x.typ = Typ[Int] + + case _Delete: + // delete(m, k) + m := asMap(x.typ) + if m == nil { + check.errorf(x, invalidArg+"%s is not a map", x) + return + } + arg(x, 1) // k + if x.mode == invalid { + return + } + + check.assignment(x, m.key, "argument to delete") + if x.mode == invalid { + return + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, m, m.key)) + } + + case _Imag, _Real: + // imag(complexT) floatT + // real(complexT) floatT + + // convert or check untyped argument + if isUntyped(x.typ) { + if x.mode == constant_ { + // an untyped constant number can always be considered + // as a complex constant + if isNumeric(x.typ) { + x.typ = Typ[UntypedComplex] + } + } else { + // an untyped non-constant argument may appear if + // it contains a (yet untyped non-constant) shift + // expression: convert it to complex128 which will + // result in an error (shift of complex value) + check.convertUntyped(x, Typ[Complex128]) + // x should be invalid now, but be conservative and check + if x.mode == invalid { + return + } + } + } + + // the argument must be of complex type + f := func(x Type) Type { + if t := asBasic(x); t != nil { + switch t.kind { + case Complex64: + return Typ[Float32] + case Complex128: + return Typ[Float64] + case UntypedComplex: + return Typ[UntypedFloat] + } + } + return nil + } + resTyp := check.applyTypeFunc(f, x.typ) + if resTyp == nil { + check.errorf(x, invalidArg+"argument has type %s, expected complex type", x.typ) + return + } + + // if the argument is a constant, the result is a constant + if x.mode == constant_ { + if id == _Real { + x.val = constant.Real(x.val) + } else { + x.val = constant.Imag(x.val) + } + } else { + x.mode = value + } + + if check.Types != nil && x.mode != constant_ { + check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ)) + } + + x.typ = resTyp + + case _Make: + // make(T, n) + // make(T, n, m) + // (no argument evaluated yet) + arg0 := call.ArgList[0] + T := check.varType(arg0) + if T == Typ[Invalid] { + return + } + + min, max := -1, 10 + var valid func(t Type) bool + valid = func(t Type) bool { + var m int + switch t := optype(t).(type) { + case *Slice: + m = 2 + case *Map, *Chan: + m = 1 + case *Sum: + return t.is(valid) + default: + return false + } + if m > min { + min = m + } + if m+1 < max { + max = m + 1 + } + return true + } + + if !valid(T) { + check.errorf(arg0, invalidArg+"cannot make %s; type must be slice, map, or channel", arg0) + return + } + if nargs < min || max < nargs { + if min == max { + check.errorf(call, "%v expects %d arguments; found %d", call, min, nargs) + } else { + check.errorf(call, "%v expects %d or %d arguments; found %d", call, min, max, nargs) + } + return + } + + types := []Type{T} + var sizes []int64 // constant integer arguments, if any + for _, arg := range call.ArgList[1:] { + typ, size := check.index(arg, -1) // ok to continue with typ == Typ[Invalid] + types = append(types, typ) + if size >= 0 { + sizes = append(sizes, size) + } + } + if len(sizes) == 2 && sizes[0] > sizes[1] { + check.error(call.ArgList[1], invalidArg+"length and capacity swapped") + // safe to continue + } + x.mode = value + x.typ = T + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ, types...)) + } + + case _New: + // new(T) + // (no argument evaluated yet) + T := check.varType(call.ArgList[0]) + if T == Typ[Invalid] { + return + } + + x.mode = value + x.typ = &Pointer{base: T} + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ, T)) + } + + case _Panic: + // panic(x) + // record panic call if inside a function with result parameters + // (for use in Checker.isTerminating) + if check.sig != nil && check.sig.results.Len() > 0 { + // function has result parameters + p := check.isPanic + if p == nil { + // allocate lazily + p = make(map[*syntax.CallExpr]bool) + check.isPanic = p + } + p[call] = true + } + + check.assignment(x, &emptyInterface, "argument to panic") + if x.mode == invalid { + return + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, &emptyInterface)) + } + + case _Print, _Println: + // print(x, y, ...) + // println(x, y, ...) + var params []Type + if nargs > 0 { + params = make([]Type, nargs) + for i := 0; i < nargs; i++ { + if i > 0 { + arg(x, i) // first argument already evaluated + } + check.assignment(x, nil, "argument to "+predeclaredFuncs[id].name) + if x.mode == invalid { + // TODO(gri) "use" all arguments? + return + } + params[i] = x.typ + } + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, params...)) + } + + case _Recover: + // recover() interface{} + x.mode = value + x.typ = &emptyInterface + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ)) + } + + case _Add: + // unsafe.Add(ptr unsafe.Pointer, len IntegerType) unsafe.Pointer + if !check.allowVersion(check.pkg, 1, 17) { + check.error(call.Fun, "unsafe.Add requires go1.17 or later") + return + } + + check.assignment(x, Typ[UnsafePointer], "argument to unsafe.Add") + if x.mode == invalid { + return + } + + var y operand + arg(&y, 1) + if !check.isValidIndex(&y, "length", true) { + return + } + + x.mode = value + x.typ = Typ[UnsafePointer] + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ, x.typ, y.typ)) + } + + case _Alignof: + // unsafe.Alignof(x T) uintptr + if asTypeParam(x.typ) != nil { + check.errorf(call, invalidOp+"unsafe.Alignof undefined for %s", x) + return + } + check.assignment(x, nil, "argument to unsafe.Alignof") + if x.mode == invalid { + return + } + + x.mode = constant_ + x.val = constant.MakeInt64(check.conf.alignof(x.typ)) + x.typ = Typ[Uintptr] + // result is constant - no need to record signature + + case _Offsetof: + // unsafe.Offsetof(x T) uintptr, where x must be a selector + // (no argument evaluated yet) + arg0 := call.ArgList[0] + selx, _ := unparen(arg0).(*syntax.SelectorExpr) + if selx == nil { + check.errorf(arg0, invalidArg+"%s is not a selector expression", arg0) + check.use(arg0) + return + } + + check.expr(x, selx.X) + if x.mode == invalid { + return + } + + base := derefStructPtr(x.typ) + sel := selx.Sel.Value + obj, index, indirect := check.lookupFieldOrMethod(base, false, check.pkg, sel) + switch obj.(type) { + case nil: + check.errorf(x, invalidArg+"%s has no single field %s", base, sel) + return + case *Func: + // TODO(gri) Using derefStructPtr may result in methods being found + // that don't actually exist. An error either way, but the error + // message is confusing. See: https://play.golang.org/p/al75v23kUy , + // but go/types reports: "invalid argument: x.m is a method value". + check.errorf(arg0, invalidArg+"%s is a method value", arg0) + return + } + if indirect { + check.errorf(x, invalidArg+"field %s is embedded via a pointer in %s", sel, base) + return + } + + // TODO(gri) Should we pass x.typ instead of base (and indirect report if derefStructPtr indirected)? + check.recordSelection(selx, FieldVal, base, obj, index, false) + + offs := check.conf.offsetof(base, index) + x.mode = constant_ + x.val = constant.MakeInt64(offs) + x.typ = Typ[Uintptr] + // result is constant - no need to record signature + + case _Sizeof: + // unsafe.Sizeof(x T) uintptr + if asTypeParam(x.typ) != nil { + check.errorf(call, invalidOp+"unsafe.Sizeof undefined for %s", x) + return + } + check.assignment(x, nil, "argument to unsafe.Sizeof") + if x.mode == invalid { + return + } + + x.mode = constant_ + x.val = constant.MakeInt64(check.conf.sizeof(x.typ)) + x.typ = Typ[Uintptr] + // result is constant - no need to record signature + + case _Slice: + // unsafe.Slice(ptr *T, len IntegerType) []T + if !check.allowVersion(check.pkg, 1, 17) { + check.error(call.Fun, "unsafe.Slice requires go1.17 or later") + return + } + + typ := asPointer(x.typ) + if typ == nil { + check.errorf(x, invalidArg+"%s is not a pointer", x) + return + } + + var y operand + arg(&y, 1) + if !check.isValidIndex(&y, "length", false) { + return + } + + x.mode = value + x.typ = NewSlice(typ.base) + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ, typ, y.typ)) + } + + case _Assert: + // assert(pred) causes a typechecker error if pred is false. + // The result of assert is the value of pred if there is no error. + // Note: assert is only available in self-test mode. + if x.mode != constant_ || !isBoolean(x.typ) { + check.errorf(x, invalidArg+"%s is not a boolean constant", x) + return + } + if x.val.Kind() != constant.Bool { + check.errorf(x, "internal error: value of %s should be a boolean constant", x) + return + } + if !constant.BoolVal(x.val) { + check.errorf(call, "%v failed", call) + // compile-time assertion failure - safe to continue + } + // result is constant - no need to record signature + + case _Trace: + // trace(x, y, z, ...) dumps the positions, expressions, and + // values of its arguments. The result of trace is the value + // of the first argument. + // Note: trace is only available in self-test mode. + // (no argument evaluated yet) + if nargs == 0 { + check.dump("%v: trace() without arguments", posFor(call)) + x.mode = novalue + break + } + var t operand + x1 := x + for _, arg := range call.ArgList { + check.rawExpr(x1, arg, nil) // permit trace for types, e.g.: new(trace(T)) + check.dump("%v: %s", posFor(x1), x1) + x1 = &t // use incoming x only for first argument + } + // trace is only available in test mode - no need to record signature + + default: + unreachable() + } + + return true +} + +// applyTypeFunc applies f to x. If x is a type parameter, +// the result is a type parameter constrained by an new +// interface bound. The type bounds for that interface +// are computed by applying f to each of the type bounds +// of x. If any of these applications of f return nil, +// applyTypeFunc returns nil. +// If x is not a type parameter, the result is f(x). +func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type { + if tp := asTypeParam(x); tp != nil { + // Test if t satisfies the requirements for the argument + // type and collect possible result types at the same time. + var rtypes []Type + if !tp.Bound().is(func(x Type) bool { + if r := f(x); r != nil { + rtypes = append(rtypes, r) + return true + } + return false + }) { + return nil + } + + // TODO(gri) Would it be ok to return just the one type + // if len(rtypes) == 1? What about top-level + // uses of real() where the result is used to + // define type and initialize a variable? + + // construct a suitable new type parameter + tpar := NewTypeName(nopos, nil /* = Universe pkg */, "", nil) + ptyp := check.NewTypeParam(tpar, 0, &emptyInterface) // assigns type to tpar as a side-effect + tsum := NewSum(rtypes) + ptyp.bound = &Interface{types: tsum, allMethods: markComplete, allTypes: tsum} + + return ptyp + } + + return f(x) +} + +// makeSig makes a signature for the given argument and result types. +// Default types are used for untyped arguments, and res may be nil. +func makeSig(res Type, args ...Type) *Signature { + list := make([]*Var, len(args)) + for i, param := range args { + list[i] = NewVar(nopos, nil, "", Default(param)) + } + params := NewTuple(list...) + var result *Tuple + if res != nil { + assert(!isUntyped(res)) + result = NewTuple(NewVar(nopos, nil, "", res)) + } + return &Signature{params: params, results: result} +} + +// implicitArrayDeref returns A if typ is of the form *A and A is an array; +// otherwise it returns typ. +// +func implicitArrayDeref(typ Type) Type { + if p, ok := typ.(*Pointer); ok { + if a := asArray(p.base); a != nil { + return a + } + } + return typ +} + +// unparen returns e with any enclosing parentheses stripped. +func unparen(e syntax.Expr) syntax.Expr { + for { + p, ok := e.(*syntax.ParenExpr) + if !ok { + return e + } + e = p.X + } +} diff --git a/src/cmd/compile/internal/types2/builtins_test.go b/src/cmd/compile/internal/types2/builtins_test.go new file mode 100644 index 0000000000000000000000000000000000000000..82c786b86ea20e5f8775d533b34d7cca8dadb3c1 --- /dev/null +++ b/src/cmd/compile/internal/types2/builtins_test.go @@ -0,0 +1,228 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2_test + +import ( + "cmd/compile/internal/syntax" + "fmt" + "testing" + + . "cmd/compile/internal/types2" +) + +var builtinCalls = []struct { + name, src, sig string +}{ + {"append", `var s []int; _ = append(s)`, `func([]int, ...int) []int`}, + {"append", `var s []int; _ = append(s, 0)`, `func([]int, ...int) []int`}, + {"append", `var s []int; _ = (append)(s, 0)`, `func([]int, ...int) []int`}, + {"append", `var s []byte; _ = ((append))(s, 0)`, `func([]byte, ...byte) []byte`}, + {"append", `var s []byte; _ = append(s, "foo"...)`, `func([]byte, string...) []byte`}, + {"append", `type T []byte; var s T; var str string; _ = append(s, str...)`, `func(p.T, string...) p.T`}, + {"append", `type T []byte; type U string; var s T; var str U; _ = append(s, str...)`, `func(p.T, p.U...) p.T`}, + + {"cap", `var s [10]int; _ = cap(s)`, `invalid type`}, // constant + {"cap", `var s [10]int; _ = cap(&s)`, `invalid type`}, // constant + {"cap", `var s []int64; _ = cap(s)`, `func([]int64) int`}, + {"cap", `var c chan<-bool; _ = cap(c)`, `func(chan<- bool) int`}, + + {"len", `_ = len("foo")`, `invalid type`}, // constant + {"len", `var s string; _ = len(s)`, `func(string) int`}, + {"len", `var s [10]int; _ = len(s)`, `invalid type`}, // constant + {"len", `var s [10]int; _ = len(&s)`, `invalid type`}, // constant + {"len", `var s []int64; _ = len(s)`, `func([]int64) int`}, + {"len", `var c chan<-bool; _ = len(c)`, `func(chan<- bool) int`}, + {"len", `var m map[string]float32; _ = len(m)`, `func(map[string]float32) int`}, + + {"close", `var c chan int; close(c)`, `func(chan int)`}, + {"close", `var c chan<- chan string; close(c)`, `func(chan<- chan string)`}, + + {"complex", `_ = complex(1, 0)`, `invalid type`}, // constant + {"complex", `var re float32; _ = complex(re, 1.0)`, `func(float32, float32) complex64`}, + {"complex", `var im float64; _ = complex(1, im)`, `func(float64, float64) complex128`}, + {"complex", `type F32 float32; var re, im F32; _ = complex(re, im)`, `func(p.F32, p.F32) complex64`}, + {"complex", `type F64 float64; var re, im F64; _ = complex(re, im)`, `func(p.F64, p.F64) complex128`}, + + {"copy", `var src, dst []byte; copy(dst, src)`, `func([]byte, []byte) int`}, + {"copy", `type T [][]int; var src, dst T; _ = copy(dst, src)`, `func(p.T, p.T) int`}, + {"copy", `var src string; var dst []byte; copy(dst, src)`, `func([]byte, string) int`}, + {"copy", `type T string; type U []byte; var src T; var dst U; copy(dst, src)`, `func(p.U, p.T) int`}, + {"copy", `var dst []byte; copy(dst, "hello")`, `func([]byte, string) int`}, + + {"delete", `var m map[string]bool; delete(m, "foo")`, `func(map[string]bool, string)`}, + {"delete", `type (K string; V int); var m map[K]V; delete(m, "foo")`, `func(map[p.K]p.V, p.K)`}, + + {"imag", `_ = imag(1i)`, `invalid type`}, // constant + {"imag", `var c complex64; _ = imag(c)`, `func(complex64) float32`}, + {"imag", `var c complex128; _ = imag(c)`, `func(complex128) float64`}, + {"imag", `type C64 complex64; var c C64; _ = imag(c)`, `func(p.C64) float32`}, + {"imag", `type C128 complex128; var c C128; _ = imag(c)`, `func(p.C128) float64`}, + + {"real", `_ = real(1i)`, `invalid type`}, // constant + {"real", `var c complex64; _ = real(c)`, `func(complex64) float32`}, + {"real", `var c complex128; _ = real(c)`, `func(complex128) float64`}, + {"real", `type C64 complex64; var c C64; _ = real(c)`, `func(p.C64) float32`}, + {"real", `type C128 complex128; var c C128; _ = real(c)`, `func(p.C128) float64`}, + + {"make", `_ = make([]int, 10)`, `func([]int, int) []int`}, + {"make", `type T []byte; _ = make(T, 10, 20)`, `func(p.T, int, int) p.T`}, + + // issue #37349 + {"make", ` _ = make([]int, 0 )`, `func([]int, int) []int`}, + {"make", `var l int; _ = make([]int, l )`, `func([]int, int) []int`}, + {"make", ` _ = make([]int, 0, 0)`, `func([]int, int, int) []int`}, + {"make", `var l int; _ = make([]int, l, 0)`, `func([]int, int, int) []int`}, + {"make", `var c int; _ = make([]int, 0, c)`, `func([]int, int, int) []int`}, + {"make", `var l, c int; _ = make([]int, l, c)`, `func([]int, int, int) []int`}, + + // issue #37393 + {"make", ` _ = make([]int , 0 )`, `func([]int, int) []int`}, + {"make", `var l byte ; _ = make([]int8 , l )`, `func([]int8, byte) []int8`}, + {"make", ` _ = make([]int16 , 0, 0)`, `func([]int16, int, int) []int16`}, + {"make", `var l int16; _ = make([]string , l, 0)`, `func([]string, int16, int) []string`}, + {"make", `var c int32; _ = make([]float64 , 0, c)`, `func([]float64, int, int32) []float64`}, + {"make", `var l, c uint ; _ = make([]complex128, l, c)`, `func([]complex128, uint, uint) []complex128`}, + + // issue #45667 + {"make", `const l uint = 1; _ = make([]int, l)`, `func([]int, uint) []int`}, + + {"new", `_ = new(int)`, `func(int) *int`}, + {"new", `type T struct{}; _ = new(T)`, `func(p.T) *p.T`}, + + {"panic", `panic(0)`, `func(interface{})`}, + {"panic", `panic("foo")`, `func(interface{})`}, + + {"print", `print()`, `func()`}, + {"print", `print(0)`, `func(int)`}, + {"print", `print(1, 2.0, "foo", true)`, `func(int, float64, string, bool)`}, + + {"println", `println()`, `func()`}, + {"println", `println(0)`, `func(int)`}, + {"println", `println(1, 2.0, "foo", true)`, `func(int, float64, string, bool)`}, + + {"recover", `recover()`, `func() interface{}`}, + {"recover", `_ = recover()`, `func() interface{}`}, + + {"Add", `var p unsafe.Pointer; _ = unsafe.Add(p, -1.0)`, `func(unsafe.Pointer, int) unsafe.Pointer`}, + {"Add", `var p unsafe.Pointer; var n uintptr; _ = unsafe.Add(p, n)`, `func(unsafe.Pointer, uintptr) unsafe.Pointer`}, + {"Add", `_ = unsafe.Add(nil, 0)`, `func(unsafe.Pointer, int) unsafe.Pointer`}, + + {"Alignof", `_ = unsafe.Alignof(0)`, `invalid type`}, // constant + {"Alignof", `var x struct{}; _ = unsafe.Alignof(x)`, `invalid type`}, // constant + + {"Offsetof", `var x struct{f bool}; _ = unsafe.Offsetof(x.f)`, `invalid type`}, // constant + {"Offsetof", `var x struct{_ int; f bool}; _ = unsafe.Offsetof((&x).f)`, `invalid type`}, // constant + + {"Sizeof", `_ = unsafe.Sizeof(0)`, `invalid type`}, // constant + {"Sizeof", `var x struct{}; _ = unsafe.Sizeof(x)`, `invalid type`}, // constant + + {"Slice", `var p *int; _ = unsafe.Slice(p, 1)`, `func(*int, int) []int`}, + {"Slice", `var p *byte; var n uintptr; _ = unsafe.Slice(p, n)`, `func(*byte, uintptr) []byte`}, + + {"assert", `assert(true)`, `invalid type`}, // constant + {"assert", `type B bool; const pred B = 1 < 2; assert(pred)`, `invalid type`}, // constant + + // no tests for trace since it produces output as a side-effect +} + +func TestBuiltinSignatures(t *testing.T) { + DefPredeclaredTestFuncs() + + seen := map[string]bool{"trace": true} // no test for trace built-in; add it manually + for _, call := range builtinCalls { + testBuiltinSignature(t, call.name, call.src, call.sig) + seen[call.name] = true + } + + // make sure we didn't miss one + for _, name := range Universe.Names() { + if _, ok := Universe.Lookup(name).(*Builtin); ok && !seen[name] { + t.Errorf("missing test for %s", name) + } + } + for _, name := range Unsafe.Scope().Names() { + if _, ok := Unsafe.Scope().Lookup(name).(*Builtin); ok && !seen[name] { + t.Errorf("missing test for unsafe.%s", name) + } + } +} + +func testBuiltinSignature(t *testing.T, name, src0, want string) { + src := fmt.Sprintf(`package p; import "unsafe"; type _ unsafe.Pointer /* use unsafe */; func _() { %s }`, src0) + f, err := parseSrc("", src) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + conf := Config{Importer: defaultImporter()} + uses := make(map[*syntax.Name]Object) + types := make(map[syntax.Expr]TypeAndValue) + _, err = conf.Check(f.PkgName.Value, []*syntax.File{f}, &Info{Uses: uses, Types: types}) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + // find called function + n := 0 + var fun syntax.Expr + for x := range types { + if call, _ := x.(*syntax.CallExpr); call != nil { + fun = call.Fun + n++ + } + } + if n != 1 { + t.Errorf("%s: got %d CallExprs; want 1", src0, n) + return + } + + // check recorded types for fun and descendents (may be parenthesized) + for { + // the recorded type for the built-in must match the wanted signature + typ := types[fun].Type + if typ == nil { + t.Errorf("%s: no type recorded for %s", src0, syntax.String(fun)) + return + } + if got := typ.String(); got != want { + t.Errorf("%s: got type %s; want %s", src0, got, want) + return + } + + // called function must be a (possibly parenthesized, qualified) + // identifier denoting the expected built-in + switch p := fun.(type) { + case *syntax.Name: + obj := uses[p] + if obj == nil { + t.Errorf("%s: no object found for %s", src0, p.Value) + return + } + bin, _ := obj.(*Builtin) + if bin == nil { + t.Errorf("%s: %s does not denote a built-in", src0, p.Value) + return + } + if bin.Name() != name { + t.Errorf("%s: got built-in %s; want %s", src0, bin.Name(), name) + return + } + return // we're done + + case *syntax.ParenExpr: + fun = p.X // unpack + + case *syntax.SelectorExpr: + // built-in from package unsafe - ignore details + return // we're done + + default: + t.Errorf("%s: invalid function call", src0) + return + } + } +} diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go new file mode 100644 index 0000000000000000000000000000000000000000..6d149340b285b3e6bd8fc241e7dd5a50a8036370 --- /dev/null +++ b/src/cmd/compile/internal/types2/call.go @@ -0,0 +1,696 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements typechecking of call and selector expressions. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "strings" + "unicode" +) + +// funcInst type-checks a function instantiation inst and returns the result in x. +// The operand x must be the evaluation of inst.X and its type must be a signature. +func (check *Checker) funcInst(x *operand, inst *syntax.IndexExpr) { + xlist := unpackExpr(inst.Index) + targs := check.typeList(xlist) + if targs == nil { + x.mode = invalid + x.expr = inst + return + } + assert(len(targs) == len(xlist)) + + // check number of type arguments (got) vs number of type parameters (want) + sig := x.typ.(*Signature) + got, want := len(targs), len(sig.tparams) + if !useConstraintTypeInference && got != want || got > want { + check.errorf(xlist[got-1], "got %d type arguments but want %d", got, want) + x.mode = invalid + x.expr = inst + return + } + + // if we don't have enough type arguments, try type inference + inferred := false + if got < want { + targs = check.infer(inst.Pos(), sig.tparams, targs, nil, nil, true) + if targs == nil { + // error was already reported + x.mode = invalid + x.expr = inst + return + } + got = len(targs) + inferred = true + } + assert(got == want) + + // determine argument positions (for error reporting) + poslist := make([]syntax.Pos, len(xlist)) + for i, x := range xlist { + poslist[i] = syntax.StartPos(x) + } + + // instantiate function signature + res := check.instantiate(x.Pos(), sig, targs, poslist).(*Signature) + assert(res.tparams == nil) // signature is not generic anymore + if inferred { + check.recordInferred(inst, targs, res) + } + x.typ = res + x.mode = value + x.expr = inst +} + +func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind { + var inst *syntax.IndexExpr // function instantiation, if any + if iexpr, _ := call.Fun.(*syntax.IndexExpr); iexpr != nil { + if check.indexExpr(x, iexpr) { + // Delay function instantiation to argument checking, + // where we combine type and value arguments for type + // inference. + assert(x.mode == value) + inst = iexpr + } + x.expr = iexpr + check.record(x) + } else { + check.exprOrType(x, call.Fun) + } + + switch x.mode { + case invalid: + check.use(call.ArgList...) + x.expr = call + return statement + + case typexpr: + // conversion + T := x.typ + x.mode = invalid + switch n := len(call.ArgList); n { + case 0: + check.errorf(call, "missing argument in conversion to %s", T) + case 1: + check.expr(x, call.ArgList[0]) + if x.mode != invalid { + if t := asInterface(T); t != nil { + check.completeInterface(nopos, t) + if t.IsConstraint() { + check.errorf(call, "cannot use interface %s in conversion (contains type list or is comparable)", T) + break + } + } + if call.HasDots { + check.errorf(call.ArgList[0], "invalid use of ... in type conversion to %s", T) + break + } + check.conversion(x, T) + } + default: + check.use(call.ArgList...) + check.errorf(call.ArgList[n-1], "too many arguments in conversion to %s", T) + } + x.expr = call + return conversion + + case builtin: + id := x.id + if !check.builtin(x, call, id) { + x.mode = invalid + } + x.expr = call + // a non-constant result implies a function call + if x.mode != invalid && x.mode != constant_ { + check.hasCallOrRecv = true + } + return predeclaredFuncs[id].kind + } + + // ordinary function/method call + cgocall := x.mode == cgofunc + + sig := asSignature(x.typ) + if sig == nil { + check.errorf(x, invalidOp+"cannot call non-function %s", x) + x.mode = invalid + x.expr = call + return statement + } + + // evaluate type arguments, if any + var targs []Type + if inst != nil { + xlist := unpackExpr(inst.Index) + targs = check.typeList(xlist) + if targs == nil { + check.use(call.ArgList...) + x.mode = invalid + x.expr = call + return statement + } + assert(len(targs) == len(xlist)) + + // check number of type arguments (got) vs number of type parameters (want) + got, want := len(targs), len(sig.tparams) + if got > want { + check.errorf(xlist[want], "got %d type arguments but want %d", got, want) + check.use(call.ArgList...) + x.mode = invalid + x.expr = call + return statement + } + } + + // evaluate arguments + args, _ := check.exprList(call.ArgList, false) + sig = check.arguments(call, sig, targs, args) + + // determine result + switch sig.results.Len() { + case 0: + x.mode = novalue + case 1: + if cgocall { + x.mode = commaerr + } else { + x.mode = value + } + x.typ = sig.results.vars[0].typ // unpack tuple + default: + x.mode = value + x.typ = sig.results + } + x.expr = call + check.hasCallOrRecv = true + + // if type inference failed, a parametrized result must be invalidated + // (operands cannot have a parametrized type) + if x.mode == value && len(sig.tparams) > 0 && isParameterized(sig.tparams, x.typ) { + x.mode = invalid + } + + return statement +} + +func (check *Checker) exprList(elist []syntax.Expr, allowCommaOk bool) (xlist []*operand, commaOk bool) { + switch len(elist) { + case 0: + // nothing to do + + case 1: + // single (possibly comma-ok) value, or function returning multiple values + e := elist[0] + var x operand + check.multiExpr(&x, e) + if t, ok := x.typ.(*Tuple); ok && x.mode != invalid { + // multiple values + xlist = make([]*operand, t.Len()) + for i, v := range t.vars { + xlist[i] = &operand{mode: value, expr: e, typ: v.typ} + } + break + } + + // exactly one (possibly invalid or comma-ok) value + xlist = []*operand{&x} + if allowCommaOk && (x.mode == mapindex || x.mode == commaok || x.mode == commaerr) { + x.mode = value + xlist = append(xlist, &operand{mode: value, expr: e, typ: Typ[UntypedBool]}) + commaOk = true + } + + default: + // multiple (possibly invalid) values + xlist = make([]*operand, len(elist)) + for i, e := range elist { + var x operand + check.expr(&x, e) + xlist[i] = &x + } + } + + return +} + +func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []Type, args []*operand) (rsig *Signature) { + rsig = sig + + // TODO(gri) try to eliminate this extra verification loop + for _, a := range args { + switch a.mode { + case typexpr: + check.errorf(a, "%s used as value", a) + return + case invalid: + return + } + } + + // Function call argument/parameter count requirements + // + // | standard call | dotdotdot call | + // --------------+------------------+----------------+ + // standard func | nargs == npars | invalid | + // --------------+------------------+----------------+ + // variadic func | nargs >= npars-1 | nargs == npars | + // --------------+------------------+----------------+ + + nargs := len(args) + npars := sig.params.Len() + ddd := call.HasDots + + // set up parameters + sigParams := sig.params // adjusted for variadic functions (may be nil for empty parameter lists!) + adjusted := false // indicates if sigParams is different from t.params + if sig.variadic { + if ddd { + // variadic_func(a, b, c...) + if len(call.ArgList) == 1 && nargs > 1 { + // f()... is not permitted if f() is multi-valued + //check.errorf(call.Ellipsis, "cannot use ... with %d-valued %s", nargs, call.ArgList[0]) + check.errorf(call, "cannot use ... with %d-valued %s", nargs, call.ArgList[0]) + return + } + } else { + // variadic_func(a, b, c) + if nargs >= npars-1 { + // Create custom parameters for arguments: keep + // the first npars-1 parameters and add one for + // each argument mapping to the ... parameter. + vars := make([]*Var, npars-1) // npars > 0 for variadic functions + copy(vars, sig.params.vars) + last := sig.params.vars[npars-1] + typ := last.typ.(*Slice).elem + for len(vars) < nargs { + vars = append(vars, NewParam(last.pos, last.pkg, last.name, typ)) + } + sigParams = NewTuple(vars...) // possibly nil! + adjusted = true + npars = nargs + } else { + // nargs < npars-1 + npars-- // for correct error message below + } + } + } else { + if ddd { + // standard_func(a, b, c...) + //check.errorf(call.Ellipsis, "cannot use ... in call to non-variadic %s", call.Fun) + check.errorf(call, "cannot use ... in call to non-variadic %s", call.Fun) + return + } + // standard_func(a, b, c) + } + + // check argument count + switch { + case nargs < npars: + check.errorf(call, "not enough arguments in call to %s", call.Fun) + return + case nargs > npars: + check.errorf(args[npars], "too many arguments in call to %s", call.Fun) // report at first extra argument + return + } + + // infer type arguments and instantiate signature if necessary + if len(sig.tparams) > 0 { + // TODO(gri) provide position information for targs so we can feed + // it to the instantiate call for better error reporting + targs = check.infer(call.Pos(), sig.tparams, targs, sigParams, args, true) + if targs == nil { + return // error already reported + } + + // compute result signature + rsig = check.instantiate(call.Pos(), sig, targs, nil).(*Signature) + assert(rsig.tparams == nil) // signature is not generic anymore + check.recordInferred(call, targs, rsig) + + // Optimization: Only if the parameter list was adjusted do we + // need to compute it from the adjusted list; otherwise we can + // simply use the result signature's parameter list. + if adjusted { + sigParams = check.subst(call.Pos(), sigParams, makeSubstMap(sig.tparams, targs)).(*Tuple) + } else { + sigParams = rsig.params + } + } + + // check arguments + for i, a := range args { + check.assignment(a, sigParams.vars[i].typ, check.sprintf("argument to %s", call.Fun)) + } + + return +} + +var cgoPrefixes = [...]string{ + "_Ciconst_", + "_Cfconst_", + "_Csconst_", + "_Ctype_", + "_Cvar_", // actually a pointer to the var + "_Cfpvar_fp_", + "_Cfunc_", + "_Cmacro_", // function to evaluate the expanded expression +} + +func (check *Checker) selector(x *operand, e *syntax.SelectorExpr) { + // these must be declared before the "goto Error" statements + var ( + obj Object + index []int + indirect bool + ) + + sel := e.Sel.Value + // If the identifier refers to a package, handle everything here + // so we don't need a "package" mode for operands: package names + // can only appear in qualified identifiers which are mapped to + // selector expressions. + if ident, ok := e.X.(*syntax.Name); ok { + obj := check.lookup(ident.Value) + if pname, _ := obj.(*PkgName); pname != nil { + assert(pname.pkg == check.pkg) + check.recordUse(ident, pname) + pname.used = true + pkg := pname.imported + + var exp Object + funcMode := value + if pkg.cgo { + // cgo special cases C.malloc: it's + // rewritten to _CMalloc and does not + // support two-result calls. + if sel == "malloc" { + sel = "_CMalloc" + } else { + funcMode = cgofunc + } + for _, prefix := range cgoPrefixes { + // cgo objects are part of the current package (in file + // _cgo_gotypes.go). Use regular lookup. + _, exp = check.scope.LookupParent(prefix+sel, check.pos) + if exp != nil { + break + } + } + if exp == nil { + check.errorf(e.Sel, "%s not declared by package C", sel) + goto Error + } + check.objDecl(exp, nil) + } else { + exp = pkg.scope.Lookup(sel) + if exp == nil { + if !pkg.fake { + if check.conf.CompilerErrorMessages { + check.errorf(e.Sel, "undefined: %s.%s", pkg.name, sel) + } else { + check.errorf(e.Sel, "%s not declared by package %s", sel, pkg.name) + } + } + goto Error + } + if !exp.Exported() { + check.errorf(e.Sel, "%s not exported by package %s", sel, pkg.name) + // ok to continue + } + } + check.recordUse(e.Sel, exp) + + // Simplified version of the code for *syntax.Names: + // - imported objects are always fully initialized + switch exp := exp.(type) { + case *Const: + assert(exp.Val() != nil) + x.mode = constant_ + x.typ = exp.typ + x.val = exp.val + case *TypeName: + x.mode = typexpr + x.typ = exp.typ + case *Var: + x.mode = variable + x.typ = exp.typ + if pkg.cgo && strings.HasPrefix(exp.name, "_Cvar_") { + x.typ = x.typ.(*Pointer).base + } + case *Func: + x.mode = funcMode + x.typ = exp.typ + if pkg.cgo && strings.HasPrefix(exp.name, "_Cmacro_") { + x.mode = value + x.typ = x.typ.(*Signature).results.vars[0].typ + } + case *Builtin: + x.mode = builtin + x.typ = exp.typ + x.id = exp.id + default: + check.dump("%v: unexpected object %v", posFor(e.Sel), exp) + unreachable() + } + x.expr = e + return + } + } + + check.exprOrType(x, e.X) + if x.mode == invalid { + goto Error + } + + check.instantiatedOperand(x) + + obj, index, indirect = check.lookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel) + if obj == nil { + switch { + case index != nil: + // TODO(gri) should provide actual type where the conflict happens + check.errorf(e.Sel, "ambiguous selector %s.%s", x.expr, sel) + case indirect: + check.errorf(e.Sel, "cannot call pointer method %s on %s", sel, x.typ) + default: + var why string + if tpar := asTypeParam(x.typ); tpar != nil { + // Type parameter bounds don't specify fields, so don't mention "field". + switch obj := tpar.Bound().obj.(type) { + case nil: + why = check.sprintf("type bound for %s has no method %s", x.typ, sel) + case *TypeName: + why = check.sprintf("interface %s has no method %s", obj.name, sel) + } + } else { + why = check.sprintf("type %s has no field or method %s", x.typ, sel) + } + + // Check if capitalization of sel matters and provide better error message in that case. + if len(sel) > 0 { + var changeCase string + if r := rune(sel[0]); unicode.IsUpper(r) { + changeCase = string(unicode.ToLower(r)) + sel[1:] + } else { + changeCase = string(unicode.ToUpper(r)) + sel[1:] + } + if obj, _, _ = check.lookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, changeCase); obj != nil { + why += ", but does have " + changeCase + } + } + + check.errorf(e.Sel, "%s.%s undefined (%s)", x.expr, sel, why) + + } + goto Error + } + + // methods may not have a fully set up signature yet + if m, _ := obj.(*Func); m != nil { + // check.dump("### found method %s", m) + check.objDecl(m, nil) + // If m has a parameterized receiver type, infer the type arguments from + // the actual receiver provided and then substitute the type parameters in + // the signature accordingly. + // TODO(gri) factor this code out + sig := m.typ.(*Signature) + if len(sig.rparams) > 0 { + // For inference to work, we must use the receiver type + // matching the receiver in the actual method declaration. + // If the method is embedded, the matching receiver is the + // embedded struct or interface that declared the method. + // Traverse the embedding to find that type (issue #44688). + recv := x.typ + for i := 0; i < len(index)-1; i++ { + // The embedded type is either a struct or a pointer to + // a struct except for the last one (which we don't need). + recv = asStruct(derefStructPtr(recv)).Field(index[i]).typ + } + //check.dump("### recv = %s", recv) + //check.dump("### method = %s rparams = %s tparams = %s", m, sig.rparams, sig.tparams) + // The method may have a pointer receiver, but the actually provided receiver + // may be a (hopefully addressable) non-pointer value, or vice versa. Here we + // only care about inferring receiver type parameters; to make the inference + // work, match up pointer-ness of receiver and argument. + if ptrRecv := isPointer(sig.recv.typ); ptrRecv != isPointer(recv) { + if ptrRecv { + recv = NewPointer(recv) + } else { + recv = recv.(*Pointer).base + } + } + // Disable reporting of errors during inference below. If we're unable to infer + // the receiver type arguments here, the receiver must be be otherwise invalid + // and an error has been reported elsewhere. + arg := operand{mode: variable, expr: x.expr, typ: recv} + targs := check.infer(m.pos, sig.rparams, nil, NewTuple(sig.recv), []*operand{&arg}, false /* no error reporting */) + //check.dump("### inferred targs = %s", targs) + if targs == nil { + // We may reach here if there were other errors (see issue #40056). + goto Error + } + // Don't modify m. Instead - for now - make a copy of m and use that instead. + // (If we modify m, some tests will fail; possibly because the m is in use.) + // TODO(gri) investigate and provide a correct explanation here + copy := *m + copy.typ = check.subst(e.Pos(), m.typ, makeSubstMap(sig.rparams, targs)) + obj = © + } + // TODO(gri) we also need to do substitution for parameterized interface methods + // (this breaks code in testdata/linalg.go2 at the moment) + // 12/20/2019: Is this TODO still correct? + } + + if x.mode == typexpr { + // method expression + m, _ := obj.(*Func) + if m == nil { + // TODO(gri) should check if capitalization of sel matters and provide better error message in that case + check.errorf(e.Sel, "%s.%s undefined (type %s has no method %s)", x.expr, sel, x.typ, sel) + goto Error + } + + check.recordSelection(e, MethodExpr, x.typ, m, index, indirect) + + // the receiver type becomes the type of the first function + // argument of the method expression's function type + var params []*Var + sig := m.typ.(*Signature) + if sig.params != nil { + params = sig.params.vars + } + x.mode = value + x.typ = &Signature{ + tparams: sig.tparams, + params: NewTuple(append([]*Var{NewVar(nopos, check.pkg, "_", x.typ)}, params...)...), + results: sig.results, + variadic: sig.variadic, + } + + check.addDeclDep(m) + + } else { + // regular selector + switch obj := obj.(type) { + case *Var: + check.recordSelection(e, FieldVal, x.typ, obj, index, indirect) + if x.mode == variable || indirect { + x.mode = variable + } else { + x.mode = value + } + x.typ = obj.typ + + case *Func: + // TODO(gri) If we needed to take into account the receiver's + // addressability, should we report the type &(x.typ) instead? + check.recordSelection(e, MethodVal, x.typ, obj, index, indirect) + + x.mode = value + + // remove receiver + sig := *obj.typ.(*Signature) + sig.recv = nil + x.typ = &sig + + check.addDeclDep(obj) + + default: + unreachable() + } + } + + // everything went well + x.expr = e + return + +Error: + x.mode = invalid + x.expr = e +} + +// use type-checks each argument. +// Useful to make sure expressions are evaluated +// (and variables are "used") in the presence of other errors. +// The arguments may be nil. +// TODO(gri) make this accept a []syntax.Expr and use an unpack function when we have a ListExpr? +func (check *Checker) use(arg ...syntax.Expr) { + var x operand + for _, e := range arg { + // Certain AST fields may legally be nil (e.g., the ast.SliceExpr.High field). + if e == nil { + continue + } + if l, _ := e.(*syntax.ListExpr); l != nil { + check.use(l.ElemList...) + continue + } + check.rawExpr(&x, e, nil) + } +} + +// useLHS is like use, but doesn't "use" top-level identifiers. +// It should be called instead of use if the arguments are +// expressions on the lhs of an assignment. +// The arguments must not be nil. +func (check *Checker) useLHS(arg ...syntax.Expr) { + var x operand + for _, e := range arg { + // If the lhs is an identifier denoting a variable v, this assignment + // is not a 'use' of v. Remember current value of v.used and restore + // after evaluating the lhs via check.rawExpr. + var v *Var + var v_used bool + if ident, _ := unparen(e).(*syntax.Name); ident != nil { + // never type-check the blank name on the lhs + if ident.Value == "_" { + continue + } + if _, obj := check.scope.LookupParent(ident.Value, nopos); obj != nil { + // It's ok to mark non-local variables, but ignore variables + // from other packages to avoid potential race conditions with + // dot-imported variables. + if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg { + v = w + v_used = v.used + } + } + } + check.rawExpr(&x, e, nil) + if v != nil { + v.used = v_used // restore v.used + } + } +} + +// instantiatedOperand reports an error of x is an uninstantiated (generic) type and sets x.typ to Typ[Invalid]. +func (check *Checker) instantiatedOperand(x *operand) { + if x.mode == typexpr && isGeneric(x.typ) { + check.errorf(x, "cannot use generic type %s without instantiation", x.typ) + x.typ = Typ[Invalid] + } +} diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go new file mode 100644 index 0000000000000000000000000000000000000000..8d6cd1edab9d4c61cd1f1d5d7a99798def7fdcde --- /dev/null +++ b/src/cmd/compile/internal/types2/check.go @@ -0,0 +1,466 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements the Check function, which drives type-checking. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "errors" + "fmt" + "go/constant" +) + +var nopos syntax.Pos + +// debugging/development support +const debug = false // leave on during development + +// If forceStrict is set, the type-checker enforces additional +// rules not specified by the Go 1 spec, but which will +// catch guaranteed run-time errors if the respective +// code is executed. In other words, programs passing in +// strict mode are Go 1 compliant, but not all Go 1 programs +// will pass in strict mode. The additional rules are: +// +// - A type assertion x.(T) where T is an interface type +// is invalid if any (statically known) method that exists +// for both x and T have different signatures. +// +const forceStrict = false + +// exprInfo stores information about an untyped expression. +type exprInfo struct { + isLhs bool // expression is lhs operand of a shift with delayed type-check + mode operandMode + typ *Basic + val constant.Value // constant value; or nil (if not a constant) +} + +// A context represents the context within which an object is type-checked. +type context struct { + decl *declInfo // package-level declaration whose init expression/function body is checked + scope *Scope // top-most scope for lookups + pos syntax.Pos // if valid, identifiers are looked up as if at position pos (used by Eval) + iota constant.Value // value of iota in a constant declaration; nil otherwise + errpos syntax.Pos // if valid, identifier position of a constant with inherited initializer + sig *Signature // function signature if inside a function; nil otherwise + isPanic map[*syntax.CallExpr]bool // set of panic call expressions (used for termination check) + hasLabel bool // set if a function makes use of labels (only ~1% of functions); unused outside functions + hasCallOrRecv bool // set if an expression contains a function call or channel receive operation +} + +// lookup looks up name in the current context and returns the matching object, or nil. +func (ctxt *context) lookup(name string) Object { + _, obj := ctxt.scope.LookupParent(name, ctxt.pos) + return obj +} + +// An importKey identifies an imported package by import path and source directory +// (directory containing the file containing the import). In practice, the directory +// may always be the same, or may not matter. Given an (import path, directory), an +// importer must always return the same package (but given two different import paths, +// an importer may still return the same package by mapping them to the same package +// paths). +type importKey struct { + path, dir string +} + +// A dotImportKey describes a dot-imported object in the given scope. +type dotImportKey struct { + scope *Scope + obj Object +} + +// A Checker maintains the state of the type checker. +// It must be created with NewChecker. +type Checker struct { + // package information + // (initialized by NewChecker, valid for the life-time of checker) + conf *Config + pkg *Package + *Info + version version // accepted language version + objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info + impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package + posMap map[*Interface][]syntax.Pos // maps interface types to lists of embedded interface positions + typMap map[string]*Named // maps an instantiated named type hash to a *Named type + + // pkgPathMap maps package names to the set of distinct import paths we've + // seen for that name, anywhere in the import graph. It is used for + // disambiguating package names in error messages. + // + // pkgPathMap is allocated lazily, so that we don't pay the price of building + // it on the happy path. seenPkgMap tracks the packages that we've already + // walked. + pkgPathMap map[string]map[string]bool + seenPkgMap map[*Package]bool + + // information collected during type-checking of a set of package files + // (initialized by Files, valid only for the duration of check.Files; + // maps and lists are allocated on demand) + files []*syntax.File // list of package files + imports []*PkgName // list of imported packages + dotImportMap map[dotImportKey]*PkgName // maps dot-imported objects to the package they were dot-imported through + + firstErr error // first error encountered + methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods + untyped map[syntax.Expr]exprInfo // map of expressions without final type + delayed []func() // stack of delayed action segments; segments are processed in FIFO order + objPath []Object // path of object dependencies during type inference (for cycle reporting) + + // context within which the current object is type-checked + // (valid only for the duration of type-checking a specific object) + context + + // debugging + indent int // indentation for tracing +} + +// addDeclDep adds the dependency edge (check.decl -> to) if check.decl exists +func (check *Checker) addDeclDep(to Object) { + from := check.decl + if from == nil { + return // not in a package-level init expression + } + if _, found := check.objMap[to]; !found { + return // to is not a package-level object + } + from.addDep(to) +} + +func (check *Checker) rememberUntyped(e syntax.Expr, lhs bool, mode operandMode, typ *Basic, val constant.Value) { + m := check.untyped + if m == nil { + m = make(map[syntax.Expr]exprInfo) + check.untyped = m + } + m[e] = exprInfo{lhs, mode, typ, val} +} + +// later pushes f on to the stack of actions that will be processed later; +// either at the end of the current statement, or in case of a local constant +// or variable declaration, before the constant or variable is in scope +// (so that f still sees the scope before any new declarations). +func (check *Checker) later(f func()) { + check.delayed = append(check.delayed, f) +} + +// push pushes obj onto the object path and returns its index in the path. +func (check *Checker) push(obj Object) int { + check.objPath = append(check.objPath, obj) + return len(check.objPath) - 1 +} + +// pop pops and returns the topmost object from the object path. +func (check *Checker) pop() Object { + i := len(check.objPath) - 1 + obj := check.objPath[i] + check.objPath[i] = nil + check.objPath = check.objPath[:i] + return obj +} + +// NewChecker returns a new Checker instance for a given package. +// Package files may be added incrementally via checker.Files. +func NewChecker(conf *Config, pkg *Package, info *Info) *Checker { + // make sure we have a configuration + if conf == nil { + conf = new(Config) + } + + // make sure we have an info struct + if info == nil { + info = new(Info) + } + + version, err := parseGoVersion(conf.GoVersion) + if err != nil { + panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err)) + } + + return &Checker{ + conf: conf, + pkg: pkg, + Info: info, + version: version, + objMap: make(map[Object]*declInfo), + impMap: make(map[importKey]*Package), + posMap: make(map[*Interface][]syntax.Pos), + typMap: make(map[string]*Named), + } +} + +// initFiles initializes the files-specific portion of checker. +// The provided files must all belong to the same package. +func (check *Checker) initFiles(files []*syntax.File) { + // start with a clean slate (check.Files may be called multiple times) + check.files = nil + check.imports = nil + check.dotImportMap = nil + + check.firstErr = nil + check.methods = nil + check.untyped = nil + check.delayed = nil + + // determine package name and collect valid files + pkg := check.pkg + for _, file := range files { + switch name := file.PkgName.Value; pkg.name { + case "": + if name != "_" { + pkg.name = name + } else { + check.error(file.PkgName, "invalid package name _") + } + fallthrough + + case name: + check.files = append(check.files, file) + + default: + check.errorf(file, "package %s; expected %s", name, pkg.name) + // ignore this file + } + } +} + +// A bailout panic is used for early termination. +type bailout struct{} + +func (check *Checker) handleBailout(err *error) { + switch p := recover().(type) { + case nil, bailout: + // normal return or early exit + *err = check.firstErr + default: + // re-panic + panic(p) + } +} + +// Files checks the provided files as part of the checker's package. +func (check *Checker) Files(files []*syntax.File) error { return check.checkFiles(files) } + +var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together") + +func (check *Checker) checkFiles(files []*syntax.File) (err error) { + if check.conf.FakeImportC && check.conf.go115UsesCgo { + return errBadCgo + } + + defer check.handleBailout(&err) + + print := func(msg string) { + if check.conf.Trace { + fmt.Println(msg) + } + } + + print("== initFiles ==") + check.initFiles(files) + + print("== collectObjects ==") + check.collectObjects() + + print("== packageObjects ==") + check.packageObjects() + + print("== processDelayed ==") + check.processDelayed(0) // incl. all functions + + print("== initOrder ==") + check.initOrder() + + if !check.conf.DisableUnusedImportCheck { + print("== unusedImports ==") + check.unusedImports() + } + + print("== recordUntyped ==") + check.recordUntyped() + + if check.Info != nil { + print("== sanitizeInfo ==") + sanitizeInfo(check.Info) + } + + check.pkg.complete = true + + // no longer needed - release memory + check.imports = nil + check.dotImportMap = nil + check.pkgPathMap = nil + check.seenPkgMap = nil + + // TODO(gri) There's more memory we should release at this point. + + return +} + +// processDelayed processes all delayed actions pushed after top. +func (check *Checker) processDelayed(top int) { + // If each delayed action pushes a new action, the + // stack will continue to grow during this loop. + // However, it is only processing functions (which + // are processed in a delayed fashion) that may + // add more actions (such as nested functions), so + // this is a sufficiently bounded process. + for i := top; i < len(check.delayed); i++ { + check.delayed[i]() // may append to check.delayed + } + assert(top <= len(check.delayed)) // stack must not have shrunk + check.delayed = check.delayed[:top] +} + +func (check *Checker) record(x *operand) { + // convert x into a user-friendly set of values + // TODO(gri) this code can be simplified + var typ Type + var val constant.Value + switch x.mode { + case invalid: + typ = Typ[Invalid] + case novalue: + typ = (*Tuple)(nil) + case constant_: + typ = x.typ + val = x.val + default: + typ = x.typ + } + assert(x.expr != nil && typ != nil) + + if isUntyped(typ) { + // delay type and value recording until we know the type + // or until the end of type checking + check.rememberUntyped(x.expr, false, x.mode, typ.(*Basic), val) + } else { + check.recordTypeAndValue(x.expr, x.mode, typ, val) + } +} + +func (check *Checker) recordUntyped() { + if !debug && check.Types == nil { + return // nothing to do + } + + for x, info := range check.untyped { + if debug && isTyped(info.typ) { + check.dump("%v: %s (type %s) is typed", posFor(x), x, info.typ) + unreachable() + } + check.recordTypeAndValue(x, info.mode, info.typ, info.val) + } +} + +func (check *Checker) recordTypeAndValue(x syntax.Expr, mode operandMode, typ Type, val constant.Value) { + assert(x != nil) + assert(typ != nil) + if mode == invalid { + return // omit + } + if mode == constant_ { + assert(val != nil) + // We check is(typ, IsConstType) here as constant expressions may be + // recorded as type parameters. + assert(typ == Typ[Invalid] || is(typ, IsConstType)) + } + if m := check.Types; m != nil { + m[x] = TypeAndValue{mode, typ, val} + } +} + +func (check *Checker) recordBuiltinType(f syntax.Expr, sig *Signature) { + // f must be a (possibly parenthesized, possibly qualified) + // identifier denoting a built-in (including unsafe's non-constant + // functions Add and Slice): record the signature for f and possible + // children. + for { + check.recordTypeAndValue(f, builtin, sig, nil) + switch p := f.(type) { + case *syntax.Name, *syntax.SelectorExpr: + return // we're done + case *syntax.ParenExpr: + f = p.X + default: + unreachable() + } + } +} + +func (check *Checker) recordCommaOkTypes(x syntax.Expr, a [2]Type) { + assert(x != nil) + if a[0] == nil || a[1] == nil { + return + } + assert(isTyped(a[0]) && isTyped(a[1]) && (isBoolean(a[1]) || a[1] == universeError)) + if m := check.Types; m != nil { + for { + tv := m[x] + assert(tv.Type != nil) // should have been recorded already + pos := x.Pos() + tv.Type = NewTuple( + NewVar(pos, check.pkg, "", a[0]), + NewVar(pos, check.pkg, "", a[1]), + ) + m[x] = tv + // if x is a parenthesized expression (p.X), update p.X + p, _ := x.(*syntax.ParenExpr) + if p == nil { + break + } + x = p.X + } + } +} + +func (check *Checker) recordInferred(call syntax.Expr, targs []Type, sig *Signature) { + assert(call != nil) + assert(sig != nil) + if m := check.Inferred; m != nil { + m[call] = Inferred{targs, sig} + } +} + +func (check *Checker) recordDef(id *syntax.Name, obj Object) { + assert(id != nil) + if m := check.Defs; m != nil { + m[id] = obj + } +} + +func (check *Checker) recordUse(id *syntax.Name, obj Object) { + assert(id != nil) + assert(obj != nil) + if m := check.Uses; m != nil { + m[id] = obj + } +} + +func (check *Checker) recordImplicit(node syntax.Node, obj Object) { + assert(node != nil) + assert(obj != nil) + if m := check.Implicits; m != nil { + m[node] = obj + } +} + +func (check *Checker) recordSelection(x *syntax.SelectorExpr, kind SelectionKind, recv Type, obj Object, index []int, indirect bool) { + assert(obj != nil && (recv == nil || len(index) > 0)) + check.recordUse(x.Sel, obj) + if m := check.Selections; m != nil { + m[x] = &Selection{kind, recv, obj, index, indirect} + } +} + +func (check *Checker) recordScope(node syntax.Node, scope *Scope) { + assert(node != nil) + assert(scope != nil) + if m := check.Scopes; m != nil { + m[node] = scope + } +} diff --git a/src/cmd/compile/internal/types2/check_test.go b/src/cmd/compile/internal/types2/check_test.go new file mode 100644 index 0000000000000000000000000000000000000000..41b0c54702d8d782ad42d4cf0c129f9540919253 --- /dev/null +++ b/src/cmd/compile/internal/types2/check_test.go @@ -0,0 +1,326 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements a typechecker test harness. The packages specified +// in tests are typechecked. Error messages reported by the typechecker are +// compared against the error messages expected in the test files. +// +// Expected errors are indicated in the test files by putting a comment +// of the form /* ERROR "rx" */ immediately following an offending token. +// The harness will verify that an error matching the regular expression +// rx is reported at that source position. Consecutive comments may be +// used to indicate multiple errors for the same token position. +// +// For instance, the following test file indicates that a "not declared" +// error should be reported for the undeclared variable x: +// +// package p +// func f() { +// _ = x /* ERROR "not declared" */ + 1 +// } + +// TODO(gri) Also collect strict mode errors of the form /* STRICT ... */ +// and test against strict mode. + +package types2_test + +import ( + "cmd/compile/internal/syntax" + "flag" + "internal/testenv" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + "testing" + + . "cmd/compile/internal/types2" +) + +var ( + haltOnError = flag.Bool("halt", false, "halt on error") + verifyErrors = flag.Bool("verify", false, "verify errors (rather than list them) in TestManual") + goVersion = flag.String("lang", "", "Go language version (e.g. \"go1.12\")") +) + +func parseFiles(t *testing.T, filenames []string, mode syntax.Mode) ([]*syntax.File, []error) { + var files []*syntax.File + var errlist []error + errh := func(err error) { errlist = append(errlist, err) } + for _, filename := range filenames { + file, err := syntax.ParseFile(filename, errh, nil, mode) + if file == nil { + t.Fatalf("%s: %s", filename, err) + } + files = append(files, file) + } + return files, errlist +} + +func unpackError(err error) syntax.Error { + switch err := err.(type) { + case syntax.Error: + return err + case Error: + return syntax.Error{Pos: err.Pos, Msg: err.Msg} + default: + return syntax.Error{Msg: err.Error()} + } +} + +// delta returns the absolute difference between x and y. +func delta(x, y uint) uint { + switch { + case x < y: + return y - x + case x > y: + return x - y + default: + return 0 + } +} + +// goVersionRx matches a Go version string using '_', e.g. "go1_12". +var goVersionRx = regexp.MustCompile(`^go[1-9][0-9]*_(0|[1-9][0-9]*)$`) + +// asGoVersion returns a regular Go language version string +// if s is a Go version string using '_' rather than '.' to +// separate the major and minor version numbers (e.g. "go1_12"). +// Otherwise it returns the empty string. +func asGoVersion(s string) string { + if goVersionRx.MatchString(s) { + return strings.Replace(s, "_", ".", 1) + } + return "" +} + +func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) { + if len(filenames) == 0 { + t.Fatal("no source files") + } + + var mode syntax.Mode + if strings.HasSuffix(filenames[0], ".go2") { + mode |= syntax.AllowGenerics + } + // parse files and collect parser errors + files, errlist := parseFiles(t, filenames, mode) + + pkgName := "" + if len(files) > 0 { + pkgName = files[0].PkgName.Value + } + + // if no Go version is given, consider the package name + goVersion := *goVersion + if goVersion == "" { + goVersion = asGoVersion(pkgName) + } + + listErrors := manual && !*verifyErrors + if listErrors && len(errlist) > 0 { + t.Errorf("--- %s:", pkgName) + for _, err := range errlist { + t.Error(err) + } + } + + // typecheck and collect typechecker errors + var conf Config + conf.GoVersion = goVersion + // special case for importC.src + if len(filenames) == 1 && strings.HasSuffix(filenames[0], "importC.src") { + conf.FakeImportC = true + } + conf.Trace = manual && testing.Verbose() + conf.Importer = defaultImporter() + conf.Error = func(err error) { + if *haltOnError { + defer panic(err) + } + if listErrors { + t.Error(err) + return + } + errlist = append(errlist, err) + } + conf.Check(pkgName, files, nil) + + if listErrors { + return + } + + // sort errlist in source order + sort.Slice(errlist, func(i, j int) bool { + pi := unpackError(errlist[i]).Pos + pj := unpackError(errlist[j]).Pos + return pi.Cmp(pj) < 0 + }) + + // collect expected errors + errmap := make(map[string]map[uint][]syntax.Error) + for _, filename := range filenames { + f, err := os.Open(filename) + if err != nil { + t.Error(err) + continue + } + if m := syntax.ErrorMap(f); len(m) > 0 { + errmap[filename] = m + } + f.Close() + } + + // match against found errors + for _, err := range errlist { + got := unpackError(err) + + // find list of errors for the respective error line + filename := got.Pos.Base().Filename() + filemap := errmap[filename] + line := got.Pos.Line() + var list []syntax.Error + if filemap != nil { + list = filemap[line] + } + // list may be nil + + // one of errors in list should match the current error + index := -1 // list index of matching message, if any + for i, want := range list { + rx, err := regexp.Compile(want.Msg) + if err != nil { + t.Errorf("%s:%d:%d: %v", filename, line, want.Pos.Col(), err) + continue + } + if rx.MatchString(got.Msg) { + index = i + break + } + } + if index < 0 { + t.Errorf("%s: no error expected: %q", got.Pos, got.Msg) + continue + } + + // column position must be within expected colDelta + want := list[index] + if delta(got.Pos.Col(), want.Pos.Col()) > colDelta { + t.Errorf("%s: got col = %d; want %d", got.Pos, got.Pos.Col(), want.Pos.Col()) + } + + // eliminate from list + if n := len(list) - 1; n > 0 { + // not the last entry - slide entries down (don't reorder) + copy(list[index:], list[index+1:]) + filemap[line] = list[:n] + } else { + // last entry - remove list from filemap + delete(filemap, line) + } + + // if filemap is empty, eliminate from errmap + if len(filemap) == 0 { + delete(errmap, filename) + } + } + + // there should be no expected errors left + if len(errmap) > 0 { + t.Errorf("--- %s: unreported errors:", pkgName) + for filename, filemap := range errmap { + for line, list := range filemap { + for _, err := range list { + t.Errorf("%s:%d:%d: %s", filename, line, err.Pos.Col(), err.Msg) + } + } + } + } +} + +// TestManual is for manual testing of a package - either provided +// as a list of filenames belonging to the package, or a directory +// name containing the package files - after the test arguments +// (and a separating "--"). For instance, to test the package made +// of the files foo.go and bar.go, use: +// +// go test -run Manual -- foo.go bar.go +// +// If no source arguments are provided, the file testdata/manual.go2 +// is used instead. +// Provide the -verify flag to verify errors against ERROR comments +// in the input files rather than having a list of errors reported. +// The accepted Go language version can be controlled with the -lang +// flag. +func TestManual(t *testing.T) { + testenv.MustHaveGoBuild(t) + + filenames := flag.Args() + if len(filenames) == 0 { + filenames = []string{filepath.FromSlash("testdata/manual.go2")} + } + + info, err := os.Stat(filenames[0]) + if err != nil { + t.Fatalf("TestManual: %v", err) + } + + DefPredeclaredTestFuncs() + if info.IsDir() { + if len(filenames) > 1 { + t.Fatal("TestManual: must have only one directory argument") + } + testDir(t, filenames[0], 0, true) + } else { + testFiles(t, filenames, 0, true) + } +} + +// TODO(gri) go/types has extra TestLongConstants and TestIndexRepresentability tests + +func TestCheck(t *testing.T) { DefPredeclaredTestFuncs(); testDirFiles(t, "testdata/check", 75, false) } // TODO(gri) narrow column tolerance +func TestExamples(t *testing.T) { testDirFiles(t, "testdata/examples", 0, false) } +func TestFixedbugs(t *testing.T) { testDirFiles(t, "testdata/fixedbugs", 0, false) } + +func testDirFiles(t *testing.T, dir string, colDelta uint, manual bool) { + testenv.MustHaveGoBuild(t) + dir = filepath.FromSlash(dir) + + fis, err := os.ReadDir(dir) + if err != nil { + t.Error(err) + return + } + + for _, fi := range fis { + path := filepath.Join(dir, fi.Name()) + + // If fi is a directory, its files make up a single package. + if fi.IsDir() { + testDir(t, path, colDelta, manual) + } else { + t.Run(filepath.Base(path), func(t *testing.T) { + testFiles(t, []string{path}, colDelta, manual) + }) + } + } +} + +func testDir(t *testing.T, dir string, colDelta uint, manual bool) { + fis, err := os.ReadDir(dir) + if err != nil { + t.Error(err) + return + } + + var filenames []string + for _, fi := range fis { + filenames = append(filenames, filepath.Join(dir, fi.Name())) + } + + t.Run(filepath.Base(dir), func(t *testing.T) { + testFiles(t, filenames, colDelta, manual) + }) +} diff --git a/src/cmd/compile/internal/types2/conversions.go b/src/cmd/compile/internal/types2/conversions.go new file mode 100644 index 0000000000000000000000000000000000000000..30201e2b7f4232a147d17a2bdbc8cbb5bc301be3 --- /dev/null +++ b/src/cmd/compile/internal/types2/conversions.go @@ -0,0 +1,188 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements typechecking of conversions. + +package types2 + +import ( + "go/constant" + "unicode" +) + +// Conversion type-checks the conversion T(x). +// The result is in x. +func (check *Checker) conversion(x *operand, T Type) { + constArg := x.mode == constant_ + + var ok bool + switch { + case constArg && isConstType(T): + // constant conversion + switch t := asBasic(T); { + case representableConst(x.val, check, t, &x.val): + ok = true + case isInteger(x.typ) && isString(t): + codepoint := unicode.ReplacementChar + if i, ok := constant.Uint64Val(x.val); ok && i <= unicode.MaxRune { + codepoint = rune(i) + } + x.val = constant.MakeString(string(codepoint)) + ok = true + } + case x.convertibleTo(check, T): + // non-constant conversion + x.mode = value + ok = true + } + + if !ok { + if x.mode != invalid { + check.errorf(x, "cannot convert %s to %s", x, T) + x.mode = invalid + } + return + } + + // The conversion argument types are final. For untyped values the + // conversion provides the type, per the spec: "A constant may be + // given a type explicitly by a constant declaration or conversion,...". + if isUntyped(x.typ) { + final := T + // - For conversions to interfaces, except for untyped nil arguments, + // use the argument's default type. + // - For conversions of untyped constants to non-constant types, also + // use the default type (e.g., []byte("foo") should report string + // not []byte as type for the constant "foo"). + // - For integer to string conversions, keep the argument type. + // (See also the TODO below.) + if x.typ == Typ[UntypedNil] { + // ok + } else if IsInterface(T) || constArg && !isConstType(T) { + final = Default(x.typ) + } else if isInteger(x.typ) && isString(T) { + final = x.typ + } + check.updateExprType(x.expr, final, true) + } + + x.typ = T +} + +// TODO(gri) convertibleTo checks if T(x) is valid. It assumes that the type +// of x is fully known, but that's not the case for say string(1<b-> ... ->g for a path [a, b, ... g]. +func pathString(path []Object) string { + var s string + for i, p := range path { + if i > 0 { + s += "->" + } + s += p.Name() + } + return s +} + +// objDecl type-checks the declaration of obj in its respective (file) context. +// For the meaning of def, see Checker.definedType, in typexpr.go. +func (check *Checker) objDecl(obj Object, def *Named) { + if check.conf.Trace && obj.Type() == nil { + if check.indent == 0 { + fmt.Println() // empty line between top-level objects for readability + } + check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath)) + check.indent++ + defer func() { + check.indent-- + check.trace(obj.Pos(), "=> %s (%s)", obj, obj.color()) + }() + } + + // Checking the declaration of obj means inferring its type + // (and possibly its value, for constants). + // An object's type (and thus the object) may be in one of + // three states which are expressed by colors: + // + // - an object whose type is not yet known is painted white (initial color) + // - an object whose type is in the process of being inferred is painted grey + // - an object whose type is fully inferred is painted black + // + // During type inference, an object's color changes from white to grey + // to black (pre-declared objects are painted black from the start). + // A black object (i.e., its type) can only depend on (refer to) other black + // ones. White and grey objects may depend on white and black objects. + // A dependency on a grey object indicates a cycle which may or may not be + // valid. + // + // When objects turn grey, they are pushed on the object path (a stack); + // they are popped again when they turn black. Thus, if a grey object (a + // cycle) is encountered, it is on the object path, and all the objects + // it depends on are the remaining objects on that path. Color encoding + // is such that the color value of a grey object indicates the index of + // that object in the object path. + + // During type-checking, white objects may be assigned a type without + // traversing through objDecl; e.g., when initializing constants and + // variables. Update the colors of those objects here (rather than + // everywhere where we set the type) to satisfy the color invariants. + if obj.color() == white && obj.Type() != nil { + obj.setColor(black) + return + } + + switch obj.color() { + case white: + assert(obj.Type() == nil) + // All color values other than white and black are considered grey. + // Because black and white are < grey, all values >= grey are grey. + // Use those values to encode the object's index into the object path. + obj.setColor(grey + color(check.push(obj))) + defer func() { + check.pop().setColor(black) + }() + + case black: + assert(obj.Type() != nil) + return + + default: + // Color values other than white or black are considered grey. + fallthrough + + case grey: + // We have a cycle. + // In the existing code, this is marked by a non-nil type + // for the object except for constants and variables whose + // type may be non-nil (known), or nil if it depends on the + // not-yet known initialization value. + // In the former case, set the type to Typ[Invalid] because + // we have an initialization cycle. The cycle error will be + // reported later, when determining initialization order. + // TODO(gri) Report cycle here and simplify initialization + // order code. + switch obj := obj.(type) { + case *Const: + if check.cycle(obj) || obj.typ == nil { + obj.typ = Typ[Invalid] + } + + case *Var: + if check.cycle(obj) || obj.typ == nil { + obj.typ = Typ[Invalid] + } + + case *TypeName: + if check.cycle(obj) { + // break cycle + // (without this, calling underlying() + // below may lead to an endless loop + // if we have a cycle for a defined + // (*Named) type) + obj.typ = Typ[Invalid] + } + + case *Func: + if check.cycle(obj) { + // Don't set obj.typ to Typ[Invalid] here + // because plenty of code type-asserts that + // functions have a *Signature type. Grey + // functions have their type set to an empty + // signature which makes it impossible to + // initialize a variable with the function. + } + + default: + unreachable() + } + assert(obj.Type() != nil) + return + } + + d := check.objMap[obj] + if d == nil { + check.dump("%v: %s should have been declared", obj.Pos(), obj) + unreachable() + } + + // save/restore current context and setup object context + defer func(ctxt context) { + check.context = ctxt + }(check.context) + check.context = context{ + scope: d.file, + } + + // Const and var declarations must not have initialization + // cycles. We track them by remembering the current declaration + // in check.decl. Initialization expressions depending on other + // consts, vars, or functions, add dependencies to the current + // check.decl. + switch obj := obj.(type) { + case *Const: + check.decl = d // new package-level const decl + check.constDecl(obj, d.vtyp, d.init, d.inherited) + case *Var: + check.decl = d // new package-level var decl + check.varDecl(obj, d.lhs, d.vtyp, d.init) + case *TypeName: + // invalid recursive types are detected via path + check.typeDecl(obj, d.tdecl, def) + check.collectMethods(obj) // methods can only be added to top-level types + case *Func: + // functions may be recursive - no need to track dependencies + check.funcDecl(obj, d) + default: + unreachable() + } +} + +// cycle checks if the cycle starting with obj is valid and +// reports an error if it is not. +func (check *Checker) cycle(obj Object) (isCycle bool) { + // The object map contains the package scope objects and the non-interface methods. + if debug { + info := check.objMap[obj] + inObjMap := info != nil && (info.fdecl == nil || info.fdecl.Recv == nil) // exclude methods + isPkgObj := obj.Parent() == check.pkg.scope + if isPkgObj != inObjMap { + check.dump("%v: inconsistent object map for %s (isPkgObj = %v, inObjMap = %v)", obj.Pos(), obj, isPkgObj, inObjMap) + unreachable() + } + } + + // Count cycle objects. + assert(obj.color() >= grey) + start := obj.color() - grey // index of obj in objPath + cycle := check.objPath[start:] + nval := 0 // number of (constant or variable) values in the cycle + ndef := 0 // number of type definitions in the cycle + for _, obj := range cycle { + switch obj := obj.(type) { + case *Const, *Var: + nval++ + case *TypeName: + // Determine if the type name is an alias or not. For + // package-level objects, use the object map which + // provides syntactic information (which doesn't rely + // on the order in which the objects are set up). For + // local objects, we can rely on the order, so use + // the object's predicate. + // TODO(gri) It would be less fragile to always access + // the syntactic information. We should consider storing + // this information explicitly in the object. + var alias bool + if d := check.objMap[obj]; d != nil { + alias = d.tdecl.Alias // package-level object + } else { + alias = obj.IsAlias() // function local object + } + if !alias { + ndef++ + } + case *Func: + // ignored for now + default: + unreachable() + } + } + + if check.conf.Trace { + check.trace(obj.Pos(), "## cycle detected: objPath = %s->%s (len = %d)", pathString(cycle), obj.Name(), len(cycle)) + check.trace(obj.Pos(), "## cycle contains: %d values, %d type definitions", nval, ndef) + defer func() { + if isCycle { + check.trace(obj.Pos(), "=> error: cycle is invalid") + } + }() + } + + // A cycle involving only constants and variables is invalid but we + // ignore them here because they are reported via the initialization + // cycle check. + if nval == len(cycle) { + return false + } + + // A cycle involving only types (and possibly functions) must have at least + // one type definition to be permitted: If there is no type definition, we + // have a sequence of alias type names which will expand ad infinitum. + if nval == 0 && ndef > 0 { + return false // cycle is permitted + } + + check.cycleError(cycle) + + return true +} + +type typeInfo uint + +// validType verifies that the given type does not "expand" infinitely +// producing a cycle in the type graph. Cycles are detected by marking +// defined types. +// (Cycles involving alias types, as in "type A = [10]A" are detected +// earlier, via the objDecl cycle detection mechanism.) +func (check *Checker) validType(typ Type, path []Object) typeInfo { + const ( + unknown typeInfo = iota + marked + valid + invalid + ) + + switch t := typ.(type) { + case *Array: + return check.validType(t.elem, path) + + case *Struct: + for _, f := range t.fields { + if check.validType(f.typ, path) == invalid { + return invalid + } + } + + case *Interface: + for _, etyp := range t.embeddeds { + if check.validType(etyp, path) == invalid { + return invalid + } + } + + case *Named: + // don't touch the type if it is from a different package or the Universe scope + // (doing so would lead to a race condition - was issue #35049) + if t.obj.pkg != check.pkg { + return valid + } + + // don't report a 2nd error if we already know the type is invalid + // (e.g., if a cycle was detected earlier, via under). + if t.underlying == Typ[Invalid] { + t.info = invalid + return invalid + } + + switch t.info { + case unknown: + t.info = marked + t.info = check.validType(t.fromRHS, append(path, t.obj)) // only types of current package added to path + case marked: + // cycle detected + for i, tn := range path { + if t.obj.pkg != check.pkg { + panic("internal error: type cycle via package-external type") + } + if tn == t.obj { + check.cycleError(path[i:]) + t.info = invalid + return t.info + } + } + panic("internal error: cycle start not found") + } + return t.info + + case *instance: + return check.validType(t.expand(), path) + } + + return valid +} + +// cycleError reports a declaration cycle starting with +// the object in cycle that is "first" in the source. +func (check *Checker) cycleError(cycle []Object) { + // TODO(gri) Should we start with the last (rather than the first) object in the cycle + // since that is the earliest point in the source where we start seeing the + // cycle? That would be more consistent with other error messages. + i := firstInSrc(cycle) + obj := cycle[i] + var err error_ + if check.conf.CompilerErrorMessages { + err.errorf(obj, "invalid recursive type %s", obj.Name()) + } else { + err.errorf(obj, "illegal cycle in declaration of %s", obj.Name()) + } + for range cycle { + err.errorf(obj, "%s refers to", obj.Name()) + i++ + if i >= len(cycle) { + i = 0 + } + obj = cycle[i] + } + err.errorf(obj, "%s", obj.Name()) + check.report(&err) +} + +// firstInSrc reports the index of the object with the "smallest" +// source position in path. path must not be empty. +func firstInSrc(path []Object) int { + fst, pos := 0, path[0].Pos() + for i, t := range path[1:] { + if t.Pos().Cmp(pos) < 0 { + fst, pos = i+1, t.Pos() + } + } + return fst +} + +func (check *Checker) constDecl(obj *Const, typ, init syntax.Expr, inherited bool) { + assert(obj.typ == nil) + + // use the correct value of iota and errpos + defer func(iota constant.Value, errpos syntax.Pos) { + check.iota = iota + check.errpos = errpos + }(check.iota, check.errpos) + check.iota = obj.val + check.errpos = nopos + + // provide valid constant value under all circumstances + obj.val = constant.MakeUnknown() + + // determine type, if any + if typ != nil { + t := check.typ(typ) + if !isConstType(t) { + // don't report an error if the type is an invalid C (defined) type + // (issue #22090) + if under(t) != Typ[Invalid] { + check.errorf(typ, "invalid constant type %s", t) + } + obj.typ = Typ[Invalid] + return + } + obj.typ = t + } + + // check initialization + var x operand + if init != nil { + if inherited { + // The initialization expression is inherited from a previous + // constant declaration, and (error) positions refer to that + // expression and not the current constant declaration. Use + // the constant identifier position for any errors during + // init expression evaluation since that is all we have + // (see issues #42991, #42992). + check.errpos = obj.pos + } + check.expr(&x, init) + } + check.initConst(obj, &x) +} + +func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init syntax.Expr) { + assert(obj.typ == nil) + + // If we have undefined variable types due to errors, + // mark variables as used to avoid follow-on errors. + // Matches compiler behavior. + defer func() { + if obj.typ == Typ[Invalid] { + obj.used = true + } + for _, lhs := range lhs { + if lhs.typ == Typ[Invalid] { + lhs.used = true + } + } + }() + + // determine type, if any + if typ != nil { + obj.typ = check.varType(typ) + // We cannot spread the type to all lhs variables if there + // are more than one since that would mark them as checked + // (see Checker.objDecl) and the assignment of init exprs, + // if any, would not be checked. + // + // TODO(gri) If we have no init expr, we should distribute + // a given type otherwise we need to re-evalate the type + // expr for each lhs variable, leading to duplicate work. + } + + // check initialization + if init == nil { + if typ == nil { + // error reported before by arityMatch + obj.typ = Typ[Invalid] + } + return + } + + if lhs == nil || len(lhs) == 1 { + assert(lhs == nil || lhs[0] == obj) + var x operand + check.expr(&x, init) + check.initVar(obj, &x, "variable declaration") + return + } + + if debug { + // obj must be one of lhs + found := false + for _, lhs := range lhs { + if obj == lhs { + found = true + break + } + } + if !found { + panic("inconsistent lhs") + } + } + + // We have multiple variables on the lhs and one init expr. + // Make sure all variables have been given the same type if + // one was specified, otherwise they assume the type of the + // init expression values (was issue #15755). + if typ != nil { + for _, lhs := range lhs { + lhs.typ = obj.typ + } + } + + check.initVars(lhs, []syntax.Expr{init}, nopos) +} + +// under returns the expanded underlying type of n0; possibly by following +// forward chains of named types. If an underlying type is found, resolve +// the chain by setting the underlying type for each defined type in the +// chain before returning it. If no underlying type is found or a cycle +// is detected, the result is Typ[Invalid]. If a cycle is detected and +// n0.check != nil, the cycle is reported. +func (n0 *Named) under() Type { + u := n0.underlying + if u == nil { + return Typ[Invalid] + } + + // If the underlying type of a defined type is not a defined + // type, then that is the desired underlying type. + n := asNamed(u) + if n == nil { + return u // common case + } + + // Otherwise, follow the forward chain. + seen := map[*Named]int{n0: 0} + path := []Object{n0.obj} + for { + u = n.underlying + if u == nil { + u = Typ[Invalid] + break + } + n1 := asNamed(u) + if n1 == nil { + break // end of chain + } + + seen[n] = len(seen) + path = append(path, n.obj) + n = n1 + + if i, ok := seen[n]; ok { + // cycle + // TODO(gri) revert this to a method on Checker. Having a possibly + // nil Checker on Named and TypeParam is too subtle. + if n0.check != nil { + n0.check.cycleError(path[i:]) + } + u = Typ[Invalid] + break + } + } + + for n := range seen { + // We should never have to update the underlying type of an imported type; + // those underlying types should have been resolved during the import. + // Also, doing so would lead to a race condition (was issue #31749). + // Do this check always, not just in debug more (it's cheap). + if n0.check != nil && n.obj.pkg != n0.check.pkg { + panic("internal error: imported type with unresolved underlying type") + } + n.underlying = u + } + + return u +} + +func (n *Named) setUnderlying(typ Type) { + if n != nil { + n.underlying = typ + } +} + +func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named) { + assert(obj.typ == nil) + + check.later(func() { + check.validType(obj.typ, nil) + }) + + alias := tdecl.Alias + if alias && tdecl.TParamList != nil { + // The parser will ensure this but we may still get an invalid AST. + // Complain and continue as regular type definition. + check.error(tdecl, "generic type cannot be alias") + alias = false + } + + if alias { + // type alias declaration + if !check.allowVersion(check.pkg, 1, 9) { + if check.conf.CompilerErrorMessages { + check.error(tdecl, "type aliases only supported as of -lang=go1.9") + } else { + check.error(tdecl, "type aliases requires go1.9 or later") + } + } + + obj.typ = Typ[Invalid] + obj.typ = check.anyType(tdecl.Type) + + } else { + // defined type declaration + + named := check.newNamed(obj, nil, nil, nil, nil) + def.setUnderlying(named) + + if tdecl.TParamList != nil { + check.openScope(tdecl, "type parameters") + defer check.closeScope() + named.tparams = check.collectTypeParams(tdecl.TParamList) + } + + // determine underlying type of named + named.fromRHS = check.definedType(tdecl.Type, named) + + // The underlying type of named may be itself a named type that is + // incomplete: + // + // type ( + // A B + // B *C + // C A + // ) + // + // The type of C is the (named) type of A which is incomplete, + // and which has as its underlying type the named type B. + // Determine the (final, unnamed) underlying type by resolving + // any forward chain. + // TODO(gri) Investigate if we can just use named.fromRHS here + // and rely on lazy computation of the underlying type. + named.underlying = under(named) + } + +} + +func (check *Checker) collectTypeParams(list []*syntax.Field) (tparams []*TypeName) { + // Type parameter lists should not be empty. The parser will + // complain but we still may get an incorrect AST: ignore it. + if len(list) == 0 { + return + } + + // Declare type parameters up-front, with empty interface as type bound. + // The scope of type parameters starts at the beginning of the type parameter + // list (so we can have mutually recursive parameterized interfaces). + for _, f := range list { + tparams = check.declareTypeParam(tparams, f.Name) + } + + var bound Type + for i, j := 0, 0; i < len(list); i = j { + f := list[i] + + // determine the range of type parameters list[i:j] with identical type bound + // (declared as in (type a, b, c B)) + j = i + 1 + for j < len(list) && list[j].Type == f.Type { + j++ + } + + // this should never be the case, but be careful + if f.Type == nil { + continue + } + + // The predeclared identifier "any" is visible only as a constraint + // in a type parameter list. Look for it before general constraint + // resolution. + if tident, _ := unparen(f.Type).(*syntax.Name); tident != nil && tident.Value == "any" && check.lookup("any") == nil { + bound = universeAny + } else { + bound = check.typ(f.Type) + } + + // type bound must be an interface + // TODO(gri) We should delay the interface check because + // we may not have a complete interface yet: + // type C(type T C) interface {} + // (issue #39724). + if _, ok := under(bound).(*Interface); ok { + // set the type bounds + for i < j { + tparams[i].typ.(*TypeParam).bound = bound + i++ + } + } else if bound != Typ[Invalid] { + check.errorf(f.Type, "%s is not an interface", bound) + } + } + + return +} + +func (check *Checker) declareTypeParam(tparams []*TypeName, name *syntax.Name) []*TypeName { + tpar := NewTypeName(name.Pos(), check.pkg, name.Value, nil) + check.NewTypeParam(tpar, len(tparams), &emptyInterface) // assigns type to tpar as a side-effect + check.declare(check.scope, name, tpar, check.scope.pos) // TODO(gri) check scope position + tparams = append(tparams, tpar) + + if check.conf.Trace { + check.trace(name.Pos(), "type param = %v", tparams[len(tparams)-1]) + } + + return tparams +} + +func (check *Checker) collectMethods(obj *TypeName) { + // get associated methods + // (Checker.collectObjects only collects methods with non-blank names; + // Checker.resolveBaseTypeName ensures that obj is not an alias name + // if it has attached methods.) + methods := check.methods[obj] + if methods == nil { + return + } + delete(check.methods, obj) + assert(!check.objMap[obj].tdecl.Alias) // don't use TypeName.IsAlias (requires fully set up object) + + // use an objset to check for name conflicts + var mset objset + + // spec: "If the base type is a struct type, the non-blank method + // and field names must be distinct." + base := asNamed(obj.typ) // shouldn't fail but be conservative + if base != nil { + if t, _ := base.underlying.(*Struct); t != nil { + for _, fld := range t.fields { + if fld.name != "_" { + assert(mset.insert(fld) == nil) + } + } + } + + // Checker.Files may be called multiple times; additional package files + // may add methods to already type-checked types. Add pre-existing methods + // so that we can detect redeclarations. + for _, m := range base.methods { + assert(m.name != "_") + assert(mset.insert(m) == nil) + } + } + + // add valid methods + for _, m := range methods { + // spec: "For a base type, the non-blank names of methods bound + // to it must be unique." + assert(m.name != "_") + if alt := mset.insert(m); alt != nil { + var err error_ + switch alt.(type) { + case *Var: + err.errorf(m.pos, "field and method with the same name %s", m.name) + case *Func: + if check.conf.CompilerErrorMessages { + err.errorf(m.pos, "%s.%s redeclared in this block", obj.Name(), m.name) + } else { + err.errorf(m.pos, "method %s already declared for %s", m.name, obj) + } + default: + unreachable() + } + err.recordAltDecl(alt) + check.report(&err) + continue + } + + if base != nil { + base.methods = append(base.methods, m) + } + } +} + +func (check *Checker) funcDecl(obj *Func, decl *declInfo) { + assert(obj.typ == nil) + + // func declarations cannot use iota + assert(check.iota == nil) + + sig := new(Signature) + obj.typ = sig // guard against cycles + + // Avoid cycle error when referring to method while type-checking the signature. + // This avoids a nuisance in the best case (non-parameterized receiver type) and + // since the method is not a type, we get an error. If we have a parameterized + // receiver type, instantiating the receiver type leads to the instantiation of + // its methods, and we don't want a cycle error in that case. + // TODO(gri) review if this is correct and/or whether we still need this? + saved := obj.color_ + obj.color_ = black + fdecl := decl.fdecl + check.funcType(sig, fdecl.Recv, fdecl.TParamList, fdecl.Type) + obj.color_ = saved + + // function body must be type-checked after global declarations + // (functions implemented elsewhere have no body) + if !check.conf.IgnoreFuncBodies && fdecl.Body != nil { + check.later(func() { + check.funcBody(decl, obj.name, sig, fdecl.Body, nil) + }) + } +} + +func (check *Checker) declStmt(list []syntax.Decl) { + pkg := check.pkg + + first := -1 // index of first ConstDecl in the current group, or -1 + var last *syntax.ConstDecl // last ConstDecl with init expressions, or nil + for index, decl := range list { + if _, ok := decl.(*syntax.ConstDecl); !ok { + first = -1 // we're not in a constant declaration + } + + switch s := decl.(type) { + case *syntax.ConstDecl: + top := len(check.delayed) + + // iota is the index of the current constDecl within the group + if first < 0 || list[index-1].(*syntax.ConstDecl).Group != s.Group { + first = index + last = nil + } + iota := constant.MakeInt64(int64(index - first)) + + // determine which initialization expressions to use + inherited := true + switch { + case s.Type != nil || s.Values != nil: + last = s + inherited = false + case last == nil: + last = new(syntax.ConstDecl) // make sure last exists + inherited = false + } + + // declare all constants + lhs := make([]*Const, len(s.NameList)) + values := unpackExpr(last.Values) + for i, name := range s.NameList { + obj := NewConst(name.Pos(), pkg, name.Value, nil, iota) + lhs[i] = obj + + var init syntax.Expr + if i < len(values) { + init = values[i] + } + + check.constDecl(obj, last.Type, init, inherited) + } + + // Constants must always have init values. + check.arity(s.Pos(), s.NameList, values, true, inherited) + + // process function literals in init expressions before scope changes + check.processDelayed(top) + + // spec: "The scope of a constant or variable identifier declared + // inside a function begins at the end of the ConstSpec or VarSpec + // (ShortVarDecl for short variable declarations) and ends at the + // end of the innermost containing block." + scopePos := syntax.EndPos(s) + for i, name := range s.NameList { + check.declare(check.scope, name, lhs[i], scopePos) + } + + case *syntax.VarDecl: + top := len(check.delayed) + + lhs0 := make([]*Var, len(s.NameList)) + for i, name := range s.NameList { + lhs0[i] = NewVar(name.Pos(), pkg, name.Value, nil) + } + + // initialize all variables + values := unpackExpr(s.Values) + for i, obj := range lhs0 { + var lhs []*Var + var init syntax.Expr + switch len(values) { + case len(s.NameList): + // lhs and rhs match + init = values[i] + case 1: + // rhs is expected to be a multi-valued expression + lhs = lhs0 + init = values[0] + default: + if i < len(values) { + init = values[i] + } + } + check.varDecl(obj, lhs, s.Type, init) + if len(values) == 1 { + // If we have a single lhs variable we are done either way. + // If we have a single rhs expression, it must be a multi- + // valued expression, in which case handling the first lhs + // variable will cause all lhs variables to have a type + // assigned, and we are done as well. + if debug { + for _, obj := range lhs0 { + assert(obj.typ != nil) + } + } + break + } + } + + // If we have no type, we must have values. + if s.Type == nil || values != nil { + check.arity(s.Pos(), s.NameList, values, false, false) + } + + // process function literals in init expressions before scope changes + check.processDelayed(top) + + // declare all variables + // (only at this point are the variable scopes (parents) set) + scopePos := syntax.EndPos(s) // see constant declarations + for i, name := range s.NameList { + // see constant declarations + check.declare(check.scope, name, lhs0[i], scopePos) + } + + case *syntax.TypeDecl: + obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, nil) + // spec: "The scope of a type identifier declared inside a function + // begins at the identifier in the TypeSpec and ends at the end of + // the innermost containing block." + scopePos := s.Name.Pos() + check.declare(check.scope, s.Name, obj, scopePos) + // mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl) + obj.setColor(grey + color(check.push(obj))) + check.typeDecl(obj, s, nil) + check.pop().setColor(black) + + default: + check.errorf(s, invalidAST+"unknown syntax.Decl node %T", s) + } + } +} diff --git a/src/cmd/compile/internal/types2/errorcalls_test.go b/src/cmd/compile/internal/types2/errorcalls_test.go new file mode 100644 index 0000000000000000000000000000000000000000..28bb33aaffd00443da31ca7086caa1e8cfba37f2 --- /dev/null +++ b/src/cmd/compile/internal/types2/errorcalls_test.go @@ -0,0 +1,49 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE ast. + +package types2_test + +import ( + "cmd/compile/internal/syntax" + "testing" +) + +// TestErrorCalls makes sure that check.errorf calls have at +// least 3 arguments (otherwise we should be using check.error). +func TestErrorCalls(t *testing.T) { + files, err := pkgFiles(".") + if err != nil { + t.Fatal(err) + } + + for _, file := range files { + syntax.Walk(file, func(n syntax.Node) bool { + call, _ := n.(*syntax.CallExpr) + if call == nil { + return false + } + selx, _ := call.Fun.(*syntax.SelectorExpr) + if selx == nil { + return false + } + if !(isName(selx.X, "check") && isName(selx.Sel, "errorf")) { + return false + } + // check.errorf calls should have more than 2 arguments: + // position, format string, and arguments to format + if n := len(call.ArgList); n <= 2 { + t.Errorf("%s: got %d arguments, want > 2", call.Pos(), n) + return true + } + return false + }) + } +} + +func isName(n syntax.Node, name string) bool { + if n, ok := n.(*syntax.Name); ok { + return n.Value == name + } + return false +} diff --git a/src/cmd/compile/internal/types2/errors.go b/src/cmd/compile/internal/types2/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..af4ecb2300a58cbfe5e8eeae32220fe72129ba2d --- /dev/null +++ b/src/cmd/compile/internal/types2/errors.go @@ -0,0 +1,257 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements various error reporters. + +package types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "strconv" + "strings" +) + +func unimplemented() { + panic("unimplemented") +} + +func assert(p bool) { + if !p { + panic("assertion failed") + } +} + +func unreachable() { + panic("unreachable") +} + +// An error_ represents a type-checking error. +// To report an error_, call Checker.report. +type error_ struct { + desc []errorDesc + soft bool // TODO(gri) eventually determine this from an error code +} + +// An errorDesc describes part of a type-checking error. +type errorDesc struct { + pos syntax.Pos + format string + args []interface{} +} + +func (err *error_) empty() bool { + return err.desc == nil +} + +func (err *error_) pos() syntax.Pos { + if err.empty() { + return nopos + } + return err.desc[0].pos +} + +func (err *error_) msg(qf Qualifier) string { + if err.empty() { + return "no error" + } + var buf bytes.Buffer + for i := range err.desc { + p := &err.desc[i] + if i > 0 { + fmt.Fprintf(&buf, "\n\t%s: ", p.pos) + } + buf.WriteString(sprintf(qf, p.format, p.args...)) + } + return buf.String() +} + +// String is for testing. +func (err *error_) String() string { + if err.empty() { + return "no error" + } + return fmt.Sprintf("%s: %s", err.pos(), err.msg(nil)) +} + +// errorf adds formatted error information to err. +// It may be called multiple times to provide additional information. +func (err *error_) errorf(at poser, format string, args ...interface{}) { + err.desc = append(err.desc, errorDesc{posFor(at), format, args}) +} + +func sprintf(qf Qualifier, format string, args ...interface{}) string { + for i, arg := range args { + switch a := arg.(type) { + case nil: + arg = "" + case operand: + panic("internal error: should always pass *operand") + case *operand: + arg = operandString(a, qf) + case syntax.Pos: + arg = a.String() + case syntax.Expr: + arg = syntax.String(a) + case Object: + arg = ObjectString(a, qf) + case Type: + arg = TypeString(a, qf) + } + args[i] = arg + } + return fmt.Sprintf(format, args...) +} + +func (check *Checker) qualifier(pkg *Package) string { + // Qualify the package unless it's the package being type-checked. + if pkg != check.pkg { + if check.pkgPathMap == nil { + check.pkgPathMap = make(map[string]map[string]bool) + check.seenPkgMap = make(map[*Package]bool) + check.markImports(pkg) + } + // If the same package name was used by multiple packages, display the full path. + if len(check.pkgPathMap[pkg.name]) > 1 { + return strconv.Quote(pkg.path) + } + return pkg.name + } + return "" +} + +// markImports recursively walks pkg and its imports, to record unique import +// paths in pkgPathMap. +func (check *Checker) markImports(pkg *Package) { + if check.seenPkgMap[pkg] { + return + } + check.seenPkgMap[pkg] = true + + forName, ok := check.pkgPathMap[pkg.name] + if !ok { + forName = make(map[string]bool) + check.pkgPathMap[pkg.name] = forName + } + forName[pkg.path] = true + + for _, imp := range pkg.imports { + check.markImports(imp) + } +} + +func (check *Checker) sprintf(format string, args ...interface{}) string { + return sprintf(check.qualifier, format, args...) +} + +func (check *Checker) report(err *error_) { + if err.empty() { + panic("internal error: reporting no error") + } + check.err(err.pos(), err.msg(check.qualifier), err.soft) +} + +func (check *Checker) trace(pos syntax.Pos, format string, args ...interface{}) { + fmt.Printf("%s:\t%s%s\n", + pos, + strings.Repeat(". ", check.indent), + check.sprintf(format, args...), + ) +} + +// dump is only needed for debugging +func (check *Checker) dump(format string, args ...interface{}) { + fmt.Println(check.sprintf(format, args...)) +} + +func (check *Checker) err(at poser, msg string, soft bool) { + // Cheap trick: Don't report errors with messages containing + // "invalid operand" or "invalid type" as those tend to be + // follow-on errors which don't add useful information. Only + // exclude them if these strings are not at the beginning, + // and only if we have at least one error already reported. + if check.firstErr != nil && (strings.Index(msg, "invalid operand") > 0 || strings.Index(msg, "invalid type") > 0) { + return + } + + pos := posFor(at) + + // If we are encountering an error while evaluating an inherited + // constant initialization expression, pos is the position of in + // the original expression, and not of the currently declared + // constant identifier. Use the provided errpos instead. + // TODO(gri) We may also want to augment the error message and + // refer to the position (pos) in the original expression. + if check.errpos.IsKnown() { + assert(check.iota != nil) + pos = check.errpos + } + + err := Error{pos, stripAnnotations(msg), msg, soft} + if check.firstErr == nil { + check.firstErr = err + } + + if check.conf.Trace { + check.trace(pos, "ERROR: %s", msg) + } + + f := check.conf.Error + if f == nil { + panic(bailout{}) // report only first error + } + f(err) +} + +const ( + invalidAST = "invalid AST: " + invalidArg = "invalid argument: " + invalidOp = "invalid operation: " +) + +type poser interface { + Pos() syntax.Pos +} + +func (check *Checker) error(at poser, msg string) { + check.err(at, msg, false) +} + +func (check *Checker) errorf(at poser, format string, args ...interface{}) { + check.err(at, check.sprintf(format, args...), false) +} + +func (check *Checker) softErrorf(at poser, format string, args ...interface{}) { + check.err(at, check.sprintf(format, args...), true) +} + +// posFor reports the left (= start) position of at. +func posFor(at poser) syntax.Pos { + switch x := at.(type) { + case *operand: + if x.expr != nil { + return syntax.StartPos(x.expr) + } + case syntax.Node: + return syntax.StartPos(x) + } + return at.Pos() +} + +// stripAnnotations removes internal (type) annotations from s. +func stripAnnotations(s string) string { + // Would like to use strings.Builder but it's not available in Go 1.4. + var b bytes.Buffer + for _, r := range s { + // strip #'s and subscript digits + if r != instanceMarker && !('₀' <= r && r < '₀'+10) { // '₀' == U+2080 + b.WriteRune(r) + } + } + if b.Len() < len(s) { + return b.String() + } + return s +} diff --git a/src/cmd/compile/internal/types2/errors_test.go b/src/cmd/compile/internal/types2/errors_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e1f0e83fc97d16f43be86cf4eb169d78e1bba284 --- /dev/null +++ b/src/cmd/compile/internal/types2/errors_test.go @@ -0,0 +1,45 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import "testing" + +func TestError(t *testing.T) { + var err error_ + want := "no error" + if got := err.String(); got != want { + t.Errorf("empty error: got %q, want %q", got, want) + } + + want = ": foo 42" + err.errorf(nopos, "foo %d", 42) + if got := err.String(); got != want { + t.Errorf("simple error: got %q, want %q", got, want) + } + + want = ": foo 42\n\t: bar 43" + err.errorf(nopos, "bar %d", 43) + if got := err.String(); got != want { + t.Errorf("simple error: got %q, want %q", got, want) + } +} + +func TestStripAnnotations(t *testing.T) { + for _, test := range []struct { + in, want string + }{ + {"", ""}, + {" ", " "}, + {"foo", "foo"}, + {"foo₀", "foo"}, + {"foo(T₀)", "foo(T)"}, + {"#foo(T₀)", "foo(T)"}, + } { + got := stripAnnotations(test.in) + if got != test.want { + t.Errorf("%q: got %q; want %q", test.in, got, test.want) + } + } +} diff --git a/src/cmd/compile/internal/types2/example_test.go b/src/cmd/compile/internal/types2/example_test.go new file mode 100644 index 0000000000000000000000000000000000000000..714bf77821399683925a986183c984869edc93b2 --- /dev/null +++ b/src/cmd/compile/internal/types2/example_test.go @@ -0,0 +1,269 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Only run where builders (build.golang.org) have +// access to compiled packages for import. +// +//go:build !arm && !arm64 +// +build !arm,!arm64 + +package types2_test + +// This file shows examples of basic usage of the go/types API. +// +// To locate a Go package, use (*go/build.Context).Import. +// To load, parse, and type-check a complete Go program +// from source, use golang.org/x/tools/go/loader. + +import ( + "bytes" + "cmd/compile/internal/syntax" + "cmd/compile/internal/types2" + "fmt" + "log" + "regexp" + "sort" + "strings" +) + +// ExampleScope prints the tree of Scopes of a package created from a +// set of parsed files. +func ExampleScope() { + // Parse the source files for a package. + var files []*syntax.File + for _, file := range []struct{ name, input string }{ + {"main.go", ` +package main +import "fmt" +func main() { + freezing := FToC(-18) + fmt.Println(freezing, Boiling) } +`}, + {"celsius.go", ` +package main +import "fmt" +type Celsius float64 +func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } +func FToC(f float64) Celsius { return Celsius(f - 32 / 9 * 5) } +const Boiling Celsius = 100 +func Unused() { {}; {{ var x int; _ = x }} } // make sure empty block scopes get printed +`}, + } { + f, err := parseSrc(file.name, file.input) + if err != nil { + log.Fatal(err) + } + files = append(files, f) + } + + // Type-check a package consisting of these files. + // Type information for the imported "fmt" package + // comes from $GOROOT/pkg/$GOOS_$GOOARCH/fmt.a. + conf := types2.Config{Importer: defaultImporter()} + pkg, err := conf.Check("temperature", files, nil) + if err != nil { + log.Fatal(err) + } + + // Print the tree of scopes. + // For determinism, we redact addresses. + var buf bytes.Buffer + pkg.Scope().WriteTo(&buf, 0, true) + rx := regexp.MustCompile(` 0x[a-fA-F0-9]*`) + fmt.Println(rx.ReplaceAllString(buf.String(), "")) + + // Output: + // package "temperature" scope { + // . const temperature.Boiling temperature.Celsius + // . type temperature.Celsius float64 + // . func temperature.FToC(f float64) temperature.Celsius + // . func temperature.Unused() + // . func temperature.main() + // . main.go scope { + // . . package fmt + // . . function scope { + // . . . var freezing temperature.Celsius + // . . } + // . } + // . celsius.go scope { + // . . package fmt + // . . function scope { + // . . . var c temperature.Celsius + // . . } + // . . function scope { + // . . . var f float64 + // . . } + // . . function scope { + // . . . block scope { + // . . . } + // . . . block scope { + // . . . . block scope { + // . . . . . var x int + // . . . . } + // . . . } + // . . } + // . } + // } +} + +// ExampleInfo prints various facts recorded by the type checker in a +// types2.Info struct: definitions of and references to each named object, +// and the type, value, and mode of every expression in the package. +func ExampleInfo() { + // Parse a single source file. + const input = ` +package fib + +type S string + +var a, b, c = len(b), S(c), "hello" + +func fib(x int) int { + if x < 2 { + return x + } + return fib(x-1) - fib(x-2) +}` + f, err := parseSrc("fib.go", input) + if err != nil { + log.Fatal(err) + } + + // Type-check the package. + // We create an empty map for each kind of input + // we're interested in, and Check populates them. + info := types2.Info{ + Types: make(map[syntax.Expr]types2.TypeAndValue), + Defs: make(map[*syntax.Name]types2.Object), + Uses: make(map[*syntax.Name]types2.Object), + } + var conf types2.Config + pkg, err := conf.Check("fib", []*syntax.File{f}, &info) + if err != nil { + log.Fatal(err) + } + + // Print package-level variables in initialization order. + fmt.Printf("InitOrder: %v\n\n", info.InitOrder) + + // For each named object, print the line and + // column of its definition and each of its uses. + fmt.Println("Defs and Uses of each named object:") + usesByObj := make(map[types2.Object][]string) + for id, obj := range info.Uses { + posn := id.Pos() + lineCol := fmt.Sprintf("%d:%d", posn.Line(), posn.Col()) + usesByObj[obj] = append(usesByObj[obj], lineCol) + } + var items []string + for obj, uses := range usesByObj { + sort.Strings(uses) + item := fmt.Sprintf("%s:\n defined at %s\n used at %s", + types2.ObjectString(obj, types2.RelativeTo(pkg)), + obj.Pos(), + strings.Join(uses, ", ")) + items = append(items, item) + } + sort.Strings(items) // sort by line:col, in effect + fmt.Println(strings.Join(items, "\n")) + fmt.Println() + + // TODO(gri) Enable once positions are updated/verified + // fmt.Println("Types and Values of each expression:") + // items = nil + // for expr, tv := range info.Types { + // var buf bytes.Buffer + // posn := expr.Pos() + // tvstr := tv.Type.String() + // if tv.Value != nil { + // tvstr += " = " + tv.Value.String() + // } + // // line:col | expr | mode : type = value + // fmt.Fprintf(&buf, "%2d:%2d | %-19s | %-7s : %s", + // posn.Line(), posn.Col(), types2.ExprString(expr), + // mode(tv), tvstr) + // items = append(items, buf.String()) + // } + // sort.Strings(items) + // fmt.Println(strings.Join(items, "\n")) + + // Output: + // InitOrder: [c = "hello" b = S(c) a = len(b)] + // + // Defs and Uses of each named object: + // builtin len: + // defined at + // used at 6:15 + // func fib(x int) int: + // defined at fib.go:8:6 + // used at 12:20, 12:9 + // type S string: + // defined at fib.go:4:6 + // used at 6:23 + // type int: + // defined at + // used at 8:12, 8:17 + // type string: + // defined at + // used at 4:8 + // var b S: + // defined at fib.go:6:8 + // used at 6:19 + // var c string: + // defined at fib.go:6:11 + // used at 6:25 + // var x int: + // defined at fib.go:8:10 + // used at 10:10, 12:13, 12:24, 9:5 + + // TODO(gri) Enable once positions are updated/verified + // Types and Values of each expression: + // 4: 8 | string | type : string + // 6:15 | len | builtin : func(string) int + // 6:15 | len(b) | value : int + // 6:19 | b | var : fib.S + // 6:23 | S | type : fib.S + // 6:23 | S(c) | value : fib.S + // 6:25 | c | var : string + // 6:29 | "hello" | value : string = "hello" + // 8:12 | int | type : int + // 8:17 | int | type : int + // 9: 5 | x | var : int + // 9: 5 | x < 2 | value : untyped bool + // 9: 9 | 2 | value : int = 2 + // 10:10 | x | var : int + // 12: 9 | fib | value : func(x int) int + // 12: 9 | fib(x - 1) | value : int + // 12: 9 | fib(x - 1) - fib(x - 2) | value : int + // 12:13 | x | var : int + // 12:13 | x - 1 | value : int + // 12:15 | 1 | value : int = 1 + // 12:20 | fib | value : func(x int) int + // 12:20 | fib(x - 2) | value : int + // 12:24 | x | var : int + // 12:24 | x - 2 | value : int + // 12:26 | 2 | value : int = 2 +} + +func mode(tv types2.TypeAndValue) string { + switch { + case tv.IsVoid(): + return "void" + case tv.IsType(): + return "type" + case tv.IsBuiltin(): + return "builtin" + case tv.IsNil(): + return "nil" + case tv.Assignable(): + if tv.Addressable() { + return "var" + } + return "mapindex" + case tv.IsValue(): + return "value" + default: + return "unknown" + } +} diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go new file mode 100644 index 0000000000000000000000000000000000000000..23b79656bb5fb0dabb7c769a27bcb86e10e68b7a --- /dev/null +++ b/src/cmd/compile/internal/types2/expr.go @@ -0,0 +1,1655 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements typechecking of expressions. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "go/constant" + "go/token" + "math" +) + +/* +Basic algorithm: + +Expressions are checked recursively, top down. Expression checker functions +are generally of the form: + + func f(x *operand, e *syntax.Expr, ...) + +where e is the expression to be checked, and x is the result of the check. +The check performed by f may fail in which case x.mode == invalid, and +related error messages will have been issued by f. + +If a hint argument is present, it is the composite literal element type +of an outer composite literal; it is used to type-check composite literal +elements that have no explicit type specification in the source +(e.g.: []T{{...}, {...}}, the hint is the type T in this case). + +All expressions are checked via rawExpr, which dispatches according +to expression kind. Upon returning, rawExpr is recording the types and +constant values for all expressions that have an untyped type (those types +may change on the way up in the expression tree). Usually these are constants, +but the results of comparisons or non-constant shifts of untyped constants +may also be untyped, but not constant. + +Untyped expressions may eventually become fully typed (i.e., not untyped), +typically when the value is assigned to a variable, or is used otherwise. +The updateExprType method is used to record this final type and update +the recorded types: the type-checked expression tree is again traversed down, +and the new type is propagated as needed. Untyped constant expression values +that become fully typed must now be representable by the full type (constant +sub-expression trees are left alone except for their roots). This mechanism +ensures that a client sees the actual (run-time) type an untyped value would +have. It also permits type-checking of lhs shift operands "as if the shift +were not present": when updateExprType visits an untyped lhs shift operand +and assigns it it's final type, that type must be an integer type, and a +constant lhs must be representable as an integer. + +When an expression gets its final type, either on the way out from rawExpr, +on the way down in updateExprType, or at the end of the type checker run, +the type (and constant value, if any) is recorded via Info.Types, if present. +*/ + +type opPredicates map[syntax.Operator]func(Type) bool + +var unaryOpPredicates opPredicates + +func init() { + // Setting unaryOpPredicates in init avoids declaration cycles. + unaryOpPredicates = opPredicates{ + syntax.Add: isNumeric, + syntax.Sub: isNumeric, + syntax.Xor: isInteger, + syntax.Not: isBoolean, + } +} + +func (check *Checker) op(m opPredicates, x *operand, op syntax.Operator) bool { + if pred := m[op]; pred != nil { + if !pred(x.typ) { + if check.conf.CompilerErrorMessages { + check.errorf(x, invalidOp+"operator %s not defined on %s", op, x) + } else { + check.errorf(x, invalidOp+"operator %s not defined for %s", op, x) + } + return false + } + } else { + check.errorf(x, invalidAST+"unknown operator %s", op) + return false + } + return true +} + +// overflow checks that the constant x is representable by its type. +// For untyped constants, it checks that the value doesn't become +// arbitrarily large. +func (check *Checker) overflow(x *operand) { + assert(x.mode == constant_) + + // If the corresponding expression is an operation, use the + // operator position rather than the start of the expression + // as error position. + pos := syntax.StartPos(x.expr) + what := "" // operator description, if any + if op, _ := x.expr.(*syntax.Operation); op != nil { + pos = op.Pos() + what = opName(op) + } + + if x.val.Kind() == constant.Unknown { + // TODO(gri) We should report exactly what went wrong. At the + // moment we don't have the (go/constant) API for that. + // See also TODO in go/constant/value.go. + check.error(pos, "constant result is not representable") + return + } + + // Typed constants must be representable in + // their type after each constant operation. + if isTyped(x.typ) { + check.representable(x, asBasic(x.typ)) + return + } + + // Untyped integer values must not grow arbitrarily. + const prec = 512 // 512 is the constant precision + if x.val.Kind() == constant.Int && constant.BitLen(x.val) > prec { + check.errorf(pos, "constant %s overflow", what) + x.val = constant.MakeUnknown() + } +} + +// opName returns the name of an operation, or the empty string. +// For now, only operations that might overflow are handled. +// TODO(gri) Expand this to a general mechanism giving names to +// nodes? +func opName(e *syntax.Operation) string { + op := int(e.Op) + if e.Y == nil { + if op < len(op2str1) { + return op2str1[op] + } + } else { + if op < len(op2str2) { + return op2str2[op] + } + } + return "" +} + +var op2str1 = [...]string{ + syntax.Xor: "bitwise complement", +} + +// This is only used for operations that may cause overflow. +var op2str2 = [...]string{ + syntax.Add: "addition", + syntax.Sub: "subtraction", + syntax.Xor: "bitwise XOR", + syntax.Mul: "multiplication", + syntax.Shl: "shift", +} + +func (check *Checker) unary(x *operand, e *syntax.Operation) { + check.expr(x, e.X) + if x.mode == invalid { + return + } + + switch e.Op { + case syntax.And: + // spec: "As an exception to the addressability + // requirement x may also be a composite literal." + if _, ok := unparen(e.X).(*syntax.CompositeLit); !ok && x.mode != variable { + check.errorf(x, invalidOp+"cannot take address of %s", x) + x.mode = invalid + return + } + x.mode = value + x.typ = &Pointer{base: x.typ} + return + + case syntax.Recv: + typ := asChan(x.typ) + if typ == nil { + check.errorf(x, invalidOp+"cannot receive from non-channel %s", x) + x.mode = invalid + return + } + if typ.dir == SendOnly { + check.errorf(x, invalidOp+"cannot receive from send-only channel %s", x) + x.mode = invalid + return + } + x.mode = commaok + x.typ = typ.elem + check.hasCallOrRecv = true + return + } + + if !check.op(unaryOpPredicates, x, e.Op) { + x.mode = invalid + return + } + + if x.mode == constant_ { + if x.val.Kind() == constant.Unknown { + // nothing to do (and don't cause an error below in the overflow check) + return + } + var prec uint + if isUnsigned(x.typ) { + prec = uint(check.conf.sizeof(x.typ) * 8) + } + x.val = constant.UnaryOp(op2tok[e.Op], x.val, prec) + x.expr = e + check.overflow(x) + return + } + + x.mode = value + // x.typ remains unchanged +} + +func isShift(op syntax.Operator) bool { + return op == syntax.Shl || op == syntax.Shr +} + +func isComparison(op syntax.Operator) bool { + // Note: tokens are not ordered well to make this much easier + switch op { + case syntax.Eql, syntax.Neq, syntax.Lss, syntax.Leq, syntax.Gtr, syntax.Geq: + return true + } + return false +} + +func fitsFloat32(x constant.Value) bool { + f32, _ := constant.Float32Val(x) + f := float64(f32) + return !math.IsInf(f, 0) +} + +func roundFloat32(x constant.Value) constant.Value { + f32, _ := constant.Float32Val(x) + f := float64(f32) + if !math.IsInf(f, 0) { + return constant.MakeFloat64(f) + } + return nil +} + +func fitsFloat64(x constant.Value) bool { + f, _ := constant.Float64Val(x) + return !math.IsInf(f, 0) +} + +func roundFloat64(x constant.Value) constant.Value { + f, _ := constant.Float64Val(x) + if !math.IsInf(f, 0) { + return constant.MakeFloat64(f) + } + return nil +} + +// representableConst reports whether x can be represented as +// value of the given basic type and for the configuration +// provided (only needed for int/uint sizes). +// +// If rounded != nil, *rounded is set to the rounded value of x for +// representable floating-point and complex values, and to an Int +// value for integer values; it is left alone otherwise. +// It is ok to provide the addressof the first argument for rounded. +// +// The check parameter may be nil if representableConst is invoked +// (indirectly) through an exported API call (AssignableTo, ConvertibleTo) +// because we don't need the Checker's config for those calls. +func representableConst(x constant.Value, check *Checker, typ *Basic, rounded *constant.Value) bool { + if x.Kind() == constant.Unknown { + return true // avoid follow-up errors + } + + var conf *Config + if check != nil { + conf = check.conf + } + + switch { + case isInteger(typ): + x := constant.ToInt(x) + if x.Kind() != constant.Int { + return false + } + if rounded != nil { + *rounded = x + } + if x, ok := constant.Int64Val(x); ok { + switch typ.kind { + case Int: + var s = uint(conf.sizeof(typ)) * 8 + return int64(-1)<<(s-1) <= x && x <= int64(1)<<(s-1)-1 + case Int8: + const s = 8 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int16: + const s = 16 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int32: + const s = 32 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int64, UntypedInt: + return true + case Uint, Uintptr: + if s := uint(conf.sizeof(typ)) * 8; s < 64 { + return 0 <= x && x <= int64(1)<= 0 && n <= int(s) + case Uint64: + return constant.Sign(x) >= 0 && n <= 64 + case UntypedInt: + return true + } + + case isFloat(typ): + x := constant.ToFloat(x) + if x.Kind() != constant.Float { + return false + } + switch typ.kind { + case Float32: + if rounded == nil { + return fitsFloat32(x) + } + r := roundFloat32(x) + if r != nil { + *rounded = r + return true + } + case Float64: + if rounded == nil { + return fitsFloat64(x) + } + r := roundFloat64(x) + if r != nil { + *rounded = r + return true + } + case UntypedFloat: + return true + default: + unreachable() + } + + case isComplex(typ): + x := constant.ToComplex(x) + if x.Kind() != constant.Complex { + return false + } + switch typ.kind { + case Complex64: + if rounded == nil { + return fitsFloat32(constant.Real(x)) && fitsFloat32(constant.Imag(x)) + } + re := roundFloat32(constant.Real(x)) + im := roundFloat32(constant.Imag(x)) + if re != nil && im != nil { + *rounded = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + return true + } + case Complex128: + if rounded == nil { + return fitsFloat64(constant.Real(x)) && fitsFloat64(constant.Imag(x)) + } + re := roundFloat64(constant.Real(x)) + im := roundFloat64(constant.Imag(x)) + if re != nil && im != nil { + *rounded = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + return true + } + case UntypedComplex: + return true + default: + unreachable() + } + + case isString(typ): + return x.Kind() == constant.String + + case isBoolean(typ): + return x.Kind() == constant.Bool + } + + return false +} + +// An errorCode is a (constant) value uniquely identifing a specific error. +type errorCode int + +// The following error codes are "borrowed" from go/types which codes for +// all errors. Here we list the few codes currently needed by the various +// conversion checking functions. +// Eventually we will switch to reporting codes for all errors, using a +// an error code table shared between types2 and go/types. +const ( + _ = errorCode(iota) + _TruncatedFloat + _NumericOverflow + _InvalidConstVal + _InvalidUntypedConversion + + // The following error codes are only returned by operand.assignableTo + // and none of its callers use the error. Still, we keep returning the + // error codes to make the transition to reporting error codes all the + // time easier in the future. + _IncompatibleAssign + _InvalidIfaceAssign + _InvalidChanAssign +) + +// representable checks that a constant operand is representable in the given +// basic type. +func (check *Checker) representable(x *operand, typ *Basic) { + v, code := check.representation(x, typ) + if code != 0 { + check.invalidConversion(code, x, typ) + x.mode = invalid + return + } + assert(v != nil) + x.val = v +} + +// representation returns the representation of the constant operand x as the +// basic type typ. +// +// If no such representation is possible, it returns a non-zero error code. +func (check *Checker) representation(x *operand, typ *Basic) (constant.Value, errorCode) { + assert(x.mode == constant_) + v := x.val + if !representableConst(x.val, check, typ, &v) { + if isNumeric(x.typ) && isNumeric(typ) { + // numeric conversion : error msg + // + // integer -> integer : overflows + // integer -> float : overflows (actually not possible) + // float -> integer : truncated + // float -> float : overflows + // + if !isInteger(x.typ) && isInteger(typ) { + return nil, _TruncatedFloat + } else { + return nil, _NumericOverflow + } + } + return nil, _InvalidConstVal + } + return v, 0 +} + +func (check *Checker) invalidConversion(code errorCode, x *operand, target Type) { + msg := "cannot convert %s to %s" + switch code { + case _TruncatedFloat: + msg = "%s truncated to %s" + case _NumericOverflow: + msg = "%s overflows %s" + } + check.errorf(x, msg, x, target) +} + +// updateExprType updates the type of x to typ and invokes itself +// recursively for the operands of x, depending on expression kind. +// If typ is still an untyped and not the final type, updateExprType +// only updates the recorded untyped type for x and possibly its +// operands. Otherwise (i.e., typ is not an untyped type anymore, +// or it is the final type for x), the type and value are recorded. +// Also, if x is a constant, it must be representable as a value of typ, +// and if x is the (formerly untyped) lhs operand of a non-constant +// shift, it must be an integer value. +// +func (check *Checker) updateExprType(x syntax.Expr, typ Type, final bool) { + old, found := check.untyped[x] + if !found { + return // nothing to do + } + + // update operands of x if necessary + switch x := x.(type) { + case *syntax.BadExpr, + *syntax.FuncLit, + *syntax.CompositeLit, + *syntax.IndexExpr, + *syntax.SliceExpr, + *syntax.AssertExpr, + *syntax.ListExpr, + //*syntax.StarExpr, + *syntax.KeyValueExpr, + *syntax.ArrayType, + *syntax.StructType, + *syntax.FuncType, + *syntax.InterfaceType, + *syntax.MapType, + *syntax.ChanType: + // These expression are never untyped - nothing to do. + // The respective sub-expressions got their final types + // upon assignment or use. + if debug { + check.dump("%v: found old type(%s): %s (new: %s)", posFor(x), x, old.typ, typ) + unreachable() + } + return + + case *syntax.CallExpr: + // Resulting in an untyped constant (e.g., built-in complex). + // The respective calls take care of calling updateExprType + // for the arguments if necessary. + + case *syntax.Name, *syntax.BasicLit, *syntax.SelectorExpr: + // An identifier denoting a constant, a constant literal, + // or a qualified identifier (imported untyped constant). + // No operands to take care of. + + case *syntax.ParenExpr: + check.updateExprType(x.X, typ, final) + + // case *syntax.UnaryExpr: + // // If x is a constant, the operands were constants. + // // The operands don't need to be updated since they + // // never get "materialized" into a typed value. If + // // left in the untyped map, they will be processed + // // at the end of the type check. + // if old.val != nil { + // break + // } + // check.updateExprType(x.X, typ, final) + + case *syntax.Operation: + if x.Y == nil { + // unary expression + if x.Op == syntax.Mul { + // see commented out code for StarExpr above + // TODO(gri) needs cleanup + if debug { + unimplemented() + } + return + } + // If x is a constant, the operands were constants. + // The operands don't need to be updated since they + // never get "materialized" into a typed value. If + // left in the untyped map, they will be processed + // at the end of the type check. + if old.val != nil { + break + } + check.updateExprType(x.X, typ, final) + break + } + + // binary expression + if old.val != nil { + break // see comment for unary expressions + } + if isComparison(x.Op) { + // The result type is independent of operand types + // and the operand types must have final types. + } else if isShift(x.Op) { + // The result type depends only on lhs operand. + // The rhs type was updated when checking the shift. + check.updateExprType(x.X, typ, final) + } else { + // The operand types match the result type. + check.updateExprType(x.X, typ, final) + check.updateExprType(x.Y, typ, final) + } + + default: + unreachable() + } + + // If the new type is not final and still untyped, just + // update the recorded type. + if !final && isUntyped(typ) { + old.typ = asBasic(typ) + check.untyped[x] = old + return + } + + // Otherwise we have the final (typed or untyped type). + // Remove it from the map of yet untyped expressions. + delete(check.untyped, x) + + if old.isLhs { + // If x is the lhs of a shift, its final type must be integer. + // We already know from the shift check that it is representable + // as an integer if it is a constant. + if !isInteger(typ) { + check.errorf(x, invalidOp+"shifted operand %s (type %s) must be integer", x, typ) + return + } + // Even if we have an integer, if the value is a constant we + // still must check that it is representable as the specific + // int type requested (was issue #22969). Fall through here. + } + if old.val != nil { + // If x is a constant, it must be representable as a value of typ. + c := operand{old.mode, x, old.typ, old.val, 0} + check.convertUntyped(&c, typ) + if c.mode == invalid { + return + } + } + + // Everything's fine, record final type and value for x. + check.recordTypeAndValue(x, old.mode, typ, old.val) +} + +// updateExprVal updates the value of x to val. +func (check *Checker) updateExprVal(x syntax.Expr, val constant.Value) { + if info, ok := check.untyped[x]; ok { + info.val = val + check.untyped[x] = info + } +} + +// convertUntyped attempts to set the type of an untyped value to the target type. +func (check *Checker) convertUntyped(x *operand, target Type) { + newType, val, code := check.implicitTypeAndValue(x, target) + if code != 0 { + check.invalidConversion(code, x, target.Underlying()) + x.mode = invalid + return + } + if val != nil { + x.val = val + check.updateExprVal(x.expr, val) + } + if newType != x.typ { + x.typ = newType + check.updateExprType(x.expr, newType, false) + } +} + +// implicitTypeAndValue returns the implicit type of x when used in a context +// where the target type is expected. If no such implicit conversion is +// possible, it returns a nil Type and non-zero error code. +// +// If x is a constant operand, the returned constant.Value will be the +// representation of x in this context. +func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, constant.Value, errorCode) { + target = expand(target) + if x.mode == invalid || isTyped(x.typ) || target == Typ[Invalid] { + return x.typ, nil, 0 + } + + if isUntyped(target) { + // both x and target are untyped + xkind := x.typ.(*Basic).kind + tkind := target.(*Basic).kind + if isNumeric(x.typ) && isNumeric(target) { + if xkind < tkind { + return target, nil, 0 + } + } else if xkind != tkind { + return nil, nil, _InvalidUntypedConversion + } + return x.typ, nil, 0 + } + + if x.isNil() { + assert(isUntyped(x.typ)) + if hasNil(target) { + return target, nil, 0 + } + return nil, nil, _InvalidUntypedConversion + } + + switch t := optype(target).(type) { + case *Basic: + if x.mode == constant_ { + v, code := check.representation(x, t) + if code != 0 { + return nil, nil, code + } + return target, v, code + } + // Non-constant untyped values may appear as the + // result of comparisons (untyped bool), intermediate + // (delayed-checked) rhs operands of shifts, and as + // the value nil. + switch x.typ.(*Basic).kind { + case UntypedBool: + if !isBoolean(target) { + return nil, nil, _InvalidUntypedConversion + } + case UntypedInt, UntypedRune, UntypedFloat, UntypedComplex: + if !isNumeric(target) { + return nil, nil, _InvalidUntypedConversion + } + case UntypedString: + // Non-constant untyped string values are not permitted by the spec and + // should not occur during normal typechecking passes, but this path is + // reachable via the AssignableTo API. + if !isString(target) { + return nil, nil, _InvalidUntypedConversion + } + default: + return nil, nil, _InvalidUntypedConversion + } + case *Sum: + ok := t.is(func(t Type) bool { + target, _, _ := check.implicitTypeAndValue(x, t) + return target != nil + }) + if !ok { + return nil, nil, _InvalidUntypedConversion + } + case *Interface: + // Update operand types to the default type rather than the target + // (interface) type: values must have concrete dynamic types. + // Untyped nil was handled upfront. + check.completeInterface(nopos, t) + if !t.Empty() { + return nil, nil, _InvalidUntypedConversion // cannot assign untyped values to non-empty interfaces + } + return Default(x.typ), nil, 0 // default type for nil is nil + default: + return nil, nil, _InvalidUntypedConversion + } + return target, nil, 0 +} + +func (check *Checker) comparison(x, y *operand, op syntax.Operator) { + // spec: "In any comparison, the first operand must be assignable + // to the type of the second operand, or vice versa." + err := "" + xok, _ := x.assignableTo(check, y.typ, nil) + yok, _ := y.assignableTo(check, x.typ, nil) + if xok || yok { + defined := false + switch op { + case syntax.Eql, syntax.Neq: + // spec: "The equality operators == and != apply to operands that are comparable." + defined = Comparable(x.typ) && Comparable(y.typ) || x.isNil() && hasNil(y.typ) || y.isNil() && hasNil(x.typ) + case syntax.Lss, syntax.Leq, syntax.Gtr, syntax.Geq: + // spec: The ordering operators <, <=, >, and >= apply to operands that are ordered." + defined = isOrdered(x.typ) && isOrdered(y.typ) + default: + unreachable() + } + if !defined { + typ := x.typ + if x.isNil() { + typ = y.typ + } + if check.conf.CompilerErrorMessages { + err = check.sprintf("operator %s not defined on %s", op, typ) + } else { + err = check.sprintf("operator %s not defined for %s", op, typ) + } + } + } else { + err = check.sprintf("mismatched types %s and %s", x.typ, y.typ) + } + + if err != "" { + // TODO(gri) better error message for cases where one can only compare against nil + check.errorf(x, invalidOp+"cannot compare %s %s %s (%s)", x.expr, op, y.expr, err) + x.mode = invalid + return + } + + if x.mode == constant_ && y.mode == constant_ { + x.val = constant.MakeBool(constant.Compare(x.val, op2tok[op], y.val)) + // The operands are never materialized; no need to update + // their types. + } else { + x.mode = value + // The operands have now their final types, which at run- + // time will be materialized. Update the expression trees. + // If the current types are untyped, the materialized type + // is the respective default type. + check.updateExprType(x.expr, Default(x.typ), true) + check.updateExprType(y.expr, Default(y.typ), true) + } + + // spec: "Comparison operators compare two operands and yield + // an untyped boolean value." + x.typ = Typ[UntypedBool] +} + +// If e != nil, it must be the shift expression; it may be nil for non-constant shifts. +func (check *Checker) shift(x, y *operand, e syntax.Expr, op syntax.Operator) { + // TODO(gri) This function seems overly complex. Revisit. + + var xval constant.Value + if x.mode == constant_ { + xval = constant.ToInt(x.val) + } + + if isInteger(x.typ) || isUntyped(x.typ) && xval != nil && xval.Kind() == constant.Int { + // The lhs is of integer type or an untyped constant representable + // as an integer. Nothing to do. + } else { + // shift has no chance + check.errorf(x, invalidOp+"shifted operand %s must be integer", x) + x.mode = invalid + return + } + + // spec: "The right operand in a shift expression must have integer type + // or be an untyped constant representable by a value of type uint." + + // Provide a good error message for negative shift counts. + if y.mode == constant_ { + yval := constant.ToInt(y.val) // consider -1, 1.0, but not -1.1 + if yval.Kind() == constant.Int && constant.Sign(yval) < 0 { + check.errorf(y, invalidOp+"negative shift count %s", y) + x.mode = invalid + return + } + } + + // Caution: Check for isUntyped first because isInteger includes untyped + // integers (was bug #43697). + if isUntyped(y.typ) { + check.convertUntyped(y, Typ[Uint]) + if y.mode == invalid { + x.mode = invalid + return + } + } else if !isInteger(y.typ) { + check.errorf(y, invalidOp+"shift count %s must be integer", y) + x.mode = invalid + return + } else if !isUnsigned(y.typ) && !check.allowVersion(check.pkg, 1, 13) { + check.errorf(y, invalidOp+"signed shift count %s requires go1.13 or later", y) + x.mode = invalid + return + } + + if x.mode == constant_ { + if y.mode == constant_ { + // if either x or y has an unknown value, the result is unknown + if x.val.Kind() == constant.Unknown || y.val.Kind() == constant.Unknown { + x.val = constant.MakeUnknown() + // ensure the correct type - see comment below + if !isInteger(x.typ) { + x.typ = Typ[UntypedInt] + } + return + } + // rhs must be within reasonable bounds in constant shifts + const shiftBound = 1023 - 1 + 52 // so we can express smallestFloat64 (see issue #44057) + s, ok := constant.Uint64Val(y.val) + if !ok || s > shiftBound { + check.errorf(y, invalidOp+"invalid shift count %s", y) + x.mode = invalid + return + } + // The lhs is representable as an integer but may not be an integer + // (e.g., 2.0, an untyped float) - this can only happen for untyped + // non-integer numeric constants. Correct the type so that the shift + // result is of integer type. + if !isInteger(x.typ) { + x.typ = Typ[UntypedInt] + } + // x is a constant so xval != nil and it must be of Int kind. + x.val = constant.Shift(xval, op2tok[op], uint(s)) + x.expr = e + check.overflow(x) + return + } + + // non-constant shift with constant lhs + if isUntyped(x.typ) { + // spec: "If the left operand of a non-constant shift + // expression is an untyped constant, the type of the + // constant is what it would be if the shift expression + // were replaced by its left operand alone.". + // + // Delay operand checking until we know the final type + // by marking the lhs expression as lhs shift operand. + // + // Usually (in correct programs), the lhs expression + // is in the untyped map. However, it is possible to + // create incorrect programs where the same expression + // is evaluated twice (via a declaration cycle) such + // that the lhs expression type is determined in the + // first round and thus deleted from the map, and then + // not found in the second round (double insertion of + // the same expr node still just leads to one entry for + // that node, and it can only be deleted once). + // Be cautious and check for presence of entry. + // Example: var e, f = int(1<<""[f]) // issue 11347 + if info, found := check.untyped[x.expr]; found { + info.isLhs = true + check.untyped[x.expr] = info + } + // keep x's type + x.mode = value + return + } + } + + // non-constant shift - lhs must be an integer + if !isInteger(x.typ) { + check.errorf(x, invalidOp+"shifted operand %s must be integer", x) + x.mode = invalid + return + } + + x.mode = value +} + +var binaryOpPredicates opPredicates + +func init() { + // Setting binaryOpPredicates in init avoids declaration cycles. + binaryOpPredicates = opPredicates{ + syntax.Add: isNumericOrString, + syntax.Sub: isNumeric, + syntax.Mul: isNumeric, + syntax.Div: isNumeric, + syntax.Rem: isInteger, + + syntax.And: isInteger, + syntax.Or: isInteger, + syntax.Xor: isInteger, + syntax.AndNot: isInteger, + + syntax.AndAnd: isBoolean, + syntax.OrOr: isBoolean, + } +} + +// If e != nil, it must be the binary expression; it may be nil for non-constant expressions +// (when invoked for an assignment operation where the binary expression is implicit). +func (check *Checker) binary(x *operand, e syntax.Expr, lhs, rhs syntax.Expr, op syntax.Operator) { + var y operand + + check.expr(x, lhs) + check.expr(&y, rhs) + + if x.mode == invalid { + return + } + if y.mode == invalid { + x.mode = invalid + x.expr = y.expr + return + } + + if isShift(op) { + check.shift(x, &y, e, op) + return + } + + check.convertUntyped(x, y.typ) + if x.mode == invalid { + return + } + check.convertUntyped(&y, x.typ) + if y.mode == invalid { + x.mode = invalid + return + } + + if isComparison(op) { + check.comparison(x, &y, op) + return + } + + if !check.identical(x.typ, y.typ) { + // only report an error if we have valid types + // (otherwise we had an error reported elsewhere already) + if x.typ != Typ[Invalid] && y.typ != Typ[Invalid] { + check.errorf(x, invalidOp+"mismatched types %s and %s", x.typ, y.typ) + } + x.mode = invalid + return + } + + if !check.op(binaryOpPredicates, x, op) { + x.mode = invalid + return + } + + if op == syntax.Div || op == syntax.Rem { + // check for zero divisor + if (x.mode == constant_ || isInteger(x.typ)) && y.mode == constant_ && constant.Sign(y.val) == 0 { + check.error(&y, invalidOp+"division by zero") + x.mode = invalid + return + } + + // check for divisor underflow in complex division (see issue 20227) + if x.mode == constant_ && y.mode == constant_ && isComplex(x.typ) { + re, im := constant.Real(y.val), constant.Imag(y.val) + re2, im2 := constant.BinaryOp(re, token.MUL, re), constant.BinaryOp(im, token.MUL, im) + if constant.Sign(re2) == 0 && constant.Sign(im2) == 0 { + check.error(&y, invalidOp+"division by zero") + x.mode = invalid + return + } + } + } + + if x.mode == constant_ && y.mode == constant_ { + // if either x or y has an unknown value, the result is unknown + if x.val.Kind() == constant.Unknown || y.val.Kind() == constant.Unknown { + x.val = constant.MakeUnknown() + // x.typ is unchanged + return + } + // force integer division for integer operands + tok := op2tok[op] + if op == syntax.Div && isInteger(x.typ) { + tok = token.QUO_ASSIGN + } + x.val = constant.BinaryOp(x.val, tok, y.val) + x.expr = e + check.overflow(x) + return + } + + x.mode = value + // x.typ is unchanged +} + +// exprKind describes the kind of an expression; the kind +// determines if an expression is valid in 'statement context'. +type exprKind int + +const ( + conversion exprKind = iota + expression + statement +) + +// rawExpr typechecks expression e and initializes x with the expression +// value or type. If an error occurred, x.mode is set to invalid. +// If hint != nil, it is the type of a composite literal element. +// +func (check *Checker) rawExpr(x *operand, e syntax.Expr, hint Type) exprKind { + if check.conf.Trace { + check.trace(e.Pos(), "expr %s", e) + check.indent++ + defer func() { + check.indent-- + check.trace(e.Pos(), "=> %s", x) + }() + } + + kind := check.exprInternal(x, e, hint) + check.record(x) + + return kind +} + +// exprInternal contains the core of type checking of expressions. +// Must only be called by rawExpr. +// +func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKind { + // make sure x has a valid state in case of bailout + // (was issue 5770) + x.mode = invalid + x.typ = Typ[Invalid] + + switch e := e.(type) { + case nil: + unreachable() + + case *syntax.BadExpr: + goto Error // error was reported before + + case *syntax.Name: + check.ident(x, e, nil, false) + + case *syntax.DotsType: + // dots are handled explicitly where they are legal + // (array composite literals and parameter lists) + check.error(e, "invalid use of '...'") + goto Error + + case *syntax.BasicLit: + if e.Bad { + goto Error // error reported during parsing + } + switch e.Kind { + case syntax.IntLit, syntax.FloatLit, syntax.ImagLit: + check.langCompat(e) + // The max. mantissa precision for untyped numeric values + // is 512 bits, or 4048 bits for each of the two integer + // parts of a fraction for floating-point numbers that are + // represented accurately in the go/constant package. + // Constant literals that are longer than this many bits + // are not meaningful; and excessively long constants may + // consume a lot of space and time for a useless conversion. + // Cap constant length with a generous upper limit that also + // allows for separators between all digits. + const limit = 10000 + if len(e.Value) > limit { + check.errorf(e, "excessively long constant: %s... (%d chars)", e.Value[:10], len(e.Value)) + goto Error + } + } + x.setConst(e.Kind, e.Value) + if x.mode == invalid { + // The parser already establishes syntactic correctness. + // If we reach here it's because of number under-/overflow. + // TODO(gri) setConst (and in turn the go/constant package) + // should return an error describing the issue. + check.errorf(e, "malformed constant: %s", e.Value) + goto Error + } + + case *syntax.FuncLit: + if sig, ok := check.typ(e.Type).(*Signature); ok { + if !check.conf.IgnoreFuncBodies && e.Body != nil { + // Anonymous functions are considered part of the + // init expression/func declaration which contains + // them: use existing package-level declaration info. + decl := check.decl // capture for use in closure below + iota := check.iota // capture for use in closure below (#22345) + // Don't type-check right away because the function may + // be part of a type definition to which the function + // body refers. Instead, type-check as soon as possible, + // but before the enclosing scope contents changes (#22992). + check.later(func() { + check.funcBody(decl, "", sig, e.Body, iota) + }) + } + x.mode = value + x.typ = sig + } else { + check.errorf(e, invalidAST+"invalid function literal %v", e) + goto Error + } + + case *syntax.CompositeLit: + var typ, base Type + + switch { + case e.Type != nil: + // composite literal type present - use it + // [...]T array types may only appear with composite literals. + // Check for them here so we don't have to handle ... in general. + if atyp, _ := e.Type.(*syntax.ArrayType); atyp != nil && atyp.Len == nil { + // We have an "open" [...]T array type. + // Create a new ArrayType with unknown length (-1) + // and finish setting it up after analyzing the literal. + typ = &Array{len: -1, elem: check.varType(atyp.Elem)} + base = typ + break + } + typ = check.typ(e.Type) + base = typ + + case hint != nil: + // no composite literal type present - use hint (element type of enclosing type) + typ = hint + base, _ = deref(under(typ)) // *T implies &T{} + + default: + // TODO(gri) provide better error messages depending on context + check.error(e, "missing type in composite literal") + goto Error + } + + switch utyp := optype(base).(type) { + case *Struct: + if len(e.ElemList) == 0 { + break + } + fields := utyp.fields + if _, ok := e.ElemList[0].(*syntax.KeyValueExpr); ok { + // all elements must have keys + visited := make([]bool, len(fields)) + for _, e := range e.ElemList { + kv, _ := e.(*syntax.KeyValueExpr) + if kv == nil { + check.error(e, "mixture of field:value and value elements in struct literal") + continue + } + key, _ := kv.Key.(*syntax.Name) + // do all possible checks early (before exiting due to errors) + // so we don't drop information on the floor + check.expr(x, kv.Value) + if key == nil { + check.errorf(kv, "invalid field name %s in struct literal", kv.Key) + continue + } + i := fieldIndex(utyp.fields, check.pkg, key.Value) + if i < 0 { + if check.conf.CompilerErrorMessages { + check.errorf(kv.Key, "unknown field '%s' in struct literal of type %s", key.Value, base) + } else { + check.errorf(kv.Key, "unknown field %s in struct literal", key.Value) + } + continue + } + fld := fields[i] + check.recordUse(key, fld) + etyp := fld.typ + check.assignment(x, etyp, "struct literal") + // 0 <= i < len(fields) + if visited[i] { + check.errorf(kv, "duplicate field name %s in struct literal", key.Value) + continue + } + visited[i] = true + } + } else { + // no element must have a key + for i, e := range e.ElemList { + if kv, _ := e.(*syntax.KeyValueExpr); kv != nil { + check.error(kv, "mixture of field:value and value elements in struct literal") + continue + } + check.expr(x, e) + if i >= len(fields) { + check.error(x, "too many values in struct literal") + break // cannot continue + } + // i < len(fields) + fld := fields[i] + if !fld.Exported() && fld.pkg != check.pkg { + check.errorf(x, "implicit assignment to unexported field %s in %s literal", fld.name, typ) + continue + } + etyp := fld.typ + check.assignment(x, etyp, "struct literal") + } + if len(e.ElemList) < len(fields) { + check.error(e.Rbrace, "too few values in struct literal") + // ok to continue + } + } + + case *Array: + // Prevent crash if the array referred to is not yet set up. Was issue #18643. + // This is a stop-gap solution. Should use Checker.objPath to report entire + // path starting with earliest declaration in the source. TODO(gri) fix this. + if utyp.elem == nil { + check.error(e, "illegal cycle in type declaration") + goto Error + } + n := check.indexedElts(e.ElemList, utyp.elem, utyp.len) + // If we have an array of unknown length (usually [...]T arrays, but also + // arrays [n]T where n is invalid) set the length now that we know it and + // record the type for the array (usually done by check.typ which is not + // called for [...]T). We handle [...]T arrays and arrays with invalid + // length the same here because it makes sense to "guess" the length for + // the latter if we have a composite literal; e.g. for [n]int{1, 2, 3} + // where n is invalid for some reason, it seems fair to assume it should + // be 3 (see also Checked.arrayLength and issue #27346). + if utyp.len < 0 { + utyp.len = n + // e.Type is missing if we have a composite literal element + // that is itself a composite literal with omitted type. In + // that case there is nothing to record (there is no type in + // the source at that point). + if e.Type != nil { + check.recordTypeAndValue(e.Type, typexpr, utyp, nil) + } + } + + case *Slice: + // Prevent crash if the slice referred to is not yet set up. + // See analogous comment for *Array. + if utyp.elem == nil { + check.error(e, "illegal cycle in type declaration") + goto Error + } + check.indexedElts(e.ElemList, utyp.elem, -1) + + case *Map: + // Prevent crash if the map referred to is not yet set up. + // See analogous comment for *Array. + if utyp.key == nil || utyp.elem == nil { + check.error(e, "illegal cycle in type declaration") + goto Error + } + visited := make(map[interface{}][]Type, len(e.ElemList)) + for _, e := range e.ElemList { + kv, _ := e.(*syntax.KeyValueExpr) + if kv == nil { + check.error(e, "missing key in map literal") + continue + } + check.exprWithHint(x, kv.Key, utyp.key) + check.assignment(x, utyp.key, "map literal") + if x.mode == invalid { + continue + } + if x.mode == constant_ { + duplicate := false + // if the key is of interface type, the type is also significant when checking for duplicates + xkey := keyVal(x.val) + if asInterface(utyp.key) != nil { + for _, vtyp := range visited[xkey] { + if check.identical(vtyp, x.typ) { + duplicate = true + break + } + } + visited[xkey] = append(visited[xkey], x.typ) + } else { + _, duplicate = visited[xkey] + visited[xkey] = nil + } + if duplicate { + check.errorf(x, "duplicate key %s in map literal", x.val) + continue + } + } + check.exprWithHint(x, kv.Value, utyp.elem) + check.assignment(x, utyp.elem, "map literal") + } + + default: + // when "using" all elements unpack KeyValueExpr + // explicitly because check.use doesn't accept them + for _, e := range e.ElemList { + if kv, _ := e.(*syntax.KeyValueExpr); kv != nil { + // Ideally, we should also "use" kv.Key but we can't know + // if it's an externally defined struct key or not. Going + // forward anyway can lead to other errors. Give up instead. + e = kv.Value + } + check.use(e) + } + // if utyp is invalid, an error was reported before + if utyp != Typ[Invalid] { + check.errorf(e, "invalid composite literal type %s", typ) + goto Error + } + } + + x.mode = value + x.typ = typ + + case *syntax.ParenExpr: + kind := check.rawExpr(x, e.X, nil) + x.expr = e + return kind + + case *syntax.SelectorExpr: + check.selector(x, e) + + case *syntax.IndexExpr: + if check.indexExpr(x, e) { + check.funcInst(x, e) + } + if x.mode == invalid { + goto Error + } + + case *syntax.SliceExpr: + check.sliceExpr(x, e) + if x.mode == invalid { + goto Error + } + + case *syntax.AssertExpr: + check.expr(x, e.X) + if x.mode == invalid { + goto Error + } + xtyp, _ := under(x.typ).(*Interface) + if xtyp == nil { + check.errorf(x, "%s is not an interface type", x) + goto Error + } + check.ordinaryType(x.Pos(), xtyp) + // x.(type) expressions are encoded via TypeSwitchGuards + if e.Type == nil { + check.error(e, invalidAST+"invalid use of AssertExpr") + goto Error + } + T := check.varType(e.Type) + if T == Typ[Invalid] { + goto Error + } + check.typeAssertion(posFor(x), x, xtyp, T) + x.mode = commaok + x.typ = T + + case *syntax.TypeSwitchGuard: + // x.(type) expressions are handled explicitly in type switches + check.error(e, invalidAST+"use of .(type) outside type switch") + goto Error + + case *syntax.CallExpr: + return check.callExpr(x, e) + + case *syntax.ListExpr: + // catch-all for unexpected expression lists + check.error(e, "unexpected list of expressions") + goto Error + + // case *syntax.UnaryExpr: + // check.expr(x, e.X) + // if x.mode == invalid { + // goto Error + // } + // check.unary(x, e, e.Op) + // if x.mode == invalid { + // goto Error + // } + // if e.Op == token.ARROW { + // x.expr = e + // return statement // receive operations may appear in statement context + // } + + // case *syntax.BinaryExpr: + // check.binary(x, e, e.X, e.Y, e.Op) + // if x.mode == invalid { + // goto Error + // } + + case *syntax.Operation: + if e.Y == nil { + // unary expression + if e.Op == syntax.Mul { + // pointer indirection + check.exprOrType(x, e.X) + switch x.mode { + case invalid: + goto Error + case typexpr: + x.typ = &Pointer{base: x.typ} + default: + if typ := asPointer(x.typ); typ != nil { + x.mode = variable + x.typ = typ.base + } else { + check.errorf(x, invalidOp+"cannot indirect %s", x) + goto Error + } + } + break + } + + check.unary(x, e) + if x.mode == invalid { + goto Error + } + if e.Op == syntax.Recv { + x.expr = e + return statement // receive operations may appear in statement context + } + break + } + + // binary expression + check.binary(x, e, e.X, e.Y, e.Op) + if x.mode == invalid { + goto Error + } + + case *syntax.KeyValueExpr: + // key:value expressions are handled in composite literals + check.error(e, invalidAST+"no key:value expected") + goto Error + + case *syntax.ArrayType, *syntax.SliceType, *syntax.StructType, *syntax.FuncType, + *syntax.InterfaceType, *syntax.MapType, *syntax.ChanType: + x.mode = typexpr + x.typ = check.typ(e) + // Note: rawExpr (caller of exprInternal) will call check.recordTypeAndValue + // even though check.typ has already called it. This is fine as both + // times the same expression and type are recorded. It is also not a + // performance issue because we only reach here for composite literal + // types, which are comparatively rare. + + default: + panic(fmt.Sprintf("%s: unknown expression type %T", posFor(e), e)) + } + + // everything went well + x.expr = e + return expression + +Error: + x.mode = invalid + x.expr = e + return statement // avoid follow-up errors +} + +func keyVal(x constant.Value) interface{} { + switch x.Kind() { + case constant.Bool: + return constant.BoolVal(x) + case constant.String: + return constant.StringVal(x) + case constant.Int: + if v, ok := constant.Int64Val(x); ok { + return v + } + if v, ok := constant.Uint64Val(x); ok { + return v + } + case constant.Float: + v, _ := constant.Float64Val(x) + return v + case constant.Complex: + r, _ := constant.Float64Val(constant.Real(x)) + i, _ := constant.Float64Val(constant.Imag(x)) + return complex(r, i) + } + return x +} + +// typeAssertion checks that x.(T) is legal; xtyp must be the type of x. +func (check *Checker) typeAssertion(pos syntax.Pos, x *operand, xtyp *Interface, T Type) { + method, wrongType := check.assertableTo(xtyp, T) + if method == nil { + return + } + var msg string + if wrongType != nil { + if check.identical(method.typ, wrongType.typ) { + msg = fmt.Sprintf("missing method %s (%s has pointer receiver)", method.name, method.name) + } else { + msg = fmt.Sprintf("wrong type for method %s (have %s, want %s)", method.name, wrongType.typ, method.typ) + } + } else { + msg = "missing method " + method.name + } + if check.conf.CompilerErrorMessages { + check.errorf(pos, "impossible type assertion: %s (%s)", x, msg) + } else { + check.errorf(pos, "%s cannot have dynamic type %s (%s)", x, T, msg) + } +} + +// expr typechecks expression e and initializes x with the expression value. +// The result must be a single value. +// If an error occurred, x.mode is set to invalid. +// +func (check *Checker) expr(x *operand, e syntax.Expr) { + check.rawExpr(x, e, nil) + check.exclude(x, 1< 0 { + g.p(", ") + } + g.p("h%d_%d", i, j) + } + if i == 0 { + g.p(" = ") + for j := 0; j < n; j++ { + if j > 0 { + g.p(", ") + } + g.p("1.0/(iota + %d)", j+1) + } + } + g.p("\n") + } + g.p(")\n\n") +} + +func (g *gen) inverse(n int) { + g.p(`// Inverse Hilbert matrix +const ( +`) + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + s := "+" + if (i+j)&1 != 0 { + s = "-" + } + g.p("\ti%d_%d = %s%d * b%d_%d * b%d_%d * b%d_%d * b%d_%d\n", + i, j, s, i+j+1, n+i, n-j-1, n+j, n-i-1, i+j, i, i+j, i) + } + g.p("\n") + } + g.p(")\n\n") +} + +func (g *gen) product(n int) { + g.p(`// Product matrix +const ( +`) + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + g.p("\tp%d_%d = ", i, j) + for k := 0; k < n; k++ { + if k > 0 { + g.p(" + ") + } + g.p("h%d_%d*i%d_%d", i, k, k, j) + } + g.p("\n") + } + g.p("\n") + } + g.p(")\n\n") +} + +func (g *gen) verify(n int) { + g.p(`// Verify that product is the identity matrix +const ok = +`) + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + if j == 0 { + g.p("\t") + } else { + g.p(" && ") + } + v := 0 + if i == j { + v = 1 + } + g.p("p%d_%d == %d", i, j, v) + } + g.p(" &&\n") + } + g.p("\ttrue\n\n") + + // verify ok at type-check time + if *out == "" { + g.p("const _ = assert(ok)\n\n") + } +} + +func (g *gen) printProduct(n int) { + g.p("func printProduct() {\n") + for i := 0; i < n; i++ { + g.p("\tprintln(") + for j := 0; j < n; j++ { + if j > 0 { + g.p(", ") + } + g.p("p%d_%d", i, j) + } + g.p(")\n") + } + g.p("}\n\n") +} + +func (g *gen) binomials(n int) { + g.p(`// Binomials +const ( +`) + for j := 0; j <= n; j++ { + if j > 0 { + g.p("\n") + } + for k := 0; k <= j; k++ { + g.p("\tb%d_%d = f%d / (f%d*f%d)\n", j, k, j, k, j-k) + } + } + g.p(")\n\n") +} + +func (g *gen) factorials(n int) { + g.p(`// Factorials +const ( + f0 = 1 + f1 = 1 +`) + for i := 2; i <= n; i++ { + g.p("\tf%d = f%d * %d\n", i, i-1, i) + } + g.p(")\n\n") +} diff --git a/src/cmd/compile/internal/types2/importer_test.go b/src/cmd/compile/internal/types2/importer_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6b9b5009186cfb100d6bbb8112ba19c1ddcda451 --- /dev/null +++ b/src/cmd/compile/internal/types2/importer_test.go @@ -0,0 +1,35 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements the (temporary) plumbing to get importing to work. + +package types2_test + +import ( + gcimporter "cmd/compile/internal/importer" + "cmd/compile/internal/types2" + "io" +) + +func defaultImporter() types2.Importer { + return &gcimports{ + packages: make(map[string]*types2.Package), + } +} + +type gcimports struct { + packages map[string]*types2.Package + lookup func(path string) (io.ReadCloser, error) +} + +func (m *gcimports) Import(path string) (*types2.Package, error) { + return m.ImportFrom(path, "" /* no vendoring */, 0) +} + +func (m *gcimports) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) { + if mode != 0 { + panic("mode must be 0") + } + return gcimporter.Import(m.packages, path, srcDir, m.lookup) +} diff --git a/src/cmd/compile/internal/types2/index.go b/src/cmd/compile/internal/types2/index.go new file mode 100644 index 0000000000000000000000000000000000000000..c94017a8fb9b98c29d55707f867b0c15bcbb3e50 --- /dev/null +++ b/src/cmd/compile/internal/types2/index.go @@ -0,0 +1,452 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements typechecking of index/slice expressions. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "go/constant" +) + +// If e is a valid function instantiation, indexExpr returns true. +// In that case x represents the uninstantiated function value and +// it is the caller's responsibility to instantiate the function. +func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) (isFuncInst bool) { + check.exprOrType(x, e.X) + + switch x.mode { + case invalid: + check.use(e.Index) + return false + + case typexpr: + // type instantiation + x.mode = invalid + x.typ = check.varType(e) + if x.typ != Typ[Invalid] { + x.mode = typexpr + } + return false + + case value: + if sig := asSignature(x.typ); sig != nil && len(sig.tparams) > 0 { + // function instantiation + return true + } + } + + // ordinary index expression + valid := false + length := int64(-1) // valid if >= 0 + switch typ := optype(x.typ).(type) { + case *Basic: + if isString(typ) { + valid = true + if x.mode == constant_ { + length = int64(len(constant.StringVal(x.val))) + } + // an indexed string always yields a byte value + // (not a constant) even if the string and the + // index are constant + x.mode = value + x.typ = universeByte // use 'byte' name + } + + case *Array: + valid = true + length = typ.len + if x.mode != variable { + x.mode = value + } + x.typ = typ.elem + + case *Pointer: + if typ := asArray(typ.base); typ != nil { + valid = true + length = typ.len + x.mode = variable + x.typ = typ.elem + } + + case *Slice: + valid = true + x.mode = variable + x.typ = typ.elem + + case *Map: + index := check.singleIndex(e) + if index == nil { + x.mode = invalid + return + } + var key operand + check.expr(&key, index) + check.assignment(&key, typ.key, "map index") + // ok to continue even if indexing failed - map element type is known + x.mode = mapindex + x.typ = typ.elem + x.expr = e + return + + case *Sum: + // A sum type can be indexed if all of the sum's types + // support indexing and have the same index and element + // type. Special rules apply for maps in the sum type. + var tkey, telem Type // key is for map types only + nmaps := 0 // number of map types in sum type + if typ.is(func(t Type) bool { + var e Type + switch t := under(t).(type) { + case *Basic: + if isString(t) { + e = universeByte + } + case *Array: + e = t.elem + case *Pointer: + if t := asArray(t.base); t != nil { + e = t.elem + } + case *Slice: + e = t.elem + case *Map: + // If there are multiple maps in the sum type, + // they must have identical key types. + // TODO(gri) We may be able to relax this rule + // but it becomes complicated very quickly. + if tkey != nil && !Identical(t.key, tkey) { + return false + } + tkey = t.key + e = t.elem + nmaps++ + case *TypeParam: + check.errorf(x, "type of %s contains a type parameter - cannot index (implementation restriction)", x) + case *instance: + panic("unimplemented") + } + if e == nil || telem != nil && !Identical(e, telem) { + return false + } + telem = e + return true + }) { + // If there are maps, the index expression must be assignable + // to the map key type (as for simple map index expressions). + if nmaps > 0 { + index := check.singleIndex(e) + if index == nil { + x.mode = invalid + return + } + var key operand + check.expr(&key, index) + check.assignment(&key, tkey, "map index") + // ok to continue even if indexing failed - map element type is known + + // If there are only maps, we are done. + if nmaps == len(typ.types) { + x.mode = mapindex + x.typ = telem + x.expr = e + return + } + + // Otherwise we have mix of maps and other types. For + // now we require that the map key be an integer type. + // TODO(gri) This is probably not good enough. + valid = isInteger(tkey) + // avoid 2nd indexing error if indexing failed above + if !valid && key.mode == invalid { + x.mode = invalid + return + } + x.mode = value // map index expressions are not addressable + } else { + // no maps + valid = true + x.mode = variable + } + x.typ = telem + } + } + + if !valid { + check.errorf(x, invalidOp+"cannot index %s", x) + x.mode = invalid + return + } + + index := check.singleIndex(e) + if index == nil { + x.mode = invalid + return + } + + // In pathological (invalid) cases (e.g.: type T1 [][[]T1{}[0][0]]T0) + // the element type may be accessed before it's set. Make sure we have + // a valid type. + if x.typ == nil { + x.typ = Typ[Invalid] + } + + check.index(index, length) + return false +} + +func (check *Checker) sliceExpr(x *operand, e *syntax.SliceExpr) { + check.expr(x, e.X) + if x.mode == invalid { + check.use(e.Index[:]...) + return + } + + valid := false + length := int64(-1) // valid if >= 0 + switch typ := optype(x.typ).(type) { + case *Basic: + if isString(typ) { + if e.Full { + check.error(x, invalidOp+"3-index slice of string") + x.mode = invalid + return + } + valid = true + if x.mode == constant_ { + length = int64(len(constant.StringVal(x.val))) + } + // spec: "For untyped string operands the result + // is a non-constant value of type string." + if typ.kind == UntypedString { + x.typ = Typ[String] + } + } + + case *Array: + valid = true + length = typ.len + if x.mode != variable { + check.errorf(x, invalidOp+"%s (slice of unaddressable value)", x) + x.mode = invalid + return + } + x.typ = &Slice{elem: typ.elem} + + case *Pointer: + if typ := asArray(typ.base); typ != nil { + valid = true + length = typ.len + x.typ = &Slice{elem: typ.elem} + } + + case *Slice: + valid = true + // x.typ doesn't change + + case *Sum, *TypeParam: + check.error(x, "generic slice expressions not yet implemented") + x.mode = invalid + return + } + + if !valid { + check.errorf(x, invalidOp+"cannot slice %s", x) + x.mode = invalid + return + } + + x.mode = value + + // spec: "Only the first index may be omitted; it defaults to 0." + if e.Full && (e.Index[1] == nil || e.Index[2] == nil) { + check.error(e, invalidAST+"2nd and 3rd index required in 3-index slice") + x.mode = invalid + return + } + + // check indices + var ind [3]int64 + for i, expr := range e.Index { + x := int64(-1) + switch { + case expr != nil: + // The "capacity" is only known statically for strings, arrays, + // and pointers to arrays, and it is the same as the length for + // those types. + max := int64(-1) + if length >= 0 { + max = length + 1 + } + if _, v := check.index(expr, max); v >= 0 { + x = v + } + case i == 0: + // default is 0 for the first index + x = 0 + case length >= 0: + // default is length (== capacity) otherwise + x = length + } + ind[i] = x + } + + // constant indices must be in range + // (check.index already checks that existing indices >= 0) +L: + for i, x := range ind[:len(ind)-1] { + if x > 0 { + for _, y := range ind[i+1:] { + if y >= 0 && x > y { + check.errorf(e, "invalid slice indices: %d > %d", x, y) + break L // only report one error, ok to continue + } + } + } + } +} + +// singleIndex returns the (single) index from the index expression e. +// If the index is missing, or if there are multiple indices, an error +// is reported and the result is nil. +func (check *Checker) singleIndex(e *syntax.IndexExpr) syntax.Expr { + index := e.Index + if index == nil { + check.errorf(e, invalidAST+"missing index for %s", e.X) + return nil + } + if l, _ := index.(*syntax.ListExpr); l != nil { + if n := len(l.ElemList); n <= 1 { + check.errorf(e, invalidAST+"invalid use of ListExpr for index expression %v with %d indices", e, n) + return nil + } + // len(l.ElemList) > 1 + check.error(l.ElemList[1], invalidOp+"more than one index") + index = l.ElemList[0] // continue with first index + } + return index +} + +// index checks an index expression for validity. +// If max >= 0, it is the upper bound for index. +// If the result typ is != Typ[Invalid], index is valid and typ is its (possibly named) integer type. +// If the result val >= 0, index is valid and val is its constant int value. +func (check *Checker) index(index syntax.Expr, max int64) (typ Type, val int64) { + typ = Typ[Invalid] + val = -1 + + var x operand + check.expr(&x, index) + if !check.isValidIndex(&x, "index", false) { + return + } + + if x.mode != constant_ { + return x.typ, -1 + } + + if x.val.Kind() == constant.Unknown { + return + } + + v, ok := constant.Int64Val(x.val) + assert(ok) + if max >= 0 && v >= max { + if check.conf.CompilerErrorMessages { + check.errorf(&x, invalidArg+"array index %s out of bounds [0:%d]", x.val.String(), max) + } else { + check.errorf(&x, invalidArg+"index %s is out of bounds", &x) + } + return + } + + // 0 <= v [ && v < max ] + return x.typ, v +} + +// isValidIndex checks whether operand x satisfies the criteria for integer +// index values. If allowNegative is set, a constant operand may be negative. +// If the operand is not valid, an error is reported (using what as context) +// and the result is false. +func (check *Checker) isValidIndex(x *operand, what string, allowNegative bool) bool { + if x.mode == invalid { + return false + } + + // spec: "a constant index that is untyped is given type int" + check.convertUntyped(x, Typ[Int]) + if x.mode == invalid { + return false + } + + // spec: "the index x must be of integer type or an untyped constant" + if !isInteger(x.typ) { + check.errorf(x, invalidArg+"%s %s must be integer", what, x) + return false + } + + if x.mode == constant_ { + // spec: "a constant index must be non-negative ..." + if !allowNegative && constant.Sign(x.val) < 0 { + check.errorf(x, invalidArg+"%s %s must not be negative", what, x) + return false + } + + // spec: "... and representable by a value of type int" + if !representableConst(x.val, check, Typ[Int], &x.val) { + check.errorf(x, invalidArg+"%s %s overflows int", what, x) + return false + } + } + + return true +} + +// indexElts checks the elements (elts) of an array or slice composite literal +// against the literal's element type (typ), and the element indices against +// the literal length if known (length >= 0). It returns the length of the +// literal (maximum index value + 1). +func (check *Checker) indexedElts(elts []syntax.Expr, typ Type, length int64) int64 { + visited := make(map[int64]bool, len(elts)) + var index, max int64 + for _, e := range elts { + // determine and check index + validIndex := false + eval := e + if kv, _ := e.(*syntax.KeyValueExpr); kv != nil { + if typ, i := check.index(kv.Key, length); typ != Typ[Invalid] { + if i >= 0 { + index = i + validIndex = true + } else { + check.errorf(e, "index %s must be integer constant", kv.Key) + } + } + eval = kv.Value + } else if length >= 0 && index >= length { + check.errorf(e, "index %d is out of bounds (>= %d)", index, length) + } else { + validIndex = true + } + + // if we have a valid index, check for duplicate entries + if validIndex { + if visited[index] { + check.errorf(e, "duplicate index %d in array or slice literal", index) + } + visited[index] = true + } + index++ + if index > max { + max = index + } + + // check element against composite literal element type + var x operand + check.exprWithHint(&x, eval, typ) + check.assignment(&x, typ, "array or slice literal") + } + return max +} diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go new file mode 100644 index 0000000000000000000000000000000000000000..f37d7f6477e96ab29bb8d0e6705cb65b0dd8cb37 --- /dev/null +++ b/src/cmd/compile/internal/types2/infer.go @@ -0,0 +1,487 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements type parameter inference. + +package types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" +) + +const useConstraintTypeInference = true + +// infer attempts to infer the complete set of type arguments for generic function instantiation/call +// based on the given type parameters tparams, type arguments targs, function parameters params, and +// function arguments args, if any. There must be at least one type parameter, no more type arguments +// than type parameters, and params and args must match in number (incl. zero). +// If successful, infer returns the complete list of type arguments, one for each type parameter. +// Otherwise the result is nil and appropriate errors will be reported unless report is set to false. +// +// Inference proceeds in 3 steps: +// +// 1) Start with given type arguments. +// 2) Infer type arguments from typed function arguments. +// 3) Infer type arguments from untyped function arguments. +// +// Constraint type inference is used after each step to expand the set of type arguments. +// +func (check *Checker) infer(pos syntax.Pos, tparams []*TypeName, targs []Type, params *Tuple, args []*operand, report bool) (result []Type) { + if debug { + defer func() { + assert(result == nil || len(result) == len(tparams)) + for _, targ := range result { + assert(targ != nil) + } + //check.dump("### inferred targs = %s", result) + }() + } + + // There must be at least one type parameter, and no more type arguments than type parameters. + n := len(tparams) + assert(n > 0 && len(targs) <= n) + + // Function parameters and arguments must match in number. + assert(params.Len() == len(args)) + + // --- 0 --- + // If we already have all type arguments, we're done. + if len(targs) == n { + return targs + } + // len(targs) < n + + // --- 1 --- + // Explicitly provided type arguments take precedence over any inferred types; + // and types inferred via constraint type inference take precedence over types + // inferred from function arguments. + // If we have type arguments, see how far we get with constraint type inference. + if len(targs) > 0 && useConstraintTypeInference { + var index int + targs, index = check.inferB(tparams, targs, report) + if targs == nil || index < 0 { + return targs + } + } + + // Continue with the type arguments we have now. Avoid matching generic + // parameters that already have type arguments against function arguments: + // It may fail because matching uses type identity while parameter passing + // uses assignment rules. Instantiate the parameter list with the type + // arguments we have, and continue with that parameter list. + + // First, make sure we have a "full" list of type arguments, so of which + // may be nil (unknown). + if len(targs) < n { + targs2 := make([]Type, n) + copy(targs2, targs) + targs = targs2 + } + // len(targs) == n + + // Substitute type arguments for their respective type parameters in params, + // if any. Note that nil targs entries are ignored by check.subst. + // TODO(gri) Can we avoid this (we're setting known type argumemts below, + // but that doesn't impact the isParameterized check for now). + if params.Len() > 0 { + smap := makeSubstMap(tparams, targs) + params = check.subst(nopos, params, smap).(*Tuple) + } + + // --- 2 --- + // Unify parameter and argument types for generic parameters with typed arguments + // and collect the indices of generic parameters with untyped arguments. + // Terminology: generic parameter = function parameter with a type-parameterized type + u := newUnifier(check, false) + u.x.init(tparams) + + // Set the type arguments which we know already. + for i, targ := range targs { + if targ != nil { + u.x.set(i, targ) + } + } + + errorf := func(kind string, tpar, targ Type, arg *operand) { + if !report { + return + } + // provide a better error message if we can + targs, index := u.x.types() + if index == 0 { + // The first type parameter couldn't be inferred. + // If none of them could be inferred, don't try + // to provide the inferred type in the error msg. + allFailed := true + for _, targ := range targs { + if targ != nil { + allFailed = false + break + } + } + if allFailed { + check.errorf(arg, "%s %s of %s does not match %s (cannot infer %s)", kind, targ, arg.expr, tpar, typeNamesString(tparams)) + return + } + } + smap := makeSubstMap(tparams, targs) + inferred := check.subst(arg.Pos(), tpar, smap) + if inferred != tpar { + check.errorf(arg, "%s %s of %s does not match inferred type %s for %s", kind, targ, arg.expr, inferred, tpar) + } else { + check.errorf(arg, "%s %s of %s does not match %s", kind, targ, arg.expr, tpar) + } + } + + // indices of the generic parameters with untyped arguments - save for later + var indices []int + for i, arg := range args { + par := params.At(i) + // If we permit bidirectional unification, this conditional code needs to be + // executed even if par.typ is not parameterized since the argument may be a + // generic function (for which we want to infer its type arguments). + if isParameterized(tparams, par.typ) { + if arg.mode == invalid { + // An error was reported earlier. Ignore this targ + // and continue, we may still be able to infer all + // targs resulting in fewer follon-on errors. + continue + } + if targ := arg.typ; isTyped(targ) { + // If we permit bidirectional unification, and targ is + // a generic function, we need to initialize u.y with + // the respective type parameters of targ. + if !u.unify(par.typ, targ) { + errorf("type", par.typ, targ, arg) + return nil + } + } else { + indices = append(indices, i) + } + } + } + + // If we've got all type arguments, we're done. + var index int + targs, index = u.x.types() + if index < 0 { + return targs + } + + // See how far we get with constraint type inference. + // Note that even if we don't have any type arguments, constraint type inference + // may produce results for constraints that explicitly specify a type. + if useConstraintTypeInference { + targs, index = check.inferB(tparams, targs, report) + if targs == nil || index < 0 { + return targs + } + } + + // --- 3 --- + // Use any untyped arguments to infer additional type arguments. + // Some generic parameters with untyped arguments may have been given + // a type by now, we can ignore them. + for _, i := range indices { + par := params.At(i) + // Since untyped types are all basic (i.e., non-composite) types, an + // untyped argument will never match a composite parameter type; the + // only parameter type it can possibly match against is a *TypeParam. + // Thus, only consider untyped arguments for generic parameters that + // are not of composite types and which don't have a type inferred yet. + if tpar, _ := par.typ.(*TypeParam); tpar != nil && targs[tpar.index] == nil { + arg := args[i] + targ := Default(arg.typ) + // The default type for an untyped nil is untyped nil. We must not + // infer an untyped nil type as type parameter type. Ignore untyped + // nil by making sure all default argument types are typed. + if isTyped(targ) && !u.unify(par.typ, targ) { + errorf("default type", par.typ, targ, arg) + return nil + } + } + } + + // If we've got all type arguments, we're done. + targs, index = u.x.types() + if index < 0 { + return targs + } + + // Again, follow up with constraint type inference. + if useConstraintTypeInference { + targs, index = check.inferB(tparams, targs, report) + if targs == nil || index < 0 { + return targs + } + } + + // At least one type argument couldn't be inferred. + assert(targs != nil && index >= 0 && targs[index] == nil) + tpar := tparams[index] + if report { + check.errorf(pos, "cannot infer %s (%s) (%s)", tpar.name, tpar.pos, targs) + } + return nil +} + +// typeNamesString produces a string containing all the +// type names in list suitable for human consumption. +func typeNamesString(list []*TypeName) string { + // common cases + n := len(list) + switch n { + case 0: + return "" + case 1: + return list[0].name + case 2: + return list[0].name + " and " + list[1].name + } + + // general case (n > 2) + // Would like to use strings.Builder but it's not available in Go 1.4. + var b bytes.Buffer + for i, tname := range list[:n-1] { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(tname.name) + } + b.WriteString(", and ") + b.WriteString(list[n-1].name) + return b.String() +} + +// IsParameterized reports whether typ contains any of the type parameters of tparams. +func isParameterized(tparams []*TypeName, typ Type) bool { + w := tpWalker{ + seen: make(map[Type]bool), + tparams: tparams, + } + return w.isParameterized(typ) +} + +type tpWalker struct { + seen map[Type]bool + tparams []*TypeName +} + +func (w *tpWalker) isParameterized(typ Type) (res bool) { + // detect cycles + if x, ok := w.seen[typ]; ok { + return x + } + w.seen[typ] = false + defer func() { + w.seen[typ] = res + }() + + switch t := typ.(type) { + case nil, *Basic: // TODO(gri) should nil be handled here? + break + + case *Array: + return w.isParameterized(t.elem) + + case *Slice: + return w.isParameterized(t.elem) + + case *Struct: + for _, fld := range t.fields { + if w.isParameterized(fld.typ) { + return true + } + } + + case *Pointer: + return w.isParameterized(t.base) + + case *Tuple: + n := t.Len() + for i := 0; i < n; i++ { + if w.isParameterized(t.At(i).typ) { + return true + } + } + + case *Sum: + return w.isParameterizedList(t.types) + + case *Signature: + // t.tparams may not be nil if we are looking at a signature + // of a generic function type (or an interface method) that is + // part of the type we're testing. We don't care about these type + // parameters. + // Similarly, the receiver of a method may declare (rather then + // use) type parameters, we don't care about those either. + // Thus, we only need to look at the input and result parameters. + return w.isParameterized(t.params) || w.isParameterized(t.results) + + case *Interface: + if t.allMethods != nil { + // interface is complete - quick test + for _, m := range t.allMethods { + if w.isParameterized(m.typ) { + return true + } + } + return w.isParameterizedList(unpack(t.allTypes)) + } + + return t.iterate(func(t *Interface) bool { + for _, m := range t.methods { + if w.isParameterized(m.typ) { + return true + } + } + return w.isParameterizedList(unpack(t.types)) + }, nil) + + case *Map: + return w.isParameterized(t.key) || w.isParameterized(t.elem) + + case *Chan: + return w.isParameterized(t.elem) + + case *Named: + return w.isParameterizedList(t.targs) + + case *TypeParam: + // t must be one of w.tparams + return t.index < len(w.tparams) && w.tparams[t.index].typ == t + + case *instance: + return w.isParameterizedList(t.targs) + + default: + unreachable() + } + + return false +} + +func (w *tpWalker) isParameterizedList(list []Type) bool { + for _, t := range list { + if w.isParameterized(t) { + return true + } + } + return false +} + +// inferB returns the list of actual type arguments inferred from the type parameters' +// bounds and an initial set of type arguments. If type inference is impossible because +// unification fails, an error is reported if report is set to true, the resulting types +// list is nil, and index is 0. +// Otherwise, types is the list of inferred type arguments, and index is the index of the +// first type argument in that list that couldn't be inferred (and thus is nil). If all +// type arguments were inferred successfully, index is < 0. The number of type arguments +// provided may be less than the number of type parameters, but there must be at least one. +func (check *Checker) inferB(tparams []*TypeName, targs []Type, report bool) (types []Type, index int) { + assert(len(tparams) >= len(targs) && len(targs) > 0) + + // Setup bidirectional unification between those structural bounds + // and the corresponding type arguments (which may be nil!). + u := newUnifier(check, false) + u.x.init(tparams) + u.y = u.x // type parameters between LHS and RHS of unification are identical + + // Set the type arguments which we know already. + for i, targ := range targs { + if targ != nil { + u.x.set(i, targ) + } + } + + // Unify type parameters with their structural constraints, if any. + for _, tpar := range tparams { + typ := tpar.typ.(*TypeParam) + sbound := check.structuralType(typ.bound) + if sbound != nil { + if !u.unify(typ, sbound) { + if report { + check.errorf(tpar, "%s does not match %s", tpar, sbound) + } + return nil, 0 + } + } + } + + // u.x.types() now contains the incoming type arguments plus any additional type + // arguments for which there were structural constraints. The newly inferred non- + // nil entries may still contain references to other type parameters. For instance, + // for [A any, B interface{type []C}, C interface{type *A}], if A == int + // was given, unification produced the type list [int, []C, *A]. We eliminate the + // remaining type parameters by substituting the type parameters in this type list + // until nothing changes anymore. + types, _ = u.x.types() + if debug { + for i, targ := range targs { + assert(targ == nil || types[i] == targ) + } + } + + // dirty tracks the indices of all types that may still contain type parameters. + // We know that nil type entries and entries corresponding to provided (non-nil) + // type arguments are clean, so exclude them from the start. + var dirty []int + for i, typ := range types { + if typ != nil && (i >= len(targs) || targs[i] == nil) { + dirty = append(dirty, i) + } + } + + for len(dirty) > 0 { + // TODO(gri) Instead of creating a new substMap for each iteration, + // provide an update operation for substMaps and only change when + // needed. Optimization. + smap := makeSubstMap(tparams, types) + n := 0 + for _, index := range dirty { + t0 := types[index] + if t1 := check.subst(nopos, t0, smap); t1 != t0 { + types[index] = t1 + dirty[n] = index + n++ + } + } + dirty = dirty[:n] + } + + // Once nothing changes anymore, we may still have type parameters left; + // e.g., a structural constraint *P may match a type parameter Q but we + // don't have any type arguments to fill in for *P or Q (issue #45548). + // Don't let such inferences escape, instead nil them out. + for i, typ := range types { + if typ != nil && isParameterized(tparams, typ) { + types[i] = nil + } + } + + // update index + index = -1 + for i, typ := range types { + if typ == nil { + index = i + break + } + } + + return +} + +// structuralType returns the structural type of a constraint, if any. +func (check *Checker) structuralType(constraint Type) Type { + if iface, _ := under(constraint).(*Interface); iface != nil { + check.completeInterface(nopos, iface) + types := unpack(iface.allTypes) + if len(types) == 1 { + return types[0] + } + return nil + } + return constraint +} diff --git a/src/cmd/compile/internal/types2/initorder.go b/src/cmd/compile/internal/types2/initorder.go new file mode 100644 index 0000000000000000000000000000000000000000..40816276665117ba4bae14c040d515544df9654b --- /dev/null +++ b/src/cmd/compile/internal/types2/initorder.go @@ -0,0 +1,303 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "container/heap" + "fmt" +) + +// initOrder computes the Info.InitOrder for package variables. +func (check *Checker) initOrder() { + // An InitOrder may already have been computed if a package is + // built from several calls to (*Checker).Files. Clear it. + check.Info.InitOrder = check.Info.InitOrder[:0] + + // Compute the object dependency graph and initialize + // a priority queue with the list of graph nodes. + pq := nodeQueue(dependencyGraph(check.objMap)) + heap.Init(&pq) + + const debug = false + if debug { + fmt.Printf("Computing initialization order for %s\n\n", check.pkg) + fmt.Println("Object dependency graph:") + for obj, d := range check.objMap { + // only print objects that may appear in the dependency graph + if obj, _ := obj.(dependency); obj != nil { + if len(d.deps) > 0 { + fmt.Printf("\t%s depends on\n", obj.Name()) + for dep := range d.deps { + fmt.Printf("\t\t%s\n", dep.Name()) + } + } else { + fmt.Printf("\t%s has no dependencies\n", obj.Name()) + } + } + } + fmt.Println() + + fmt.Println("Transposed object dependency graph (functions eliminated):") + for _, n := range pq { + fmt.Printf("\t%s depends on %d nodes\n", n.obj.Name(), n.ndeps) + for p := range n.pred { + fmt.Printf("\t\t%s is dependent\n", p.obj.Name()) + } + } + fmt.Println() + + fmt.Println("Processing nodes:") + } + + // Determine initialization order by removing the highest priority node + // (the one with the fewest dependencies) and its edges from the graph, + // repeatedly, until there are no nodes left. + // In a valid Go program, those nodes always have zero dependencies (after + // removing all incoming dependencies), otherwise there are initialization + // cycles. + emitted := make(map[*declInfo]bool) + for len(pq) > 0 { + // get the next node + n := heap.Pop(&pq).(*graphNode) + + if debug { + fmt.Printf("\t%s (src pos %d) depends on %d nodes now\n", + n.obj.Name(), n.obj.order(), n.ndeps) + } + + // if n still depends on other nodes, we have a cycle + if n.ndeps > 0 { + cycle := findPath(check.objMap, n.obj, n.obj, make(map[Object]bool)) + // If n.obj is not part of the cycle (e.g., n.obj->b->c->d->c), + // cycle will be nil. Don't report anything in that case since + // the cycle is reported when the algorithm gets to an object + // in the cycle. + // Furthermore, once an object in the cycle is encountered, + // the cycle will be broken (dependency count will be reduced + // below), and so the remaining nodes in the cycle don't trigger + // another error (unless they are part of multiple cycles). + if cycle != nil { + check.reportCycle(cycle) + } + // Ok to continue, but the variable initialization order + // will be incorrect at this point since it assumes no + // cycle errors. + } + + // reduce dependency count of all dependent nodes + // and update priority queue + for p := range n.pred { + p.ndeps-- + heap.Fix(&pq, p.index) + } + + // record the init order for variables with initializers only + v, _ := n.obj.(*Var) + info := check.objMap[v] + if v == nil || !info.hasInitializer() { + continue + } + + // n:1 variable declarations such as: a, b = f() + // introduce a node for each lhs variable (here: a, b); + // but they all have the same initializer - emit only + // one, for the first variable seen + if emitted[info] { + continue // initializer already emitted, if any + } + emitted[info] = true + + infoLhs := info.lhs // possibly nil (see declInfo.lhs field comment) + if infoLhs == nil { + infoLhs = []*Var{v} + } + init := &Initializer{infoLhs, info.init} + check.Info.InitOrder = append(check.Info.InitOrder, init) + } + + if debug { + fmt.Println() + fmt.Println("Initialization order:") + for _, init := range check.Info.InitOrder { + fmt.Printf("\t%s\n", init) + } + fmt.Println() + } +} + +// findPath returns the (reversed) list of objects []Object{to, ... from} +// such that there is a path of object dependencies from 'from' to 'to'. +// If there is no such path, the result is nil. +func findPath(objMap map[Object]*declInfo, from, to Object, seen map[Object]bool) []Object { + if seen[from] { + return nil + } + seen[from] = true + + for d := range objMap[from].deps { + if d == to { + return []Object{d} + } + if P := findPath(objMap, d, to, seen); P != nil { + return append(P, d) + } + } + + return nil +} + +// reportCycle reports an error for the given cycle. +func (check *Checker) reportCycle(cycle []Object) { + obj := cycle[0] + var err error_ + if check.conf.CompilerErrorMessages { + err.errorf(obj, "initialization loop for %s", obj.Name()) + } else { + err.errorf(obj, "initialization cycle for %s", obj.Name()) + } + // subtle loop: print cycle[i] for i = 0, n-1, n-2, ... 1 for len(cycle) = n + for i := len(cycle) - 1; i >= 0; i-- { + err.errorf(obj, "%s refers to", obj.Name()) + obj = cycle[i] + } + // print cycle[0] again to close the cycle + err.errorf(obj, "%s", obj.Name()) + check.report(&err) +} + +// ---------------------------------------------------------------------------- +// Object dependency graph + +// A dependency is an object that may be a dependency in an initialization +// expression. Only constants, variables, and functions can be dependencies. +// Constants are here because constant expression cycles are reported during +// initialization order computation. +type dependency interface { + Object + isDependency() +} + +// A graphNode represents a node in the object dependency graph. +// Each node p in n.pred represents an edge p->n, and each node +// s in n.succ represents an edge n->s; with a->b indicating that +// a depends on b. +type graphNode struct { + obj dependency // object represented by this node + pred, succ nodeSet // consumers and dependencies of this node (lazily initialized) + index int // node index in graph slice/priority queue + ndeps int // number of outstanding dependencies before this object can be initialized +} + +type nodeSet map[*graphNode]bool + +func (s *nodeSet) add(p *graphNode) { + if *s == nil { + *s = make(nodeSet) + } + (*s)[p] = true +} + +// dependencyGraph computes the object dependency graph from the given objMap, +// with any function nodes removed. The resulting graph contains only constants +// and variables. +func dependencyGraph(objMap map[Object]*declInfo) []*graphNode { + // M is the dependency (Object) -> graphNode mapping + M := make(map[dependency]*graphNode) + for obj := range objMap { + // only consider nodes that may be an initialization dependency + if obj, _ := obj.(dependency); obj != nil { + M[obj] = &graphNode{obj: obj} + } + } + + // compute edges for graph M + // (We need to include all nodes, even isolated ones, because they still need + // to be scheduled for initialization in correct order relative to other nodes.) + for obj, n := range M { + // for each dependency obj -> d (= deps[i]), create graph edges n->s and s->n + for d := range objMap[obj].deps { + // only consider nodes that may be an initialization dependency + if d, _ := d.(dependency); d != nil { + d := M[d] + n.succ.add(d) + d.pred.add(n) + } + } + } + + // remove function nodes and collect remaining graph nodes in G + // (Mutually recursive functions may introduce cycles among themselves + // which are permitted. Yet such cycles may incorrectly inflate the dependency + // count for variables which in turn may not get scheduled for initialization + // in correct order.) + var G []*graphNode + for obj, n := range M { + if _, ok := obj.(*Func); ok { + // connect each predecessor p of n with each successor s + // and drop the function node (don't collect it in G) + for p := range n.pred { + // ignore self-cycles + if p != n { + // Each successor s of n becomes a successor of p, and + // each predecessor p of n becomes a predecessor of s. + for s := range n.succ { + // ignore self-cycles + if s != n { + p.succ.add(s) + s.pred.add(p) + delete(s.pred, n) // remove edge to n + } + } + delete(p.succ, n) // remove edge to n + } + } + } else { + // collect non-function nodes + G = append(G, n) + } + } + + // fill in index and ndeps fields + for i, n := range G { + n.index = i + n.ndeps = len(n.succ) + } + + return G +} + +// ---------------------------------------------------------------------------- +// Priority queue + +// nodeQueue implements the container/heap interface; +// a nodeQueue may be used as a priority queue. +type nodeQueue []*graphNode + +func (a nodeQueue) Len() int { return len(a) } + +func (a nodeQueue) Swap(i, j int) { + x, y := a[i], a[j] + a[i], a[j] = y, x + x.index, y.index = j, i +} + +func (a nodeQueue) Less(i, j int) bool { + x, y := a[i], a[j] + // nodes are prioritized by number of incoming dependencies (1st key) + // and source order (2nd key) + return x.ndeps < y.ndeps || x.ndeps == y.ndeps && x.obj.order() < y.obj.order() +} + +func (a *nodeQueue) Push(x interface{}) { + panic("unreachable") +} + +func (a *nodeQueue) Pop() interface{} { + n := len(*a) + x := (*a)[n-1] + x.index = -1 // for safety + *a = (*a)[:n-1] + return x +} diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go new file mode 100644 index 0000000000000000000000000000000000000000..0df52e851c9de4b6f02726325ad1698fb73c060e --- /dev/null +++ b/src/cmd/compile/internal/types2/instantiate.go @@ -0,0 +1,63 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" +) + +// Instantiate instantiates the type typ with the given type arguments. +// typ must be a *Named or a *Signature type, it must be generic, and +// its number of type parameters must match the number of provided type +// arguments. The result is a new, instantiated (not generic) type of +// the same kind (either a *Named or a *Signature). The type arguments +// are not checked against the constraints of the type parameters. +// Any methods attached to a *Named are simply copied; they are not +// instantiated. +func Instantiate(pos syntax.Pos, typ Type, targs []Type) (res Type) { + // TODO(gri) This code is basically identical to the prolog + // in Checker.instantiate. Factor. + var tparams []*TypeName + switch t := typ.(type) { + case *Named: + tparams = t.tparams + case *Signature: + tparams = t.tparams + defer func() { + // If we had an unexpected failure somewhere don't panic below when + // asserting res.(*Signature). Check for *Signature in case Typ[Invalid] + // is returned. + if _, ok := res.(*Signature); !ok { + return + } + // If the signature doesn't use its type parameters, subst + // will not make a copy. In that case, make a copy now (so + // we can set tparams to nil w/o causing side-effects). + if t == res { + copy := *t + res = © + } + // After instantiating a generic signature, it is not generic + // anymore; we need to set tparams to nil. + res.(*Signature).tparams = nil + }() + + default: + panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ)) + } + + // the number of supplied types must match the number of type parameters + if len(targs) != len(tparams) { + panic(fmt.Sprintf("%v: got %d arguments but %d type parameters", pos, len(targs), len(tparams))) + } + + if len(tparams) == 0 { + return typ // nothing to do (minor optimization) + } + + smap := makeSubstMap(tparams, targs) + return (*Checker)(nil).subst(pos, typ, smap) +} diff --git a/src/cmd/compile/internal/types2/issues_test.go b/src/cmd/compile/internal/types2/issues_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e716a48038510a2320c02524c4375d0bdaa9ef6f --- /dev/null +++ b/src/cmd/compile/internal/types2/issues_test.go @@ -0,0 +1,612 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements tests for various issues. + +package types2_test + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "internal/testenv" + "sort" + "strings" + "testing" + + . "cmd/compile/internal/types2" +) + +func mustParse(t *testing.T, src string) *syntax.File { + f, err := parseSrc("", src) + if err != nil { + t.Fatal(err) + } + return f +} +func TestIssue5770(t *testing.T) { + f := mustParse(t, `package p; type S struct{T}`) + var conf Config + _, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, nil) // do not crash + want := "undeclared name: T" + if err == nil || !strings.Contains(err.Error(), want) { + t.Errorf("got: %v; want: %s", err, want) + } +} + +func TestIssue5849(t *testing.T) { + src := ` +package p +var ( + s uint + _ = uint8(8) + _ = uint16(16) << s + _ = uint32(32 << s) + _ = uint64(64 << s + s) + _ = (interface{})("foo") + _ = (interface{})(nil) +)` + f := mustParse(t, src) + + var conf Config + types := make(map[syntax.Expr]TypeAndValue) + _, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, &Info{Types: types}) + if err != nil { + t.Fatal(err) + } + + for x, tv := range types { + var want Type + switch x := x.(type) { + case *syntax.BasicLit: + switch x.Value { + case `8`: + want = Typ[Uint8] + case `16`: + want = Typ[Uint16] + case `32`: + want = Typ[Uint32] + case `64`: + want = Typ[Uint] // because of "+ s", s is of type uint + case `"foo"`: + want = Typ[String] + } + case *syntax.Name: + if x.Value == "nil" { + want = NewInterfaceType(nil, nil) // interface{} (for now, go/types types this as "untyped nil") + } + } + if want != nil && !Identical(tv.Type, want) { + t.Errorf("got %s; want %s", tv.Type, want) + } + } +} + +func TestIssue6413(t *testing.T) { + src := ` +package p +func f() int { + defer f() + go f() + return 0 +} +` + f := mustParse(t, src) + + var conf Config + types := make(map[syntax.Expr]TypeAndValue) + _, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, &Info{Types: types}) + if err != nil { + t.Fatal(err) + } + + want := Typ[Int] + n := 0 + for x, tv := range types { + if _, ok := x.(*syntax.CallExpr); ok { + if tv.Type != want { + t.Errorf("%s: got %s; want %s", x.Pos(), tv.Type, want) + } + n++ + } + } + + if n != 2 { + t.Errorf("got %d CallExprs; want 2", n) + } +} + +func TestIssue7245(t *testing.T) { + src := ` +package p +func (T) m() (res bool) { return } +type T struct{} // receiver type after method declaration +` + f := mustParse(t, src) + + var conf Config + defs := make(map[*syntax.Name]Object) + _, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, &Info{Defs: defs}) + if err != nil { + t.Fatal(err) + } + + m := f.DeclList[0].(*syntax.FuncDecl) + res1 := defs[m.Name].(*Func).Type().(*Signature).Results().At(0) + res2 := defs[m.Type.ResultList[0].Name].(*Var) + + if res1 != res2 { + t.Errorf("got %s (%p) != %s (%p)", res1, res2, res1, res2) + } +} + +// This tests that uses of existing vars on the LHS of an assignment +// are Uses, not Defs; and also that the (illegal) use of a non-var on +// the LHS of an assignment is a Use nonetheless. +func TestIssue7827(t *testing.T) { + const src = ` +package p +func _() { + const w = 1 // defs w + x, y := 2, 3 // defs x, y + w, x, z := 4, 5, 6 // uses w, x, defs z; error: cannot assign to w + _, _, _ = x, y, z // uses x, y, z +} +` + f := mustParse(t, src) + + const want = `L3 defs func p._() +L4 defs const w untyped int +L5 defs var x int +L5 defs var y int +L6 defs var z int +L6 uses const w untyped int +L6 uses var x int +L7 uses var x int +L7 uses var y int +L7 uses var z int` + + // don't abort at the first error + conf := Config{Error: func(err error) { t.Log(err) }} + defs := make(map[*syntax.Name]Object) + uses := make(map[*syntax.Name]Object) + _, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, &Info{Defs: defs, Uses: uses}) + if s := fmt.Sprint(err); !strings.HasSuffix(s, "cannot assign to w") { + t.Errorf("Check: unexpected error: %s", s) + } + + var facts []string + for id, obj := range defs { + if obj != nil { + fact := fmt.Sprintf("L%d defs %s", id.Pos().Line(), obj) + facts = append(facts, fact) + } + } + for id, obj := range uses { + fact := fmt.Sprintf("L%d uses %s", id.Pos().Line(), obj) + facts = append(facts, fact) + } + sort.Strings(facts) + + got := strings.Join(facts, "\n") + if got != want { + t.Errorf("Unexpected defs/uses\ngot:\n%s\nwant:\n%s", got, want) + } +} + +// This tests that the package associated with the types2.Object.Pkg method +// is the type's package independent of the order in which the imports are +// listed in the sources src1, src2 below. +// The actual issue is in go/internal/gcimporter which has a corresponding +// test; we leave this test here to verify correct behavior at the go/types +// level. +func TestIssue13898(t *testing.T) { + testenv.MustHaveGoBuild(t) + + const src0 = ` +package main + +import "go/types" + +func main() { + var info types.Info + for _, obj := range info.Uses { + _ = obj.Pkg() + } +} +` + // like src0, but also imports go/importer + const src1 = ` +package main + +import ( + "go/types" + _ "go/importer" +) + +func main() { + var info types.Info + for _, obj := range info.Uses { + _ = obj.Pkg() + } +} +` + // like src1 but with different import order + // (used to fail with this issue) + const src2 = ` +package main + +import ( + _ "go/importer" + "go/types" +) + +func main() { + var info types.Info + for _, obj := range info.Uses { + _ = obj.Pkg() + } +} +` + f := func(test, src string) { + f := mustParse(t, src) + conf := Config{Importer: defaultImporter()} + info := Info{Uses: make(map[*syntax.Name]Object)} + _, err := conf.Check("main", []*syntax.File{f}, &info) + if err != nil { + t.Fatal(err) + } + + var pkg *Package + count := 0 + for id, obj := range info.Uses { + if id.Value == "Pkg" { + pkg = obj.Pkg() + count++ + } + } + if count != 1 { + t.Fatalf("%s: got %d entries named Pkg; want 1", test, count) + } + if pkg.Name() != "types" { + t.Fatalf("%s: got %v; want package types2", test, pkg) + } + } + + f("src0", src0) + f("src1", src1) + f("src2", src2) +} + +func TestIssue22525(t *testing.T) { + f := mustParse(t, `package p; func f() { var a, b, c, d, e int }`) + + got := "\n" + conf := Config{Error: func(err error) { got += err.Error() + "\n" }} + conf.Check(f.PkgName.Value, []*syntax.File{f}, nil) // do not crash + want := ` +:1:27: a declared but not used +:1:30: b declared but not used +:1:33: c declared but not used +:1:36: d declared but not used +:1:39: e declared but not used +` + if got != want { + t.Errorf("got: %swant: %s", got, want) + } +} + +func TestIssue25627(t *testing.T) { + const prefix = `package p; import "unsafe"; type P *struct{}; type I interface{}; type T ` + // The src strings (without prefix) are constructed such that the number of semicolons + // plus one corresponds to the number of fields expected in the respective struct. + for _, src := range []string{ + `struct { x Missing }`, + `struct { Missing }`, + `struct { *Missing }`, + `struct { unsafe.Pointer }`, + `struct { P }`, + `struct { *I }`, + `struct { a int; b Missing; *Missing }`, + } { + f := mustParse(t, prefix+src) + + conf := Config{Importer: defaultImporter(), Error: func(err error) {}} + info := &Info{Types: make(map[syntax.Expr]TypeAndValue)} + _, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, info) + if err != nil { + if _, ok := err.(Error); !ok { + t.Fatal(err) + } + } + + syntax.Walk(f, func(n syntax.Node) bool { + if decl, _ := n.(*syntax.TypeDecl); decl != nil { + if tv, ok := info.Types[decl.Type]; ok && decl.Name.Value == "T" { + want := strings.Count(src, ";") + 1 + if got := tv.Type.(*Struct).NumFields(); got != want { + t.Errorf("%s: got %d fields; want %d", src, got, want) + } + } + } + return false + }) + } +} + +func TestIssue28005(t *testing.T) { + // method names must match defining interface name for this test + // (see last comment in this function) + sources := [...]string{ + "package p; type A interface{ A() }", + "package p; type B interface{ B() }", + "package p; type X interface{ A; B }", + } + + // compute original file ASTs + var orig [len(sources)]*syntax.File + for i, src := range sources { + orig[i] = mustParse(t, src) + } + + // run the test for all order permutations of the incoming files + for _, perm := range [][len(sources)]int{ + {0, 1, 2}, + {0, 2, 1}, + {1, 0, 2}, + {1, 2, 0}, + {2, 0, 1}, + {2, 1, 0}, + } { + // create file order permutation + files := make([]*syntax.File, len(sources)) + for i := range perm { + files[i] = orig[perm[i]] + } + + // type-check package with given file order permutation + var conf Config + info := &Info{Defs: make(map[*syntax.Name]Object)} + _, err := conf.Check("", files, info) + if err != nil { + t.Fatal(err) + } + + // look for interface object X + var obj Object + for name, def := range info.Defs { + if name.Value == "X" { + obj = def + break + } + } + if obj == nil { + t.Fatal("object X not found") + } + iface := obj.Type().Underlying().(*Interface) // object X must be an interface + + // Each iface method m is embedded; and m's receiver base type name + // must match the method's name per the choice in the source file. + for i := 0; i < iface.NumMethods(); i++ { + m := iface.Method(i) + recvName := m.Type().(*Signature).Recv().Type().(*Named).Obj().Name() + if recvName != m.Name() { + t.Errorf("perm %v: got recv %s; want %s", perm, recvName, m.Name()) + } + } + } +} + +func TestIssue28282(t *testing.T) { + // create type interface { error } + et := Universe.Lookup("error").Type() + it := NewInterfaceType(nil, []Type{et}) + it.Complete() + // verify that after completing the interface, the embedded method remains unchanged + want := et.Underlying().(*Interface).Method(0) + got := it.Method(0) + if got != want { + t.Fatalf("%s.Method(0): got %q (%p); want %q (%p)", it, got, got, want, want) + } + // verify that lookup finds the same method in both interfaces (redundant check) + obj, _, _ := LookupFieldOrMethod(et, false, nil, "Error") + if obj != want { + t.Fatalf("%s.Lookup: got %q (%p); want %q (%p)", et, obj, obj, want, want) + } + obj, _, _ = LookupFieldOrMethod(it, false, nil, "Error") + if obj != want { + t.Fatalf("%s.Lookup: got %q (%p); want %q (%p)", it, obj, obj, want, want) + } +} + +func TestIssue29029(t *testing.T) { + f1 := mustParse(t, `package p; type A interface { M() }`) + f2 := mustParse(t, `package p; var B interface { A }`) + + // printInfo prints the *Func definitions recorded in info, one *Func per line. + printInfo := func(info *Info) string { + var buf bytes.Buffer + for _, obj := range info.Defs { + if fn, ok := obj.(*Func); ok { + fmt.Fprintln(&buf, fn) + } + } + return buf.String() + } + + // The *Func (method) definitions for package p must be the same + // independent on whether f1 and f2 are type-checked together, or + // incrementally. + + // type-check together + var conf Config + info := &Info{Defs: make(map[*syntax.Name]Object)} + check := NewChecker(&conf, NewPackage("", "p"), info) + if err := check.Files([]*syntax.File{f1, f2}); err != nil { + t.Fatal(err) + } + want := printInfo(info) + + // type-check incrementally + info = &Info{Defs: make(map[*syntax.Name]Object)} + check = NewChecker(&conf, NewPackage("", "p"), info) + if err := check.Files([]*syntax.File{f1}); err != nil { + t.Fatal(err) + } + if err := check.Files([]*syntax.File{f2}); err != nil { + t.Fatal(err) + } + got := printInfo(info) + + if got != want { + t.Errorf("\ngot : %swant: %s", got, want) + } +} + +func TestIssue34151(t *testing.T) { + const asrc = `package a; type I interface{ M() }; type T struct { F interface { I } }` + const bsrc = `package b; import "a"; type T struct { F interface { a.I } }; var _ = a.T(T{})` + + a, err := pkgFor("a", asrc, nil) + if err != nil { + t.Fatalf("package %s failed to typecheck: %v", a.Name(), err) + } + + bast := mustParse(t, bsrc) + conf := Config{Importer: importHelper{pkg: a}} + b, err := conf.Check(bast.PkgName.Value, []*syntax.File{bast}, nil) + if err != nil { + t.Errorf("package %s failed to typecheck: %v", b.Name(), err) + } +} + +type importHelper struct { + pkg *Package + fallback Importer +} + +func (h importHelper) Import(path string) (*Package, error) { + if path == h.pkg.Path() { + return h.pkg, nil + } + if h.fallback == nil { + return nil, fmt.Errorf("got package path %q; want %q", path, h.pkg.Path()) + } + return h.fallback.Import(path) +} + +// TestIssue34921 verifies that we don't update an imported type's underlying +// type when resolving an underlying type. Specifically, when determining the +// underlying type of b.T (which is the underlying type of a.T, which is int) +// we must not set the underlying type of a.T again since that would lead to +// a race condition if package b is imported elsewhere, in a package that is +// concurrently type-checked. +func TestIssue34921(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Error(r) + } + }() + + var sources = []string{ + `package a; type T int`, + `package b; import "a"; type T a.T`, + } + + var pkg *Package + for _, src := range sources { + f := mustParse(t, src) + conf := Config{Importer: importHelper{pkg: pkg}} + res, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, nil) + if err != nil { + t.Errorf("%q failed to typecheck: %v", src, err) + } + pkg = res // res is imported by the next package in this test + } +} + +func TestIssue43088(t *testing.T) { + // type T1 struct { + // _ T2 + // } + // + // type T2 struct { + // _ struct { + // _ T2 + // } + // } + n1 := NewTypeName(syntax.Pos{}, nil, "T1", nil) + T1 := NewNamed(n1, nil, nil) + n2 := NewTypeName(syntax.Pos{}, nil, "T2", nil) + T2 := NewNamed(n2, nil, nil) + s1 := NewStruct([]*Var{NewField(syntax.Pos{}, nil, "_", T2, false)}, nil) + T1.SetUnderlying(s1) + s2 := NewStruct([]*Var{NewField(syntax.Pos{}, nil, "_", T2, false)}, nil) + s3 := NewStruct([]*Var{NewField(syntax.Pos{}, nil, "_", s2, false)}, nil) + T2.SetUnderlying(s3) + + // These calls must terminate (no endless recursion). + Comparable(T1) + Comparable(T2) +} + +func TestIssue44515(t *testing.T) { + typ := Unsafe.Scope().Lookup("Pointer").Type() + + got := TypeString(typ, nil) + want := "unsafe.Pointer" + if got != want { + t.Errorf("got %q; want %q", got, want) + } + + qf := func(pkg *Package) string { + if pkg == Unsafe { + return "foo" + } + return "" + } + got = TypeString(typ, qf) + want = "foo.Pointer" + if got != want { + t.Errorf("got %q; want %q", got, want) + } +} + +func TestIssue43124(t *testing.T) { + // All involved packages have the same name (template). Error messages should + // disambiguate between text/template and html/template by printing the full + // path. + const ( + asrc = `package a; import "text/template"; func F(template.Template) {}; func G(int) {}` + bsrc = `package b; import ("a"; "html/template"); func _() { a.F(template.Template{}) }` + csrc = `package c; import ("a"; "html/template"); func _() { a.G(template.Template{}) }` + ) + + a, err := pkgFor("a", asrc, nil) + if err != nil { + t.Fatalf("package a failed to typecheck: %v", err) + } + conf := Config{Importer: importHelper{pkg: a, fallback: defaultImporter()}} + + // Packages should be fully qualified when there is ambiguity within the + // error string itself. + bast := mustParse(t, bsrc) + _, err = conf.Check(bast.PkgName.Value, []*syntax.File{bast}, nil) + if err == nil { + t.Fatal("package b had no errors") + } + if !strings.Contains(err.Error(), "text/template") || !strings.Contains(err.Error(), "html/template") { + t.Errorf("type checking error for b does not disambiguate package template: %q", err) + } + + // ...and also when there is any ambiguity in reachable packages. + cast := mustParse(t, csrc) + _, err = conf.Check(cast.PkgName.Value, []*syntax.File{cast}, nil) + if err == nil { + t.Fatal("package c had no errors") + } + if !strings.Contains(err.Error(), "html/template") { + t.Errorf("type checking error for c does not disambiguate package template: %q", err) + } +} diff --git a/src/cmd/compile/internal/types2/labels.go b/src/cmd/compile/internal/types2/labels.go new file mode 100644 index 0000000000000000000000000000000000000000..d3206988b54b69d41fd525e6ba9beec487b3e8ba --- /dev/null +++ b/src/cmd/compile/internal/types2/labels.go @@ -0,0 +1,262 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "cmd/compile/internal/syntax" +) + +// labels checks correct label use in body. +func (check *Checker) labels(body *syntax.BlockStmt) { + // set of all labels in this body + all := NewScope(nil, body.Pos(), syntax.EndPos(body), "label") + + fwdJumps := check.blockBranches(all, nil, nil, body.List) + + // If there are any forward jumps left, no label was found for + // the corresponding goto statements. Either those labels were + // never defined, or they are inside blocks and not reachable + // for the respective gotos. + for _, jmp := range fwdJumps { + var msg string + name := jmp.Label.Value + if alt := all.Lookup(name); alt != nil { + msg = "goto %s jumps into block" + alt.(*Label).used = true // avoid another error + } else { + msg = "label %s not declared" + } + check.errorf(jmp.Label, msg, name) + } + + // spec: "It is illegal to define a label that is never used." + for _, obj := range all.elems { + if lbl := obj.(*Label); !lbl.used { + check.softErrorf(lbl.pos, "label %s declared but not used", lbl.name) + } + } +} + +// A block tracks label declarations in a block and its enclosing blocks. +type block struct { + parent *block // enclosing block + lstmt *syntax.LabeledStmt // labeled statement to which this block belongs, or nil + labels map[string]*syntax.LabeledStmt // allocated lazily +} + +// insert records a new label declaration for the current block. +// The label must not have been declared before in any block. +func (b *block) insert(s *syntax.LabeledStmt) { + name := s.Label.Value + if debug { + assert(b.gotoTarget(name) == nil) + } + labels := b.labels + if labels == nil { + labels = make(map[string]*syntax.LabeledStmt) + b.labels = labels + } + labels[name] = s +} + +// gotoTarget returns the labeled statement in the current +// or an enclosing block with the given label name, or nil. +func (b *block) gotoTarget(name string) *syntax.LabeledStmt { + for s := b; s != nil; s = s.parent { + if t := s.labels[name]; t != nil { + return t + } + } + return nil +} + +// enclosingTarget returns the innermost enclosing labeled +// statement with the given label name, or nil. +func (b *block) enclosingTarget(name string) *syntax.LabeledStmt { + for s := b; s != nil; s = s.parent { + if t := s.lstmt; t != nil && t.Label.Value == name { + return t + } + } + return nil +} + +// blockBranches processes a block's statement list and returns the set of outgoing forward jumps. +// all is the scope of all declared labels, parent the set of labels declared in the immediately +// enclosing block, and lstmt is the labeled statement this block is associated with (or nil). +func (check *Checker) blockBranches(all *Scope, parent *block, lstmt *syntax.LabeledStmt, list []syntax.Stmt) []*syntax.BranchStmt { + b := &block{parent, lstmt, nil} + + var ( + varDeclPos syntax.Pos + fwdJumps, badJumps []*syntax.BranchStmt + ) + + // All forward jumps jumping over a variable declaration are possibly + // invalid (they may still jump out of the block and be ok). + // recordVarDecl records them for the given position. + recordVarDecl := func(pos syntax.Pos) { + varDeclPos = pos + badJumps = append(badJumps[:0], fwdJumps...) // copy fwdJumps to badJumps + } + + jumpsOverVarDecl := func(jmp *syntax.BranchStmt) bool { + if varDeclPos.IsKnown() { + for _, bad := range badJumps { + if jmp == bad { + return true + } + } + } + return false + } + + var stmtBranches func(syntax.Stmt) + stmtBranches = func(s syntax.Stmt) { + switch s := s.(type) { + case *syntax.DeclStmt: + for _, d := range s.DeclList { + if d, _ := d.(*syntax.VarDecl); d != nil { + recordVarDecl(d.Pos()) + } + } + + case *syntax.LabeledStmt: + // declare non-blank label + if name := s.Label.Value; name != "_" { + lbl := NewLabel(s.Label.Pos(), check.pkg, name) + if alt := all.Insert(lbl); alt != nil { + var err error_ + err.soft = true + err.errorf(lbl.pos, "label %s already declared", name) + err.recordAltDecl(alt) + check.report(&err) + // ok to continue + } else { + b.insert(s) + check.recordDef(s.Label, lbl) + } + // resolve matching forward jumps and remove them from fwdJumps + i := 0 + for _, jmp := range fwdJumps { + if jmp.Label.Value == name { + // match + lbl.used = true + check.recordUse(jmp.Label, lbl) + if jumpsOverVarDecl(jmp) { + check.softErrorf( + jmp.Label, + "goto %s jumps over variable declaration at line %d", + name, + varDeclPos.Line(), + ) + // ok to continue + } + } else { + // no match - record new forward jump + fwdJumps[i] = jmp + i++ + } + } + fwdJumps = fwdJumps[:i] + lstmt = s + } + stmtBranches(s.Stmt) + + case *syntax.BranchStmt: + if s.Label == nil { + return // checked in 1st pass (check.stmt) + } + + // determine and validate target + name := s.Label.Value + switch s.Tok { + case syntax.Break: + // spec: "If there is a label, it must be that of an enclosing + // "for", "switch", or "select" statement, and that is the one + // whose execution terminates." + valid := false + if t := b.enclosingTarget(name); t != nil { + switch t.Stmt.(type) { + case *syntax.SwitchStmt, *syntax.SelectStmt, *syntax.ForStmt: + valid = true + } + } + if !valid { + check.errorf(s.Label, "invalid break label %s", name) + return + } + + case syntax.Continue: + // spec: "If there is a label, it must be that of an enclosing + // "for" statement, and that is the one whose execution advances." + valid := false + if t := b.enclosingTarget(name); t != nil { + switch t.Stmt.(type) { + case *syntax.ForStmt: + valid = true + } + } + if !valid { + check.errorf(s.Label, "invalid continue label %s", name) + return + } + + case syntax.Goto: + if b.gotoTarget(name) == nil { + // label may be declared later - add branch to forward jumps + fwdJumps = append(fwdJumps, s) + return + } + + default: + check.errorf(s, invalidAST+"branch statement: %s %s", s.Tok, name) + return + } + + // record label use + obj := all.Lookup(name) + obj.(*Label).used = true + check.recordUse(s.Label, obj) + + case *syntax.AssignStmt: + if s.Op == syntax.Def { + recordVarDecl(s.Pos()) + } + + case *syntax.BlockStmt: + // Unresolved forward jumps inside the nested block + // become forward jumps in the current block. + fwdJumps = append(fwdJumps, check.blockBranches(all, b, lstmt, s.List)...) + + case *syntax.IfStmt: + stmtBranches(s.Then) + if s.Else != nil { + stmtBranches(s.Else) + } + + case *syntax.SwitchStmt: + b := &block{b, lstmt, nil} + for _, s := range s.Body { + fwdJumps = append(fwdJumps, check.blockBranches(all, b, nil, s.Body)...) + } + + case *syntax.SelectStmt: + b := &block{b, lstmt, nil} + for _, s := range s.Body { + fwdJumps = append(fwdJumps, check.blockBranches(all, b, nil, s.Body)...) + } + + case *syntax.ForStmt: + stmtBranches(s.Body) + } + } + + for _, s := range list { + stmtBranches(s) + } + + return fwdJumps +} diff --git a/src/cmd/compile/internal/types2/lookup.go b/src/cmd/compile/internal/types2/lookup.go new file mode 100644 index 0000000000000000000000000000000000000000..78299502e9c00d9ab176733912a31c60d2602a50 --- /dev/null +++ b/src/cmd/compile/internal/types2/lookup.go @@ -0,0 +1,511 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements various field and method lookup functions. + +package types2 + +// LookupFieldOrMethod looks up a field or method with given package and name +// in T and returns the corresponding *Var or *Func, an index sequence, and a +// bool indicating if there were any pointer indirections on the path to the +// field or method. If addressable is set, T is the type of an addressable +// variable (only matters for method lookups). +// +// The last index entry is the field or method index in the (possibly embedded) +// type where the entry was found, either: +// +// 1) the list of declared methods of a named type; or +// 2) the list of all methods (method set) of an interface type; or +// 3) the list of fields of a struct type. +// +// The earlier index entries are the indices of the embedded struct fields +// traversed to get to the found entry, starting at depth 0. +// +// If no entry is found, a nil object is returned. In this case, the returned +// index and indirect values have the following meaning: +// +// - If index != nil, the index sequence points to an ambiguous entry +// (the same name appeared more than once at the same embedding level). +// +// - If indirect is set, a method with a pointer receiver type was found +// but there was no pointer on the path from the actual receiver type to +// the method's formal receiver base type, nor was the receiver addressable. +// +func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { + return (*Checker)(nil).lookupFieldOrMethod(T, addressable, pkg, name) +} + +// Internal use of Checker.lookupFieldOrMethod: If the obj result is a method +// associated with a concrete (non-interface) type, the method's signature +// may not be fully set up. Call Checker.objDecl(obj, nil) before accessing +// the method's type. +// TODO(gri) Now that we provide the *Checker, we can probably remove this +// caveat by calling Checker.objDecl from lookupFieldOrMethod. Investigate. + +// lookupFieldOrMethod is like the external version but completes interfaces +// as necessary. +func (check *Checker) lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { + // Methods cannot be associated to a named pointer type + // (spec: "The type denoted by T is called the receiver base type; + // it must not be a pointer or interface type and it must be declared + // in the same package as the method."). + // Thus, if we have a named pointer type, proceed with the underlying + // pointer type but discard the result if it is a method since we would + // not have found it for T (see also issue 8590). + if t := asNamed(T); t != nil { + if p, _ := t.underlying.(*Pointer); p != nil { + obj, index, indirect = check.rawLookupFieldOrMethod(p, false, pkg, name) + if _, ok := obj.(*Func); ok { + return nil, nil, false + } + return + } + } + + return check.rawLookupFieldOrMethod(T, addressable, pkg, name) +} + +// TODO(gri) The named type consolidation and seen maps below must be +// indexed by unique keys for a given type. Verify that named +// types always have only one representation (even when imported +// indirectly via different packages.) + +// rawLookupFieldOrMethod should only be called by lookupFieldOrMethod and missingMethod. +func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { + // WARNING: The code in this function is extremely subtle - do not modify casually! + // This function and NewMethodSet should be kept in sync. + + if name == "_" { + return // blank fields/methods are never found + } + + typ, isPtr := deref(T) + + // *typ where typ is an interface has no methods. + // Be cautious: typ may be nil (issue 39634, crash #3). + if typ == nil || isPtr && IsInterface(typ) { + return + } + + // Start with typ as single entry at shallowest depth. + current := []embeddedType{{typ, nil, isPtr, false}} + + // Named types that we have seen already, allocated lazily. + // Used to avoid endless searches in case of recursive types. + // Since only Named types can be used for recursive types, we + // only need to track those. + // (If we ever allow type aliases to construct recursive types, + // we must use type identity rather than pointer equality for + // the map key comparison, as we do in consolidateMultiples.) + var seen map[*Named]bool + + // search current depth + for len(current) > 0 { + var next []embeddedType // embedded types found at current depth + + // look for (pkg, name) in all types at current depth + var tpar *TypeParam // set if obj receiver is a type parameter + for _, e := range current { + typ := e.typ + + // If we have a named type, we may have associated methods. + // Look for those first. + if named := asNamed(typ); named != nil { + if seen[named] { + // We have seen this type before, at a more shallow depth + // (note that multiples of this type at the current depth + // were consolidated before). The type at that depth shadows + // this same type at the current depth, so we can ignore + // this one. + continue + } + if seen == nil { + seen = make(map[*Named]bool) + } + seen[named] = true + + // look for a matching attached method + if i, m := lookupMethod(named.methods, pkg, name); m != nil { + // potential match + // caution: method may not have a proper signature yet + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = m + indirect = e.indirect + continue // we can't have a matching field or interface method + } + + // continue with underlying type, but only if it's not a type parameter + // TODO(gri) is this what we want to do for type parameters? (spec question) + typ = named.under() + if asTypeParam(typ) != nil { + continue + } + } + + tpar = nil + switch t := typ.(type) { + case *Struct: + // look for a matching field and collect embedded types + for i, f := range t.fields { + if f.sameId(pkg, name) { + assert(f.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = f + indirect = e.indirect + continue // we can't have a matching interface method + } + // Collect embedded struct fields for searching the next + // lower depth, but only if we have not seen a match yet + // (if we have a match it is either the desired field or + // we have a name collision on the same depth; in either + // case we don't need to look further). + // Embedded fields are always of the form T or *T where + // T is a type name. If e.typ appeared multiple times at + // this depth, f.typ appears multiple times at the next + // depth. + if obj == nil && f.embedded { + typ, isPtr := deref(f.typ) + // TODO(gri) optimization: ignore types that can't + // have fields or methods (only Named, Struct, and + // Interface types need to be considered). + next = append(next, embeddedType{typ, concat(e.index, i), e.indirect || isPtr, e.multiples}) + } + } + + case *Interface: + // look for a matching method + // TODO(gri) t.allMethods is sorted - use binary search + check.completeInterface(nopos, t) + if i, m := lookupMethod(t.allMethods, pkg, name); m != nil { + assert(m.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = m + indirect = e.indirect + } + + case *TypeParam: + if i, m := lookupMethod(t.Bound().allMethods, pkg, name); m != nil { + assert(m.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + tpar = t + obj = m + indirect = e.indirect + } + if obj == nil { + // At this point we're not (yet) looking into methods + // that any underlying type of the types in the type list + // might have. + // TODO(gri) Do we want to specify the language that way? + } + } + } + + if obj != nil { + // found a potential match + // spec: "A method call x.m() is valid if the method set of (the type of) x + // contains m and the argument list can be assigned to the parameter + // list of m. If x is addressable and &x's method set contains m, x.m() + // is shorthand for (&x).m()". + if f, _ := obj.(*Func); f != nil { + // determine if method has a pointer receiver + hasPtrRecv := tpar == nil && ptrRecv(f) + if hasPtrRecv && !indirect && !addressable { + return nil, nil, true // pointer/addressable receiver required + } + } + return + } + + current = check.consolidateMultiples(next) + } + + return nil, nil, false // not found +} + +// embeddedType represents an embedded type +type embeddedType struct { + typ Type + index []int // embedded field indices, starting with index at depth 0 + indirect bool // if set, there was a pointer indirection on the path to this field + multiples bool // if set, typ appears multiple times at this depth +} + +// consolidateMultiples collects multiple list entries with the same type +// into a single entry marked as containing multiples. The result is the +// consolidated list. +func (check *Checker) consolidateMultiples(list []embeddedType) []embeddedType { + if len(list) <= 1 { + return list // at most one entry - nothing to do + } + + n := 0 // number of entries w/ unique type + prev := make(map[Type]int) // index at which type was previously seen + for _, e := range list { + if i, found := check.lookupType(prev, e.typ); found { + list[i].multiples = true + // ignore this entry + } else { + prev[e.typ] = n + list[n] = e + n++ + } + } + return list[:n] +} + +func (check *Checker) lookupType(m map[Type]int, typ Type) (int, bool) { + // fast path: maybe the types are equal + if i, found := m[typ]; found { + return i, true + } + + for t, i := range m { + if check.identical(t, typ) { + return i, true + } + } + + return 0, false +} + +// MissingMethod returns (nil, false) if V implements T, otherwise it +// returns a missing method required by T and whether it is missing or +// just has the wrong type. +// +// For non-interface types V, or if static is set, V implements T if all +// methods of T are present in V. Otherwise (V is an interface and static +// is not set), MissingMethod only checks that methods of T which are also +// present in V have matching types (e.g., for a type assertion x.(T) where +// x is of interface type V). +// +func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType bool) { + m, typ := (*Checker)(nil).missingMethod(V, T, static) + return m, typ != nil +} + +// missingMethod is like MissingMethod but accepts a *Checker as +// receiver and an addressable flag. +// The receiver may be nil if missingMethod is invoked through +// an exported API call (such as MissingMethod), i.e., when all +// methods have been type-checked. +// If the type has the correctly named method, but with the wrong +// signature, the existing method is returned as well. +// To improve error messages, also report the wrong signature +// when the method exists on *V instead of V. +func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, wrongType *Func) { + check.completeInterface(nopos, T) + + // fast path for common case + if T.Empty() { + return + } + + if ityp := asInterface(V); ityp != nil { + check.completeInterface(nopos, ityp) + // TODO(gri) allMethods is sorted - can do this more efficiently + for _, m := range T.allMethods { + _, f := lookupMethod(ityp.allMethods, m.pkg, m.name) + + if f == nil { + // if m is the magic method == we're ok (interfaces are comparable) + if m.name == "==" || !static { + continue + } + return m, f + } + + // both methods must have the same number of type parameters + ftyp := f.typ.(*Signature) + mtyp := m.typ.(*Signature) + if len(ftyp.tparams) != len(mtyp.tparams) { + return m, f + } + + // If the methods have type parameters we don't care whether they + // are the same or not, as long as they match up. Use unification + // to see if they can be made to match. + // TODO(gri) is this always correct? what about type bounds? + // (Alternative is to rename/subst type parameters and compare.) + u := newUnifier(check, true) + u.x.init(ftyp.tparams) + if !u.unify(ftyp, mtyp) { + return m, f + } + } + + return + } + + // A concrete type implements T if it implements all methods of T. + Vd, _ := deref(V) + Vn := asNamed(Vd) + for _, m := range T.allMethods { + // TODO(gri) should this be calling lookupFieldOrMethod instead (and why not)? + obj, _, _ := check.rawLookupFieldOrMethod(V, false, m.pkg, m.name) + + // Check if *V implements this method of T. + if obj == nil { + ptr := NewPointer(V) + obj, _, _ = check.rawLookupFieldOrMethod(ptr, false, m.pkg, m.name) + if obj != nil { + return m, obj.(*Func) + } + } + + // we must have a method (not a field of matching function type) + f, _ := obj.(*Func) + if f == nil { + // if m is the magic method == and V is comparable, we're ok + if m.name == "==" && Comparable(V) { + continue + } + return m, nil + } + + // methods may not have a fully set up signature yet + if check != nil { + check.objDecl(f, nil) + } + + // both methods must have the same number of type parameters + ftyp := f.typ.(*Signature) + mtyp := m.typ.(*Signature) + if len(ftyp.tparams) != len(mtyp.tparams) { + return m, f + } + + // If V is a (instantiated) generic type, its methods are still + // parameterized using the original (declaration) receiver type + // parameters (subst simply copies the existing method list, it + // does not instantiate the methods). + // In order to compare the signatures, substitute the receiver + // type parameters of ftyp with V's instantiation type arguments. + // This lazily instantiates the signature of method f. + if Vn != nil && len(Vn.tparams) > 0 { + // Be careful: The number of type arguments may not match + // the number of receiver parameters. If so, an error was + // reported earlier but the length discrepancy is still + // here. Exit early in this case to prevent an assertion + // failure in makeSubstMap. + // TODO(gri) Can we avoid this check by fixing the lengths? + if len(ftyp.rparams) != len(Vn.targs) { + return + } + ftyp = check.subst(nopos, ftyp, makeSubstMap(ftyp.rparams, Vn.targs)).(*Signature) + } + + // If the methods have type parameters we don't care whether they + // are the same or not, as long as they match up. Use unification + // to see if they can be made to match. + // TODO(gri) is this always correct? what about type bounds? + // (Alternative is to rename/subst type parameters and compare.) + u := newUnifier(check, true) + u.x.init(ftyp.tparams) + if !u.unify(ftyp, mtyp) { + return m, f + } + } + + return +} + +// assertableTo reports whether a value of type V can be asserted to have type T. +// It returns (nil, false) as affirmative answer. Otherwise it returns a missing +// method required by V and whether it is missing or just has the wrong type. +// The receiver may be nil if assertableTo is invoked through an exported API call +// (such as AssertableTo), i.e., when all methods have been type-checked. +// If the global constant forceStrict is set, assertions that are known to fail +// are not permitted. +func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Func) { + // no static check is required if T is an interface + // spec: "If T is an interface type, x.(T) asserts that the + // dynamic type of x implements the interface T." + if asInterface(T) != nil && !forceStrict { + return + } + return check.missingMethod(T, V, false) +} + +// deref dereferences typ if it is a *Pointer and returns its base and true. +// Otherwise it returns (typ, false). +func deref(typ Type) (Type, bool) { + if p, _ := typ.(*Pointer); p != nil { + return p.base, true + } + return typ, false +} + +// derefStructPtr dereferences typ if it is a (named or unnamed) pointer to a +// (named or unnamed) struct and returns its base. Otherwise it returns typ. +func derefStructPtr(typ Type) Type { + if p := asPointer(typ); p != nil { + if asStruct(p.base) != nil { + return p.base + } + } + return typ +} + +// concat returns the result of concatenating list and i. +// The result does not share its underlying array with list. +func concat(list []int, i int) []int { + var t []int + t = append(t, list...) + return append(t, i) +} + +// fieldIndex returns the index for the field with matching package and name, or a value < 0. +func fieldIndex(fields []*Var, pkg *Package, name string) int { + if name != "_" { + for i, f := range fields { + if f.sameId(pkg, name) { + return i + } + } + } + return -1 +} + +// lookupMethod returns the index of and method with matching package and name, or (-1, nil). +func lookupMethod(methods []*Func, pkg *Package, name string) (int, *Func) { + if name != "_" { + for i, m := range methods { + if m.sameId(pkg, name) { + return i, m + } + } + } + return -1, nil +} + +// ptrRecv reports whether the receiver is of the form *T. +func ptrRecv(f *Func) bool { + // If a method's receiver type is set, use that as the source of truth for the receiver. + // Caution: Checker.funcDecl (decl.go) marks a function by setting its type to an empty + // signature. We may reach here before the signature is fully set up: we must explicitly + // check if the receiver is set (we cannot just look for non-nil f.typ). + if sig, _ := f.typ.(*Signature); sig != nil && sig.recv != nil { + _, isPtr := deref(sig.recv.typ) + return isPtr + } + + // If a method's type is not set it may be a method/function that is: + // 1) client-supplied (via NewFunc with no signature), or + // 2) internally created but not yet type-checked. + // For case 1) we can't do anything; the client must know what they are doing. + // For case 2) we can use the information gathered by the resolver. + return f.hasPtrRecv +} diff --git a/src/cmd/compile/internal/types2/object.go b/src/cmd/compile/internal/types2/object.go new file mode 100644 index 0000000000000000000000000000000000000000..844bc34b6a3d1e7034880d667b48b1bedcef87f2 --- /dev/null +++ b/src/cmd/compile/internal/types2/object.go @@ -0,0 +1,527 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "go/constant" + "unicode" + "unicode/utf8" +) + +// An Object describes a named language entity such as a package, +// constant, type, variable, function (incl. methods), or label. +// All objects implement the Object interface. +// +type Object interface { + Parent() *Scope // scope in which this object is declared; nil for methods and struct fields + Pos() syntax.Pos // position of object identifier in declaration + Pkg() *Package // package to which this object belongs; nil for labels and objects in the Universe scope + Name() string // package local object name + Type() Type // object type + Exported() bool // reports whether the name starts with a capital letter + Id() string // object name if exported, qualified name if not exported (see func Id) + + // String returns a human-readable string of the object. + String() string + + // order reflects a package-level object's source order: if object + // a is before object b in the source, then a.order() < b.order(). + // order returns a value > 0 for package-level objects; it returns + // 0 for all other objects (including objects in file scopes). + order() uint32 + + // color returns the object's color. + color() color + + // setType sets the type of the object. + setType(Type) + + // setOrder sets the order number of the object. It must be > 0. + setOrder(uint32) + + // setColor sets the object's color. It must not be white. + setColor(color color) + + // setParent sets the parent scope of the object. + setParent(*Scope) + + // sameId reports whether obj.Id() and Id(pkg, name) are the same. + sameId(pkg *Package, name string) bool + + // scopePos returns the start position of the scope of this Object + scopePos() syntax.Pos + + // setScopePos sets the start position of the scope for this Object. + setScopePos(pos syntax.Pos) +} + +func isExported(name string) bool { + ch, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(ch) +} + +// Id returns name if it is exported, otherwise it +// returns the name qualified with the package path. +func Id(pkg *Package, name string) string { + if isExported(name) { + return name + } + // unexported names need the package path for differentiation + // (if there's no package, make sure we don't start with '.' + // as that may change the order of methods between a setup + // inside a package and outside a package - which breaks some + // tests) + path := "_" + // pkg is nil for objects in Universe scope and possibly types + // introduced via Eval (see also comment in object.sameId) + if pkg != nil && pkg.path != "" { + path = pkg.path + } + return path + "." + name +} + +// An object implements the common parts of an Object. +type object struct { + parent *Scope + pos syntax.Pos + pkg *Package + name string + typ Type + order_ uint32 + color_ color + scopePos_ syntax.Pos +} + +// color encodes the color of an object (see Checker.objDecl for details). +type color uint32 + +// An object may be painted in one of three colors. +// Color values other than white or black are considered grey. +const ( + white color = iota + black + grey // must be > white and black +) + +func (c color) String() string { + switch c { + case white: + return "white" + case black: + return "black" + default: + return "grey" + } +} + +// colorFor returns the (initial) color for an object depending on +// whether its type t is known or not. +func colorFor(t Type) color { + if t != nil { + return black + } + return white +} + +// Parent returns the scope in which the object is declared. +// The result is nil for methods and struct fields. +func (obj *object) Parent() *Scope { return obj.parent } + +// Pos returns the declaration position of the object's identifier. +func (obj *object) Pos() syntax.Pos { return obj.pos } + +// Pkg returns the package to which the object belongs. +// The result is nil for labels and objects in the Universe scope. +func (obj *object) Pkg() *Package { return obj.pkg } + +// Name returns the object's (package-local, unqualified) name. +func (obj *object) Name() string { return obj.name } + +// Type returns the object's type. +func (obj *object) Type() Type { return obj.typ } + +// Exported reports whether the object is exported (starts with a capital letter). +// It doesn't take into account whether the object is in a local (function) scope +// or not. +func (obj *object) Exported() bool { return isExported(obj.name) } + +// Id is a wrapper for Id(obj.Pkg(), obj.Name()). +func (obj *object) Id() string { return Id(obj.pkg, obj.name) } + +func (obj *object) String() string { panic("abstract") } +func (obj *object) order() uint32 { return obj.order_ } +func (obj *object) color() color { return obj.color_ } +func (obj *object) scopePos() syntax.Pos { return obj.scopePos_ } + +func (obj *object) setParent(parent *Scope) { obj.parent = parent } +func (obj *object) setType(typ Type) { obj.typ = typ } +func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order } +func (obj *object) setColor(color color) { assert(color != white); obj.color_ = color } +func (obj *object) setScopePos(pos syntax.Pos) { obj.scopePos_ = pos } + +func (obj *object) sameId(pkg *Package, name string) bool { + // spec: + // "Two identifiers are different if they are spelled differently, + // or if they appear in different packages and are not exported. + // Otherwise, they are the same." + if name != obj.name { + return false + } + // obj.Name == name + if obj.Exported() { + return true + } + // not exported, so packages must be the same (pkg == nil for + // fields in Universe scope; this can only happen for types + // introduced via Eval) + if pkg == nil || obj.pkg == nil { + return pkg == obj.pkg + } + // pkg != nil && obj.pkg != nil + return pkg.path == obj.pkg.path +} + +// A PkgName represents an imported Go package. +// PkgNames don't have a type. +type PkgName struct { + object + imported *Package + used bool // set if the package was used +} + +// NewPkgName returns a new PkgName object representing an imported package. +// The remaining arguments set the attributes found with all Objects. +func NewPkgName(pos syntax.Pos, pkg *Package, name string, imported *Package) *PkgName { + return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, nopos}, imported, false} +} + +// Imported returns the package that was imported. +// It is distinct from Pkg(), which is the package containing the import statement. +func (obj *PkgName) Imported() *Package { return obj.imported } + +// A Const represents a declared constant. +type Const struct { + object + val constant.Value +} + +// NewConst returns a new constant with value val. +// The remaining arguments set the attributes found with all Objects. +func NewConst(pos syntax.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const { + return &Const{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, val} +} + +// Val returns the constant's value. +func (obj *Const) Val() constant.Value { return obj.val } + +func (*Const) isDependency() {} // a constant may be a dependency of an initialization expression + +// A TypeName represents a name for a (defined or alias) type. +type TypeName struct { + object +} + +// NewTypeName returns a new type name denoting the given typ. +// The remaining arguments set the attributes found with all Objects. +// +// The typ argument may be a defined (Named) type or an alias type. +// It may also be nil such that the returned TypeName can be used as +// argument for NewNamed, which will set the TypeName's type as a side- +// effect. +func NewTypeName(pos syntax.Pos, pkg *Package, name string, typ Type) *TypeName { + return &TypeName{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}} +} + +// IsAlias reports whether obj is an alias name for a type. +func (obj *TypeName) IsAlias() bool { + switch t := obj.typ.(type) { + case nil: + return false + case *Basic: + // unsafe.Pointer is not an alias. + if obj.pkg == Unsafe { + return false + } + // Any user-defined type name for a basic type is an alias for a + // basic type (because basic types are pre-declared in the Universe + // scope, outside any package scope), and so is any type name with + // a different name than the name of the basic type it refers to. + // Additionally, we need to look for "byte" and "rune" because they + // are aliases but have the same names (for better error messages). + return obj.pkg != nil || t.name != obj.name || t == universeByte || t == universeRune + case *Named: + return obj != t.obj + default: + return true + } +} + +// A Variable represents a declared variable (including function parameters and results, and struct fields). +type Var struct { + object + embedded bool // if set, the variable is an embedded struct field, and name is the type name + isField bool // var is struct field + used bool // set if the variable was used +} + +// NewVar returns a new variable. +// The arguments set the attributes found with all Objects. +func NewVar(pos syntax.Pos, pkg *Package, name string, typ Type) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}} +} + +// NewParam returns a new variable representing a function parameter. +func NewParam(pos syntax.Pos, pkg *Package, name string, typ Type) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, used: true} // parameters are always 'used' +} + +// NewField returns a new variable representing a struct field. +// For embedded fields, the name is the unqualified type name +/// under which the field is accessible. +func NewField(pos syntax.Pos, pkg *Package, name string, typ Type, embedded bool) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, embedded: embedded, isField: true} +} + +// Anonymous reports whether the variable is an embedded field. +// Same as Embedded; only present for backward-compatibility. +func (obj *Var) Anonymous() bool { return obj.embedded } + +// Embedded reports whether the variable is an embedded field. +func (obj *Var) Embedded() bool { return obj.embedded } + +// IsField reports whether the variable is a struct field. +func (obj *Var) IsField() bool { return obj.isField } + +func (*Var) isDependency() {} // a variable may be a dependency of an initialization expression + +// A Func represents a declared function, concrete method, or abstract +// (interface) method. Its Type() is always a *Signature. +// An abstract method may belong to many interfaces due to embedding. +type Func struct { + object + hasPtrRecv bool // only valid for methods that don't have a type yet +} + +// NewFunc returns a new function with the given signature, representing +// the function's type. +func NewFunc(pos syntax.Pos, pkg *Package, name string, sig *Signature) *Func { + // don't store a (typed) nil signature + var typ Type + if sig != nil { + typ = sig + } + return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, false} +} + +// FullName returns the package- or receiver-type-qualified name of +// function or method obj. +func (obj *Func) FullName() string { + var buf bytes.Buffer + writeFuncName(&buf, obj, nil) + return buf.String() +} + +// Scope returns the scope of the function's body block. +func (obj *Func) Scope() *Scope { return obj.typ.(*Signature).scope } + +// Less reports whether function a is ordered before function b. +// +// Functions are ordered exported before non-exported, then by name, +// and finally (for non-exported functions) by package path. +// +// TODO(gri) The compiler also sorts by package height before package +// path for non-exported names. +func (a *Func) less(b *Func) bool { + if a == b { + return false + } + + // Exported functions before non-exported. + ea := isExported(a.name) + eb := isExported(b.name) + if ea != eb { + return ea + } + + // Order by name and then (for non-exported names) by package. + if a.name != b.name { + return a.name < b.name + } + if !ea { + return a.pkg.path < b.pkg.path + } + + return false +} + +func (*Func) isDependency() {} // a function may be a dependency of an initialization expression + +// A Label represents a declared label. +// Labels don't have a type. +type Label struct { + object + used bool // set if the label was used +} + +// NewLabel returns a new label. +func NewLabel(pos syntax.Pos, pkg *Package, name string) *Label { + return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid], color_: black}, false} +} + +// A Builtin represents a built-in function. +// Builtins don't have a valid type. +type Builtin struct { + object + id builtinId +} + +func newBuiltin(id builtinId) *Builtin { + return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid], color_: black}, id} +} + +// Nil represents the predeclared value nil. +type Nil struct { + object +} + +func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { + var tname *TypeName + typ := obj.Type() + + switch obj := obj.(type) { + case *PkgName: + fmt.Fprintf(buf, "package %s", obj.Name()) + if path := obj.imported.path; path != "" && path != obj.name { + fmt.Fprintf(buf, " (%q)", path) + } + return + + case *Const: + buf.WriteString("const") + + case *TypeName: + tname = obj + buf.WriteString("type") + + case *Var: + if obj.isField { + buf.WriteString("field") + } else { + buf.WriteString("var") + } + + case *Func: + buf.WriteString("func ") + writeFuncName(buf, obj, qf) + if typ != nil { + WriteSignature(buf, typ.(*Signature), qf) + } + return + + case *Label: + buf.WriteString("label") + typ = nil + + case *Builtin: + buf.WriteString("builtin") + typ = nil + + case *Nil: + buf.WriteString("nil") + return + + default: + panic(fmt.Sprintf("writeObject(%T)", obj)) + } + + buf.WriteByte(' ') + + // For package-level objects, qualify the name. + if obj.Pkg() != nil && obj.Pkg().scope.Lookup(obj.Name()) == obj { + writePackage(buf, obj.Pkg(), qf) + } + buf.WriteString(obj.Name()) + + if typ == nil { + return + } + + if tname != nil { + // We have a type object: Don't print anything more for + // basic types since there's no more information (names + // are the same; see also comment in TypeName.IsAlias). + if _, ok := typ.(*Basic); ok { + return + } + if tname.IsAlias() { + buf.WriteString(" =") + } else { + typ = under(typ) + } + } + + buf.WriteByte(' ') + WriteType(buf, typ, qf) +} + +func writePackage(buf *bytes.Buffer, pkg *Package, qf Qualifier) { + if pkg == nil { + return + } + var s string + if qf != nil { + s = qf(pkg) + } else { + s = pkg.Path() + } + if s != "" { + buf.WriteString(s) + buf.WriteByte('.') + } +} + +// ObjectString returns the string form of obj. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func ObjectString(obj Object, qf Qualifier) string { + var buf bytes.Buffer + writeObject(&buf, obj, qf) + return buf.String() +} + +func (obj *PkgName) String() string { return ObjectString(obj, nil) } +func (obj *Const) String() string { return ObjectString(obj, nil) } +func (obj *TypeName) String() string { return ObjectString(obj, nil) } +func (obj *Var) String() string { return ObjectString(obj, nil) } +func (obj *Func) String() string { return ObjectString(obj, nil) } +func (obj *Label) String() string { return ObjectString(obj, nil) } +func (obj *Builtin) String() string { return ObjectString(obj, nil) } +func (obj *Nil) String() string { return ObjectString(obj, nil) } + +func writeFuncName(buf *bytes.Buffer, f *Func, qf Qualifier) { + if f.typ != nil { + sig := f.typ.(*Signature) + if recv := sig.Recv(); recv != nil { + buf.WriteByte('(') + if _, ok := recv.Type().(*Interface); ok { + // gcimporter creates abstract methods of + // named interfaces using the interface type + // (not the named type) as the receiver. + // Don't print it in full. + buf.WriteString("interface") + } else { + WriteType(buf, recv.Type(), qf) + } + buf.WriteByte(')') + buf.WriteByte('.') + } else if f.pkg != nil { + writePackage(buf, f.pkg, qf) + } + } + buf.WriteString(f.name) +} diff --git a/src/cmd/compile/internal/types2/object_test.go b/src/cmd/compile/internal/types2/object_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7f63c793325721dca1512a4628d970e31f523892 --- /dev/null +++ b/src/cmd/compile/internal/types2/object_test.go @@ -0,0 +1,88 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "strings" + "testing" +) + +func parseSrc(path, src string) (*syntax.File, error) { + return syntax.Parse(syntax.NewFileBase(path), strings.NewReader(src), nil, nil, 0) +} + +func TestIsAlias(t *testing.T) { + check := func(obj *TypeName, want bool) { + if got := obj.IsAlias(); got != want { + t.Errorf("%v: got IsAlias = %v; want %v", obj, got, want) + } + } + + // predeclared types + check(Unsafe.Scope().Lookup("Pointer").(*TypeName), false) + for _, name := range Universe.Names() { + if obj, _ := Universe.Lookup(name).(*TypeName); obj != nil { + check(obj, name == "byte" || name == "rune") + } + } + + // various other types + pkg := NewPackage("p", "p") + t1 := NewTypeName(nopos, pkg, "t1", nil) + n1 := NewNamed(t1, new(Struct), nil) + for _, test := range []struct { + name *TypeName + alias bool + }{ + {NewTypeName(nopos, nil, "t0", nil), false}, // no type yet + {NewTypeName(nopos, pkg, "t0", nil), false}, // no type yet + {t1, false}, // type name refers to named type and vice versa + {NewTypeName(nopos, nil, "t2", &emptyInterface), true}, // type name refers to unnamed type + {NewTypeName(nopos, pkg, "t3", n1), true}, // type name refers to named type with different type name + {NewTypeName(nopos, nil, "t4", Typ[Int32]), true}, // type name refers to basic type with different name + {NewTypeName(nopos, nil, "int32", Typ[Int32]), false}, // type name refers to basic type with same name + {NewTypeName(nopos, pkg, "int32", Typ[Int32]), true}, // type name is declared in user-defined package (outside Universe) + {NewTypeName(nopos, nil, "rune", Typ[Rune]), true}, // type name refers to basic type rune which is an alias already + } { + check(test.name, test.alias) + } +} + +// TestEmbeddedMethod checks that an embedded method is represented by +// the same Func Object as the original method. See also issue #34421. +func TestEmbeddedMethod(t *testing.T) { + const src = `package p; type I interface { error }` + + // type-check src + f, err := parseSrc("", src) + if err != nil { + t.Fatalf("parse failed: %s", err) + } + var conf Config + pkg, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, nil) + if err != nil { + t.Fatalf("typecheck failed: %s", err) + } + + // get original error.Error method + eface := Universe.Lookup("error") + orig, _, _ := LookupFieldOrMethod(eface.Type(), false, nil, "Error") + if orig == nil { + t.Fatalf("original error.Error not found") + } + + // get embedded error.Error method + iface := pkg.Scope().Lookup("I") + embed, _, _ := LookupFieldOrMethod(iface.Type(), false, nil, "Error") + if embed == nil { + t.Fatalf("embedded error.Error not found") + } + + // original and embedded Error object should be identical + if orig != embed { + t.Fatalf("%s (%p) != %s (%p)", orig, orig, embed, embed) + } +} diff --git a/src/cmd/compile/internal/types2/objset.go b/src/cmd/compile/internal/types2/objset.go new file mode 100644 index 0000000000000000000000000000000000000000..88ff0af9cab8c06fa44b7c41e1c02c9b0f2d6d14 --- /dev/null +++ b/src/cmd/compile/internal/types2/objset.go @@ -0,0 +1,31 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements objsets. +// +// An objset is similar to a Scope but objset elements +// are identified by their unique id, instead of their +// object name. + +package types2 + +// An objset is a set of objects identified by their unique id. +// The zero value for objset is a ready-to-use empty objset. +type objset map[string]Object // initialized lazily + +// insert attempts to insert an object obj into objset s. +// If s already contains an alternative object alt with +// the same name, insert leaves s unchanged and returns alt. +// Otherwise it inserts obj and returns nil. +func (s *objset) insert(obj Object) Object { + id := obj.Id() + if alt := (*s)[id]; alt != nil { + return alt + } + if *s == nil { + *s = make(map[string]Object) + } + (*s)[id] = obj + return nil +} diff --git a/src/cmd/compile/internal/types2/operand.go b/src/cmd/compile/internal/types2/operand.go new file mode 100644 index 0000000000000000000000000000000000000000..455d8b5dd1df22c40fb3b0bf12d8f8b914ed1ea9 --- /dev/null +++ b/src/cmd/compile/internal/types2/operand.go @@ -0,0 +1,318 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file defines operands and associated operations. + +package types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "go/constant" + "go/token" +) + +// An operandMode specifies the (addressing) mode of an operand. +type operandMode byte + +const ( + invalid operandMode = iota // operand is invalid + novalue // operand represents no value (result of a function call w/o result) + builtin // operand is a built-in function + typexpr // operand is a type + constant_ // operand is a constant; the operand's typ is a Basic type + variable // operand is an addressable variable + mapindex // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment) + value // operand is a computed value + nilvalue // operand is the nil value + commaok // like value, but operand may be used in a comma,ok expression + commaerr // like commaok, but second value is error, not boolean + cgofunc // operand is a cgo function +) + +var operandModeString = [...]string{ + invalid: "invalid operand", + novalue: "no value", + builtin: "built-in", + typexpr: "type", + constant_: "constant", + variable: "variable", + mapindex: "map index expression", + value: "value", + nilvalue: "nil", + commaok: "comma, ok expression", + commaerr: "comma, error expression", + cgofunc: "cgo function", +} + +// An operand represents an intermediate value during type checking. +// Operands have an (addressing) mode, the expression evaluating to +// the operand, the operand's type, a value for constants, and an id +// for built-in functions. +// The zero value of operand is a ready to use invalid operand. +// +type operand struct { + mode operandMode + expr syntax.Expr + typ Type + val constant.Value + id builtinId +} + +// Pos returns the position of the expression corresponding to x. +// If x is invalid the position is nopos. +// +func (x *operand) Pos() syntax.Pos { + // x.expr may not be set if x is invalid + if x.expr == nil { + return nopos + } + return x.expr.Pos() +} + +// Operand string formats +// (not all "untyped" cases can appear due to the type system, +// but they fall out naturally here) +// +// mode format +// +// invalid ( ) +// novalue ( ) +// builtin ( ) +// typexpr ( ) +// +// constant ( ) +// constant ( of type ) +// constant ( ) +// constant ( of type ) +// +// variable ( ) +// variable ( of type ) +// +// mapindex ( ) +// mapindex ( of type ) +// +// value ( ) +// value ( of type ) +// +// nilvalue untyped nil +// nilvalue nil ( of type ) +// +// commaok ( ) +// commaok ( of type ) +// +// commaerr ( ) +// commaerr ( of type ) +// +// cgofunc ( ) +// cgofunc ( of type ) +// +func operandString(x *operand, qf Qualifier) string { + // special-case nil + if x.mode == nilvalue { + switch x.typ { + case nil, Typ[Invalid]: + return "nil (with invalid type)" + case Typ[UntypedNil]: + return "untyped nil" + default: + return fmt.Sprintf("nil (of type %s)", TypeString(x.typ, qf)) + } + } + + var buf bytes.Buffer + + var expr string + if x.expr != nil { + expr = syntax.String(x.expr) + } else { + switch x.mode { + case builtin: + expr = predeclaredFuncs[x.id].name + case typexpr: + expr = TypeString(x.typ, qf) + case constant_: + expr = x.val.String() + } + } + + // ( + if expr != "" { + buf.WriteString(expr) + buf.WriteString(" (") + } + + // + hasType := false + switch x.mode { + case invalid, novalue, builtin, typexpr: + // no type + default: + // should have a type, but be cautious (don't crash during printing) + if x.typ != nil { + if isUntyped(x.typ) { + buf.WriteString(x.typ.(*Basic).name) + buf.WriteByte(' ') + break + } + hasType = true + } + } + + // + buf.WriteString(operandModeString[x.mode]) + + // + if x.mode == constant_ { + if s := x.val.String(); s != expr { + buf.WriteByte(' ') + buf.WriteString(s) + } + } + + // + if hasType { + if x.typ != Typ[Invalid] { + var intro string + switch { + case isGeneric(x.typ): + intro = " of generic type " + case asTypeParam(x.typ) != nil: + intro = " of type parameter type " + default: + intro = " of type " + } + buf.WriteString(intro) + WriteType(&buf, x.typ, qf) + } else { + buf.WriteString(" with invalid type") + } + } + + // ) + if expr != "" { + buf.WriteByte(')') + } + + return buf.String() +} + +func (x *operand) String() string { + return operandString(x, nil) +} + +// setConst sets x to the untyped constant for literal lit. +func (x *operand) setConst(k syntax.LitKind, lit string) { + var kind BasicKind + switch k { + case syntax.IntLit: + kind = UntypedInt + case syntax.FloatLit: + kind = UntypedFloat + case syntax.ImagLit: + kind = UntypedComplex + case syntax.RuneLit: + kind = UntypedRune + case syntax.StringLit: + kind = UntypedString + default: + unreachable() + } + + val := constant.MakeFromLiteral(lit, kind2tok[k], 0) + if val.Kind() == constant.Unknown { + x.mode = invalid + x.typ = Typ[Invalid] + return + } + x.mode = constant_ + x.typ = Typ[kind] + x.val = val +} + +// isNil reports whether x is a typed or the untyped nil value. +func (x *operand) isNil() bool { return x.mode == nilvalue } + +// assignableTo reports whether x is assignable to a variable of type T. If the +// result is false and a non-nil reason is provided, it may be set to a more +// detailed explanation of the failure (result != ""). The returned error code +// is only valid if the (first) result is false. The check parameter may be nil +// if assignableTo is invoked through an exported API call, i.e., when all +// methods have been type-checked. +func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, errorCode) { + if x.mode == invalid || T == Typ[Invalid] { + return true, 0 // avoid spurious errors + } + + V := x.typ + + // x's type is identical to T + if check.identical(V, T) { + return true, 0 + } + + Vu := optype(V) + Tu := optype(T) + + // x is an untyped value representable by a value of type T. + if isUntyped(Vu) { + if t, ok := Tu.(*Sum); ok { + return t.is(func(t Type) bool { + // TODO(gri) this could probably be more efficient + ok, _ := x.assignableTo(check, t, reason) + return ok + }), _IncompatibleAssign + } + newType, _, _ := check.implicitTypeAndValue(x, Tu) + return newType != nil, _IncompatibleAssign + } + // Vu is typed + + // x's type V and T have identical underlying types + // and at least one of V or T is not a named type + if check.identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) { + return true, 0 + } + + // T is an interface type and x implements T + if Ti, ok := Tu.(*Interface); ok { + if m, wrongType := check.missingMethod(V, Ti, true); m != nil /* Implements(V, Ti) */ { + if reason != nil { + if wrongType != nil { + if check.identical(m.typ, wrongType.typ) { + *reason = fmt.Sprintf("missing method %s (%s has pointer receiver)", m.name, m.name) + } else { + *reason = fmt.Sprintf("wrong type for method %s (have %s, want %s)", m.Name(), wrongType.typ, m.typ) + } + + } else { + *reason = "missing method " + m.Name() + } + } + return false, _InvalidIfaceAssign + } + return true, 0 + } + + // x is a bidirectional channel value, T is a channel + // type, x's type V and T have identical element types, + // and at least one of V or T is not a named type + if Vc, ok := Vu.(*Chan); ok && Vc.dir == SendRecv { + if Tc, ok := Tu.(*Chan); ok && check.identical(Vc.elem, Tc.elem) { + return !isNamed(V) || !isNamed(T), _InvalidChanAssign + } + } + + return false, _IncompatibleAssign +} + +// kind2tok translates syntax.LitKinds into token.Tokens. +var kind2tok = [...]token.Token{ + syntax.IntLit: token.INT, + syntax.FloatLit: token.FLOAT, + syntax.ImagLit: token.IMAG, + syntax.RuneLit: token.CHAR, + syntax.StringLit: token.STRING, +} diff --git a/src/cmd/compile/internal/types2/package.go b/src/cmd/compile/internal/types2/package.go new file mode 100644 index 0000000000000000000000000000000000000000..31b1e7178771f5db37264b4886b8b3604e03b4b3 --- /dev/null +++ b/src/cmd/compile/internal/types2/package.go @@ -0,0 +1,64 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "fmt" +) + +// A Package describes a Go package. +type Package struct { + path string + name string + scope *Scope + complete bool + imports []*Package + fake bool // scope lookup errors are silently dropped if package is fake (internal use only) + cgo bool // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go +} + +// NewPackage returns a new Package for the given package path and name. +// The package is not complete and contains no explicit imports. +func NewPackage(path, name string) *Package { + scope := NewScope(Universe, nopos, nopos, fmt.Sprintf("package %q", path)) + return &Package{path: path, name: name, scope: scope} +} + +// Path returns the package path. +func (pkg *Package) Path() string { return pkg.path } + +// Name returns the package name. +func (pkg *Package) Name() string { return pkg.name } + +// SetName sets the package name. +func (pkg *Package) SetName(name string) { pkg.name = name } + +// Scope returns the (complete or incomplete) package scope +// holding the objects declared at package level (TypeNames, +// Consts, Vars, and Funcs). +func (pkg *Package) Scope() *Scope { return pkg.scope } + +// A package is complete if its scope contains (at least) all +// exported objects; otherwise it is incomplete. +func (pkg *Package) Complete() bool { return pkg.complete } + +// MarkComplete marks a package as complete. +func (pkg *Package) MarkComplete() { pkg.complete = true } + +// Imports returns the list of packages directly imported by +// pkg; the list is in source order. +// +// If pkg was loaded from export data, Imports includes packages that +// provide package-level objects referenced by pkg. This may be more or +// less than the set of packages directly imported by pkg's source code. +func (pkg *Package) Imports() []*Package { return pkg.imports } + +// SetImports sets the list of explicitly imported packages to list. +// It is the caller's responsibility to make sure list elements are unique. +func (pkg *Package) SetImports(list []*Package) { pkg.imports = list } + +func (pkg *Package) String() string { + return fmt.Sprintf("package %s (%q)", pkg.name, pkg.path) +} diff --git a/src/cmd/compile/internal/types2/predicates.go b/src/cmd/compile/internal/types2/predicates.go new file mode 100644 index 0000000000000000000000000000000000000000..ae186a0b5d1efd402335101d6caa125ff277c3a2 --- /dev/null +++ b/src/cmd/compile/internal/types2/predicates.go @@ -0,0 +1,425 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements commonly used type predicates. + +package types2 + +// isNamed reports whether typ has a name. +// isNamed may be called with types that are not fully set up. +func isNamed(typ Type) bool { + switch typ.(type) { + case *Basic, *Named, *TypeParam, *instance: + return true + } + return false +} + +// isGeneric reports whether a type is a generic, uninstantiated type (generic +// signatures are not included). +func isGeneric(typ Type) bool { + // A parameterized type is only instantiated if it doesn't have an instantiation already. + named, _ := typ.(*Named) + return named != nil && named.obj != nil && named.tparams != nil && named.targs == nil +} + +func is(typ Type, what BasicInfo) bool { + switch t := optype(typ).(type) { + case *Basic: + return t.info&what != 0 + case *Sum: + return t.is(func(typ Type) bool { return is(typ, what) }) + } + return false +} + +func isBoolean(typ Type) bool { return is(typ, IsBoolean) } +func isInteger(typ Type) bool { return is(typ, IsInteger) } +func isUnsigned(typ Type) bool { return is(typ, IsUnsigned) } +func isFloat(typ Type) bool { return is(typ, IsFloat) } +func isComplex(typ Type) bool { return is(typ, IsComplex) } +func isNumeric(typ Type) bool { return is(typ, IsNumeric) } +func isString(typ Type) bool { return is(typ, IsString) } + +// Note that if typ is a type parameter, isInteger(typ) || isFloat(typ) does not +// produce the expected result because a type list that contains both an integer +// and a floating-point type is neither (all) integers, nor (all) floats. +// Use isIntegerOrFloat instead. +func isIntegerOrFloat(typ Type) bool { return is(typ, IsInteger|IsFloat) } + +// isNumericOrString is the equivalent of isIntegerOrFloat for isNumeric(typ) || isString(typ). +func isNumericOrString(typ Type) bool { return is(typ, IsNumeric|IsString) } + +// isTyped reports whether typ is typed; i.e., not an untyped +// constant or boolean. isTyped may be called with types that +// are not fully set up. +func isTyped(typ Type) bool { + // isTyped is called with types that are not fully + // set up. Must not call Basic()! + // A *Named or *instance type is always typed, so + // we only need to check if we have a true *Basic + // type. + t, _ := typ.(*Basic) + return t == nil || t.info&IsUntyped == 0 +} + +// isUntyped(typ) is the same as !isTyped(typ). +func isUntyped(typ Type) bool { + return !isTyped(typ) +} + +func isOrdered(typ Type) bool { return is(typ, IsOrdered) } + +func isConstType(typ Type) bool { + // Type parameters are never const types. + t, _ := under(typ).(*Basic) + return t != nil && t.info&IsConstType != 0 +} + +// IsInterface reports whether typ is an interface type. +func IsInterface(typ Type) bool { + return asInterface(typ) != nil +} + +// Comparable reports whether values of type T are comparable. +func Comparable(T Type) bool { + return comparable(T, nil) +} + +func comparable(T Type, seen map[Type]bool) bool { + if seen[T] { + return true + } + if seen == nil { + seen = make(map[Type]bool) + } + seen[T] = true + + // If T is a type parameter not constrained by any type + // list (i.e., it's underlying type is the top type), + // T is comparable if it has the == method. Otherwise, + // the underlying type "wins". For instance + // + // interface{ comparable; type []byte } + // + // is not comparable because []byte is not comparable. + if t := asTypeParam(T); t != nil && optype(t) == theTop { + return t.Bound().IsComparable() + } + + switch t := optype(T).(type) { + case *Basic: + // assume invalid types to be comparable + // to avoid follow-up errors + return t.kind != UntypedNil + case *Pointer, *Interface, *Chan: + return true + case *Struct: + for _, f := range t.fields { + if !comparable(f.typ, seen) { + return false + } + } + return true + case *Array: + return comparable(t.elem, seen) + case *Sum: + pred := func(t Type) bool { + return comparable(t, seen) + } + return t.is(pred) + case *TypeParam: + return t.Bound().IsComparable() + } + return false +} + +// hasNil reports whether a type includes the nil value. +func hasNil(typ Type) bool { + switch t := optype(typ).(type) { + case *Basic: + return t.kind == UnsafePointer + case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan: + return true + case *Sum: + return t.is(hasNil) + } + return false +} + +// identical reports whether x and y are identical types. +// Receivers of Signature types are ignored. +func (check *Checker) identical(x, y Type) bool { + return check.identical0(x, y, true, nil) +} + +// identicalIgnoreTags reports whether x and y are identical types if tags are ignored. +// Receivers of Signature types are ignored. +func (check *Checker) identicalIgnoreTags(x, y Type) bool { + return check.identical0(x, y, false, nil) +} + +// An ifacePair is a node in a stack of interface type pairs compared for identity. +type ifacePair struct { + x, y *Interface + prev *ifacePair +} + +func (p *ifacePair) identical(q *ifacePair) bool { + return p.x == q.x && p.y == q.y || p.x == q.y && p.y == q.x +} + +// For changes to this code the corresponding changes should be made to unifier.nify. +func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool { + // types must be expanded for comparison + x = expandf(x) + y = expandf(y) + + if x == y { + return true + } + + switch x := x.(type) { + case *Basic: + // Basic types are singletons except for the rune and byte + // aliases, thus we cannot solely rely on the x == y check + // above. See also comment in TypeName.IsAlias. + if y, ok := y.(*Basic); ok { + return x.kind == y.kind + } + + case *Array: + // Two array types are identical if they have identical element types + // and the same array length. + if y, ok := y.(*Array); ok { + // If one or both array lengths are unknown (< 0) due to some error, + // assume they are the same to avoid spurious follow-on errors. + return (x.len < 0 || y.len < 0 || x.len == y.len) && check.identical0(x.elem, y.elem, cmpTags, p) + } + + case *Slice: + // Two slice types are identical if they have identical element types. + if y, ok := y.(*Slice); ok { + return check.identical0(x.elem, y.elem, cmpTags, p) + } + + case *Struct: + // Two struct types are identical if they have the same sequence of fields, + // and if corresponding fields have the same names, and identical types, + // and identical tags. Two embedded fields are considered to have the same + // name. Lower-case field names from different packages are always different. + if y, ok := y.(*Struct); ok { + if x.NumFields() == y.NumFields() { + for i, f := range x.fields { + g := y.fields[i] + if f.embedded != g.embedded || + cmpTags && x.Tag(i) != y.Tag(i) || + !f.sameId(g.pkg, g.name) || + !check.identical0(f.typ, g.typ, cmpTags, p) { + return false + } + } + return true + } + } + + case *Pointer: + // Two pointer types are identical if they have identical base types. + if y, ok := y.(*Pointer); ok { + return check.identical0(x.base, y.base, cmpTags, p) + } + + case *Tuple: + // Two tuples types are identical if they have the same number of elements + // and corresponding elements have identical types. + if y, ok := y.(*Tuple); ok { + if x.Len() == y.Len() { + if x != nil { + for i, v := range x.vars { + w := y.vars[i] + if !check.identical0(v.typ, w.typ, cmpTags, p) { + return false + } + } + } + return true + } + } + + case *Signature: + // Two function types are identical if they have the same number of parameters + // and result values, corresponding parameter and result types are identical, + // and either both functions are variadic or neither is. Parameter and result + // names are not required to match. + // Generic functions must also have matching type parameter lists, but for the + // parameter names. + if y, ok := y.(*Signature); ok { + return x.variadic == y.variadic && + check.identicalTParams(x.tparams, y.tparams, cmpTags, p) && + check.identical0(x.params, y.params, cmpTags, p) && + check.identical0(x.results, y.results, cmpTags, p) + } + + case *Sum: + // Two sum types are identical if they contain the same types. + // (Sum types always consist of at least two types. Also, the + // the set (list) of types in a sum type consists of unique + // types - each type appears exactly once. Thus, two sum types + // must contain the same number of types to have chance of + // being equal. + if y, ok := y.(*Sum); ok && len(x.types) == len(y.types) { + // Every type in x.types must be in y.types. + // Quadratic algorithm, but probably good enough for now. + // TODO(gri) we need a fast quick type ID/hash for all types. + L: + for _, x := range x.types { + for _, y := range y.types { + if Identical(x, y) { + continue L // x is in y.types + } + } + return false // x is not in y.types + } + return true + } + + case *Interface: + // Two interface types are identical if they have the same set of methods with + // the same names and identical function types. Lower-case method names from + // different packages are always different. The order of the methods is irrelevant. + if y, ok := y.(*Interface); ok { + // If identical0 is called (indirectly) via an external API entry point + // (such as Identical, IdenticalIgnoreTags, etc.), check is nil. But in + // that case, interfaces are expected to be complete and lazy completion + // here is not needed. + if check != nil { + check.completeInterface(nopos, x) + check.completeInterface(nopos, y) + } + a := x.allMethods + b := y.allMethods + if len(a) == len(b) { + // Interface types are the only types where cycles can occur + // that are not "terminated" via named types; and such cycles + // can only be created via method parameter types that are + // anonymous interfaces (directly or indirectly) embedding + // the current interface. Example: + // + // type T interface { + // m() interface{T} + // } + // + // If two such (differently named) interfaces are compared, + // endless recursion occurs if the cycle is not detected. + // + // If x and y were compared before, they must be equal + // (if they were not, the recursion would have stopped); + // search the ifacePair stack for the same pair. + // + // This is a quadratic algorithm, but in practice these stacks + // are extremely short (bounded by the nesting depth of interface + // type declarations that recur via parameter types, an extremely + // rare occurrence). An alternative implementation might use a + // "visited" map, but that is probably less efficient overall. + q := &ifacePair{x, y, p} + for p != nil { + if p.identical(q) { + return true // same pair was compared before + } + p = p.prev + } + if debug { + assertSortedMethods(a) + assertSortedMethods(b) + } + for i, f := range a { + g := b[i] + if f.Id() != g.Id() || !check.identical0(f.typ, g.typ, cmpTags, q) { + return false + } + } + return true + } + } + + case *Map: + // Two map types are identical if they have identical key and value types. + if y, ok := y.(*Map); ok { + return check.identical0(x.key, y.key, cmpTags, p) && check.identical0(x.elem, y.elem, cmpTags, p) + } + + case *Chan: + // Two channel types are identical if they have identical value types + // and the same direction. + if y, ok := y.(*Chan); ok { + return x.dir == y.dir && check.identical0(x.elem, y.elem, cmpTags, p) + } + + case *Named: + // Two named types are identical if their type names originate + // in the same type declaration. + if y, ok := y.(*Named); ok { + // TODO(gri) Why is x == y not sufficient? And if it is, + // we can just return false here because x == y + // is caught in the very beginning of this function. + return x.obj == y.obj + } + + case *TypeParam: + // nothing to do (x and y being equal is caught in the very beginning of this function) + + // case *instance: + // unreachable since types are expanded + + case *bottom, *top: + // Either both types are theBottom, or both are theTop in which + // case the initial x == y check will have caught them. Otherwise + // they are not identical. + + case nil: + // avoid a crash in case of nil type + + default: + unreachable() + } + + return false +} + +func (check *Checker) identicalTParams(x, y []*TypeName, cmpTags bool, p *ifacePair) bool { + if len(x) != len(y) { + return false + } + for i, x := range x { + y := y[i] + if !check.identical0(x.typ.(*TypeParam).bound, y.typ.(*TypeParam).bound, cmpTags, p) { + return false + } + } + return true +} + +// Default returns the default "typed" type for an "untyped" type; +// it returns the incoming type for all other types. The default type +// for untyped nil is untyped nil. +// +func Default(typ Type) Type { + if t, ok := typ.(*Basic); ok { + switch t.kind { + case UntypedBool: + return Typ[Bool] + case UntypedInt: + return Typ[Int] + case UntypedRune: + return universeRune // use 'rune' name + case UntypedFloat: + return Typ[Float64] + case UntypedComplex: + return Typ[Complex128] + case UntypedString: + return Typ[String] + } + } + return typ +} diff --git a/src/cmd/compile/internal/types2/resolver.go b/src/cmd/compile/internal/types2/resolver.go new file mode 100644 index 0000000000000000000000000000000000000000..fa30650bd444f3d995b1939e897783e6ef50b768 --- /dev/null +++ b/src/cmd/compile/internal/types2/resolver.go @@ -0,0 +1,732 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "go/constant" + "sort" + "strconv" + "strings" + "unicode" +) + +// A declInfo describes a package-level const, type, var, or func declaration. +type declInfo struct { + file *Scope // scope of file containing this declaration + lhs []*Var // lhs of n:1 variable declarations, or nil + vtyp syntax.Expr // type, or nil (for const and var declarations only) + init syntax.Expr // init/orig expression, or nil (for const and var declarations only) + inherited bool // if set, the init expression is inherited from a previous constant declaration + tdecl *syntax.TypeDecl // type declaration, or nil + fdecl *syntax.FuncDecl // func declaration, or nil + + // The deps field tracks initialization expression dependencies. + deps map[Object]bool // lazily initialized +} + +// hasInitializer reports whether the declared object has an initialization +// expression or function body. +func (d *declInfo) hasInitializer() bool { + return d.init != nil || d.fdecl != nil && d.fdecl.Body != nil +} + +// addDep adds obj to the set of objects d's init expression depends on. +func (d *declInfo) addDep(obj Object) { + m := d.deps + if m == nil { + m = make(map[Object]bool) + d.deps = m + } + m[obj] = true +} + +// arity checks that the lhs and rhs of a const or var decl +// have a matching number of names and initialization values. +// If inherited is set, the initialization values are from +// another (constant) declaration. +func (check *Checker) arity(pos syntax.Pos, names []*syntax.Name, inits []syntax.Expr, constDecl, inherited bool) { + l := len(names) + r := len(inits) + + switch { + case l < r: + n := inits[l] + if inherited { + check.errorf(pos, "extra init expr at %s", n.Pos()) + } else { + check.errorf(n, "extra init expr %s", n) + } + case l > r && (constDecl || r != 1): // if r == 1 it may be a multi-valued function and we can't say anything yet + n := names[r] + check.errorf(n, "missing init expr for %s", n.Value) + } +} + +func validatedImportPath(path string) (string, error) { + s, err := strconv.Unquote(path) + if err != nil { + return "", err + } + if s == "" { + return "", fmt.Errorf("empty string") + } + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" + for _, r := range s { + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { + return s, fmt.Errorf("invalid character %#U", r) + } + } + return s, nil +} + +// declarePkgObj declares obj in the package scope, records its ident -> obj mapping, +// and updates check.objMap. The object must not be a function or method. +func (check *Checker) declarePkgObj(ident *syntax.Name, obj Object, d *declInfo) { + assert(ident.Value == obj.Name()) + + // spec: "A package-scope or file-scope identifier with name init + // may only be declared to be a function with this (func()) signature." + if ident.Value == "init" { + check.error(ident, "cannot declare init - must be func") + return + } + + // spec: "The main package must have package name main and declare + // a function main that takes no arguments and returns no value." + if ident.Value == "main" && check.pkg.name == "main" { + check.error(ident, "cannot declare main - must be func") + return + } + + check.declare(check.pkg.scope, ident, obj, nopos) + check.objMap[obj] = d + obj.setOrder(uint32(len(check.objMap))) +} + +// filename returns a filename suitable for debugging output. +func (check *Checker) filename(fileNo int) string { + file := check.files[fileNo] + if pos := file.Pos(); pos.IsKnown() { + // return check.fset.File(pos).Name() + // TODO(gri) do we need the actual file name here? + return pos.RelFilename() + } + return fmt.Sprintf("file[%d]", fileNo) +} + +func (check *Checker) importPackage(pos syntax.Pos, path, dir string) *Package { + // If we already have a package for the given (path, dir) + // pair, use it instead of doing a full import. + // Checker.impMap only caches packages that are marked Complete + // or fake (dummy packages for failed imports). Incomplete but + // non-fake packages do require an import to complete them. + key := importKey{path, dir} + imp := check.impMap[key] + if imp != nil { + return imp + } + + // no package yet => import it + if path == "C" && (check.conf.FakeImportC || check.conf.go115UsesCgo) { + imp = NewPackage("C", "C") + imp.fake = true // package scope is not populated + imp.cgo = check.conf.go115UsesCgo + } else { + // ordinary import + var err error + if importer := check.conf.Importer; importer == nil { + err = fmt.Errorf("Config.Importer not installed") + } else if importerFrom, ok := importer.(ImporterFrom); ok { + imp, err = importerFrom.ImportFrom(path, dir, 0) + if imp == nil && err == nil { + err = fmt.Errorf("Config.Importer.ImportFrom(%s, %s, 0) returned nil but no error", path, dir) + } + } else { + imp, err = importer.Import(path) + if imp == nil && err == nil { + err = fmt.Errorf("Config.Importer.Import(%s) returned nil but no error", path) + } + } + // make sure we have a valid package name + // (errors here can only happen through manipulation of packages after creation) + if err == nil && imp != nil && (imp.name == "_" || imp.name == "") { + err = fmt.Errorf("invalid package name: %q", imp.name) + imp = nil // create fake package below + } + if err != nil { + check.errorf(pos, "could not import %s (%s)", path, err) + if imp == nil { + // create a new fake package + // come up with a sensible package name (heuristic) + name := path + if i := len(name); i > 0 && name[i-1] == '/' { + name = name[:i-1] + } + if i := strings.LastIndex(name, "/"); i >= 0 { + name = name[i+1:] + } + imp = NewPackage(path, name) + } + // continue to use the package as best as we can + imp.fake = true // avoid follow-up lookup failures + } + } + + // package should be complete or marked fake, but be cautious + if imp.complete || imp.fake { + check.impMap[key] = imp + // Once we've formatted an error message once, keep the pkgPathMap + // up-to-date on subsequent imports. + if check.pkgPathMap != nil { + check.markImports(imp) + } + return imp + } + + // something went wrong (importer may have returned incomplete package without error) + return nil +} + +// collectObjects collects all file and package objects and inserts them +// into their respective scopes. It also performs imports and associates +// methods with receiver base type names. +func (check *Checker) collectObjects() { + pkg := check.pkg + + // pkgImports is the set of packages already imported by any package file seen + // so far. Used to avoid duplicate entries in pkg.imports. Allocate and populate + // it (pkg.imports may not be empty if we are checking test files incrementally). + // Note that pkgImports is keyed by package (and thus package path), not by an + // importKey value. Two different importKey values may map to the same package + // which is why we cannot use the check.impMap here. + var pkgImports = make(map[*Package]bool) + for _, imp := range pkg.imports { + pkgImports[imp] = true + } + + type methodInfo struct { + obj *Func // method + ptr bool // true if pointer receiver + recv *syntax.Name // receiver type name + } + var methods []methodInfo // collected methods with valid receivers and non-blank _ names + var fileScopes []*Scope + for fileNo, file := range check.files { + // The package identifier denotes the current package, + // but there is no corresponding package object. + check.recordDef(file.PkgName, nil) + + fileScope := NewScope(check.pkg.scope, syntax.StartPos(file), syntax.EndPos(file), check.filename(fileNo)) + fileScopes = append(fileScopes, fileScope) + check.recordScope(file, fileScope) + + // determine file directory, necessary to resolve imports + // FileName may be "" (typically for tests) in which case + // we get "." as the directory which is what we would want. + fileDir := dir(file.PkgName.Pos().RelFilename()) // TODO(gri) should this be filename? + + first := -1 // index of first ConstDecl in the current group, or -1 + var last *syntax.ConstDecl // last ConstDecl with init expressions, or nil + for index, decl := range file.DeclList { + if _, ok := decl.(*syntax.ConstDecl); !ok { + first = -1 // we're not in a constant declaration + } + + switch s := decl.(type) { + case *syntax.ImportDecl: + // import package + if s.Path == nil || s.Path.Bad { + continue // error reported during parsing + } + path, err := validatedImportPath(s.Path.Value) + if err != nil { + check.errorf(s.Path, "invalid import path (%s)", err) + continue + } + + imp := check.importPackage(s.Path.Pos(), path, fileDir) + if imp == nil { + continue + } + + // local name overrides imported package name + name := imp.name + if s.LocalPkgName != nil { + name = s.LocalPkgName.Value + if path == "C" { + // match cmd/compile (not prescribed by spec) + check.error(s.LocalPkgName, `cannot rename import "C"`) + continue + } + } + + if name == "init" { + check.error(s.LocalPkgName, "cannot import package as init - init must be a func") + continue + } + + // add package to list of explicit imports + // (this functionality is provided as a convenience + // for clients; it is not needed for type-checking) + if !pkgImports[imp] { + pkgImports[imp] = true + pkg.imports = append(pkg.imports, imp) + } + + pkgName := NewPkgName(s.Pos(), pkg, name, imp) + if s.LocalPkgName != nil { + // in a dot-import, the dot represents the package + check.recordDef(s.LocalPkgName, pkgName) + } else { + check.recordImplicit(s, pkgName) + } + + if path == "C" { + // match cmd/compile (not prescribed by spec) + pkgName.used = true + } + + // add import to file scope + check.imports = append(check.imports, pkgName) + if name == "." { + // dot-import + if check.dotImportMap == nil { + check.dotImportMap = make(map[dotImportKey]*PkgName) + } + // merge imported scope with file scope + for _, obj := range imp.scope.elems { + // A package scope may contain non-exported objects, + // do not import them! + if obj.Exported() { + // declare dot-imported object + // (Do not use check.declare because it modifies the object + // via Object.setScopePos, which leads to a race condition; + // the object may be imported into more than one file scope + // concurrently. See issue #32154.) + if alt := fileScope.Insert(obj); alt != nil { + var err error_ + err.errorf(s.LocalPkgName, "%s redeclared in this block", obj.Name()) + err.recordAltDecl(alt) + check.report(&err) + } else { + check.dotImportMap[dotImportKey{fileScope, obj}] = pkgName + } + } + } + } else { + // declare imported package object in file scope + // (no need to provide s.LocalPkgName since we called check.recordDef earlier) + check.declare(fileScope, nil, pkgName, nopos) + } + + case *syntax.ConstDecl: + // iota is the index of the current constDecl within the group + if first < 0 || file.DeclList[index-1].(*syntax.ConstDecl).Group != s.Group { + first = index + last = nil + } + iota := constant.MakeInt64(int64(index - first)) + + // determine which initialization expressions to use + inherited := true + switch { + case s.Type != nil || s.Values != nil: + last = s + inherited = false + case last == nil: + last = new(syntax.ConstDecl) // make sure last exists + inherited = false + } + + // declare all constants + values := unpackExpr(last.Values) + for i, name := range s.NameList { + obj := NewConst(name.Pos(), pkg, name.Value, nil, iota) + + var init syntax.Expr + if i < len(values) { + init = values[i] + } + + d := &declInfo{file: fileScope, vtyp: last.Type, init: init, inherited: inherited} + check.declarePkgObj(name, obj, d) + } + + // Constants must always have init values. + check.arity(s.Pos(), s.NameList, values, true, inherited) + + case *syntax.VarDecl: + lhs := make([]*Var, len(s.NameList)) + // If there's exactly one rhs initializer, use + // the same declInfo d1 for all lhs variables + // so that each lhs variable depends on the same + // rhs initializer (n:1 var declaration). + var d1 *declInfo + if _, ok := s.Values.(*syntax.ListExpr); !ok { + // The lhs elements are only set up after the for loop below, + // but that's ok because declarePkgObj only collects the declInfo + // for a later phase. + d1 = &declInfo{file: fileScope, lhs: lhs, vtyp: s.Type, init: s.Values} + } + + // declare all variables + values := unpackExpr(s.Values) + for i, name := range s.NameList { + obj := NewVar(name.Pos(), pkg, name.Value, nil) + lhs[i] = obj + + d := d1 + if d == nil { + // individual assignments + var init syntax.Expr + if i < len(values) { + init = values[i] + } + d = &declInfo{file: fileScope, vtyp: s.Type, init: init} + } + + check.declarePkgObj(name, obj, d) + } + + // If we have no type, we must have values. + if s.Type == nil || values != nil { + check.arity(s.Pos(), s.NameList, values, false, false) + } + + case *syntax.TypeDecl: + obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, nil) + check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, tdecl: s}) + + case *syntax.FuncDecl: + d := s // TODO(gri) get rid of this + name := d.Name.Value + obj := NewFunc(d.Name.Pos(), pkg, name, nil) + if d.Recv == nil { + // regular function + if name == "init" || name == "main" && pkg.name == "main" { + if d.TParamList != nil { + check.softErrorf(d, "func %s must have no type parameters", name) + } + if t := d.Type; len(t.ParamList) != 0 || len(t.ResultList) != 0 { + check.softErrorf(d, "func %s must have no arguments and no return values", name) + } + } + // don't declare init functions in the package scope - they are invisible + if name == "init" { + obj.parent = pkg.scope + check.recordDef(d.Name, obj) + // init functions must have a body + if d.Body == nil { + // TODO(gri) make this error message consistent with the others above + check.softErrorf(obj.pos, "missing function body") + } + } else { + check.declare(pkg.scope, d.Name, obj, nopos) + } + } else { + // method + // d.Recv != nil + if !acceptMethodTypeParams && len(d.TParamList) != 0 { + //check.error(d.TParamList.Pos(), invalidAST + "method must have no type parameters") + check.error(d, invalidAST+"method must have no type parameters") + } + ptr, recv, _ := check.unpackRecv(d.Recv.Type, false) + // (Methods with invalid receiver cannot be associated to a type, and + // methods with blank _ names are never found; no need to collect any + // of them. They will still be type-checked with all the other functions.) + if recv != nil && name != "_" { + methods = append(methods, methodInfo{obj, ptr, recv}) + } + check.recordDef(d.Name, obj) + } + info := &declInfo{file: fileScope, fdecl: d} + // Methods are not package-level objects but we still track them in the + // object map so that we can handle them like regular functions (if the + // receiver is invalid); also we need their fdecl info when associating + // them with their receiver base type, below. + check.objMap[obj] = info + obj.setOrder(uint32(len(check.objMap))) + + default: + check.errorf(s, invalidAST+"unknown syntax.Decl node %T", s) + } + } + } + + // verify that objects in package and file scopes have different names + for _, scope := range fileScopes { + for _, obj := range scope.elems { + if alt := pkg.scope.Lookup(obj.Name()); alt != nil { + var err error_ + if pkg, ok := obj.(*PkgName); ok { + err.errorf(alt, "%s already declared through import of %s", alt.Name(), pkg.Imported()) + err.recordAltDecl(pkg) + } else { + err.errorf(alt, "%s already declared through dot-import of %s", alt.Name(), obj.Pkg()) + // TODO(gri) dot-imported objects don't have a position; recordAltDecl won't print anything + err.recordAltDecl(obj) + } + check.report(&err) + } + } + } + + // Now that we have all package scope objects and all methods, + // associate methods with receiver base type name where possible. + // Ignore methods that have an invalid receiver. They will be + // type-checked later, with regular functions. + if methods != nil { + check.methods = make(map[*TypeName][]*Func) + for i := range methods { + m := &methods[i] + // Determine the receiver base type and associate m with it. + ptr, base := check.resolveBaseTypeName(m.ptr, m.recv) + if base != nil { + m.obj.hasPtrRecv = ptr + check.methods[base] = append(check.methods[base], m.obj) + } + } + } +} + +// unpackRecv unpacks a receiver type and returns its components: ptr indicates whether +// rtyp is a pointer receiver, rname is the receiver type name, and tparams are its +// type parameters, if any. The type parameters are only unpacked if unpackParams is +// set. If rname is nil, the receiver is unusable (i.e., the source has a bug which we +// cannot easily work around). +func (check *Checker) unpackRecv(rtyp syntax.Expr, unpackParams bool) (ptr bool, rname *syntax.Name, tparams []*syntax.Name) { +L: // unpack receiver type + // This accepts invalid receivers such as ***T and does not + // work for other invalid receivers, but we don't care. The + // validity of receiver expressions is checked elsewhere. + for { + switch t := rtyp.(type) { + case *syntax.ParenExpr: + rtyp = t.X + // case *ast.StarExpr: + // ptr = true + // rtyp = t.X + case *syntax.Operation: + if t.Op != syntax.Mul || t.Y != nil { + break + } + ptr = true + rtyp = t.X + default: + break L + } + } + + // unpack type parameters, if any + if ptyp, _ := rtyp.(*syntax.IndexExpr); ptyp != nil { + rtyp = ptyp.X + if unpackParams { + for _, arg := range unpackExpr(ptyp.Index) { + var par *syntax.Name + switch arg := arg.(type) { + case *syntax.Name: + par = arg + case *syntax.BadExpr: + // ignore - error already reported by parser + case nil: + check.error(ptyp, invalidAST+"parameterized receiver contains nil parameters") + default: + check.errorf(arg, "receiver type parameter %s must be an identifier", arg) + } + if par == nil { + par = syntax.NewName(arg.Pos(), "_") + } + tparams = append(tparams, par) + } + + } + } + + // unpack receiver name + if name, _ := rtyp.(*syntax.Name); name != nil { + rname = name + } + + return +} + +// resolveBaseTypeName returns the non-alias base type name for typ, and whether +// there was a pointer indirection to get to it. The base type name must be declared +// in package scope, and there can be at most one pointer indirection. If no such type +// name exists, the returned base is nil. +func (check *Checker) resolveBaseTypeName(seenPtr bool, typ syntax.Expr) (ptr bool, base *TypeName) { + // Algorithm: Starting from a type expression, which may be a name, + // we follow that type through alias declarations until we reach a + // non-alias type name. If we encounter anything but pointer types or + // parentheses we're done. If we encounter more than one pointer type + // we're done. + ptr = seenPtr + var seen map[*TypeName]bool + for { + typ = unparen(typ) + + // check if we have a pointer type + // if pexpr, _ := typ.(*ast.StarExpr); pexpr != nil { + if pexpr, _ := typ.(*syntax.Operation); pexpr != nil && pexpr.Op == syntax.Mul && pexpr.Y == nil { + // if we've already seen a pointer, we're done + if ptr { + return false, nil + } + ptr = true + typ = unparen(pexpr.X) // continue with pointer base type + } + + // typ must be a name + name, _ := typ.(*syntax.Name) + if name == nil { + return false, nil + } + + // name must denote an object found in the current package scope + // (note that dot-imported objects are not in the package scope!) + obj := check.pkg.scope.Lookup(name.Value) + if obj == nil { + return false, nil + } + + // the object must be a type name... + tname, _ := obj.(*TypeName) + if tname == nil { + return false, nil + } + + // ... which we have not seen before + if seen[tname] { + return false, nil + } + + // we're done if tdecl defined tname as a new type + // (rather than an alias) + tdecl := check.objMap[tname].tdecl // must exist for objects in package scope + if !tdecl.Alias { + return ptr, tname + } + + // otherwise, continue resolving + typ = tdecl.Type + if seen == nil { + seen = make(map[*TypeName]bool) + } + seen[tname] = true + } +} + +// packageObjects typechecks all package objects, but not function bodies. +func (check *Checker) packageObjects() { + // process package objects in source order for reproducible results + objList := make([]Object, len(check.objMap)) + i := 0 + for obj := range check.objMap { + objList[i] = obj + i++ + } + sort.Sort(inSourceOrder(objList)) + + // add new methods to already type-checked types (from a prior Checker.Files call) + for _, obj := range objList { + if obj, _ := obj.(*TypeName); obj != nil && obj.typ != nil { + check.collectMethods(obj) + } + } + + // We process non-alias declarations first, in order to avoid situations where + // the type of an alias declaration is needed before it is available. In general + // this is still not enough, as it is possible to create sufficiently convoluted + // recursive type definitions that will cause a type alias to be needed before it + // is available (see issue #25838 for examples). + // As an aside, the cmd/compiler suffers from the same problem (#25838). + var aliasList []*TypeName + // phase 1 + for _, obj := range objList { + // If we have a type alias, collect it for the 2nd phase. + if tname, _ := obj.(*TypeName); tname != nil && check.objMap[tname].tdecl.Alias { + aliasList = append(aliasList, tname) + continue + } + + check.objDecl(obj, nil) + } + // phase 2 + for _, obj := range aliasList { + check.objDecl(obj, nil) + } + + // At this point we may have a non-empty check.methods map; this means that not all + // entries were deleted at the end of typeDecl because the respective receiver base + // types were not found. In that case, an error was reported when declaring those + // methods. We can now safely discard this map. + check.methods = nil +} + +// inSourceOrder implements the sort.Sort interface. +type inSourceOrder []Object + +func (a inSourceOrder) Len() int { return len(a) } +func (a inSourceOrder) Less(i, j int) bool { return a[i].order() < a[j].order() } +func (a inSourceOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// unusedImports checks for unused imports. +func (check *Checker) unusedImports() { + // if function bodies are not checked, packages' uses are likely missing - don't check + if check.conf.IgnoreFuncBodies { + return + } + + // spec: "It is illegal (...) to directly import a package without referring to + // any of its exported identifiers. To import a package solely for its side-effects + // (initialization), use the blank identifier as explicit package name." + + for _, obj := range check.imports { + if !obj.used && obj.name != "_" { + check.errorUnusedPkg(obj) + } + } +} + +func (check *Checker) errorUnusedPkg(obj *PkgName) { + // If the package was imported with a name other than the final + // import path element, show it explicitly in the error message. + // Note that this handles both renamed imports and imports of + // packages containing unconventional package declarations. + // Note that this uses / always, even on Windows, because Go import + // paths always use forward slashes. + path := obj.imported.path + elem := path + if i := strings.LastIndex(elem, "/"); i >= 0 { + elem = elem[i+1:] + } + if obj.name == "" || obj.name == "." || obj.name == elem { + if check.conf.CompilerErrorMessages { + check.softErrorf(obj, "imported and not used: %q", path) + } else { + check.softErrorf(obj, "%q imported but not used", path) + } + } else { + if check.conf.CompilerErrorMessages { + check.softErrorf(obj, "imported and not used: %q as %s", path, obj.name) + } else { + check.softErrorf(obj, "%q imported but not used as %s", path, obj.name) + } + } +} + +// dir makes a good-faith attempt to return the directory +// portion of path. If path is empty, the result is ".". +// (Per the go/build package dependency tests, we cannot import +// path/filepath and simply use filepath.Dir.) +func dir(path string) string { + if i := strings.LastIndexAny(path, `/\`); i > 0 { + return path[:i] + } + // i <= 0 + return "." +} diff --git a/src/cmd/compile/internal/types2/resolver_test.go b/src/cmd/compile/internal/types2/resolver_test.go new file mode 100644 index 0000000000000000000000000000000000000000..aee435ff5fb6cd7e0aa6d0d34028290197e71968 --- /dev/null +++ b/src/cmd/compile/internal/types2/resolver_test.go @@ -0,0 +1,222 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2_test + +import ( + "cmd/compile/internal/syntax" + "fmt" + "internal/testenv" + "sort" + "testing" + + . "cmd/compile/internal/types2" +) + +type resolveTestImporter struct { + importer ImporterFrom + imported map[string]bool +} + +func (imp *resolveTestImporter) Import(string) (*Package, error) { + panic("should not be called") +} + +func (imp *resolveTestImporter) ImportFrom(path, srcDir string, mode ImportMode) (*Package, error) { + if mode != 0 { + panic("mode must be 0") + } + if imp.importer == nil { + imp.importer = defaultImporter().(ImporterFrom) + imp.imported = make(map[string]bool) + } + pkg, err := imp.importer.ImportFrom(path, srcDir, mode) + if err != nil { + return nil, err + } + imp.imported[path] = true + return pkg, nil +} + +func TestResolveIdents(t *testing.T) { + testenv.MustHaveGoBuild(t) + + sources := []string{ + ` + package p + import "fmt" + import "math" + const pi = math.Pi + func sin(x float64) float64 { + return math.Sin(x) + } + var Println = fmt.Println + `, + ` + package p + import "fmt" + type errorStringer struct { fmt.Stringer; error } + func f() string { + _ = "foo" + return fmt.Sprintf("%d", g()) + } + func g() (x int) { return } + `, + ` + package p + import . "go/parser" + import "sync" + func h() Mode { return ImportsOnly } + var _, x int = 1, 2 + func init() {} + type T struct{ *sync.Mutex; a, b, c int} + type I interface{ m() } + var _ = T{a: 1, b: 2, c: 3} + func (_ T) m() {} + func (T) _() {} + var i I + var _ = i.m + func _(s []int) { for i, x := range s { _, _ = i, x } } + func _(x interface{}) { + switch x := x.(type) { + case int: + _ = x + } + switch {} // implicit 'true' tag + } + `, + ` + package p + type S struct{} + func (T) _() {} + func (T) _() {} + `, + ` + package p + func _() { + L0: + L1: + goto L0 + for { + goto L1 + } + if true { + goto L2 + } + L2: + } + `, + } + + pkgnames := []string{ + "fmt", + "math", + } + + // parse package files + var files []*syntax.File + for i, src := range sources { + f, err := parseSrc(fmt.Sprintf("sources[%d]", i), src) + if err != nil { + t.Fatal(err) + } + files = append(files, f) + } + + // resolve and type-check package AST + importer := new(resolveTestImporter) + conf := Config{Importer: importer} + uses := make(map[*syntax.Name]Object) + defs := make(map[*syntax.Name]Object) + _, err := conf.Check("testResolveIdents", files, &Info{Defs: defs, Uses: uses}) + if err != nil { + t.Fatal(err) + } + + // check that all packages were imported + for _, name := range pkgnames { + if !importer.imported[name] { + t.Errorf("package %s not imported", name) + } + } + + // check that qualified identifiers are resolved + for _, f := range files { + syntax.Walk(f, func(n syntax.Node) bool { + if s, ok := n.(*syntax.SelectorExpr); ok { + if x, ok := s.X.(*syntax.Name); ok { + obj := uses[x] + if obj == nil { + t.Errorf("%s: unresolved qualified identifier %s", x.Pos(), x.Value) + return true + } + if _, ok := obj.(*PkgName); ok && uses[s.Sel] == nil { + t.Errorf("%s: unresolved selector %s", s.Sel.Pos(), s.Sel.Value) + return true + } + return true + } + return true + } + return false + }) + } + + for id, obj := range uses { + if obj == nil { + t.Errorf("%s: Uses[%s] == nil", id.Pos(), id.Value) + } + } + + // Check that each identifier in the source is found in uses or defs or both. + // We need the foundUses/Defs maps (rather then just deleting the found objects + // from the uses and defs maps) because syntax.Walk traverses shared nodes multiple + // times (e.g. types in field lists such as "a, b, c int"). + foundUses := make(map[*syntax.Name]bool) + foundDefs := make(map[*syntax.Name]bool) + var both []string + for _, f := range files { + syntax.Walk(f, func(n syntax.Node) bool { + if x, ok := n.(*syntax.Name); ok { + var objects int + if _, found := uses[x]; found { + objects |= 1 + foundUses[x] = true + } + if _, found := defs[x]; found { + objects |= 2 + foundDefs[x] = true + } + switch objects { + case 0: + t.Errorf("%s: unresolved identifier %s", x.Pos(), x.Value) + case 3: + both = append(both, x.Value) + } + return true + } + return false + }) + } + + // check the expected set of idents that are simultaneously uses and defs + sort.Strings(both) + if got, want := fmt.Sprint(both), "[Mutex Stringer error]"; got != want { + t.Errorf("simultaneous uses/defs = %s, want %s", got, want) + } + + // any left-over identifiers didn't exist in the source + for x := range uses { + if !foundUses[x] { + t.Errorf("%s: identifier %s not present in source", x.Pos(), x.Value) + } + } + for x := range defs { + if !foundDefs[x] { + t.Errorf("%s: identifier %s not present in source", x.Pos(), x.Value) + } + } + + // TODO(gri) add tests to check ImplicitObj callbacks +} diff --git a/src/cmd/compile/internal/types2/return.go b/src/cmd/compile/internal/types2/return.go new file mode 100644 index 0000000000000000000000000000000000000000..204e456a916fb127e02b3fa9aee0f6c41cd5971b --- /dev/null +++ b/src/cmd/compile/internal/types2/return.go @@ -0,0 +1,179 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements isTerminating. + +package types2 + +import ( + "cmd/compile/internal/syntax" +) + +// isTerminating reports if s is a terminating statement. +// If s is labeled, label is the label name; otherwise s +// is "". +func (check *Checker) isTerminating(s syntax.Stmt, label string) bool { + switch s := s.(type) { + default: + unreachable() + + case *syntax.DeclStmt, *syntax.EmptyStmt, *syntax.SendStmt, + *syntax.AssignStmt, *syntax.CallStmt: + // no chance + + case *syntax.LabeledStmt: + return check.isTerminating(s.Stmt, s.Label.Value) + + case *syntax.ExprStmt: + // calling the predeclared (possibly parenthesized) panic() function is terminating + if call, ok := unparen(s.X).(*syntax.CallExpr); ok && check.isPanic[call] { + return true + } + + case *syntax.ReturnStmt: + return true + + case *syntax.BranchStmt: + if s.Tok == syntax.Goto || s.Tok == syntax.Fallthrough { + return true + } + + case *syntax.BlockStmt: + return check.isTerminatingList(s.List, "") + + case *syntax.IfStmt: + if s.Else != nil && + check.isTerminating(s.Then, "") && + check.isTerminating(s.Else, "") { + return true + } + + case *syntax.SwitchStmt: + return check.isTerminatingSwitch(s.Body, label) + + case *syntax.SelectStmt: + for _, cc := range s.Body { + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + + } + return true + + case *syntax.ForStmt: + if s.Cond == nil && !hasBreak(s.Body, label, true) { + return true + } + } + + return false +} + +func (check *Checker) isTerminatingList(list []syntax.Stmt, label string) bool { + // trailing empty statements are permitted - skip them + for i := len(list) - 1; i >= 0; i-- { + if _, ok := list[i].(*syntax.EmptyStmt); !ok { + return check.isTerminating(list[i], label) + } + } + return false // all statements are empty +} + +func (check *Checker) isTerminatingSwitch(body []*syntax.CaseClause, label string) bool { + hasDefault := false + for _, cc := range body { + if cc.Cases == nil { + hasDefault = true + } + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + } + return hasDefault +} + +// TODO(gri) For nested breakable statements, the current implementation of hasBreak +// will traverse the same subtree repeatedly, once for each label. Replace +// with a single-pass label/break matching phase. + +// hasBreak reports if s is or contains a break statement +// referring to the label-ed statement or implicit-ly the +// closest outer breakable statement. +func hasBreak(s syntax.Stmt, label string, implicit bool) bool { + switch s := s.(type) { + default: + unreachable() + + case *syntax.DeclStmt, *syntax.EmptyStmt, *syntax.ExprStmt, + *syntax.SendStmt, *syntax.AssignStmt, *syntax.CallStmt, + *syntax.ReturnStmt: + // no chance + + case *syntax.LabeledStmt: + return hasBreak(s.Stmt, label, implicit) + + case *syntax.BranchStmt: + if s.Tok == syntax.Break { + if s.Label == nil { + return implicit + } + if s.Label.Value == label { + return true + } + } + + case *syntax.BlockStmt: + return hasBreakList(s.List, label, implicit) + + case *syntax.IfStmt: + if hasBreak(s.Then, label, implicit) || + s.Else != nil && hasBreak(s.Else, label, implicit) { + return true + } + + case *syntax.SwitchStmt: + if label != "" && hasBreakCaseList(s.Body, label, false) { + return true + } + + case *syntax.SelectStmt: + if label != "" && hasBreakCommList(s.Body, label, false) { + return true + } + + case *syntax.ForStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + } + + return false +} + +func hasBreakList(list []syntax.Stmt, label string, implicit bool) bool { + for _, s := range list { + if hasBreak(s, label, implicit) { + return true + } + } + return false +} + +func hasBreakCaseList(list []*syntax.CaseClause, label string, implicit bool) bool { + for _, s := range list { + if hasBreakList(s.Body, label, implicit) { + return true + } + } + return false +} + +func hasBreakCommList(list []*syntax.CommClause, label string, implicit bool) bool { + for _, s := range list { + if hasBreakList(s.Body, label, implicit) { + return true + } + } + return false +} diff --git a/src/cmd/compile/internal/types2/sanitize.go b/src/cmd/compile/internal/types2/sanitize.go new file mode 100644 index 0000000000000000000000000000000000000000..64a2dedc7d83d9972af155599757891b41e8a549 --- /dev/null +++ b/src/cmd/compile/internal/types2/sanitize.go @@ -0,0 +1,202 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +// sanitizeInfo walks the types contained in info to ensure that all instances +// are expanded. +// +// This includes some objects that may be shared across concurrent +// type-checking passes (such as those in the universe scope), so we are +// careful here not to write types that are already sanitized. This avoids a +// data race as any shared types should already be sanitized. +func sanitizeInfo(info *Info) { + var s sanitizer = make(map[Type]Type) + + // Note: Some map entries are not references. + // If modified, they must be assigned back. + + for e, tv := range info.Types { + if typ := s.typ(tv.Type); typ != tv.Type { + tv.Type = typ + info.Types[e] = tv + } + } + + for e, inf := range info.Inferred { + changed := false + for i, targ := range inf.Targs { + if typ := s.typ(targ); typ != targ { + inf.Targs[i] = typ + changed = true + } + } + if typ := s.typ(inf.Sig); typ != inf.Sig { + inf.Sig = typ.(*Signature) + changed = true + } + if changed { + info.Inferred[e] = inf + } + } + + for _, obj := range info.Defs { + if obj != nil { + if typ := s.typ(obj.Type()); typ != obj.Type() { + obj.setType(typ) + } + } + } + + for _, obj := range info.Uses { + if obj != nil { + if typ := s.typ(obj.Type()); typ != obj.Type() { + obj.setType(typ) + } + } + } + + // TODO(gri) sanitize as needed + // - info.Implicits + // - info.Selections + // - info.Scopes + // - info.InitOrder +} + +type sanitizer map[Type]Type + +func (s sanitizer) typ(typ Type) Type { + if typ == nil { + return nil + } + + if t, found := s[typ]; found { + return t + } + s[typ] = typ + + switch t := typ.(type) { + case *Basic, *bottom, *top: + // nothing to do + + case *Array: + if elem := s.typ(t.elem); elem != t.elem { + t.elem = elem + } + + case *Slice: + if elem := s.typ(t.elem); elem != t.elem { + t.elem = elem + } + + case *Struct: + s.varList(t.fields) + + case *Pointer: + if base := s.typ(t.base); base != t.base { + t.base = base + } + + case *Tuple: + s.tuple(t) + + case *Signature: + s.var_(t.recv) + s.tuple(t.params) + s.tuple(t.results) + + case *Sum: + s.typeList(t.types) + + case *Interface: + s.funcList(t.methods) + if types := s.typ(t.types); types != t.types { + t.types = types + } + s.typeList(t.embeddeds) + s.funcList(t.allMethods) + if allTypes := s.typ(t.allTypes); allTypes != t.allTypes { + t.allTypes = allTypes + } + + case *Map: + if key := s.typ(t.key); key != t.key { + t.key = key + } + if elem := s.typ(t.elem); elem != t.elem { + t.elem = elem + } + + case *Chan: + if elem := s.typ(t.elem); elem != t.elem { + t.elem = elem + } + + case *Named: + if orig := s.typ(t.fromRHS); orig != t.fromRHS { + t.fromRHS = orig + } + if under := s.typ(t.underlying); under != t.underlying { + t.underlying = under + } + s.typeList(t.targs) + s.funcList(t.methods) + + case *TypeParam: + if bound := s.typ(t.bound); bound != t.bound { + t.bound = bound + } + + case *instance: + typ = t.expand() + s[t] = typ + + default: + panic("unimplemented") + } + + return typ +} + +func (s sanitizer) var_(v *Var) { + if v != nil { + if typ := s.typ(v.typ); typ != v.typ { + v.typ = typ + } + } +} + +func (s sanitizer) varList(list []*Var) { + for _, v := range list { + s.var_(v) + } +} + +func (s sanitizer) tuple(t *Tuple) { + if t != nil { + s.varList(t.vars) + } +} + +func (s sanitizer) func_(f *Func) { + if f != nil { + if typ := s.typ(f.typ); typ != f.typ { + f.typ = typ + } + } +} + +func (s sanitizer) funcList(list []*Func) { + for _, f := range list { + s.func_(f) + } +} + +func (s sanitizer) typeList(list []Type) { + for i, t := range list { + if typ := s.typ(t); typ != t { + list[i] = typ + } + } +} diff --git a/src/cmd/compile/internal/types2/scope.go b/src/cmd/compile/internal/types2/scope.go new file mode 100644 index 0000000000000000000000000000000000000000..ade0a79b31d11daf4fab28c7094d15b8175f22b7 --- /dev/null +++ b/src/cmd/compile/internal/types2/scope.go @@ -0,0 +1,216 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements Scopes. + +package types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "io" + "sort" + "strings" +) + +// A Scope maintains a set of objects and links to its containing +// (parent) and contained (children) scopes. Objects may be inserted +// and looked up by name. The zero value for Scope is a ready-to-use +// empty scope. +type Scope struct { + parent *Scope + children []*Scope + elems map[string]Object // lazily allocated + pos, end syntax.Pos // scope extent; may be invalid + comment string // for debugging only + isFunc bool // set if this is a function scope (internal use only) +} + +// NewScope returns a new, empty scope contained in the given parent +// scope, if any. The comment is for debugging only. +func NewScope(parent *Scope, pos, end syntax.Pos, comment string) *Scope { + s := &Scope{parent, nil, nil, pos, end, comment, false} + // don't add children to Universe scope! + if parent != nil && parent != Universe { + parent.children = append(parent.children, s) + } + return s +} + +// Parent returns the scope's containing (parent) scope. +func (s *Scope) Parent() *Scope { return s.parent } + +// Len returns the number of scope elements. +func (s *Scope) Len() int { return len(s.elems) } + +// Names returns the scope's element names in sorted order. +func (s *Scope) Names() []string { + names := make([]string, len(s.elems)) + i := 0 + for name := range s.elems { + names[i] = name + i++ + } + sort.Strings(names) + return names +} + +// NumChildren returns the number of scopes nested in s. +func (s *Scope) NumChildren() int { return len(s.children) } + +// Child returns the i'th child scope for 0 <= i < NumChildren(). +func (s *Scope) Child(i int) *Scope { return s.children[i] } + +// Lookup returns the object in scope s with the given name if such an +// object exists; otherwise the result is nil. +func (s *Scope) Lookup(name string) Object { + return s.elems[name] +} + +// LookupParent follows the parent chain of scopes starting with s until +// it finds a scope where Lookup(name) returns a non-nil object, and then +// returns that scope and object. If a valid position pos is provided, +// only objects that were declared at or before pos are considered. +// If no such scope and object exists, the result is (nil, nil). +// +// Note that obj.Parent() may be different from the returned scope if the +// object was inserted into the scope and already had a parent at that +// time (see Insert). This can only happen for dot-imported objects +// whose scope is the scope of the package that exported them. +func (s *Scope) LookupParent(name string, pos syntax.Pos) (*Scope, Object) { + for ; s != nil; s = s.parent { + if obj := s.elems[name]; obj != nil && (!pos.IsKnown() || obj.scopePos().Cmp(pos) <= 0) { + return s, obj + } + } + return nil, nil +} + +// Insert attempts to insert an object obj into scope s. +// If s already contains an alternative object alt with +// the same name, Insert leaves s unchanged and returns alt. +// Otherwise it inserts obj, sets the object's parent scope +// if not already set, and returns nil. +func (s *Scope) Insert(obj Object) Object { + name := obj.Name() + if alt := s.elems[name]; alt != nil { + return alt + } + if s.elems == nil { + s.elems = make(map[string]Object) + } + s.elems[name] = obj + if obj.Parent() == nil { + obj.setParent(s) + } + return nil +} + +// Squash merges s with its parent scope p by adding all +// objects of s to p, adding all children of s to the +// children of p, and removing s from p's children. +// The function f is called for each object obj in s which +// has an object alt in p. s should be discarded after +// having been squashed. +func (s *Scope) Squash(err func(obj, alt Object)) { + p := s.parent + assert(p != nil) + for _, obj := range s.elems { + obj.setParent(nil) + if alt := p.Insert(obj); alt != nil { + err(obj, alt) + } + } + + j := -1 // index of s in p.children + for i, ch := range p.children { + if ch == s { + j = i + break + } + } + assert(j >= 0) + k := len(p.children) - 1 + p.children[j] = p.children[k] + p.children = p.children[:k] + + p.children = append(p.children, s.children...) + + s.children = nil + s.elems = nil +} + +// Pos and End describe the scope's source code extent [pos, end). +// The results are guaranteed to be valid only if the type-checked +// AST has complete position information. The extent is undefined +// for Universe and package scopes. +func (s *Scope) Pos() syntax.Pos { return s.pos } +func (s *Scope) End() syntax.Pos { return s.end } + +// Contains reports whether pos is within the scope's extent. +// The result is guaranteed to be valid only if the type-checked +// AST has complete position information. +func (s *Scope) Contains(pos syntax.Pos) bool { + return s.pos.Cmp(pos) <= 0 && pos.Cmp(s.end) < 0 +} + +// Innermost returns the innermost (child) scope containing +// pos. If pos is not within any scope, the result is nil. +// The result is also nil for the Universe scope. +// The result is guaranteed to be valid only if the type-checked +// AST has complete position information. +func (s *Scope) Innermost(pos syntax.Pos) *Scope { + // Package scopes do not have extents since they may be + // discontiguous, so iterate over the package's files. + if s.parent == Universe { + for _, s := range s.children { + if inner := s.Innermost(pos); inner != nil { + return inner + } + } + } + + if s.Contains(pos) { + for _, s := range s.children { + if s.Contains(pos) { + return s.Innermost(pos) + } + } + return s + } + return nil +} + +// WriteTo writes a string representation of the scope to w, +// with the scope elements sorted by name. +// The level of indentation is controlled by n >= 0, with +// n == 0 for no indentation. +// If recurse is set, it also writes nested (children) scopes. +func (s *Scope) WriteTo(w io.Writer, n int, recurse bool) { + const ind = ". " + indn := strings.Repeat(ind, n) + + fmt.Fprintf(w, "%s%s scope %p {\n", indn, s.comment, s) + + indn1 := indn + ind + for _, name := range s.Names() { + fmt.Fprintf(w, "%s%s\n", indn1, s.elems[name]) + } + + if recurse { + for _, s := range s.children { + s.WriteTo(w, n+1, recurse) + } + } + + fmt.Fprintf(w, "%s}\n", indn) +} + +// String returns a string representation of the scope, for debugging. +func (s *Scope) String() string { + var buf bytes.Buffer + s.WriteTo(&buf, 0, false) + return buf.String() +} diff --git a/src/cmd/compile/internal/types2/selection.go b/src/cmd/compile/internal/types2/selection.go new file mode 100644 index 0000000000000000000000000000000000000000..8128aeee2e5113b6979990cfba4e94e373b206a9 --- /dev/null +++ b/src/cmd/compile/internal/types2/selection.go @@ -0,0 +1,143 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements Selections. + +package types2 + +import ( + "bytes" + "fmt" +) + +// SelectionKind describes the kind of a selector expression x.f +// (excluding qualified identifiers). +type SelectionKind int + +const ( + FieldVal SelectionKind = iota // x.f is a struct field selector + MethodVal // x.f is a method selector + MethodExpr // x.f is a method expression +) + +// A Selection describes a selector expression x.f. +// For the declarations: +// +// type T struct{ x int; E } +// type E struct{} +// func (e E) m() {} +// var p *T +// +// the following relations exist: +// +// Selector Kind Recv Obj Type Index Indirect +// +// p.x FieldVal T x int {0} true +// p.m MethodVal *T m func() {1, 0} true +// T.m MethodExpr T m func(T) {1, 0} false +// +type Selection struct { + kind SelectionKind + recv Type // type of x + obj Object // object denoted by x.f + index []int // path from x to x.f + indirect bool // set if there was any pointer indirection on the path +} + +// Kind returns the selection kind. +func (s *Selection) Kind() SelectionKind { return s.kind } + +// Recv returns the type of x in x.f. +func (s *Selection) Recv() Type { return s.recv } + +// Obj returns the object denoted by x.f; a *Var for +// a field selection, and a *Func in all other cases. +func (s *Selection) Obj() Object { return s.obj } + +// Type returns the type of x.f, which may be different from the type of f. +// See Selection for more information. +func (s *Selection) Type() Type { + switch s.kind { + case MethodVal: + // The type of x.f is a method with its receiver type set + // to the type of x. + sig := *s.obj.(*Func).typ.(*Signature) + recv := *sig.recv + recv.typ = s.recv + sig.recv = &recv + return &sig + + case MethodExpr: + // The type of x.f is a function (without receiver) + // and an additional first argument with the same type as x. + // TODO(gri) Similar code is already in call.go - factor! + // TODO(gri) Compute this eagerly to avoid allocations. + sig := *s.obj.(*Func).typ.(*Signature) + arg0 := *sig.recv + sig.recv = nil + arg0.typ = s.recv + var params []*Var + if sig.params != nil { + params = sig.params.vars + } + sig.params = NewTuple(append([]*Var{&arg0}, params...)...) + return &sig + } + + // In all other cases, the type of x.f is the type of x. + return s.obj.Type() +} + +// Index describes the path from x to f in x.f. +// The last index entry is the field or method index of the type declaring f; +// either: +// +// 1) the list of declared methods of a named type; or +// 2) the list of methods of an interface type; or +// 3) the list of fields of a struct type. +// +// The earlier index entries are the indices of the embedded fields implicitly +// traversed to get from (the type of) x to f, starting at embedding depth 0. +func (s *Selection) Index() []int { return s.index } + +// Indirect reports whether any pointer indirection was required to get from +// x to f in x.f. +func (s *Selection) Indirect() bool { return s.indirect } + +func (s *Selection) String() string { return SelectionString(s, nil) } + +// SelectionString returns the string form of s. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +// +// Examples: +// "field (T) f int" +// "method (T) f(X) Y" +// "method expr (T) f(X) Y" +// +func SelectionString(s *Selection, qf Qualifier) string { + var k string + switch s.kind { + case FieldVal: + k = "field " + case MethodVal: + k = "method " + case MethodExpr: + k = "method expr " + default: + unreachable() + } + var buf bytes.Buffer + buf.WriteString(k) + buf.WriteByte('(') + WriteType(&buf, s.Recv(), qf) + fmt.Fprintf(&buf, ") %s", s.obj.Name()) + if T := s.Type(); s.kind == FieldVal { + buf.WriteByte(' ') + WriteType(&buf, T, qf) + } else { + WriteSignature(&buf, T.(*Signature), qf) + } + return buf.String() +} diff --git a/src/cmd/compile/internal/types2/self_test.go b/src/cmd/compile/internal/types2/self_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4722fec9889f0f53670daef90d75f537962f378e --- /dev/null +++ b/src/cmd/compile/internal/types2/self_test.go @@ -0,0 +1,117 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2_test + +import ( + "cmd/compile/internal/syntax" + "path" + "path/filepath" + "runtime" + "testing" + "time" + + . "cmd/compile/internal/types2" +) + +func TestSelf(t *testing.T) { + files, err := pkgFiles(".") + if err != nil { + t.Fatal(err) + } + + conf := Config{Importer: defaultImporter()} + _, err = conf.Check("cmd/compile/internal/types2", files, nil) + if err != nil { + // Importing go/constant doesn't work in the + // build dashboard environment. Don't report an error + // for now so that the build remains green. + // TODO(gri) fix this + t.Log(err) // replace w/ t.Fatal eventually + return + } +} + +func BenchmarkCheck(b *testing.B) { + for _, p := range []string{ + filepath.Join("src", "net", "http"), + filepath.Join("src", "go", "parser"), + filepath.Join("src", "go", "constant"), + filepath.Join("src", "go", "internal", "gcimporter"), + } { + b.Run(path.Base(p), func(b *testing.B) { + path := filepath.Join(runtime.GOROOT(), p) + for _, ignoreFuncBodies := range []bool{false, true} { + name := "funcbodies" + if ignoreFuncBodies { + name = "nofuncbodies" + } + b.Run(name, func(b *testing.B) { + b.Run("info", func(b *testing.B) { + runbench(b, path, ignoreFuncBodies, true) + }) + b.Run("noinfo", func(b *testing.B) { + runbench(b, path, ignoreFuncBodies, false) + }) + }) + } + }) + } +} + +func runbench(b *testing.B, path string, ignoreFuncBodies, writeInfo bool) { + files, err := pkgFiles(path) + if err != nil { + b.Fatal(err) + } + + // determine line count + var lines uint + for _, f := range files { + lines += f.EOF.Line() + } + + b.ResetTimer() + start := time.Now() + for i := 0; i < b.N; i++ { + conf := Config{ + IgnoreFuncBodies: ignoreFuncBodies, + Importer: defaultImporter(), + } + var info *Info + if writeInfo { + info = &Info{ + Types: make(map[syntax.Expr]TypeAndValue), + Defs: make(map[*syntax.Name]Object), + Uses: make(map[*syntax.Name]Object), + Implicits: make(map[syntax.Node]Object), + Selections: make(map[*syntax.SelectorExpr]*Selection), + Scopes: make(map[syntax.Node]*Scope), + } + } + if _, err := conf.Check(path, files, info); err != nil { + b.Fatal(err) + } + } + b.StopTimer() + b.ReportMetric(float64(lines)*float64(b.N)/time.Since(start).Seconds(), "lines/s") +} + +func pkgFiles(path string) ([]*syntax.File, error) { + filenames, err := pkgFilenames(path) // from stdlib_test.go + if err != nil { + return nil, err + } + + var files []*syntax.File + for _, filename := range filenames { + file, err := syntax.ParseFile(filename, nil, nil, 0) + if err != nil { + return nil, err + } + files = append(files, file) + } + + return files, nil +} diff --git a/src/cmd/compile/internal/types2/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go new file mode 100644 index 0000000000000000000000000000000000000000..236feb0404ebbd84d894690d7227a352a29ba896 --- /dev/null +++ b/src/cmd/compile/internal/types2/sizeof_test.go @@ -0,0 +1,65 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "reflect" + "testing" +) + +// Signal size changes of important structures. + +func TestSizeof(t *testing.T) { + const _64bit = ^uint(0)>>32 != 0 + + var tests = []struct { + val interface{} // type as a value + _32bit uintptr // size on 32bit platforms + _64bit uintptr // size on 64bit platforms + }{ + // Types + {Basic{}, 16, 32}, + {Array{}, 16, 24}, + {Slice{}, 8, 16}, + {Struct{}, 24, 48}, + {Pointer{}, 8, 16}, + {Tuple{}, 12, 24}, + {Signature{}, 44, 88}, + {Sum{}, 12, 24}, + {Interface{}, 60, 120}, + {Map{}, 16, 32}, + {Chan{}, 12, 24}, + {Named{}, 68, 136}, + {TypeParam{}, 28, 48}, + {instance{}, 52, 96}, + {bottom{}, 0, 0}, + {top{}, 0, 0}, + + // Objects + {PkgName{}, 64, 104}, + {Const{}, 64, 104}, + {TypeName{}, 56, 88}, + {Var{}, 60, 96}, + {Func{}, 60, 96}, + {Label{}, 60, 96}, + {Builtin{}, 60, 96}, + {Nil{}, 56, 88}, + + // Misc + {Scope{}, 56, 96}, + {Package{}, 40, 80}, + } + + for _, test := range tests { + got := reflect.TypeOf(test.val).Size() + want := test._32bit + if _64bit { + want = test._64bit + } + if got != want { + t.Errorf("unsafe.Sizeof(%T) = %d, want %d", test.val, got, want) + } + } +} diff --git a/src/cmd/compile/internal/types2/sizes.go b/src/cmd/compile/internal/types2/sizes.go new file mode 100644 index 0000000000000000000000000000000000000000..aa0fbf40fced43115bd43e899920214bd11e5b47 --- /dev/null +++ b/src/cmd/compile/internal/types2/sizes.go @@ -0,0 +1,265 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements Sizes. + +package types2 + +// Sizes defines the sizing functions for package unsafe. +type Sizes interface { + // Alignof returns the alignment of a variable of type T. + // Alignof must implement the alignment guarantees required by the spec. + Alignof(T Type) int64 + + // Offsetsof returns the offsets of the given struct fields, in bytes. + // Offsetsof must implement the offset guarantees required by the spec. + Offsetsof(fields []*Var) []int64 + + // Sizeof returns the size of a variable of type T. + // Sizeof must implement the size guarantees required by the spec. + Sizeof(T Type) int64 +} + +// StdSizes is a convenience type for creating commonly used Sizes. +// It makes the following simplifying assumptions: +// +// - The size of explicitly sized basic types (int16, etc.) is the +// specified size. +// - The size of strings and interfaces is 2*WordSize. +// - The size of slices is 3*WordSize. +// - The size of an array of n elements corresponds to the size of +// a struct of n consecutive fields of the array's element type. +// - The size of a struct is the offset of the last field plus that +// field's size. As with all element types, if the struct is used +// in an array its size must first be aligned to a multiple of the +// struct's alignment. +// - All other types have size WordSize. +// - Arrays and structs are aligned per spec definition; all other +// types are naturally aligned with a maximum alignment MaxAlign. +// +// *StdSizes implements Sizes. +// +type StdSizes struct { + WordSize int64 // word size in bytes - must be >= 4 (32bits) + MaxAlign int64 // maximum alignment in bytes - must be >= 1 +} + +func (s *StdSizes) Alignof(T Type) int64 { + // For arrays and structs, alignment is defined in terms + // of alignment of the elements and fields, respectively. + switch t := optype(T).(type) { + case *Array: + // spec: "For a variable x of array type: unsafe.Alignof(x) + // is the same as unsafe.Alignof(x[0]), but at least 1." + return s.Alignof(t.elem) + case *Struct: + // spec: "For a variable x of struct type: unsafe.Alignof(x) + // is the largest of the values unsafe.Alignof(x.f) for each + // field f of x, but at least 1." + max := int64(1) + for _, f := range t.fields { + if a := s.Alignof(f.typ); a > max { + max = a + } + } + return max + case *Slice, *Interface: + // Multiword data structures are effectively structs + // in which each element has size WordSize. + return s.WordSize + case *Basic: + // Strings are like slices and interfaces. + if t.Info()&IsString != 0 { + return s.WordSize + } + } + a := s.Sizeof(T) // may be 0 + // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." + if a < 1 { + return 1 + } + // complex{64,128} are aligned like [2]float{32,64}. + if isComplex(T) { + a /= 2 + } + if a > s.MaxAlign { + return s.MaxAlign + } + return a +} + +func (s *StdSizes) Offsetsof(fields []*Var) []int64 { + offsets := make([]int64, len(fields)) + var o int64 + for i, f := range fields { + a := s.Alignof(f.typ) + o = align(o, a) + offsets[i] = o + o += s.Sizeof(f.typ) + } + return offsets +} + +var basicSizes = [...]byte{ + Bool: 1, + Int8: 1, + Int16: 2, + Int32: 4, + Int64: 8, + Uint8: 1, + Uint16: 2, + Uint32: 4, + Uint64: 8, + Float32: 4, + Float64: 8, + Complex64: 8, + Complex128: 16, +} + +func (s *StdSizes) Sizeof(T Type) int64 { + switch t := optype(T).(type) { + case *Basic: + assert(isTyped(T)) + k := t.kind + if int(k) < len(basicSizes) { + if s := basicSizes[k]; s > 0 { + return int64(s) + } + } + if k == String { + return s.WordSize * 2 + } + case *Array: + n := t.len + if n <= 0 { + return 0 + } + // n > 0 + a := s.Alignof(t.elem) + z := s.Sizeof(t.elem) + return align(z, a)*(n-1) + z + case *Slice: + return s.WordSize * 3 + case *Struct: + n := t.NumFields() + if n == 0 { + return 0 + } + offsets := s.Offsetsof(t.fields) + return offsets[n-1] + s.Sizeof(t.fields[n-1].typ) + case *Sum: + panic("Sizeof unimplemented for type sum") + case *Interface: + return s.WordSize * 2 + } + return s.WordSize // catch-all +} + +// common architecture word sizes and alignments +var gcArchSizes = map[string]*StdSizes{ + "386": {4, 4}, + "arm": {4, 4}, + "arm64": {8, 8}, + "amd64": {8, 8}, + "amd64p32": {4, 8}, + "mips": {4, 4}, + "mipsle": {4, 4}, + "mips64": {8, 8}, + "mips64le": {8, 8}, + "ppc64": {8, 8}, + "ppc64le": {8, 8}, + "riscv64": {8, 8}, + "s390x": {8, 8}, + "sparc64": {8, 8}, + "wasm": {8, 8}, + // When adding more architectures here, + // update the doc string of SizesFor below. +} + +// SizesFor returns the Sizes used by a compiler for an architecture. +// The result is nil if a compiler/architecture pair is not known. +// +// Supported architectures for compiler "gc": +// "386", "arm", "arm64", "amd64", "amd64p32", "mips", "mipsle", +// "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x", "sparc64", "wasm". +func SizesFor(compiler, arch string) Sizes { + var m map[string]*StdSizes + switch compiler { + case "gc": + m = gcArchSizes + case "gccgo": + m = gccgoArchSizes + default: + return nil + } + s, ok := m[arch] + if !ok { + return nil + } + return s +} + +// stdSizes is used if Config.Sizes == nil. +var stdSizes = SizesFor("gc", "amd64") + +func (conf *Config) alignof(T Type) int64 { + if s := conf.Sizes; s != nil { + if a := s.Alignof(T); a >= 1 { + return a + } + panic("Config.Sizes.Alignof returned an alignment < 1") + } + return stdSizes.Alignof(T) +} + +func (conf *Config) offsetsof(T *Struct) []int64 { + var offsets []int64 + if T.NumFields() > 0 { + // compute offsets on demand + if s := conf.Sizes; s != nil { + offsets = s.Offsetsof(T.fields) + // sanity checks + if len(offsets) != T.NumFields() { + panic("Config.Sizes.Offsetsof returned the wrong number of offsets") + } + for _, o := range offsets { + if o < 0 { + panic("Config.Sizes.Offsetsof returned an offset < 0") + } + } + } else { + offsets = stdSizes.Offsetsof(T.fields) + } + } + return offsets +} + +// offsetof returns the offset of the field specified via +// the index sequence relative to typ. All embedded fields +// must be structs (rather than pointer to structs). +func (conf *Config) offsetof(typ Type, index []int) int64 { + var o int64 + for _, i := range index { + s := asStruct(typ) + o += conf.offsetsof(s)[i] + typ = s.fields[i].typ + } + return o +} + +func (conf *Config) sizeof(T Type) int64 { + if s := conf.Sizes; s != nil { + if z := s.Sizeof(T); z >= 0 { + return z + } + panic("Config.Sizes.Sizeof returned a size < 0") + } + return stdSizes.Sizeof(T) +} + +// align returns the smallest y >= x such that y % a == 0. +func align(x, a int64) int64 { + y := x + a - 1 + return y - y%a +} diff --git a/src/cmd/compile/internal/types2/sizes_test.go b/src/cmd/compile/internal/types2/sizes_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c9a4942bed80c6f6fe902c7ce678a801570bb0f0 --- /dev/null +++ b/src/cmd/compile/internal/types2/sizes_test.go @@ -0,0 +1,107 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for sizes. + +package types2_test + +import ( + "cmd/compile/internal/syntax" + "cmd/compile/internal/types2" + "testing" +) + +// findStructType typechecks src and returns the first struct type encountered. +func findStructType(t *testing.T, src string) *types2.Struct { + f, err := parseSrc("x.go", src) + if err != nil { + t.Fatal(err) + } + info := types2.Info{Types: make(map[syntax.Expr]types2.TypeAndValue)} + var conf types2.Config + _, err = conf.Check("x", []*syntax.File{f}, &info) + if err != nil { + t.Fatal(err) + } + for _, tv := range info.Types { + if ts, ok := tv.Type.(*types2.Struct); ok { + return ts + } + } + t.Fatalf("failed to find a struct type in src:\n%s\n", src) + return nil +} + +// Issue 16316 +func TestMultipleSizeUse(t *testing.T) { + const src = ` +package main + +type S struct { + i int + b bool + s string + n int +} +` + ts := findStructType(t, src) + sizes := types2.StdSizes{WordSize: 4, MaxAlign: 4} + if got := sizes.Sizeof(ts); got != 20 { + t.Errorf("Sizeof(%v) with WordSize 4 = %d want 20", ts, got) + } + sizes = types2.StdSizes{WordSize: 8, MaxAlign: 8} + if got := sizes.Sizeof(ts); got != 40 { + t.Errorf("Sizeof(%v) with WordSize 8 = %d want 40", ts, got) + } +} + +// Issue 16464 +func TestAlignofNaclSlice(t *testing.T) { + const src = ` +package main + +var s struct { + x *int + y []byte +} +` + ts := findStructType(t, src) + sizes := &types2.StdSizes{WordSize: 4, MaxAlign: 8} + var fields []*types2.Var + // Make a copy manually :( + for i := 0; i < ts.NumFields(); i++ { + fields = append(fields, ts.Field(i)) + } + offsets := sizes.Offsetsof(fields) + if offsets[0] != 0 || offsets[1] != 4 { + t.Errorf("OffsetsOf(%v) = %v want %v", ts, offsets, []int{0, 4}) + } +} + +func TestIssue16902(t *testing.T) { + const src = ` +package a + +import "unsafe" + +const _ = unsafe.Offsetof(struct{ x int64 }{}.x) +` + f, err := parseSrc("x.go", src) + if err != nil { + t.Fatal(err) + } + info := types2.Info{Types: make(map[syntax.Expr]types2.TypeAndValue)} + conf := types2.Config{ + Importer: defaultImporter(), + Sizes: &types2.StdSizes{WordSize: 8, MaxAlign: 8}, + } + _, err = conf.Check("x", []*syntax.File{f}, &info) + if err != nil { + t.Fatal(err) + } + for _, tv := range info.Types { + _ = conf.Sizes.Sizeof(tv.Type) + _ = conf.Sizes.Alignof(tv.Type) + } +} diff --git a/src/cmd/compile/internal/types2/stdlib_test.go b/src/cmd/compile/internal/types2/stdlib_test.go new file mode 100644 index 0000000000000000000000000000000000000000..cde35c17b6f717f97dd3781f914ee0fcfe0afc99 --- /dev/null +++ b/src/cmd/compile/internal/types2/stdlib_test.go @@ -0,0 +1,326 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file tests types2.Check by using it to +// typecheck the standard library and tests. + +package types2_test + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "go/build" + "internal/testenv" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + . "cmd/compile/internal/types2" +) + +var stdLibImporter = defaultImporter() + +func TestStdlib(t *testing.T) { + testenv.MustHaveGoBuild(t) + + pkgCount := 0 + duration := walkPkgDirs(filepath.Join(runtime.GOROOT(), "src"), func(dir string, filenames []string) { + typecheck(t, dir, filenames) + pkgCount++ + }, t.Error) + + if testing.Verbose() { + fmt.Println(pkgCount, "packages typechecked in", duration) + } +} + +// firstComment returns the contents of the first non-empty comment in +// the given file, "skip", or the empty string. No matter the present +// comments, if any of them contains a build tag, the result is always +// "skip". Only comments within the first 4K of the file are considered. +// TODO(gri) should only read until we see "package" token. +func firstComment(filename string) (first string) { + f, err := os.Open(filename) + if err != nil { + return "" + } + defer f.Close() + + // read at most 4KB + var buf [4 << 10]byte + n, _ := f.Read(buf[:]) + src := bytes.NewBuffer(buf[:n]) + + // TODO(gri) we need a better way to terminate CommentsDo + defer func() { + if p := recover(); p != nil { + if s, ok := p.(string); ok { + first = s + } + } + }() + + syntax.CommentsDo(src, func(_, _ uint, text string) { + if text[0] != '/' { + return // not a comment + } + + // extract comment text + if text[1] == '*' { + text = text[:len(text)-2] + } + text = strings.TrimSpace(text[2:]) + + if strings.HasPrefix(text, "+build ") { + panic("skip") + } + if first == "" { + first = text // text may be "" but that's ok + } + // continue as we may still see build tags + }) + + return +} + +func testTestDir(t *testing.T, path string, ignore ...string) { + files, err := os.ReadDir(path) + if err != nil { + t.Fatal(err) + } + + excluded := make(map[string]bool) + for _, filename := range ignore { + excluded[filename] = true + } + + for _, f := range files { + // filter directory contents + if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] { + continue + } + + // get per-file instructions + expectErrors := false + filename := filepath.Join(path, f.Name()) + goVersion := "" + if comment := firstComment(filename); comment != "" { + fields := strings.Fields(comment) + switch fields[0] { + case "skip", "compiledir": + continue // ignore this file + case "errorcheck": + expectErrors = true + for _, arg := range fields[1:] { + if arg == "-0" || arg == "-+" || arg == "-std" { + // Marked explicitly as not expecting errors (-0), + // or marked as compiling runtime/stdlib, which is only done + // to trigger runtime/stdlib-only error output. + // In both cases, the code should typecheck. + expectErrors = false + break + } + const prefix = "-lang=" + if strings.HasPrefix(arg, prefix) { + goVersion = arg[len(prefix):] + } + } + } + } + + // parse and type-check file + if testing.Verbose() { + fmt.Println("\t", filename) + } + file, err := syntax.ParseFile(filename, nil, nil, 0) + if err == nil { + conf := Config{GoVersion: goVersion, Importer: stdLibImporter} + _, err = conf.Check(filename, []*syntax.File{file}, nil) + } + + if expectErrors { + if err == nil { + t.Errorf("expected errors but found none in %s", filename) + } + } else { + if err != nil { + t.Error(err) + } + } + } +} + +func TestStdTest(t *testing.T) { + testenv.MustHaveGoBuild(t) + + if testing.Short() && testenv.Builder() == "" { + t.Skip("skipping in short mode") + } + + testTestDir(t, filepath.Join(runtime.GOROOT(), "test"), + "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore + "directive.go", // tests compiler rejection of bad directive placement - ignore + "embedfunc.go", // tests //go:embed + "embedvers.go", // tests //go:embed + "linkname2.go", // types2 doesn't check validity of //go:xxx directives + ) +} + +func TestStdFixed(t *testing.T) { + testenv.MustHaveGoBuild(t) + + if testing.Short() && testenv.Builder() == "" { + t.Skip("skipping in short mode") + } + + testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "fixedbugs"), + "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore + "issue6889.go", // gc-specific test + "issue11362.go", // canonical import path check + "issue16369.go", // types2 handles this correctly - not an issue + "issue18459.go", // types2 doesn't check validity of //go:xxx directives + "issue18882.go", // types2 doesn't check validity of //go:xxx directives + "issue20529.go", // types2 does not have constraints on stack size + "issue22200.go", // types2 does not have constraints on stack size + "issue22200b.go", // types2 does not have constraints on stack size + "issue25507.go", // types2 does not have constraints on stack size + "issue20780.go", // types2 does not have constraints on stack size + "issue42058a.go", // types2 does not have constraints on channel element size + "issue42058b.go", // types2 does not have constraints on channel element size + ) +} + +func TestStdKen(t *testing.T) { + testenv.MustHaveGoBuild(t) + + testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "ken")) +} + +// Package paths of excluded packages. +var excluded = map[string]bool{ + "builtin": true, + + // See #46027: some imports are missing for this submodule. + "crypto/ed25519/internal/edwards25519/field/_asm": true, +} + +// typecheck typechecks the given package files. +func typecheck(t *testing.T, path string, filenames []string) { + // parse package files + var files []*syntax.File + for _, filename := range filenames { + errh := func(err error) { t.Error(err) } + file, err := syntax.ParseFile(filename, errh, nil, 0) + if err != nil { + return + } + + if testing.Verbose() { + if len(files) == 0 { + fmt.Println("package", file.PkgName.Value) + } + fmt.Println("\t", filename) + } + + files = append(files, file) + } + + // typecheck package files + conf := Config{ + Error: func(err error) { t.Error(err) }, + Importer: stdLibImporter, + } + info := Info{Uses: make(map[*syntax.Name]Object)} + conf.Check(path, files, &info) + + // Perform checks of API invariants. + + // All Objects have a package, except predeclared ones. + errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error + for id, obj := range info.Uses { + predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError + if predeclared == (obj.Pkg() != nil) { + posn := id.Pos() + if predeclared { + t.Errorf("%s: predeclared object with package: %s", posn, obj) + } else { + t.Errorf("%s: user-defined object without package: %s", posn, obj) + } + } + } +} + +// pkgFilenames returns the list of package filenames for the given directory. +func pkgFilenames(dir string) ([]string, error) { + ctxt := build.Default + ctxt.CgoEnabled = false + pkg, err := ctxt.ImportDir(dir, 0) + if err != nil { + if _, nogo := err.(*build.NoGoError); nogo { + return nil, nil // no *.go files, not an error + } + return nil, err + } + if excluded[pkg.ImportPath] { + return nil, nil + } + var filenames []string + for _, name := range pkg.GoFiles { + filenames = append(filenames, filepath.Join(pkg.Dir, name)) + } + for _, name := range pkg.TestGoFiles { + filenames = append(filenames, filepath.Join(pkg.Dir, name)) + } + return filenames, nil +} + +func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...interface{})) time.Duration { + w := walker{time.Now(), 10 * time.Millisecond, pkgh, errh} + w.walk(dir) + return time.Since(w.start) +} + +type walker struct { + start time.Time + dmax time.Duration + pkgh func(dir string, filenames []string) + errh func(args ...interface{}) +} + +func (w *walker) walk(dir string) { + // limit run time for short tests + if testing.Short() && time.Since(w.start) >= w.dmax { + return + } + + files, err := os.ReadDir(dir) + if err != nil { + w.errh(err) + return + } + + // apply pkgh to the files in directory dir + // but ignore files directly under $GOROOT/src (might be temporary test files). + if dir != filepath.Join(runtime.GOROOT(), "src") { + files, err := pkgFilenames(dir) + if err != nil { + w.errh(err) + return + } + if files != nil { + w.pkgh(dir, files) + } + } + + // traverse subdirectories, but don't walk into testdata + for _, f := range files { + if f.IsDir() && f.Name() != "testdata" { + w.walk(filepath.Join(dir, f.Name())) + } + } +} diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go new file mode 100644 index 0000000000000000000000000000000000000000..c3e646c80c142d91434991bc5f37d57ab20d4225 --- /dev/null +++ b/src/cmd/compile/internal/types2/stmt.go @@ -0,0 +1,943 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements typechecking of statements. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "go/constant" + "sort" +) + +func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body *syntax.BlockStmt, iota constant.Value) { + if check.conf.IgnoreFuncBodies { + panic("internal error: function body not ignored") + } + + if check.conf.Trace { + check.trace(body.Pos(), "--- %s: %s", name, sig) + defer func() { + check.trace(syntax.EndPos(body), "--- ") + }() + } + + // set function scope extent + sig.scope.pos = body.Pos() + sig.scope.end = syntax.EndPos(body) + + // save/restore current context and setup function context + // (and use 0 indentation at function start) + defer func(ctxt context, indent int) { + check.context = ctxt + check.indent = indent + }(check.context, check.indent) + check.context = context{ + decl: decl, + scope: sig.scope, + iota: iota, + sig: sig, + } + check.indent = 0 + + check.stmtList(0, body.List) + + if check.hasLabel && !check.conf.IgnoreLabels { + check.labels(body) + } + + if sig.results.Len() > 0 && !check.isTerminating(body, "") { + check.error(body.Rbrace, "missing return") + } + + // TODO(gri) Should we make it an error to declare generic functions + // where the type parameters are not used? + // 12/19/2018: Probably not - it can make sense to have an API with + // all functions uniformly sharing the same type parameters. + + // spec: "Implementation restriction: A compiler may make it illegal to + // declare a variable inside a function body if the variable is never used." + check.usage(sig.scope) +} + +func (check *Checker) usage(scope *Scope) { + var unused []*Var + for _, elem := range scope.elems { + if v, _ := elem.(*Var); v != nil && !v.used { + unused = append(unused, v) + } + } + sort.Slice(unused, func(i, j int) bool { + return unused[i].pos.Cmp(unused[j].pos) < 0 + }) + for _, v := range unused { + check.softErrorf(v.pos, "%s declared but not used", v.name) + } + + for _, scope := range scope.children { + // Don't go inside function literal scopes a second time; + // they are handled explicitly by funcBody. + if !scope.isFunc { + check.usage(scope) + } + } +} + +// stmtContext is a bitset describing which +// control-flow statements are permissible, +// and provides additional context information +// for better error messages. +type stmtContext uint + +const ( + // permissible control-flow statements + breakOk stmtContext = 1 << iota + continueOk + fallthroughOk + + // additional context information + finalSwitchCase +) + +func (check *Checker) simpleStmt(s syntax.Stmt) { + if s != nil { + check.stmt(0, s) + } +} + +func trimTrailingEmptyStmts(list []syntax.Stmt) []syntax.Stmt { + for i := len(list); i > 0; i-- { + if _, ok := list[i-1].(*syntax.EmptyStmt); !ok { + return list[:i] + } + } + return nil +} + +func (check *Checker) stmtList(ctxt stmtContext, list []syntax.Stmt) { + ok := ctxt&fallthroughOk != 0 + inner := ctxt &^ fallthroughOk + list = trimTrailingEmptyStmts(list) // trailing empty statements are "invisible" to fallthrough analysis + for i, s := range list { + inner := inner + if ok && i+1 == len(list) { + inner |= fallthroughOk + } + check.stmt(inner, s) + } +} + +func (check *Checker) multipleSwitchDefaults(list []*syntax.CaseClause) { + var first *syntax.CaseClause + for _, c := range list { + if c.Cases == nil { + if first != nil { + check.errorf(c, "multiple defaults (first at %s)", first.Pos()) + // TODO(gri) probably ok to bail out after first error (and simplify this code) + } else { + first = c + } + } + } +} + +func (check *Checker) multipleSelectDefaults(list []*syntax.CommClause) { + var first *syntax.CommClause + for _, c := range list { + if c.Comm == nil { + if first != nil { + check.errorf(c, "multiple defaults (first at %s)", first.Pos()) + // TODO(gri) probably ok to bail out after first error (and simplify this code) + } else { + first = c + } + } + } +} + +func (check *Checker) openScope(node syntax.Node, comment string) { + check.openScopeUntil(node, syntax.EndPos(node), comment) +} + +func (check *Checker) openScopeUntil(node syntax.Node, end syntax.Pos, comment string) { + scope := NewScope(check.scope, node.Pos(), end, comment) + check.recordScope(node, scope) + check.scope = scope +} + +func (check *Checker) closeScope() { + check.scope = check.scope.Parent() +} + +func (check *Checker) suspendedCall(keyword string, call *syntax.CallExpr) { + var x operand + var msg string + switch check.rawExpr(&x, call, nil) { + case conversion: + msg = "requires function call, not conversion" + case expression: + msg = "discards result of" + case statement: + return + default: + unreachable() + } + check.errorf(&x, "%s %s %s", keyword, msg, &x) +} + +// goVal returns the Go value for val, or nil. +func goVal(val constant.Value) interface{} { + // val should exist, but be conservative and check + if val == nil { + return nil + } + // Match implementation restriction of other compilers. + // gc only checks duplicates for integer, floating-point + // and string values, so only create Go values for these + // types. + switch val.Kind() { + case constant.Int: + if x, ok := constant.Int64Val(val); ok { + return x + } + if x, ok := constant.Uint64Val(val); ok { + return x + } + case constant.Float: + if x, ok := constant.Float64Val(val); ok { + return x + } + case constant.String: + return constant.StringVal(val) + } + return nil +} + +// A valueMap maps a case value (of a basic Go type) to a list of positions +// where the same case value appeared, together with the corresponding case +// types. +// Since two case values may have the same "underlying" value but different +// types we need to also check the value's types (e.g., byte(1) vs myByte(1)) +// when the switch expression is of interface type. +type ( + valueMap map[interface{}][]valueType // underlying Go value -> valueType + valueType struct { + pos syntax.Pos + typ Type + } +) + +func (check *Checker) caseValues(x *operand, values []syntax.Expr, seen valueMap) { +L: + for _, e := range values { + var v operand + check.expr(&v, e) + if x.mode == invalid || v.mode == invalid { + continue L + } + check.convertUntyped(&v, x.typ) + if v.mode == invalid { + continue L + } + // Order matters: By comparing v against x, error positions are at the case values. + res := v // keep original v unchanged + check.comparison(&res, x, syntax.Eql) + if res.mode == invalid { + continue L + } + if v.mode != constant_ { + continue L // we're done + } + // look for duplicate values + if val := goVal(v.val); val != nil { + // look for duplicate types for a given value + // (quadratic algorithm, but these lists tend to be very short) + for _, vt := range seen[val] { + if check.identical(v.typ, vt.typ) { + var err error_ + err.errorf(&v, "duplicate case %s in expression switch", &v) + err.errorf(vt.pos, "previous case") + check.report(&err) + continue L + } + } + seen[val] = append(seen[val], valueType{v.Pos(), v.typ}) + } + } +} + +func (check *Checker) caseTypes(x *operand, xtyp *Interface, types []syntax.Expr, seen map[Type]syntax.Expr) (T Type) { +L: + for _, e := range types { + T = check.typOrNil(e) + if T == Typ[Invalid] { + continue L + } + if T != nil { + check.ordinaryType(e.Pos(), T) + } + // look for duplicate types + // (quadratic algorithm, but type switches tend to be reasonably small) + for t, other := range seen { + if T == nil && t == nil || T != nil && t != nil && check.identical(T, t) { + // talk about "case" rather than "type" because of nil case + Ts := "nil" + if T != nil { + Ts = T.String() + } + var err error_ + err.errorf(e, "duplicate case %s in type switch", Ts) + err.errorf(other, "previous case") + check.report(&err) + continue L + } + } + seen[T] = e + if T != nil { + check.typeAssertion(e.Pos(), x, xtyp, T) + } + } + return +} + +// stmt typechecks statement s. +func (check *Checker) stmt(ctxt stmtContext, s syntax.Stmt) { + // statements must end with the same top scope as they started with + if debug { + defer func(scope *Scope) { + // don't check if code is panicking + if p := recover(); p != nil { + panic(p) + } + assert(scope == check.scope) + }(check.scope) + } + + // process collected function literals before scope changes + defer check.processDelayed(len(check.delayed)) + + inner := ctxt &^ (fallthroughOk | finalSwitchCase) + switch s := s.(type) { + case *syntax.EmptyStmt: + // ignore + + case *syntax.DeclStmt: + check.declStmt(s.DeclList) + + case *syntax.LabeledStmt: + check.hasLabel = true + check.stmt(ctxt, s.Stmt) + + case *syntax.ExprStmt: + // spec: "With the exception of specific built-in functions, + // function and method calls and receive operations can appear + // in statement context. Such statements may be parenthesized." + var x operand + kind := check.rawExpr(&x, s.X, nil) + var msg string + switch x.mode { + default: + if kind == statement { + return + } + msg = "is not used" + case builtin: + msg = "must be called" + case typexpr: + msg = "is not an expression" + } + check.errorf(&x, "%s %s", &x, msg) + + case *syntax.SendStmt: + var ch, x operand + check.expr(&ch, s.Chan) + check.expr(&x, s.Value) + if ch.mode == invalid || x.mode == invalid { + return + } + + tch := asChan(ch.typ) + if tch == nil { + check.errorf(s, invalidOp+"cannot send to non-chan type %s", ch.typ) + return + } + + if tch.dir == RecvOnly { + check.errorf(s, invalidOp+"cannot send to receive-only type %s", tch) + return + } + + check.assignment(&x, tch.elem, "send") + + case *syntax.AssignStmt: + lhs := unpackExpr(s.Lhs) + if s.Rhs == nil { + // x++ or x-- + if len(lhs) != 1 { + check.errorf(s, invalidAST+"%s%s requires one operand", s.Op, s.Op) + return + } + var x operand + check.expr(&x, lhs[0]) + if x.mode == invalid { + return + } + if !isNumeric(x.typ) { + check.errorf(lhs[0], invalidOp+"%s%s%s (non-numeric type %s)", lhs[0], s.Op, s.Op, x.typ) + return + } + check.assignVar(lhs[0], &x) + return + } + + rhs := unpackExpr(s.Rhs) + switch s.Op { + case 0: + check.assignVars(lhs, rhs) + return + case syntax.Def: + check.shortVarDecl(s.Pos(), lhs, rhs) + return + } + + // assignment operations + if len(lhs) != 1 || len(rhs) != 1 { + check.errorf(s, "assignment operation %s requires single-valued expressions", s.Op) + return + } + + var x operand + check.binary(&x, nil, lhs[0], rhs[0], s.Op) + check.assignVar(lhs[0], &x) + + case *syntax.CallStmt: + // TODO(gri) get rid of this conversion to string + kind := "go" + if s.Tok == syntax.Defer { + kind = "defer" + } + check.suspendedCall(kind, s.Call) + + case *syntax.ReturnStmt: + res := check.sig.results + results := unpackExpr(s.Results) + if res.Len() > 0 { + // function returns results + // (if one, say the first, result parameter is named, all of them are named) + if len(results) == 0 && res.vars[0].name != "" { + // spec: "Implementation restriction: A compiler may disallow an empty expression + // list in a "return" statement if a different entity (constant, type, or variable) + // with the same name as a result parameter is in scope at the place of the return." + for _, obj := range res.vars { + if alt := check.lookup(obj.name); alt != nil && alt != obj { + var err error_ + err.errorf(s, "result parameter %s not in scope at return", obj.name) + err.errorf(alt, "inner declaration of %s", obj) + check.report(&err) + // ok to continue + } + } + } else { + // return has results or result parameters are unnamed + check.initVars(res.vars, results, s.Pos()) + } + } else if len(results) > 0 { + check.error(results[0], "no result values expected") + check.use(results...) + } + + case *syntax.BranchStmt: + if s.Label != nil { + check.hasLabel = true + break // checked in 2nd pass (check.labels) + } + switch s.Tok { + case syntax.Break: + if ctxt&breakOk == 0 { + if check.conf.CompilerErrorMessages { + check.error(s, "break is not in a loop, switch, or select statement") + } else { + check.error(s, "break not in for, switch, or select statement") + } + } + case syntax.Continue: + if ctxt&continueOk == 0 { + if check.conf.CompilerErrorMessages { + check.error(s, "continue is not in a loop") + } else { + check.error(s, "continue not in for statement") + } + } + case syntax.Fallthrough: + if ctxt&fallthroughOk == 0 { + msg := "fallthrough statement out of place" + if ctxt&finalSwitchCase != 0 { + msg = "cannot fallthrough final case in switch" + } + check.error(s, msg) + } + case syntax.Goto: + // goto's must have labels, should have been caught above + fallthrough + default: + check.errorf(s, invalidAST+"branch statement: %s", s.Tok) + } + + case *syntax.BlockStmt: + check.openScope(s, "block") + defer check.closeScope() + + check.stmtList(inner, s.List) + + case *syntax.IfStmt: + check.openScope(s, "if") + defer check.closeScope() + + check.simpleStmt(s.Init) + var x operand + check.expr(&x, s.Cond) + if x.mode != invalid && !isBoolean(x.typ) { + check.error(s.Cond, "non-boolean condition in if statement") + } + check.stmt(inner, s.Then) + // The parser produces a correct AST but if it was modified + // elsewhere the else branch may be invalid. Check again. + switch s.Else.(type) { + case nil: + // valid or error already reported + case *syntax.IfStmt, *syntax.BlockStmt: + check.stmt(inner, s.Else) + default: + check.error(s.Else, "invalid else branch in if statement") + } + + case *syntax.SwitchStmt: + inner |= breakOk + check.openScope(s, "switch") + defer check.closeScope() + + check.simpleStmt(s.Init) + + if g, _ := s.Tag.(*syntax.TypeSwitchGuard); g != nil { + check.typeSwitchStmt(inner, s, g) + } else { + check.switchStmt(inner, s) + } + + case *syntax.SelectStmt: + inner |= breakOk + + check.multipleSelectDefaults(s.Body) + + for i, clause := range s.Body { + if clause == nil { + continue // error reported before + } + + // clause.Comm must be a SendStmt, RecvStmt, or default case + valid := false + var rhs syntax.Expr // rhs of RecvStmt, or nil + switch s := clause.Comm.(type) { + case nil, *syntax.SendStmt: + valid = true + case *syntax.AssignStmt: + if _, ok := s.Rhs.(*syntax.ListExpr); !ok { + rhs = s.Rhs + } + case *syntax.ExprStmt: + rhs = s.X + } + + // if present, rhs must be a receive operation + if rhs != nil { + if x, _ := unparen(rhs).(*syntax.Operation); x != nil && x.Y == nil && x.Op == syntax.Recv { + valid = true + } + } + + if !valid { + check.error(clause.Comm, "select case must be send or receive (possibly with assignment)") + continue + } + end := s.Rbrace + if i+1 < len(s.Body) { + end = s.Body[i+1].Pos() + } + check.openScopeUntil(clause, end, "case") + if clause.Comm != nil { + check.stmt(inner, clause.Comm) + } + check.stmtList(inner, clause.Body) + check.closeScope() + } + + case *syntax.ForStmt: + inner |= breakOk | continueOk + check.openScope(s, "for") + defer check.closeScope() + + if rclause, _ := s.Init.(*syntax.RangeClause); rclause != nil { + check.rangeStmt(inner, s, rclause) + break + } + + check.simpleStmt(s.Init) + if s.Cond != nil { + var x operand + check.expr(&x, s.Cond) + if x.mode != invalid && !isBoolean(x.typ) { + check.error(s.Cond, "non-boolean condition in for statement") + } + } + check.simpleStmt(s.Post) + // spec: "The init statement may be a short variable + // declaration, but the post statement must not." + if s, _ := s.Post.(*syntax.AssignStmt); s != nil && s.Op == syntax.Def { + // The parser already reported an error. + // Don't call useLHS here because we want to use the lhs in + // this erroneous statement so that we don't get errors about + // these lhs variables being declared but not used. + check.use(s.Lhs) // avoid follow-up errors + } + check.stmt(inner, s.Body) + + default: + check.error(s, "invalid statement") + } +} + +func (check *Checker) switchStmt(inner stmtContext, s *syntax.SwitchStmt) { + // init statement already handled + + var x operand + if s.Tag != nil { + check.expr(&x, s.Tag) + // By checking assignment of x to an invisible temporary + // (as a compiler would), we get all the relevant checks. + check.assignment(&x, nil, "switch expression") + if x.mode != invalid && !Comparable(x.typ) && !hasNil(x.typ) { + check.errorf(&x, "cannot switch on %s (%s is not comparable)", &x, x.typ) + x.mode = invalid + } + } else { + // spec: "A missing switch expression is + // equivalent to the boolean value true." + x.mode = constant_ + x.typ = Typ[Bool] + x.val = constant.MakeBool(true) + // TODO(gri) should have a better position here + pos := s.Rbrace + if len(s.Body) > 0 { + pos = s.Body[0].Pos() + } + x.expr = syntax.NewName(pos, "true") + } + + check.multipleSwitchDefaults(s.Body) + + seen := make(valueMap) // map of seen case values to positions and types + for i, clause := range s.Body { + if clause == nil { + check.error(clause, invalidAST+"incorrect expression switch case") + continue + } + end := s.Rbrace + inner := inner + if i+1 < len(s.Body) { + end = s.Body[i+1].Pos() + inner |= fallthroughOk + } else { + inner |= finalSwitchCase + } + check.caseValues(&x, unpackExpr(clause.Cases), seen) + check.openScopeUntil(clause, end, "case") + check.stmtList(inner, clause.Body) + check.closeScope() + } +} + +func (check *Checker) typeSwitchStmt(inner stmtContext, s *syntax.SwitchStmt, guard *syntax.TypeSwitchGuard) { + // init statement already handled + + // A type switch guard must be of the form: + // + // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . + // \__lhs__/ \___rhs___/ + + // check lhs, if any + lhs := guard.Lhs + if lhs != nil { + if lhs.Value == "_" { + // _ := x.(type) is an invalid short variable declaration + check.softErrorf(lhs, "no new variable on left side of :=") + lhs = nil // avoid declared but not used error below + } else { + check.recordDef(lhs, nil) // lhs variable is implicitly declared in each cause clause + } + } + + // check rhs + var x operand + check.expr(&x, guard.X) + if x.mode == invalid { + return + } + // Caution: We're not using asInterface here because we don't want + // to switch on a suitably constrained type parameter (for + // now). + // TODO(gri) Need to revisit this. + xtyp, _ := under(x.typ).(*Interface) + if xtyp == nil { + check.errorf(&x, "%s is not an interface type", &x) + return + } + check.ordinaryType(x.Pos(), xtyp) + + check.multipleSwitchDefaults(s.Body) + + var lhsVars []*Var // list of implicitly declared lhs variables + seen := make(map[Type]syntax.Expr) // map of seen types to positions + for i, clause := range s.Body { + if clause == nil { + check.error(s, invalidAST+"incorrect type switch case") + continue + } + end := s.Rbrace + if i+1 < len(s.Body) { + end = s.Body[i+1].Pos() + } + // Check each type in this type switch case. + cases := unpackExpr(clause.Cases) + T := check.caseTypes(&x, xtyp, cases, seen) + check.openScopeUntil(clause, end, "case") + // If lhs exists, declare a corresponding variable in the case-local scope. + if lhs != nil { + // spec: "The TypeSwitchGuard may include a short variable declaration. + // When that form is used, the variable is declared at the beginning of + // the implicit block in each clause. In clauses with a case listing + // exactly one type, the variable has that type; otherwise, the variable + // has the type of the expression in the TypeSwitchGuard." + if len(cases) != 1 || T == nil { + T = x.typ + } + obj := NewVar(lhs.Pos(), check.pkg, lhs.Value, T) + // TODO(mdempsky): Just use clause.Colon? Why did I even suggest + // "at the end of the TypeSwitchCase" in #16794 instead? + scopePos := clause.Pos() // for default clause (len(List) == 0) + if n := len(cases); n > 0 { + scopePos = syntax.EndPos(cases[n-1]) + } + check.declare(check.scope, nil, obj, scopePos) + check.recordImplicit(clause, obj) + // For the "declared but not used" error, all lhs variables act as + // one; i.e., if any one of them is 'used', all of them are 'used'. + // Collect them for later analysis. + lhsVars = append(lhsVars, obj) + } + check.stmtList(inner, clause.Body) + check.closeScope() + } + + // If lhs exists, we must have at least one lhs variable that was used. + // (We can't use check.usage because that only looks at one scope; and + // we don't want to use the same variable for all scopes and change the + // variable type underfoot.) + if lhs != nil { + var used bool + for _, v := range lhsVars { + if v.used { + used = true + } + v.used = true // avoid usage error when checking entire function + } + if !used { + check.softErrorf(lhs, "%s declared but not used", lhs.Value) + } + } +} + +func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *syntax.RangeClause) { + // scope already opened + + // check expression to iterate over + var x operand + check.expr(&x, rclause.X) + + // determine lhs, if any + sKey := rclause.Lhs // possibly nil + var sValue syntax.Expr + if p, _ := sKey.(*syntax.ListExpr); p != nil { + if len(p.ElemList) != 2 { + check.error(s, invalidAST+"invalid lhs in range clause") + return + } + sKey = p.ElemList[0] + sValue = p.ElemList[1] + } + + // determine key/value types + var key, val Type + if x.mode != invalid { + typ := optype(x.typ) + if _, ok := typ.(*Chan); ok && sValue != nil { + // TODO(gri) this also needs to happen for channels in generic variables + check.softErrorf(sValue, "range over %s permits only one iteration variable", &x) + // ok to continue + } + var msg string + key, val, msg = rangeKeyVal(typ, isVarName(sKey), isVarName(sValue)) + if key == nil || msg != "" { + if msg != "" { + msg = ": " + msg + } + check.softErrorf(&x, "cannot range over %s%s", &x, msg) + // ok to continue + } + } + + // check assignment to/declaration of iteration variables + // (irregular assignment, cannot easily map to existing assignment checks) + + // lhs expressions and initialization value (rhs) types + lhs := [2]syntax.Expr{sKey, sValue} + rhs := [2]Type{key, val} // key, val may be nil + + if rclause.Def { + // short variable declaration; variable scope starts after the range clause + // (the for loop opens a new scope, so variables on the lhs never redeclare + // previously declared variables) + var vars []*Var + for i, lhs := range lhs { + if lhs == nil { + continue + } + + // determine lhs variable + var obj *Var + if ident, _ := lhs.(*syntax.Name); ident != nil { + // declare new variable + name := ident.Value + obj = NewVar(ident.Pos(), check.pkg, name, nil) + check.recordDef(ident, obj) + // _ variables don't count as new variables + if name != "_" { + vars = append(vars, obj) + } + } else { + check.errorf(lhs, "cannot declare %s", lhs) + obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable + } + + // initialize lhs variable + if typ := rhs[i]; typ != nil { + x.mode = value + x.expr = lhs // we don't have a better rhs expression to use here + x.typ = typ + check.initVar(obj, &x, "range clause") + } else { + obj.typ = Typ[Invalid] + obj.used = true // don't complain about unused variable + } + } + + // declare variables + if len(vars) > 0 { + scopePos := syntax.EndPos(rclause.X) // TODO(gri) should this just be s.Body.Pos (spec clarification)? + for _, obj := range vars { + // spec: "The scope of a constant or variable identifier declared inside + // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl + // for short variable declarations) and ends at the end of the innermost + // containing block." + check.declare(check.scope, nil /* recordDef already called */, obj, scopePos) + } + } else { + check.error(s, "no new variables on left side of :=") + } + } else { + // ordinary assignment + for i, lhs := range lhs { + if lhs == nil { + continue + } + if typ := rhs[i]; typ != nil { + x.mode = value + x.expr = lhs // we don't have a better rhs expression to use here + x.typ = typ + check.assignVar(lhs, &x) + } + } + } + + check.stmt(inner, s.Body) +} + +// isVarName reports whether x is a non-nil, non-blank (_) expression. +func isVarName(x syntax.Expr) bool { + if x == nil { + return false + } + ident, _ := unparen(x).(*syntax.Name) + return ident == nil || ident.Value != "_" +} + +// rangeKeyVal returns the key and value type produced by a range clause +// over an expression of type typ, and possibly an error message. If the +// range clause is not permitted the returned key is nil or msg is not +// empty (in that case we still may have a non-nil key type which can be +// used to reduce the chance for follow-on errors). +// The wantKey, wantVal, and hasVal flags indicate which of the iteration +// variables are used or present; this matters if we range over a generic +// type where not all keys or values are of the same type. +func rangeKeyVal(typ Type, wantKey, wantVal bool) (Type, Type, string) { + switch typ := typ.(type) { + case *Basic: + if isString(typ) { + return Typ[Int], universeRune, "" // use 'rune' name + } + case *Array: + return Typ[Int], typ.elem, "" + case *Slice: + return Typ[Int], typ.elem, "" + case *Pointer: + if typ := asArray(typ.base); typ != nil { + return Typ[Int], typ.elem, "" + } + case *Map: + return typ.key, typ.elem, "" + case *Chan: + var msg string + if typ.dir == SendOnly { + msg = "receive from send-only channel" + } + return typ.elem, Typ[Invalid], msg + case *Sum: + first := true + var key, val Type + var msg string + typ.is(func(t Type) bool { + k, v, m := rangeKeyVal(under(t), wantKey, wantVal) + if k == nil || m != "" { + key, val, msg = k, v, m + return false + } + if first { + key, val, msg = k, v, m + first = false + return true + } + if wantKey && !Identical(key, k) { + key, val, msg = nil, nil, "all possible values must have the same key type" + return false + } + if wantVal && !Identical(val, v) { + key, val, msg = nil, nil, "all possible values must have the same element type" + return false + } + return true + }) + return key, val, msg + } + return nil, nil, "" +} diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go new file mode 100644 index 0000000000000000000000000000000000000000..c8e428c1832311bed33fa8abf7965a01c3dce2a4 --- /dev/null +++ b/src/cmd/compile/internal/types2/subst.go @@ -0,0 +1,547 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements instantiation of generic types +// through substitution of type parameters by actual +// types. + +package types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" +) + +type substMap struct { + // The targs field is currently needed for *Named type substitution. + // TODO(gri) rewrite that code, get rid of this field, and make this + // struct just the map (proj) + targs []Type + proj map[*TypeParam]Type +} + +// makeSubstMap creates a new substitution map mapping tpars[i] to targs[i]. +// If targs[i] is nil, tpars[i] is not substituted. +func makeSubstMap(tpars []*TypeName, targs []Type) *substMap { + assert(len(tpars) == len(targs)) + proj := make(map[*TypeParam]Type, len(tpars)) + for i, tpar := range tpars { + // We must expand type arguments otherwise *instance + // types end up as components in composite types. + // TODO(gri) explain why this causes problems, if it does + targ := expand(targs[i]) // possibly nil + targs[i] = targ + proj[tpar.typ.(*TypeParam)] = targ + } + return &substMap{targs, proj} +} + +func (m *substMap) String() string { + return fmt.Sprintf("%s", m.proj) +} + +func (m *substMap) empty() bool { + return len(m.proj) == 0 +} + +func (m *substMap) lookup(tpar *TypeParam) Type { + if t := m.proj[tpar]; t != nil { + return t + } + return tpar +} + +func (check *Checker) instantiate(pos syntax.Pos, typ Type, targs []Type, poslist []syntax.Pos) (res Type) { + if check.conf.Trace { + check.trace(pos, "-- instantiating %s with %s", typ, typeListString(targs)) + check.indent++ + defer func() { + check.indent-- + var under Type + if res != nil { + // Calling under() here may lead to endless instantiations. + // Test case: type T[P any] T[P] + // TODO(gri) investigate if that's a bug or to be expected. + under = res.Underlying() + } + check.trace(pos, "=> %s (under = %s)", res, under) + }() + } + + assert(len(poslist) <= len(targs)) + + // TODO(gri) What is better here: work with TypeParams, or work with TypeNames? + var tparams []*TypeName + switch t := typ.(type) { + case *Named: + tparams = t.tparams + case *Signature: + tparams = t.tparams + defer func() { + // If we had an unexpected failure somewhere don't panic below when + // asserting res.(*Signature). Check for *Signature in case Typ[Invalid] + // is returned. + if _, ok := res.(*Signature); !ok { + return + } + // If the signature doesn't use its type parameters, subst + // will not make a copy. In that case, make a copy now (so + // we can set tparams to nil w/o causing side-effects). + if t == res { + copy := *t + res = © + } + // After instantiating a generic signature, it is not generic + // anymore; we need to set tparams to nil. + res.(*Signature).tparams = nil + }() + + default: + check.dump("%v: cannot instantiate %v", pos, typ) + unreachable() // only defined types and (defined) functions can be generic + + } + + // the number of supplied types must match the number of type parameters + if len(targs) != len(tparams) { + // TODO(gri) provide better error message + check.errorf(pos, "got %d arguments but %d type parameters", len(targs), len(tparams)) + return Typ[Invalid] + } + + if len(tparams) == 0 { + return typ // nothing to do (minor optimization) + } + + smap := makeSubstMap(tparams, targs) + + // check bounds + for i, tname := range tparams { + tpar := tname.typ.(*TypeParam) + iface := tpar.Bound() + if iface.Empty() { + continue // no type bound + } + + targ := targs[i] + + // best position for error reporting + pos := pos + if i < len(poslist) { + pos = poslist[i] + } + + // The type parameter bound is parameterized with the same type parameters + // as the instantiated type; before we can use it for bounds checking we + // need to instantiate it with the type arguments with which we instantiate + // the parameterized type. + iface = check.subst(pos, iface, smap).(*Interface) + + // targ must implement iface (methods) + // - check only if we have methods + check.completeInterface(nopos, iface) + if len(iface.allMethods) > 0 { + // If the type argument is a pointer to a type parameter, the type argument's + // method set is empty. + // TODO(gri) is this what we want? (spec question) + if base, isPtr := deref(targ); isPtr && asTypeParam(base) != nil { + check.errorf(pos, "%s has no methods", targ) + break + } + if m, wrong := check.missingMethod(targ, iface, true); m != nil { + // TODO(gri) needs to print updated name to avoid major confusion in error message! + // (print warning for now) + // Old warning: + // check.softErrorf(pos, "%s does not satisfy %s (warning: name not updated) = %s (missing method %s)", targ, tpar.bound, iface, m) + if m.name == "==" { + // We don't want to report "missing method ==". + check.softErrorf(pos, "%s does not satisfy comparable", targ) + } else if wrong != nil { + // TODO(gri) This can still report uninstantiated types which makes the error message + // more difficult to read then necessary. + check.softErrorf(pos, + "%s does not satisfy %s: wrong method signature\n\tgot %s\n\twant %s", + targ, tpar.bound, wrong, m, + ) + } else { + check.softErrorf(pos, "%s does not satisfy %s (missing method %s)", targ, tpar.bound, m.name) + } + break + } + } + + // targ's underlying type must also be one of the interface types listed, if any + if iface.allTypes == nil { + continue // nothing to do + } + + // If targ is itself a type parameter, each of its possible types, but at least one, must be in the + // list of iface types (i.e., the targ type list must be a non-empty subset of the iface types). + if targ := asTypeParam(targ); targ != nil { + targBound := targ.Bound() + if targBound.allTypes == nil { + check.softErrorf(pos, "%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ) + break + } + for _, t := range unpack(targBound.allTypes) { + if !iface.isSatisfiedBy(t) { + // TODO(gri) match this error message with the one below (or vice versa) + check.softErrorf(pos, "%s does not satisfy %s (%s type constraint %s not found in %s)", targ, tpar.bound, targ, t, iface.allTypes) + break + } + } + break + } + + // Otherwise, targ's type or underlying type must also be one of the interface types listed, if any. + if !iface.isSatisfiedBy(targ) { + check.softErrorf(pos, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, under(targ), iface.allTypes) + break + } + } + + return check.subst(pos, typ, smap) +} + +// subst returns the type typ with its type parameters tpars replaced by +// the corresponding type arguments targs, recursively. +// subst is functional in the sense that it doesn't modify the incoming +// type. If a substitution took place, the result type is different from +// from the incoming type. +func (check *Checker) subst(pos syntax.Pos, typ Type, smap *substMap) Type { + if smap.empty() { + return typ + } + + // common cases + switch t := typ.(type) { + case *Basic: + return typ // nothing to do + case *TypeParam: + return smap.lookup(t) + } + + // general case + subst := subster{check, pos, make(map[Type]Type), smap} + return subst.typ(typ) +} + +type subster struct { + check *Checker + pos syntax.Pos + cache map[Type]Type + smap *substMap +} + +func (subst *subster) typ(typ Type) Type { + switch t := typ.(type) { + case nil: + // Call typOrNil if it's possible that typ is nil. + panic("nil typ") + + case *Basic, *bottom, *top: + // nothing to do + + case *Array: + elem := subst.typOrNil(t.elem) + if elem != t.elem { + return &Array{len: t.len, elem: elem} + } + + case *Slice: + elem := subst.typOrNil(t.elem) + if elem != t.elem { + return &Slice{elem: elem} + } + + case *Struct: + if fields, copied := subst.varList(t.fields); copied { + return &Struct{fields: fields, tags: t.tags} + } + + case *Pointer: + base := subst.typ(t.base) + if base != t.base { + return &Pointer{base: base} + } + + case *Tuple: + return subst.tuple(t) + + case *Signature: + // TODO(gri) rethink the recv situation with respect to methods on parameterized types + // recv := subst.var_(t.recv) // TODO(gri) this causes a stack overflow - explain + recv := t.recv + params := subst.tuple(t.params) + results := subst.tuple(t.results) + if recv != t.recv || params != t.params || results != t.results { + return &Signature{ + rparams: t.rparams, + // TODO(gri) Why can't we nil out tparams here, rather than in + // instantiate above? + tparams: t.tparams, + scope: t.scope, + recv: recv, + params: params, + results: results, + variadic: t.variadic, + } + } + + case *Sum: + types, copied := subst.typeList(t.types) + if copied { + // Don't do it manually, with a Sum literal: the new + // types list may not be unique and NewSum may remove + // duplicates. + return NewSum(types) + } + + case *Interface: + methods, mcopied := subst.funcList(t.methods) + types := t.types + if t.types != nil { + types = subst.typ(t.types) + } + embeddeds, ecopied := subst.typeList(t.embeddeds) + if mcopied || types != t.types || ecopied { + iface := &Interface{methods: methods, types: types, embeddeds: embeddeds} + if subst.check == nil { + panic("internal error: cannot instantiate interfaces yet") + } + subst.check.posMap[iface] = subst.check.posMap[t] // satisfy completeInterface requirement + subst.check.completeInterface(nopos, iface) + return iface + } + + case *Map: + key := subst.typ(t.key) + elem := subst.typ(t.elem) + if key != t.key || elem != t.elem { + return &Map{key: key, elem: elem} + } + + case *Chan: + elem := subst.typ(t.elem) + if elem != t.elem { + return &Chan{dir: t.dir, elem: elem} + } + + case *Named: + // dump is for debugging + dump := func(string, ...interface{}) {} + if subst.check != nil && subst.check.conf.Trace { + subst.check.indent++ + defer func() { + subst.check.indent-- + }() + dump = func(format string, args ...interface{}) { + subst.check.trace(subst.pos, format, args...) + } + } + + if t.tparams == nil { + dump(">>> %s is not parameterized", t) + return t // type is not parameterized + } + + var new_targs []Type + + if len(t.targs) > 0 { + // already instantiated + dump(">>> %s already instantiated", t) + assert(len(t.targs) == len(t.tparams)) + // For each (existing) type argument targ, determine if it needs + // to be substituted; i.e., if it is or contains a type parameter + // that has a type argument for it. + for i, targ := range t.targs { + dump(">>> %d targ = %s", i, targ) + new_targ := subst.typ(targ) + if new_targ != targ { + dump(">>> substituted %d targ %s => %s", i, targ, new_targ) + if new_targs == nil { + new_targs = make([]Type, len(t.tparams)) + copy(new_targs, t.targs) + } + new_targs[i] = new_targ + } + } + + if new_targs == nil { + dump(">>> nothing to substitute in %s", t) + return t // nothing to substitute + } + } else { + // not yet instantiated + dump(">>> first instantiation of %s", t) + new_targs = subst.smap.targs + } + + // before creating a new named type, check if we have this one already + h := instantiatedHash(t, new_targs) + dump(">>> new type hash: %s", h) + if subst.check != nil { + if named, found := subst.check.typMap[h]; found { + dump(">>> found %s", named) + subst.cache[t] = named + return named + } + } + + // create a new named type and populate caches to avoid endless recursion + tname := NewTypeName(subst.pos, t.obj.pkg, t.obj.name, nil) + named := subst.check.newNamed(tname, t, t.underlying, t.tparams, t.methods) // method signatures are updated lazily + named.targs = new_targs + if subst.check != nil { + subst.check.typMap[h] = named + } + subst.cache[t] = named + + // do the substitution + dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, new_targs) + named.underlying = subst.typOrNil(t.underlying) + named.fromRHS = named.underlying // for cycle detection (Checker.validType) + + return named + + case *TypeParam: + return subst.smap.lookup(t) + + case *instance: + // TODO(gri) can we avoid the expansion here and just substitute the type parameters? + return subst.typ(t.expand()) + + default: + unimplemented() + } + + return typ +} + +// TODO(gri) Eventually, this should be more sophisticated. +// It won't work correctly for locally declared types. +func instantiatedHash(typ *Named, targs []Type) string { + var buf bytes.Buffer + writeTypeName(&buf, typ.obj, nil) + buf.WriteByte('[') + writeTypeList(&buf, targs, nil, nil) + buf.WriteByte(']') + + // With respect to the represented type, whether a + // type is fully expanded or stored as instance + // does not matter - they are the same types. + // Remove the instanceMarkers printed for instances. + res := buf.Bytes() + i := 0 + for _, b := range res { + if b != instanceMarker { + res[i] = b + i++ + } + } + + return string(res[:i]) +} + +func typeListString(list []Type) string { + var buf bytes.Buffer + writeTypeList(&buf, list, nil, nil) + return buf.String() +} + +// typOrNil is like typ but if the argument is nil it is replaced with Typ[Invalid]. +// A nil type may appear in pathological cases such as type T[P any] []func(_ T([]_)) +// where an array/slice element is accessed before it is set up. +func (subst *subster) typOrNil(typ Type) Type { + if typ == nil { + return Typ[Invalid] + } + return subst.typ(typ) +} + +func (subst *subster) var_(v *Var) *Var { + if v != nil { + if typ := subst.typ(v.typ); typ != v.typ { + copy := *v + copy.typ = typ + return © + } + } + return v +} + +func (subst *subster) tuple(t *Tuple) *Tuple { + if t != nil { + if vars, copied := subst.varList(t.vars); copied { + return &Tuple{vars: vars} + } + } + return t +} + +func (subst *subster) varList(in []*Var) (out []*Var, copied bool) { + out = in + for i, v := range in { + if w := subst.var_(v); w != v { + if !copied { + // first variable that got substituted => allocate new out slice + // and copy all variables + new := make([]*Var, len(in)) + copy(new, out) + out = new + copied = true + } + out[i] = w + } + } + return +} + +func (subst *subster) func_(f *Func) *Func { + if f != nil { + if typ := subst.typ(f.typ); typ != f.typ { + copy := *f + copy.typ = typ + return © + } + } + return f +} + +func (subst *subster) funcList(in []*Func) (out []*Func, copied bool) { + out = in + for i, f := range in { + if g := subst.func_(f); g != f { + if !copied { + // first function that got substituted => allocate new out slice + // and copy all functions + new := make([]*Func, len(in)) + copy(new, out) + out = new + copied = true + } + out[i] = g + } + } + return +} + +func (subst *subster) typeList(in []Type) (out []Type, copied bool) { + out = in + for i, t := range in { + if u := subst.typ(t); u != t { + if !copied { + // first function that got substituted => allocate new out slice + // and copy all functions + new := make([]Type, len(in)) + copy(new, out) + out = new + copied = true + } + out[i] = u + } + } + return +} diff --git a/src/go/types/testdata/blank.src b/src/cmd/compile/internal/types2/testdata/check/blank.src similarity index 100% rename from src/go/types/testdata/blank.src rename to src/cmd/compile/internal/types2/testdata/check/blank.src diff --git a/src/cmd/compile/internal/types2/testdata/check/builtins.go2 b/src/cmd/compile/internal/types2/testdata/check/builtins.go2 new file mode 100644 index 0000000000000000000000000000000000000000..3918d836b5277a8a71d46380b441b1a29d409bd7 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/builtins.go2 @@ -0,0 +1,53 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file tests built-in calls on generic types. + +package builtins + +type Bmc interface { + type map[rune]string, chan int +} + +type Bms interface { + type map[string]int, []int +} + +type Bcs interface { + type chan bool, []float64 +} + +type Bss interface { + type []int, []string +} + +func _[T any] () { + _ = make(T /* ERROR invalid argument */ ) + _ = make(T /* ERROR invalid argument */ , 10) + _ = make(T /* ERROR invalid argument */ , 10, 20) +} + +func _[T Bmc] () { + _ = make(T) + _ = make(T, 10) + _ = make /* ERROR expects 1 or 2 arguments */ (T, 10, 20) +} + +func _[T Bms] () { + _ = make /* ERROR expects 2 arguments */ (T) + _ = make(T, 10) + _ = make /* ERROR expects 2 arguments */ (T, 10, 20) +} + +func _[T Bcs] () { + _ = make /* ERROR expects 2 arguments */ (T) + _ = make(T, 10) + _ = make /* ERROR expects 2 arguments */ (T, 10, 20) +} + +func _[T Bss] () { + _ = make /* ERROR expects 2 or 3 arguments */ (T) + _ = make(T, 10) + _ = make(T, 10, 20) +} diff --git a/src/cmd/compile/internal/types2/testdata/check/builtins.src b/src/cmd/compile/internal/types2/testdata/check/builtins.src new file mode 100644 index 0000000000000000000000000000000000000000..6d1f47129b9c16d2935a66c6eb97a740550041ae --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/builtins.src @@ -0,0 +1,902 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// builtin calls + +package builtins + +import "unsafe" + +func f0() {} + +func append1() { + var b byte + var x int + var s []byte + _ = append() // ERROR not enough arguments + _ = append("foo" /* ERROR not a slice */ ) + _ = append(nil /* ERROR not a slice */ , s) + _ = append(x /* ERROR not a slice */ , s) + _ = append(s) + _ = append(s, nil...) + append /* ERROR not used */ (s) + + _ = append(s, b) + _ = append(s, x /* ERROR cannot use x */ ) + _ = append(s, s /* ERROR cannot use s */ ) + _ = append(s... ) /* ERROR not enough arguments */ + _ = append(s, b, s /* ERROR too many arguments */ ... ) + _ = append(s, 1, 2, 3) + _ = append(s, 1, 2, 3, x /* ERROR cannot use x */ , 5, 6, 6) + _ = append(s, 1, 2 /* ERROR too many arguments */ , s... ) + _ = append([]interface{}(nil), 1, 2, "foo", x, 3.1425, false) + + type S []byte + type T string + var t T + _ = append(s, "foo" /* ERROR cannot use .* in argument to append */ ) + _ = append(s, "foo"...) + _ = append(S(s), "foo" /* ERROR cannot use .* in argument to append */ ) + _ = append(S(s), "foo"...) + _ = append(s, t /* ERROR cannot use t */ ) + _ = append(s, t...) + _ = append(s, T("foo")...) + _ = append(S(s), t /* ERROR cannot use t */ ) + _ = append(S(s), t...) + _ = append(S(s), T("foo")...) + _ = append([]string{}, t /* ERROR cannot use t */ , "foo") + _ = append([]T{}, t, "foo") +} + +// from the spec +func append2() { + s0 := []int{0, 0} + s1 := append(s0, 2) // append a single element s1 == []int{0, 0, 2} + s2 := append(s1, 3, 5, 7) // append multiple elements s2 == []int{0, 0, 2, 3, 5, 7} + s3 := append(s2, s0...) // append a slice s3 == []int{0, 0, 2, 3, 5, 7, 0, 0} + s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0} + + var t []interface{} + t = append(t, 42, 3.1415, "foo") // t == []interface{}{42, 3.1415, "foo"} + + var b []byte + b = append(b, "bar"...) // append string contents b == []byte{'b', 'a', 'r' } + + _ = s4 +} + +func append3() { + f1 := func() (s []int) { return } + f2 := func() (s []int, x int) { return } + f3 := func() (s []int, x, y int) { return } + f5 := func() (s []interface{}, x int, y float32, z string, b bool) { return } + ff := func() (int, float32) { return 0, 0 } + _ = append(f0 /* ERROR used as value */ ()) + _ = append(f1()) + _ = append(f2()) + _ = append(f3()) + _ = append(f5()) + _ = append(ff /* ERROR not a slice */ ()) // TODO(gri) better error message +} + +func cap1() { + var a [10]bool + var p *[20]int + var c chan string + _ = cap() // ERROR not enough arguments + _ = cap(1, 2) // ERROR too many arguments + _ = cap(42 /* ERROR invalid */) + const _3 = cap(a) + assert(_3 == 10) + const _4 = cap(p) + assert(_4 == 20) + _ = cap(c) + cap /* ERROR not used */ (c) + + // issue 4744 + type T struct{ a [10]int } + const _ = cap(((*T)(nil)).a) + + var s [][]byte + _ = cap(s) + _ = cap(s... /* ERROR invalid use of \.\.\. */ ) +} + +func cap2() { + f1a := func() (a [10]int) { return } + f1s := func() (s []int) { return } + f2 := func() (s []int, x int) { return } + _ = cap(f0 /* ERROR used as value */ ()) + _ = cap(f1a()) + _ = cap(f1s()) + _ = cap(f2()) // ERROR too many arguments +} + +// test cases for issue 7387 +func cap3() { + var f = func() int { return 0 } + var x = f() + const ( + _ = cap([4]int{}) + _ = cap([4]int{x}) + _ = cap /* ERROR not constant */ ([4]int{f()}) + _ = cap /* ERROR not constant */ ([4]int{cap([]int{})}) + _ = cap([4]int{cap([4]int{})}) + ) + var y float64 + var z complex128 + const ( + _ = cap([4]float64{}) + _ = cap([4]float64{y}) + _ = cap([4]float64{real(2i)}) + _ = cap /* ERROR not constant */ ([4]float64{real(z)}) + ) + var ch chan [10]int + const ( + _ = cap /* ERROR not constant */ (<-ch) + _ = cap /* ERROR not constant */ ([4]int{(<-ch)[0]}) + ) +} + +func close1() { + var c chan int + var r <-chan int + close() // ERROR not enough arguments + close(1, 2) // ERROR too many arguments + close(42 /* ERROR not a channel */) + close(r /* ERROR receive-only channel */) + close(c) + _ = close /* ERROR used as value */ (c) + + var s []chan int + close(s... /* ERROR invalid use of \.\.\. */ ) +} + +func close2() { + f1 := func() (ch chan int) { return } + f2 := func() (ch chan int, x int) { return } + close(f0 /* ERROR used as value */ ()) + close(f1()) + close(f2()) // ERROR too many arguments +} + +func complex1() { + var i32 int32 + var f32 float32 + var f64 float64 + var c64 complex64 + var c128 complex128 + _ = complex() // ERROR not enough arguments + _ = complex(1) // ERROR not enough arguments + _ = complex(true /* ERROR mismatched types */ , 0) + _ = complex(i32 /* ERROR expected floating-point */ , 0) + _ = complex("foo" /* ERROR mismatched types */ , 0) + _ = complex(c64 /* ERROR expected floating-point */ , 0) + _ = complex(0 /* ERROR mismatched types */ , true) + _ = complex(0 /* ERROR expected floating-point */ , i32) + _ = complex(0 /* ERROR mismatched types */ , "foo") + _ = complex(0 /* ERROR expected floating-point */ , c64) + _ = complex(f32, f32) + _ = complex(f32, 1) + _ = complex(f32, 1.0) + _ = complex(f32, 'a') + _ = complex(f64, f64) + _ = complex(f64, 1) + _ = complex(f64, 1.0) + _ = complex(f64, 'a') + _ = complex(f32 /* ERROR mismatched types */ , f64) + _ = complex(f64 /* ERROR mismatched types */ , f32) + _ = complex(1, 1) + _ = complex(1, 1.1) + _ = complex(1, 'a') + complex /* ERROR not used */ (1, 2) + + var _ complex64 = complex(f32, f32) + var _ complex64 = complex /* ERROR cannot use .* in variable declaration */ (f64, f64) + + var _ complex128 = complex /* ERROR cannot use .* in variable declaration */ (f32, f32) + var _ complex128 = complex(f64, f64) + + // untyped constants + const _ int = complex(1, 0) + const _ float32 = complex(1, 0) + const _ complex64 = complex(1, 0) + const _ complex128 = complex(1, 0) + const _ = complex(0i, 0i) + const _ = complex(0i, 0) + const _ int = 1.0 + complex(1, 0i) + + const _ int = complex /* ERROR int */ (1.1, 0) + const _ float32 = complex /* ERROR float32 */ (1, 2) + + // untyped values + var s uint + _ = complex(1 /* ERROR integer */ <X, T2->X + ) + + var t T3 + _ = t.X /* ERROR "ambiguous selector t.X" */ +} + +func _() { + type ( + T1 struct { X int } + T2 struct { T1 } + T3 struct { T1 } + T4 struct { T2; T3 } // X is embedded twice at the same level via T2->T1->X, T3->T1->X + ) + + var t T4 + _ = t.X /* ERROR "ambiguous selector t.X" */ +} + +func issue4355() { + type ( + T1 struct {X int} + T2 struct {T1} + T3 struct {T2} + T4 struct {T2} + T5 struct {T3; T4} // X is embedded twice at the same level via T3->T2->T1->X, T4->T2->T1->X + ) + + var t T5 + _ = t.X /* ERROR "ambiguous selector t.X" */ +} + +func _() { + type State int + type A struct{ State } + type B struct{ fmt.State } + type T struct{ A; B } + + var t T + _ = t.State /* ERROR "ambiguous selector t.State" */ +} + +// Embedded fields can be predeclared types. + +func _() { + type T0 struct{ + int + float32 + f int + } + var x T0 + _ = x.int + _ = x.float32 + _ = x.f + + type T1 struct{ + T0 + } + var y T1 + _ = y.int + _ = y.float32 + _ = y.f +} + +// Restrictions on embedded field types. + +func _() { + type I1 interface{} + type I2 interface{} + type P1 *int + type P2 *int + type UP unsafe.Pointer + + type T1 struct { + I1 + * /* ERROR "cannot be a pointer to an interface" */ I2 + * /* ERROR "cannot be a pointer to an interface" */ error + P1 /* ERROR "cannot be a pointer" */ + * /* ERROR "cannot be a pointer" */ P2 + } + + // unsafe.Pointers are treated like regular pointers when embedded + type T2 struct { + unsafe /* ERROR "cannot be unsafe.Pointer" */ .Pointer + */* ERROR "cannot be unsafe.Pointer" */ /* ERROR "Pointer redeclared" */ unsafe.Pointer + UP /* ERROR "cannot be unsafe.Pointer" */ + * /* ERROR "cannot be unsafe.Pointer" */ /* ERROR "UP redeclared" */ UP + } +} + +// Named types that are pointers. + +type S struct{ x int } +func (*S) m() {} +type P *S + +func _() { + var s *S + _ = s.x + _ = s.m + + var p P + _ = p.x + _ = p.m /* ERROR "no field or method" */ + _ = P.m /* ERROR "no field or method" */ +} + +// Borrowed from the FieldByName test cases in reflect/all_test.go. + +type D1 struct { + d int +} +type D2 struct { + d int +} + +type S0 struct { + A, B, C int + D1 + D2 +} + +type S1 struct { + B int + S0 +} + +type S2 struct { + A int + *S1 +} + +type S1x struct { + S1 +} + +type S1y struct { + S1 +} + +type S3 struct { + S1x + S2 + D, E int + *S1y +} + +type S4 struct { + *S4 + A int +} + +// The X in S6 and S7 annihilate, but they also block the X in S8.S9. +type S5 struct { + S6 + S7 + S8 +} + +type S6 struct { + X int +} + +type S7 S6 + +type S8 struct { + S9 +} + +type S9 struct { + X int + Y int +} + +// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. +type S10 struct { + S11 + S12 + S13 +} + +type S11 struct { + S6 +} + +type S12 struct { + S6 +} + +type S13 struct { + S8 +} + +func _() { + _ = struct{}{}.Foo /* ERROR "no field or method" */ + _ = S0{}.A + _ = S0{}.D /* ERROR "no field or method" */ + _ = S1{}.A + _ = S1{}.B + _ = S1{}.S0 + _ = S1{}.C + _ = S2{}.A + _ = S2{}.S1 + _ = S2{}.B + _ = S2{}.C + _ = S2{}.D /* ERROR "no field or method" */ + _ = S3{}.S1 /* ERROR "ambiguous selector S3\{\}.S1" */ + _ = S3{}.A + _ = S3{}.B /* ERROR "ambiguous selector" S3\{\}.B */ + _ = S3{}.D + _ = S3{}.E + _ = S4{}.A + _ = S4{}.B /* ERROR "no field or method" */ + _ = S5{}.X /* ERROR "ambiguous selector S5\{\}.X" */ + _ = S5{}.Y + _ = S10{}.X /* ERROR "ambiguous selector S10\{\}.X" */ + _ = S10{}.Y +} + +// Borrowed from the FieldByName benchmark in reflect/all_test.go. + +type R0 struct { + *R1 + *R2 + *R3 + *R4 +} + +type R1 struct { + *R5 + *R6 + *R7 + *R8 +} + +type R2 R1 +type R3 R1 +type R4 R1 + +type R5 struct { + *R9 + *R10 + *R11 + *R12 +} + +type R6 R5 +type R7 R5 +type R8 R5 + +type R9 struct { + *R13 + *R14 + *R15 + *R16 +} + +type R10 R9 +type R11 R9 +type R12 R9 + +type R13 struct { + *R17 + *R18 + *R19 + *R20 +} + +type R14 R13 +type R15 R13 +type R16 R13 + +type R17 struct { + *R21 + *R22 + *R23 + *R24 +} + +type R18 R17 +type R19 R17 +type R20 R17 + +type R21 struct { + X int +} + +type R22 R21 +type R23 R21 +type R24 R21 + +var _ = R0{}.X /* ERROR "ambiguous selector R0\{\}.X" */ \ No newline at end of file diff --git a/src/cmd/compile/internal/types2/testdata/check/decls4.src b/src/cmd/compile/internal/types2/testdata/check/decls4.src new file mode 100644 index 0000000000000000000000000000000000000000..eb08421beee0011f7064f735c6c7417dc5d73c64 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/decls4.src @@ -0,0 +1,199 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// type aliases + +package decls4 + +type ( + T0 [10]int + T1 []byte + T2 struct { + x int + } + T3 interface{ + m() T2 + } + T4 func(int, T0) chan T2 +) + +type ( + Ai = int + A0 = T0 + A1 = T1 + A2 = T2 + A3 = T3 + A4 = T4 + + A10 = [10]int + A11 = []byte + A12 = struct { + x int + } + A13 = interface{ + m() A2 + } + A14 = func(int, A0) chan A2 +) + +// check assignment compatibility due to equality of types +var ( + xi_ int + ai Ai = xi_ + + x0 T0 + a0 A0 = x0 + + x1 T1 + a1 A1 = x1 + + x2 T2 + a2 A2 = x2 + + x3 T3 + a3 A3 = x3 + + x4 T4 + a4 A4 = x4 +) + +// alias receiver types +func (Ai /* ERROR "invalid receiver" */) m1() {} +func (T0) m1() {} +func (A0) m1 /* ERROR already declared */ () {} +func (A0) m2 () {} +func (A3 /* ERROR invalid receiver */ ) m1 () {} +func (A10 /* ERROR invalid receiver */ ) m1() {} + +// x0 has methods m1, m2 declared via receiver type names T0 and A0 +var _ interface{ m1(); m2() } = x0 + +// alias receiver types (test case for issue #23042) +type T struct{} + +var ( + _ = T.m + _ = T{}.m + _ interface{m()} = T{} +) + +var ( + _ = T.n + _ = T{}.n + _ interface{m(); n()} = T{} +) + +type U = T +func (U) m() {} + +// alias receiver types (long type declaration chains) +type ( + V0 = V1 + V1 = (V2) + V2 = ((V3)) + V3 = T +) + +func (V0) m /* ERROR already declared */ () {} +func (V1) n() {} + +// alias receiver types (invalid due to cycles) +type ( + W0 /* ERROR illegal cycle */ = W1 + W1 = (W2) + W2 = ((W0)) +) + +func (W0) m() {} // no error expected (due to above cycle error) +func (W1) n() {} + +// alias receiver types (invalid due to builtin underlying type) +type ( + B0 = B1 + B1 = B2 + B2 = int +) + +func (B0 /* ERROR invalid receiver */ ) m() {} +func (B1 /* ERROR invalid receiver */ ) n() {} + +// cycles +type ( + C2 /* ERROR illegal cycle */ = C2 + C3 /* ERROR illegal cycle */ = C4 + C4 = C3 + C5 struct { + f *C6 + } + C6 = C5 + C7 /* ERROR illegal cycle */ struct { + f C8 + } + C8 = C7 +) + +// embedded fields +var ( + s0 struct { T0 } + s1 struct { A0 } = s0 /* ERROR cannot use */ // embedded field names are different +) + +// embedding and lookup of fields and methods +func _(s struct{A0}) { s.A0 = x0 } + +type eX struct{xf int} + +func (eX) xm() + +type eY = struct{eX} // field/method set of eY includes xf, xm + +type eZ = *struct{eX} // field/method set of eZ includes xf, xm + +type eA struct { + eX // eX contributes xf, xm to eA +} + +type eA2 struct { + *eX // *eX contributes xf, xm to eA +} + +type eB struct { + eY // eY contributes xf, xm to eB +} + +type eB2 struct { + *eY // *eY contributes xf, xm to eB +} + +type eC struct { + eZ // eZ contributes xf, xm to eC +} + +var ( + _ = eA{}.xf + _ = eA{}.xm + _ = eA2{}.xf + _ = eA2{}.xm + _ = eB{}.xf + _ = eB{}.xm + _ = eB2{}.xf + _ = eB2{}.xm + _ = eC{}.xf + _ = eC{}.xm +) + +// ambiguous selectors due to embedding via type aliases +type eD struct { + eY + eZ +} + +var ( + _ = eD{}.xf /* ERROR ambiguous selector eD\{\}.xf */ + _ = eD{}.xm /* ERROR ambiguous selector eD\{\}.xm */ +) + +var ( + _ interface{ xm() } = eD /* ERROR missing method xm */ {} +) \ No newline at end of file diff --git a/src/go/types/testdata/decls5.src b/src/cmd/compile/internal/types2/testdata/check/decls5.src similarity index 100% rename from src/go/types/testdata/decls5.src rename to src/cmd/compile/internal/types2/testdata/check/decls5.src diff --git a/src/go/types/testdata/errors.src b/src/cmd/compile/internal/types2/testdata/check/errors.src similarity index 100% rename from src/go/types/testdata/errors.src rename to src/cmd/compile/internal/types2/testdata/check/errors.src diff --git a/src/go/types/testdata/expr0.src b/src/cmd/compile/internal/types2/testdata/check/expr0.src similarity index 100% rename from src/go/types/testdata/expr0.src rename to src/cmd/compile/internal/types2/testdata/check/expr0.src diff --git a/src/go/types/testdata/expr1.src b/src/cmd/compile/internal/types2/testdata/check/expr1.src similarity index 100% rename from src/go/types/testdata/expr1.src rename to src/cmd/compile/internal/types2/testdata/check/expr1.src diff --git a/src/go/types/testdata/expr2.src b/src/cmd/compile/internal/types2/testdata/check/expr2.src similarity index 100% rename from src/go/types/testdata/expr2.src rename to src/cmd/compile/internal/types2/testdata/check/expr2.src diff --git a/src/cmd/compile/internal/types2/testdata/check/expr3.src b/src/cmd/compile/internal/types2/testdata/check/expr3.src new file mode 100644 index 0000000000000000000000000000000000000000..eab3f72c4d5ff0d323151f3c3b9feff11cf29de3 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/expr3.src @@ -0,0 +1,565 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package expr3 + +import "time" + +func indexes() { + var x int + _ = 1 /* ERROR "cannot index" */ [0] + _ = x /* ERROR "cannot index" */ [0] + _ = ( /* ERROR "cannot slice" */ 12 + 3)[1:2] + + var a [10]int + _ = a[true /* ERROR "cannot convert" */ ] + _ = a["foo" /* ERROR "cannot convert" */ ] + _ = a[1.1 /* ERROR "truncated" */ ] + _ = a[1.0] + _ = a[- /* ERROR "negative" */ 1] + _ = a[- /* ERROR "negative" */ 1 :] + _ = a[: - /* ERROR "negative" */ 1] + _ = a[: /* ERROR "middle index required" */ : /* ERROR "final index required" */ ] + _ = a[0: /* ERROR "middle index required" */ : /* ERROR "final index required" */ ] + _ = a[0: /* ERROR "middle index required" */ :10] + _ = a[:10:10] + + var a0 int + a0 = a[0] + _ = a0 + var a1 int32 + a1 = a /* ERROR "cannot use .* in assignment" */ [1] + _ = a1 + + _ = a[9] + _ = a[10 /* ERROR "index .* out of bounds" */ ] + _ = a[1 /* ERROR "overflows" */ <<100] + _ = a[1<< /* ERROR "constant shift overflow" */ 1000] // no out-of-bounds follow-on error + _ = a[10:] + _ = a[:10] + _ = a[10:10] + _ = a[11 /* ERROR "index .* out of bounds" */ :] + _ = a[: 11 /* ERROR "index .* out of bounds" */ ] + _ = a[: 1 /* ERROR "overflows" */ <<100] + _ = a[:10:10] + _ = a[:11 /* ERROR "index .* out of bounds" */ :10] + _ = a[:10:11 /* ERROR "index .* out of bounds" */ ] + _ = a[10:0:10] /* ERROR "invalid slice indices" */ + _ = a[0:10:0] /* ERROR "invalid slice indices" */ + _ = a[10:0:0] /* ERROR "invalid slice indices" */ + _ = &a /* ERROR "cannot take address" */ [:10] + + pa := &a + _ = pa[9] + _ = pa[10 /* ERROR "index .* out of bounds" */ ] + _ = pa[1 /* ERROR "overflows" */ <<100] + _ = pa[10:] + _ = pa[:10] + _ = pa[10:10] + _ = pa[11 /* ERROR "index .* out of bounds" */ :] + _ = pa[: 11 /* ERROR "index .* out of bounds" */ ] + _ = pa[: 1 /* ERROR "overflows" */ <<100] + _ = pa[:10:10] + _ = pa[:11 /* ERROR "index .* out of bounds" */ :10] + _ = pa[:10:11 /* ERROR "index .* out of bounds" */ ] + _ = pa[10:0:10] /* ERROR "invalid slice indices" */ + _ = pa[0:10:0] /* ERROR "invalid slice indices" */ + _ = pa[10:0:0] /* ERROR "invalid slice indices" */ + _ = &pa /* ERROR "cannot take address" */ [:10] + + var b [0]int + _ = b[0 /* ERROR "index .* out of bounds" */ ] + _ = b[:] + _ = b[0:] + _ = b[:0] + _ = b[0:0] + _ = b[0:0:0] + _ = b[1 /* ERROR "index .* out of bounds" */ :0:0] + + var s []int + _ = s[- /* ERROR "negative" */ 1] + _ = s[- /* ERROR "negative" */ 1 :] + _ = s[: - /* ERROR "negative" */ 1] + _ = s[0] + _ = s[1:2] + _ = s[2:1] /* ERROR "invalid slice indices" */ + _ = s[2:] + _ = s[: 1 /* ERROR "overflows" */ <<100] + _ = s[1 /* ERROR "overflows" */ <<100 :] + _ = s[1 /* ERROR "overflows" */ <<100 : 1 /* ERROR "overflows" */ <<100] + _ = s[: /* ERROR "middle index required" */ : /* ERROR "final index required" */ ] + _ = s[:10:10] + _ = s[10:0:10] /* ERROR "invalid slice indices" */ + _ = s[0:10:0] /* ERROR "invalid slice indices" */ + _ = s[10:0:0] /* ERROR "invalid slice indices" */ + _ = &s /* ERROR "cannot take address" */ [:10] + + var m map[string]int + _ = m[0 /* ERROR "cannot use .* in map index" */ ] + _ = m /* ERROR "cannot slice" */ ["foo" : "bar"] + _ = m["foo"] + // ok is of type bool + type mybool bool + var ok mybool + _, ok = m["bar"] + _ = ok + _ = m[0 /* ERROR "cannot use 0" */ ] + "foo" // ERROR "cannot convert" + + var t string + _ = t[- /* ERROR "negative" */ 1] + _ = t[- /* ERROR "negative" */ 1 :] + _ = t[: - /* ERROR "negative" */ 1] + _ = t /* ERROR "3-index slice of string" */ [1:2:3] + _ = "foo" /* ERROR "3-index slice of string" */ [1:2:3] + var t0 byte + t0 = t[0] + _ = t0 + var t1 rune + t1 = t /* ERROR "cannot use .* in assignment" */ [2] + _ = t1 + _ = ("foo" + "bar")[5] + _ = ("foo" + "bar")[6 /* ERROR "index .* out of bounds" */ ] + + const c = "foo" + _ = c[- /* ERROR "negative" */ 1] + _ = c[- /* ERROR "negative" */ 1 :] + _ = c[: - /* ERROR "negative" */ 1] + var c0 byte + c0 = c[0] + _ = c0 + var c2 float32 + c2 = c /* ERROR "cannot use .* in assignment" */ [2] + _ = c[3 /* ERROR "index .* out of bounds" */ ] + _ = ""[0 /* ERROR "index .* out of bounds" */ ] + _ = c2 + + _ = s[1<<30] // no compile-time error here + + // issue 4913 + type mystring string + var ss string + var ms mystring + var i, j int + ss = "foo"[1:2] + ss = "foo"[i:j] + ms = "foo" /* ERROR "cannot use .* in assignment" */ [1:2] + ms = "foo" /* ERROR "cannot use .* in assignment" */ [i:j] + _, _ = ss, ms +} + +type T struct { + x int + y func() +} + +func (*T) m() {} + +func method_expressions() { + _ = T.a /* ERROR "no field or method" */ + _ = T.x /* ERROR "has no method" */ + _ = T.m /* ERROR "cannot call pointer method m on T" */ + _ = (*T).m + + var f func(*T) = T.m /* ERROR "cannot call pointer method m on T" */ + var g func(*T) = (*T).m + _, _ = f, g + + _ = T.y /* ERROR "has no method" */ + _ = (*T).y /* ERROR "has no method" */ +} + +func struct_literals() { + type T0 struct { + a, b, c int + } + + type T1 struct { + T0 + a, b int + u float64 + s string + } + + // keyed elements + _ = T1{} + _ = T1{a: 0, 1 /* ERROR "mixture of .* elements" */ } + _ = T1{aa /* ERROR "unknown field" */ : 0} + _ = T1{1 /* ERROR "invalid field name" */ : 0} + _ = T1{a: 0, s: "foo", u: 0, a /* ERROR "duplicate field" */: 10} + _ = T1{a: "foo" /* ERROR "cannot use .* in struct literal" */ } + _ = T1{c /* ERROR "unknown field" */ : 0} + _ = T1{T0: { /* ERROR "missing type" */ }} // struct literal element type may not be elided + _ = T1{T0: T0{}} + _ = T1{T0 /* ERROR "invalid field name" */ .a: 0} + + // unkeyed elements + _ = T0{1, 2, 3} + _ = T0{1, b /* ERROR "mixture" */ : 2, 3} + _ = T0{1, 2} /* ERROR "too few values" */ + _ = T0{1, 2, 3, 4 /* ERROR "too many values" */ } + _ = T0{1, "foo" /* ERROR "cannot use .* in struct literal" */, 3.4 /* ERROR "cannot use .*\(truncated\)" */} + + // invalid type + type P *struct{ + x int + } + _ = P /* ERROR "invalid composite literal type" */ {} + + // unexported fields + _ = time.Time{} + _ = time.Time{sec /* ERROR "unknown field" */ : 0} + _ = time.Time{ + 0 /* ERROR implicit assignment to unexported field wall in time.Time literal */, + 0 /* ERROR implicit assignment */ , + nil /* ERROR implicit assignment */ , + } +} + +func array_literals() { + type A0 [0]int + _ = A0{} + _ = A0{0 /* ERROR "index .* out of bounds" */} + _ = A0{0 /* ERROR "index .* out of bounds" */ : 0} + + type A1 [10]int + _ = A1{} + _ = A1{0, 1, 2} + _ = A1{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + _ = A1{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /* ERROR "index .* out of bounds" */ } + _ = A1{- /* ERROR "negative" */ 1: 0} + _ = A1{8: 8, 9} + _ = A1{8: 8, 9, 10 /* ERROR "index .* out of bounds" */ } + _ = A1{0, 1, 2, 0 /* ERROR "duplicate index" */ : 0, 3: 3, 4} + _ = A1{5: 5, 6, 7, 3: 3, 4} + _ = A1{5: 5, 6, 7, 3: 3, 4, 5 /* ERROR "duplicate index" */ } + _ = A1{10 /* ERROR "index .* out of bounds" */ : 10, 10 /* ERROR "index .* out of bounds" */ : 10} + _ = A1{5: 5, 6, 7, 3: 3, 1 /* ERROR "overflows" */ <<100: 4, 5 /* ERROR "duplicate index" */ } + _ = A1{5: 5, 6, 7, 4: 4, 1 /* ERROR "overflows" */ <<100: 4} + _ = A1{2.0} + _ = A1{2.1 /* ERROR "truncated" */ } + _ = A1{"foo" /* ERROR "cannot use .* in array or slice literal" */ } + + // indices must be integer constants + i := 1 + const f = 2.1 + const s = "foo" + _ = A1{i /* ERROR "index i must be integer constant" */ : 0} + _ = A1{f /* ERROR "truncated" */ : 0} + _ = A1{s /* ERROR "cannot convert" */ : 0} + + a0 := [...]int{} + assert(len(a0) == 0) + + a1 := [...]int{0, 1, 2} + assert(len(a1) == 3) + var a13 [3]int + var a14 [4]int + a13 = a1 + a14 = a1 /* ERROR "cannot use .* in assignment" */ + _, _ = a13, a14 + + a2 := [...]int{- /* ERROR "negative" */ 1: 0} + _ = a2 + + a3 := [...]int{0, 1, 2, 0 /* ERROR "duplicate index" */ : 0, 3: 3, 4} + assert(len(a3) == 5) // somewhat arbitrary + + a4 := [...]complex128{0, 1, 2, 1<<10-2: -1i, 1i, 400: 10, 12, 14} + assert(len(a4) == 1024) + + // composite literal element types may be elided + type T []int + _ = [10]T{T{}, {}, 5: T{1, 2, 3}, 7: {1, 2, 3}} + a6 := [...]T{T{}, {}, 5: T{1, 2, 3}, 7: {1, 2, 3}} + assert(len(a6) == 8) + + // recursively so + _ = [10][10]T{{}, [10]T{{}}, {{1, 2, 3}}} + + // from the spec + type Point struct { x, y float32 } + _ = [...]Point{Point{1.5, -3.5}, Point{0, 0}} + _ = [...]Point{{1.5, -3.5}, {0, 0}} + _ = [][]int{[]int{1, 2, 3}, []int{4, 5}} + _ = [][]int{{1, 2, 3}, {4, 5}} + _ = [...]*Point{&Point{1.5, -3.5}, &Point{0, 0}} + _ = [...]*Point{{1.5, -3.5}, {0, 0}} +} + +func slice_literals() { + type S0 []int + _ = S0{} + _ = S0{0, 1, 2} + _ = S0{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + _ = S0{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + _ = S0{- /* ERROR "negative" */ 1: 0} + _ = S0{8: 8, 9} + _ = S0{8: 8, 9, 10} + _ = S0{0, 1, 2, 0 /* ERROR "duplicate index" */ : 0, 3: 3, 4} + _ = S0{5: 5, 6, 7, 3: 3, 4} + _ = S0{5: 5, 6, 7, 3: 3, 4, 5 /* ERROR "duplicate index" */ } + _ = S0{10: 10, 10 /* ERROR "duplicate index" */ : 10} + _ = S0{5: 5, 6, 7, 3: 3, 1 /* ERROR "overflows" */ <<100: 4, 5 /* ERROR "duplicate index" */ } + _ = S0{5: 5, 6, 7, 4: 4, 1 /* ERROR "overflows" */ <<100: 4} + _ = S0{2.0} + _ = S0{2.1 /* ERROR "truncated" */ } + _ = S0{"foo" /* ERROR "cannot use .* in array or slice literal" */ } + + // indices must be resolved correctly + const index1 = 1 + _ = S0{index1: 1} + _ = S0{index2: 2} + _ = S0{index3 /* ERROR "undeclared name" */ : 3} + + // indices must be integer constants + i := 1 + const f = 2.1 + const s = "foo" + _ = S0{i /* ERROR "index i must be integer constant" */ : 0} + _ = S0{f /* ERROR "truncated" */ : 0} + _ = S0{s /* ERROR "cannot convert" */ : 0} + + // composite literal element types may be elided + type T []int + _ = []T{T{}, {}, 5: T{1, 2, 3}, 7: {1, 2, 3}} + _ = [][]int{{1, 2, 3}, {4, 5}} + + // recursively so + _ = [][]T{{}, []T{{}}, {{1, 2, 3}}} + + // issue 17954 + type T0 *struct { s string } + _ = []T0{{}} + _ = []T0{{"foo"}} + + type T1 *struct{ int } + _ = []T1{} + _ = []T1{{0}, {1}, {2}} + + type T2 T1 + _ = []T2{} + _ = []T2{{0}, {1}, {2}} + + _ = map[T0]T2{} + _ = map[T0]T2{{}: {}} +} + +const index2 int = 2 + +type N int +func (N) f() {} + +func map_literals() { + type M0 map[string]int + type M1 map[bool]int + type M2 map[*int]int + + _ = M0{} + _ = M0{1 /* ERROR "missing key" */ } + _ = M0{1 /* ERROR "cannot use .* in map literal" */ : 2} + _ = M0{"foo": "bar" /* ERROR "cannot use .* in map literal" */ } + _ = M0{"foo": 1, "bar": 2, "foo" /* ERROR "duplicate key" */ : 3 } + + _ = map[interface{}]int{2: 1, 2 /* ERROR "duplicate key" */ : 1} + _ = map[interface{}]int{int(2): 1, int16(2): 1} + _ = map[interface{}]int{int16(2): 1, int16 /* ERROR "duplicate key" */ (2): 1} + + type S string + + _ = map[interface{}]int{"a": 1, "a" /* ERROR "duplicate key" */ : 1} + _ = map[interface{}]int{"a": 1, S("a"): 1} + _ = map[interface{}]int{S("a"): 1, S /* ERROR "duplicate key" */ ("a"): 1} + _ = map[interface{}]int{1.0: 1, 1.0 /* ERROR "duplicate key" */: 1} + _ = map[interface{}]int{int64(-1): 1, int64 /* ERROR "duplicate key" */ (-1) : 1} + _ = map[interface{}]int{^uint64(0): 1, ^ /* ERROR "duplicate key" */ uint64(0): 1} + _ = map[interface{}]int{complex(1,2): 1, complex /* ERROR "duplicate key" */ (1,2) : 1} + + type I interface { + f() + } + + _ = map[I]int{N(0): 1, N(2): 1} + _ = map[I]int{N(2): 1, N /* ERROR "duplicate key" */ (2): 1} + + // map keys must be resolved correctly + key1 := "foo" + _ = M0{key1: 1} + _ = M0{key2: 2} + _ = M0{key3 /* ERROR "undeclared name" */ : 2} + + var value int + _ = M1{true: 1, false: 0} + _ = M2{nil: 0, &value: 1} + + // composite literal element types may be elided + type T [2]int + _ = map[int]T{0: T{3, 4}, 1: {5, 6}} + + // recursively so + _ = map[int][]T{0: {}, 1: {{}, T{1, 2}}} + + // composite literal key types may be elided + _ = map[T]int{T{3, 4}: 0, {5, 6}: 1} + + // recursively so + _ = map[[2]T]int{{}: 0, {{}}: 1, [2]T{{}}: 2, {T{1, 2}}: 3} + + // composite literal element and key types may be elided + _ = map[T]T{{}: {}, {1, 2}: T{3, 4}, T{4, 5}: {}} + _ = map[T]M0{{} : {}, T{1, 2}: M0{"foo": 0}, {1, 3}: {"foo": 1}} + + // recursively so + _ = map[[2]T][]T{{}: {}, {{}}: {{}, T{1, 2}}, [2]T{{}}: nil, {T{1, 2}}: {{}, {}}} + + // from the spec + type Point struct { x, y float32 } + _ = map[string]Point{"orig": {0, 0}} + _ = map[*Point]string{{0, 0}: "orig"} + + // issue 17954 + type T0 *struct{ s string } + type T1 *struct{ int } + type T2 T1 + + _ = map[T0]T2{} + _ = map[T0]T2{{}: {}} +} + +var key2 string = "bar" + +type I interface { + m() +} + +type I2 interface { + m(int) +} + +type T1 struct{} +type T2 struct{} + +func (T2) m(int) {} + +type mybool bool + +func type_asserts() { + var x int + _ = x /* ERROR "not an interface" */ .(int) + + var e interface{} + var ok bool + x, ok = e.(int) + _ = ok + + // ok value is of type bool + var myok mybool + _, myok = e.(int) + _ = myok + + var t I + _ = t /* ERROR "use of .* outside type switch" */ .(type) + _ = t /* ERROR "missing method m" */ .(T) + _ = t.(*T) + _ = t /* ERROR "missing method m" */ .(T1) + _ = t /* ERROR "wrong type for method m" */ .(T2) + _ = t /* STRICT "wrong type for method m" */ .(I2) // only an error in strict mode (issue 8561) + + // e doesn't statically have an m, but may have one dynamically. + _ = e.(I2) +} + +func f0() {} +func f1(x int) {} +func f2(u float32, s string) {} +func fs(s []byte) {} +func fv(x ...int) {} +func fi(x ... interface{}) {} +func (T) fm(x ...int) + +func g0() {} +func g1() int { return 0} +func g2() (u float32, s string) { return } +func gs() []byte { return nil } + +func _calls() { + var x int + var y float32 + var s []int + + f0() + _ = f0 /* ERROR "used as value" */ () + f0(g0 /* ERROR "too many arguments" */ ) + + f1(0) + f1(x) + f1(10.0) + f1() /* ERROR "not enough arguments" */ + f1(x, y /* ERROR "too many arguments" */ ) + f1(s /* ERROR "cannot use .* in argument" */ ) + f1(x ... /* ERROR "cannot use ..." */ ) + f1(g0 /* ERROR "used as value" */ ()) + f1(g1()) + f1(g2 /* ERROR "too many arguments" */ ()) + + f2() /* ERROR "not enough arguments" */ + f2(3.14) /* ERROR "not enough arguments" */ + f2(3.14, "foo") + f2(x /* ERROR "cannot use .* in argument" */ , "foo") + f2(g0 /* ERROR "used as value" */ ()) + f2(g1()) /* ERROR "not enough arguments" */ + f2(g2()) + + fs() /* ERROR "not enough arguments" */ + fs(g0 /* ERROR "used as value" */ ()) + fs(g1 /* ERROR "cannot use .* in argument" */ ()) + fs(g2 /* ERROR "too many arguments" */ ()) + fs(gs()) + + fv() + fv(1, 2.0, x) + fv(s /* ERROR "cannot use .* in argument" */ ) + fv(s...) + fv(x /* ERROR "cannot use" */ ...) + fv(1, s /* ERROR "too many arguments" */ ... ) + fv(gs /* ERROR "cannot use .* in argument" */ ()) + fv(gs /* ERROR "cannot use .* in argument" */ ()...) + + var t T + t.fm() + t.fm(1, 2.0, x) + t.fm(s /* ERROR "cannot use .* in argument" */ ) + t.fm(g1()) + t.fm(1, s /* ERROR "too many arguments" */ ... ) + t.fm(gs /* ERROR "cannot use .* in argument" */ ()) + t.fm(gs /* ERROR "cannot use .* in argument" */ ()...) + + T.fm(t, ) + T.fm(t, 1, 2.0, x) + T.fm(t, s /* ERROR "cannot use .* in argument" */ ) + T.fm(t, g1()) + T.fm(t, 1, s /* ERROR "too many arguments" */ ... ) + T.fm(t, gs /* ERROR "cannot use .* in argument" */ ()) + T.fm(t, gs /* ERROR "cannot use .* in argument" */ ()...) + + var i interface{ fm(x ...int) } = t + i.fm() + i.fm(1, 2.0, x) + i.fm(s /* ERROR "cannot use .* in argument" */ ) + i.fm(g1()) + i.fm(1, s /* ERROR "too many arguments" */ ... ) + i.fm(gs /* ERROR "cannot use .* in argument" */ ()) + i.fm(gs /* ERROR "cannot use .* in argument" */ ()...) + + fi() + fi(1, 2.0, x, 3.14, "foo") + fi(g2()) + fi(0, g2) + fi(0, g2 /* ERROR "2-valued g2" */ ()) +} + +func issue6344() { + type T []interface{} + var x T + fi(x...) // ... applies also to named slices +} diff --git a/src/cmd/compile/internal/types2/testdata/check/go1_12.src b/src/cmd/compile/internal/types2/testdata/check/go1_12.src new file mode 100644 index 0000000000000000000000000000000000000000..75a602b8ff9bd9917ddf46180749e3a80c7ba155 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/go1_12.src @@ -0,0 +1,34 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check Go language version-specific errors. + +package go1_12 // go1.12 + +// numeric literals +const ( + _ = 1_000 // ERROR "underscores in numeric literals requires go1.13 or later" + _ = 0b111 // ERROR "binary literals requires go1.13 or later" + _ = 0o567 // ERROR "0o/0O-style octal literals requires go1.13 or later" + _ = 0xabc // ok + _ = 0x0p1 // ERROR "hexadecimal floating-point literals requires go1.13 or later" + + _ = 0B111 // ERROR "binary" + _ = 0O567 // ERROR "octal" + _ = 0Xabc // ok + _ = 0X0P1 // ERROR "hexadecimal floating-point" + + _ = 1_000i // ERROR "underscores" + _ = 0b111i // ERROR "binary" + _ = 0o567i // ERROR "octal" + _ = 0xabci // ERROR "hexadecimal floating-point" + _ = 0x0p1i // ERROR "hexadecimal floating-point" +) + +// signed shift counts +var ( + s int + _ = 1 << s // ERROR "invalid operation: signed shift count s \(variable of type int\) requires go1.13 or later" + _ = 1 >> s // ERROR "signed shift count" +) diff --git a/src/cmd/compile/internal/types2/testdata/check/go1_13.src b/src/cmd/compile/internal/types2/testdata/check/go1_13.src new file mode 100644 index 0000000000000000000000000000000000000000..93cb4c72a7e1a69cc62df8b19c8c068bfd0611a7 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/go1_13.src @@ -0,0 +1,21 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check Go language version-specific errors. + +package go1_13 // go1.13 + +// interface embedding + +type I interface { m() } + +type _ interface { + m() + I // ERROR "duplicate method m" +} + +type _ interface { + I + I // ERROR "duplicate method m" +} diff --git a/src/cmd/compile/internal/types2/testdata/check/go1_16.src b/src/cmd/compile/internal/types2/testdata/check/go1_16.src new file mode 100644 index 0000000000000000000000000000000000000000..fdf5c99d7e3248f76e0bd4f7208c4c5aed6e2bc8 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/go1_16.src @@ -0,0 +1,13 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check Go language version-specific errors. + +package go1_16 // go1.16 + +type Slice []byte +type Array [8]byte + +var s Slice +var p = (*Array)(s /* ERROR requires go1.17 or later */ ) diff --git a/src/cmd/compile/internal/types2/testdata/check/go1_8.src b/src/cmd/compile/internal/types2/testdata/check/go1_8.src new file mode 100644 index 0000000000000000000000000000000000000000..0f3ba9443bd5317f6ef4b235e98e74deee0db572 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/go1_8.src @@ -0,0 +1,10 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check Go language version-specific errors. + +package go1_8 // go1.8 + +// type alias declarations +type any /* ERROR type aliases requires go1.9 or later */ = interface{} diff --git a/src/go/types/testdata/gotos.src b/src/cmd/compile/internal/types2/testdata/check/gotos.src similarity index 100% rename from src/go/types/testdata/gotos.src rename to src/cmd/compile/internal/types2/testdata/check/gotos.src diff --git a/src/go/types/testdata/importC.src b/src/cmd/compile/internal/types2/testdata/check/importC.src similarity index 100% rename from src/go/types/testdata/importC.src rename to src/cmd/compile/internal/types2/testdata/check/importC.src diff --git a/src/go/types/testdata/importdecl0a.src b/src/cmd/compile/internal/types2/testdata/check/importdecl0/importdecl0a.src similarity index 95% rename from src/go/types/testdata/importdecl0a.src rename to src/cmd/compile/internal/types2/testdata/check/importdecl0/importdecl0a.src index e96fca3cdd56fa86cb284276e78412b3c5f296a8..5ceb96e1fada684387f20c56894810a772875de2 100644 --- a/src/go/types/testdata/importdecl0a.src +++ b/src/cmd/compile/internal/types2/testdata/check/importdecl0/importdecl0a.src @@ -10,7 +10,7 @@ import ( // we can have multiple blank imports (was bug) _ "math" _ "net/rpc" - init /* ERROR "cannot declare init" */ "fmt" + init /* ERROR "cannot import package as init" */ "fmt" // reflect defines a type "flag" which shows up in the gc export data "reflect" . /* ERROR "imported but not used" */ "reflect" diff --git a/src/cmd/compile/internal/types2/testdata/check/importdecl0/importdecl0b.src b/src/cmd/compile/internal/types2/testdata/check/importdecl0/importdecl0b.src new file mode 100644 index 0000000000000000000000000000000000000000..19b55aff76410b8304eba0c89c26463674a42cff --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/importdecl0/importdecl0b.src @@ -0,0 +1,30 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package importdecl0 + +import "math" +import m "math" + +import . "testing" // declares T in file scope +import . /* ERROR .unsafe. imported but not used */ "unsafe" +import . "fmt" // declares Println in file scope + +import ( + "" /* ERROR invalid import path */ + "a!b" /* ERROR invalid import path */ + "abc\xffdef" /* ERROR invalid import path */ +) + +// using "math" in this file doesn't affect its use in other files +const Pi0 = math.Pi +const Pi1 = m.Pi + +type _ T // use "testing" + +func _() func() interface{} { + return func() interface{} { + return Println // use "fmt" + } +} diff --git a/src/go/types/testdata/importdecl1a.src b/src/cmd/compile/internal/types2/testdata/check/importdecl1/importdecl1a.src similarity index 100% rename from src/go/types/testdata/importdecl1a.src rename to src/cmd/compile/internal/types2/testdata/check/importdecl1/importdecl1a.src diff --git a/src/go/types/testdata/importdecl1b.src b/src/cmd/compile/internal/types2/testdata/check/importdecl1/importdecl1b.src similarity index 77% rename from src/go/types/testdata/importdecl1b.src rename to src/cmd/compile/internal/types2/testdata/check/importdecl1/importdecl1b.src index ee70bbd8e73f787c8aa3b3d792f5673d0f0aacbb..43a7bcd75396cbb34ab5ab3c1e354f9d1f4642b7 100644 --- a/src/go/types/testdata/importdecl1b.src +++ b/src/cmd/compile/internal/types2/testdata/check/importdecl1/importdecl1b.src @@ -4,7 +4,7 @@ package importdecl1 -import . /* ERROR "imported but not used" */ "unsafe" +import . /* ERROR .unsafe. imported but not used */ "unsafe" type B interface { A diff --git a/src/go/types/testdata/init0.src b/src/cmd/compile/internal/types2/testdata/check/init0.src similarity index 100% rename from src/go/types/testdata/init0.src rename to src/cmd/compile/internal/types2/testdata/check/init0.src diff --git a/src/go/types/testdata/init1.src b/src/cmd/compile/internal/types2/testdata/check/init1.src similarity index 100% rename from src/go/types/testdata/init1.src rename to src/cmd/compile/internal/types2/testdata/check/init1.src diff --git a/src/go/types/testdata/init2.src b/src/cmd/compile/internal/types2/testdata/check/init2.src similarity index 100% rename from src/go/types/testdata/init2.src rename to src/cmd/compile/internal/types2/testdata/check/init2.src diff --git a/src/go/types/testdata/issue25008a.src b/src/cmd/compile/internal/types2/testdata/check/issue25008/issue25008a.src similarity index 100% rename from src/go/types/testdata/issue25008a.src rename to src/cmd/compile/internal/types2/testdata/check/issue25008/issue25008a.src diff --git a/src/go/types/testdata/issue25008b.src b/src/cmd/compile/internal/types2/testdata/check/issue25008/issue25008b.src similarity index 100% rename from src/go/types/testdata/issue25008b.src rename to src/cmd/compile/internal/types2/testdata/check/issue25008/issue25008b.src diff --git a/src/cmd/compile/internal/types2/testdata/check/issues.go2 b/src/cmd/compile/internal/types2/testdata/check/issues.go2 new file mode 100644 index 0000000000000000000000000000000000000000..1c73b5da9219c04c1b2a479f594fc7d315ab3948 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/issues.go2 @@ -0,0 +1,249 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains regression tests for bugs found. + +package p + +import "io" +import "context" + +// Interfaces are always comparable (though the comparison may panic at runtime). +func eql[T comparable](x, y T) bool { + return x == y +} + +func _() { + var x interface{} + var y interface{ m() } + eql(x, y /* ERROR does not match */ ) // interfaces of different types + eql(x, x) + eql(y, y) + eql(y, nil) + eql[io.Reader](nil, nil) +} + +// If we have a receiver of pointer type (below: *T) we must ignore +// the pointer in the implementation of the method lookup because +// the type bound of T is an interface and pointer to interface types +// have no methods and then the lookup would fail. +type C[T any] interface { + m() +} + +// using type bound C +func _[T C[T]](x *T) { + x.m() +} + +// using an interface literal as bound +func _[T interface{ m() }](x *T) { + x.m() +} + +func f2[_ interface{ m1(); m2() }]() + +type T struct{} +func (T) m1() +func (*T) m2() + +func _() { + f2[T /* ERROR wrong method signature */ ]() + f2[*T]() +} + +// When a type parameter is used as an argument to instantiate a parameterized +// type with a type list constraint, all of the type argument's types in its +// bound, but at least one (!), must be in the type list of the bound of the +// corresponding parameterized type's type parameter. +type T1[P interface{type uint}] struct{} + +func _[P any]() { + _ = T1[P /* ERROR P has no type constraints */ ]{} +} + +// This is the original (simplified) program causing the same issue. +type Unsigned interface { + type uint +} + +type T2[U Unsigned] struct { + s U +} + +func (u T2[U]) Add1() U { + return u.s + 1 +} + +func NewT2[U any]() T2[U /* ERROR U has no type constraints */ ] { + return T2[U /* ERROR U has no type constraints */ ]{} +} + +func _() { + u := NewT2[string]() + _ = u.Add1() +} + +// When we encounter an instantiated type such as Elem[T] we must +// not "expand" the instantiation when the type to be instantiated +// (Elem in this case) is not yet fully set up. +type Elem[T any] struct { + next *Elem[T] + list *List[T] +} + +type List[T any] struct { + root Elem[T] +} + +func (l *List[T]) Init() { + l.root.next = &l.root +} + +// This is the original program causing the same issue. +type Element2[TElem any] struct { + next, prev *Element2[TElem] + list *List2[TElem] + Value TElem +} + +type List2[TElem any] struct { + root Element2[TElem] + len int +} + +func (l *List2[TElem]) Init() *List2[TElem] { + l.root.next = &l.root + l.root.prev = &l.root + l.len = 0 + return l +} + +// Self-recursive instantiations must work correctly. +type A[P any] struct { _ *A[P] } + +type AB[P any] struct { _ *BA[P] } +type BA[P any] struct { _ *AB[P] } + +// And a variation that also caused a problem with an +// unresolved underlying type. +type Element3[TElem any] struct { + next, prev *Element3[TElem] + list *List3[TElem] + Value TElem +} + +func (e *Element3[TElem]) Next() *Element3[TElem] { + if p := e.next; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +type List3[TElem any] struct { + root Element3[TElem] + len int +} + +// Infinite generic type declarations must lead to an error. +type inf1[T any] struct{ _ inf1 /* ERROR illegal cycle */ [T] } +type inf2[T any] struct{ inf2 /* ERROR illegal cycle */ [T] } + +// The implementation of conversions T(x) between integers and floating-point +// numbers checks that both T and x have either integer or floating-point +// type. When the type of T or x is a type parameter, the respective simple +// predicate disjunction in the implementation was wrong because if a type list +// contains both an integer and a floating-point type, the type parameter is +// neither an integer or a floating-point number. +func convert[T1, T2 interface{type int, uint, float32}](v T1) T2 { + return T2(v) +} + +func _() { + convert[int, uint](5) +} + +// When testing binary operators, for +, the operand types must either be +// both numeric, or both strings. The implementation had the same problem +// with this check as the conversion issue above (issue #39623). + +func issue39623[T interface{type int, string}](x, y T) T { + return x + y +} + +// Simplified, from https://go2goplay.golang.org/p/efS6x6s-9NI: +func Sum[T interface{type int, string}](s []T) (sum T) { + for _, v := range s { + sum += v + } + return +} + +// Assignability of an unnamed pointer type to a type parameter that +// has a matching underlying type. +func _[T interface{}, PT interface{type *T}] (x T) PT { + return &x +} + +// Indexing of generic types containing type parameters in their type list: +func at[T interface{ type []E }, E interface{}](x T, i int) E { + return x[i] +} + +// A generic type inside a function acts like a named type. Its underlying +// type is itself, its "operational type" is defined by the type list in +// the tybe bound, if any. +func _[T interface{type int}](x T) { + type myint int + var _ int = int(x) + var _ T = 42 + var _ T = T(myint(42)) +} + +// Indexing a generic type with an array type bound checks length. +// (Example by mdempsky@.) +func _[T interface { type [10]int }](x T) { + _ = x[9] // ok + _ = x[20 /* ERROR out of bounds */ ] +} + +// Pointer indirection of a generic type. +func _[T interface{ type *int }](p T) int { + return *p +} + +// Channel sends and receives on generic types. +func _[T interface{ type chan int }](ch T) int { + ch <- 0 + return <- ch +} + +// Calling of a generic variable. +func _[T interface{ type func() }](f T) { + f() + go f() +} + +// We must compare against the underlying type of type list entries +// when checking if a constraint is satisfied by a type. The under- +// lying type of each type list entry must be computed after the +// interface has been instantiated as its typelist may contain a +// type parameter that was substituted with a defined type. +// Test case from an (originally) failing example. + +type sliceOf[E any] interface{ type []E } + +func append[T interface{}, S sliceOf[T], T2 interface{ type T }](s S, t ...T2) S + +var f func() +var cancelSlice []context.CancelFunc +var _ = append[context.CancelFunc, []context.CancelFunc, context.CancelFunc](cancelSlice, f) + +// A generic function must be instantiated with a type, not a value. + +func g[T any](T) T + +var _ = g[int] +var _ = g[nil /* ERROR is not a type */ ] +var _ = g(0) diff --git a/src/go/types/testdata/issues.src b/src/cmd/compile/internal/types2/testdata/check/issues.src similarity index 97% rename from src/go/types/testdata/issues.src rename to src/cmd/compile/internal/types2/testdata/check/issues.src index e0c5d7a37cf48f339273e849b07e9c88c2610aec..21aa208cc769d2b872dbf1c46cacf1e7ae9d2382 100644 --- a/src/go/types/testdata/issues.src +++ b/src/cmd/compile/internal/types2/testdata/check/issues.src @@ -325,8 +325,8 @@ func issue28281c(a, b, c ... /* ERROR can only use ... with final parameter */ i func issue28281d(... /* ERROR can only use ... with final parameter */ int, int) func issue28281e(a, b, c ... /* ERROR can only use ... with final parameter */ int, d int) func issue28281f(... /* ERROR can only use ... with final parameter */ int, ... /* ERROR can only use ... with final parameter */ int, int) -func (... /* ERROR expected type */ TT) f() -func issue28281g() (... /* ERROR expected type */ TT) +func (... /* ERROR can only use ... with final parameter in list */ TT) f() +func issue28281g() (... /* ERROR can only use ... with final parameter in list */ TT) // Issue #26234: Make various field/method lookup errors easier to read by matching cmd/compile's output func issue26234a(f *syn.File) { @@ -363,3 +363,9 @@ func issue35895() { // qualify packages with full path name in this case. var _ t1.Template = t2 /* ERROR cannot use .* \(value of type "html/template".Template\) as "text/template".Template */ .Template{} } + +func issue42989(s uint) { + var m map[int]string + delete(m, 1< 0: + pn = &(*pn).right + default: + return pn + } + } + return pn +} + +// Insert inserts a new key/value into the map. +// If the key is already present, the value is replaced. +// Returns true if this is a new key, false if already present. +func (m *Map[K, V]) Insert(key K, val V) bool { + pn := m.find(key) + if *pn != nil { + (*pn).val = val + return false + } + *pn = &node[K, V]{key: key, val: val} + return true +} + +// Find returns the value associated with a key, or zero if not present. +// The found result reports whether the key was found. +func (m *Map[K, V]) Find(key K) (V, bool) { + pn := m.find(key) + if *pn == nil { + var zero V // see the discussion of zero values, above + return zero, false + } + return (*pn).val, true +} + +// keyValue is a pair of key and value used when iterating. +type keyValue[K, V any] struct { + key K + val V +} + +// InOrder returns an iterator that does an in-order traversal of the map. +func (m *Map[K, V]) InOrder() *Iterator[K, V] { + sender, receiver := chans.Ranger[keyValue[K, V]]() + var f func(*node[K, V]) bool + f = func(n *node[K, V]) bool { + if n == nil { + return true + } + // Stop sending values if sender.Send returns false, + // meaning that nothing is listening at the receiver end. + return f(n.left) && + sender.Send(keyValue[K, V]{n.key, n.val}) && + f(n.right) + } + go func() { + f(m.root) + sender.Close() + }() + return &Iterator[K, V]{receiver} +} + +// Iterator is used to iterate over the map. +type Iterator[K, V any] struct { + r *chans.Receiver[keyValue[K, V]] +} + +// Next returns the next key and value pair, and a boolean indicating +// whether they are valid or whether we have reached the end. +func (it *Iterator[K, V]) Next() (K, V, bool) { + keyval, ok := it.r.Next() + if !ok { + var zerok K + var zerov V + return zerok, zerov, false + } + return keyval.key, keyval.val, true +} diff --git a/src/cmd/compile/internal/types2/testdata/check/map2.go2 b/src/cmd/compile/internal/types2/testdata/check/map2.go2 new file mode 100644 index 0000000000000000000000000000000000000000..2833445662de813d6d3eeed95978c8342316b29f --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/map2.go2 @@ -0,0 +1,146 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is like map.go2, but instead if importing chans, it contains +// the necessary functionality at the end of the file. + +// Package orderedmap provides an ordered map, implemented as a binary tree. +package orderedmap + +// Map is an ordered map. +type Map[K, V any] struct { + root *node[K, V] + compare func(K, K) int +} + +// node is the type of a node in the binary tree. +type node[K, V any] struct { + key K + val V + left, right *node[K, V] +} + +// New returns a new map. +func New[K, V any](compare func(K, K) int) *Map[K, V] { + return &Map[K, V]{compare: compare} +} + +// find looks up key in the map, and returns either a pointer +// to the node holding key, or a pointer to the location where +// such a node would go. +func (m *Map[K, V]) find(key K) **node[K, V] { + pn := &m.root + for *pn != nil { + switch cmp := m.compare(key, (*pn).key); { + case cmp < 0: + pn = &(*pn).left + case cmp > 0: + pn = &(*pn).right + default: + return pn + } + } + return pn +} + +// Insert inserts a new key/value into the map. +// If the key is already present, the value is replaced. +// Returns true if this is a new key, false if already present. +func (m *Map[K, V]) Insert(key K, val V) bool { + pn := m.find(key) + if *pn != nil { + (*pn).val = val + return false + } + *pn = &node[K, V]{key: key, val: val} + return true +} + +// Find returns the value associated with a key, or zero if not present. +// The found result reports whether the key was found. +func (m *Map[K, V]) Find(key K) (V, bool) { + pn := m.find(key) + if *pn == nil { + var zero V // see the discussion of zero values, above + return zero, false + } + return (*pn).val, true +} + +// keyValue is a pair of key and value used when iterating. +type keyValue[K, V any] struct { + key K + val V +} + +// InOrder returns an iterator that does an in-order traversal of the map. +func (m *Map[K, V]) InOrder() *Iterator[K, V] { + sender, receiver := chans_Ranger[keyValue[K, V]]() + var f func(*node[K, V]) bool + f = func(n *node[K, V]) bool { + if n == nil { + return true + } + // Stop sending values if sender.Send returns false, + // meaning that nothing is listening at the receiver end. + return f(n.left) && + sender.Send(keyValue[K, V]{n.key, n.val}) && + f(n.right) + } + go func() { + f(m.root) + sender.Close() + }() + return &Iterator[K, V]{receiver} +} + +// Iterator is used to iterate over the map. +type Iterator[K, V any] struct { + r *chans_Receiver[keyValue[K, V]] +} + +// Next returns the next key and value pair, and a boolean indicating +// whether they are valid or whether we have reached the end. +func (it *Iterator[K, V]) Next() (K, V, bool) { + keyval, ok := it.r.Next() + if !ok { + var zerok K + var zerov V + return zerok, zerov, false + } + return keyval.key, keyval.val, true +} + +// chans + +func chans_Ranger[T any]() (*chans_Sender[T], *chans_Receiver[T]) + +// A sender is used to send values to a Receiver. +type chans_Sender[T any] struct { + values chan<- T + done <-chan bool +} + +func (s *chans_Sender[T]) Send(v T) bool { + select { + case s.values <- v: + return true + case <-s.done: + return false + } +} + +func (s *chans_Sender[T]) Close() { + close(s.values) +} + +type chans_Receiver[T any] struct { + values <-chan T + done chan<- bool +} + +func (r *chans_Receiver[T]) Next() (T, bool) { + v, ok := <-r.values + return v, ok +} \ No newline at end of file diff --git a/src/go/types/testdata/methodsets.src b/src/cmd/compile/internal/types2/testdata/check/methodsets.src similarity index 100% rename from src/go/types/testdata/methodsets.src rename to src/cmd/compile/internal/types2/testdata/check/methodsets.src diff --git a/src/cmd/compile/internal/types2/testdata/check/mtypeparams.go2 b/src/cmd/compile/internal/types2/testdata/check/mtypeparams.go2 new file mode 100644 index 0000000000000000000000000000000000000000..c2f282bae11a9817620365e445a9bc8b3f3b310f --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/mtypeparams.go2 @@ -0,0 +1,52 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// If types2.Config.AcceptMethodTypeParams is set, +// the type checker accepts methods that have their +// own type parameter list. + +package p + +type S struct{} + +func (S) m[T any](v T) + +// TODO(gri) Once we collect interface method type parameters +// in the parser, we can enable these tests again. +/* +type I interface { + m[T any](v T) +} + +type J interface { + m[T any](v T) +} + +var _ I = S{} +var _ I = J(nil) + +type C interface{ n() } + +type Sc struct{} + +func (Sc) m[T C](v T) + +type Ic interface { + m[T C](v T) +} + +type Jc interface { + m[T C](v T) +} + +var _ Ic = Sc{} +var _ Ic = Jc(nil) + +// TODO(gri) These should fail because the constraints don't match. +var _ I = Sc{} +var _ I = Jc(nil) + +var _ Ic = S{} +var _ Ic = J(nil) +*/ \ No newline at end of file diff --git a/src/cmd/compile/internal/types2/testdata/check/shifts.src b/src/cmd/compile/internal/types2/testdata/check/shifts.src new file mode 100644 index 0000000000000000000000000000000000000000..60db731cf4e03d3d2ee9c02bc1fb623ab89cb6c4 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/shifts.src @@ -0,0 +1,398 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package shifts + +func shifts0() { + // basic constant shifts + const ( + s = 10 + _ = 0<<0 + _ = 1<> s) + _, _, _ = u, v, x +} + +func shifts4() { + // shifts in comparisons w/ untyped operands + var s uint + + _ = 1<> 1.1 /* ERROR "truncated to uint" */ // example from issue 11325 + _ = 0 >> 1.1 /* ERROR "truncated to uint" */ + _ = 0 << 1.1 /* ERROR "truncated to uint" */ + _ = 0 >> 1. + _ = 1 >> 1.1 /* ERROR "truncated to uint" */ + _ = 1 >> 1. + _ = 1. >> 1 + _ = 1. >> 1. + _ = 1.1 /* ERROR "must be integer" */ >> 1 +} + +func issue11594() { + var _ = complex64 /* ERROR "must be integer" */ (1) << 2 // example from issue 11594 + _ = float32 /* ERROR "must be integer" */ (0) << 1 + _ = float64 /* ERROR "must be integer" */ (0) >> 2 + _ = complex64 /* ERROR "must be integer" */ (0) << 3 + _ = complex64 /* ERROR "must be integer" */ (0) >> 4 +} + +func issue21727() { + var s uint + var a = make([]int, 1< 255: + return 255 + } +} + +var input = []int{-4, 68954, 7, 44, 0, -555, 6945} +var limited1 = Map[int, byte](input, limiter) +var limited2 = Map(input, limiter) // using type inference + +func reducer(x float64, y int) float64 { + return x + float64(y) +} + +var reduced1 = Reduce[int, float64](input, 0, reducer) +var reduced2 = Reduce(input, 1i /* ERROR overflows */, reducer) // using type inference +var reduced3 = Reduce(input, 1, reducer) // using type inference + +func filter(x int) bool { + return x&1 != 0 +} + +var filtered1 = Filter[int](input, filter) +var filtered2 = Filter(input, filter) // using type inference + diff --git a/src/cmd/compile/internal/types2/testdata/check/stmt0.src b/src/cmd/compile/internal/types2/testdata/check/stmt0.src new file mode 100644 index 0000000000000000000000000000000000000000..bedcbe5fce3573f8aea5c8c8b2fb73786c0a9b17 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/stmt0.src @@ -0,0 +1,980 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// statements + +package stmt0 + +func assignments0() (int, int) { + var a, b, c int + var ch chan int + f0 := func() {} + f1 := func() int { return 1 } + f2 := func() (int, int) { return 1, 2 } + f3 := func() (int, int, int) { return 1, 2, 3 } + + a, b, c = 1, 2, 3 + a, b, c = 1 /* ERROR "cannot assign [1-9]+ values to [1-9]+ variables" */ , 2 + a, b, c = 1 /* ERROR "cannot assign [1-9]+ values to [1-9]+ variables" */ , 2, 3, 4 + _, _, _ = a, b, c + + a = f0 /* ERROR "used as value" */ () + a = f1() + a = f2 /* ERROR "cannot assign [1-9]+ values to [1-9]+ variables" */ () + a, b = f2() + a, b, c = f2 /* ERROR "cannot assign [1-9]+ values to [1-9]+ variables" */ () + a, b, c = f3() + a, b = f3 /* ERROR "cannot assign [1-9]+ values to [1-9]+ variables" */ () + + a, b, c = <- /* ERROR "cannot assign [1-9]+ values to [1-9]+ variables" */ ch + + return /* ERROR "wrong number of return values" */ + return /* ERROR "wrong number of return values" */ 1 + return 1, 2 + return /* ERROR "wrong number of return values" */ 1, 2, 3 +} + +func assignments1() { + b, i, f, c, s := false, 1, 1.0, 1i, "foo" + b = i /* ERROR "cannot use .* in assignment" */ + i = f /* ERROR "cannot use .* in assignment" */ + f = c /* ERROR "cannot use .* in assignment" */ + c = s /* ERROR "cannot use .* in assignment" */ + s = b /* ERROR "cannot use .* in assignment" */ + + v0, v1, v2 := 1 /* ERROR "cannot initialize" */ , 2, 3, 4 + _, _, _ = v0, v1, v2 + + b = true + + i += 1 + i += "foo" /* ERROR "cannot convert.*int" */ + + f -= 1 + f /= 0 + f = float32(0)/0 /* ERROR "division by zero" */ + f -= "foo" /* ERROR "cannot convert.*float64" */ + + c *= 1 + c /= 0 + + s += "bar" + s += 1 /* ERROR "cannot convert.*string" */ + + var u64 uint64 + u64 += 1< 0 { + return l[0], true + } + return +} + +// A test case for instantiating types with other types (extracted from map.go2) + +type Pair[K any] struct { + key K +} + +type Receiver[T any] struct { + values T +} + +type Iterator[K any] struct { + r Receiver[Pair[K]] +} + +func Values [T any] (r Receiver[T]) T { + return r.values +} + +func (it Iterator[K]) Next() K { + return Values[Pair[K]](it.r).key +} + +// A more complex test case testing type bounds (extracted from linalg.go2 and reduced to essence) + +type NumericAbs[T any] interface { + Abs() T +} + +func AbsDifference[T NumericAbs[T]](x T) + +type OrderedAbs[T any] T + +func (a OrderedAbs[T]) Abs() OrderedAbs[T] + +func OrderedAbsDifference[T any](x T) { + AbsDifference(OrderedAbs[T](x)) +} + +// same code, reduced to essence + +func g[P interface{ m() P }](x P) + +type T4[P any] P + +func (_ T4[P]) m() T4[P] + +func _[Q any](x Q) { + g(T4[Q](x)) +} + +// Another test case that caused problems in the past + +type T5[_ interface { a() }, _ interface{}] struct{} + +type A[P any] struct{ x P } + +func (_ A[P]) a() {} + +var _ T5[A[int], int] + +// Invoking methods with parameterized receiver types uses +// type inference to determine the actual type arguments matching +// the receiver type parameters from the actual receiver argument. +// Go does implicit address-taking and dereferenciation depending +// on the actual receiver and the method's receiver type. To make +// type inference work, the type-checker matches "pointer-ness" +// of the actual receiver and the method's receiver type. +// The following code tests this mechanism. + +type R1[A any] struct{} +func (_ R1[A]) vm() +func (_ *R1[A]) pm() + +func _[T any](r R1[T], p *R1[T]) { + r.vm() + r.pm() + p.vm() + p.pm() +} + +type R2[A, B any] struct{} +func (_ R2[A, B]) vm() +func (_ *R2[A, B]) pm() + +func _[T any](r R2[T, int], p *R2[string, T]) { + r.vm() + r.pm() + p.vm() + p.pm() +} + +// An interface can (explicitly) declare at most one type list. +type _ interface { + m0() + type int, string, bool + type /* ERROR multiple type lists */ float32, float64 + m1() + m2() + type /* ERROR multiple type lists */ complex64, complex128 + type /* ERROR multiple type lists */ rune +} + +// Interface type lists may contain each type at most once. +// (If there are multiple lists, we assume the author intended +// for them to be all in a single list, and we report the error +// as well.) +type _ interface { + type int, int /* ERROR duplicate type int */ + type /* ERROR multiple type lists */ int /* ERROR duplicate type int */ +} + +type _ interface { + type struct{f int}, struct{g int}, struct /* ERROR duplicate type */ {f int} +} + +// Interface type lists can contain any type, incl. *Named types. +// Verify that we use the underlying type to compute the operational type. +type MyInt int +func add1[T interface{type MyInt}](x T) T { + return x + 1 +} + +type MyString string +func double[T interface{type MyInt, MyString}](x T) T { + return x + x +} + +// Embedding of interfaces with type lists leads to interfaces +// with type lists that are the intersection of the embedded +// type lists. + +type E0 interface { + type int, bool, string +} + +type E1 interface { + type int, float64, string +} + +type E2 interface { + type float64 +} + +type I0 interface { + E0 +} + +func f0[T I0]() +var _ = f0[int] +var _ = f0[bool] +var _ = f0[string] +var _ = f0[float64 /* ERROR does not satisfy I0 */ ] + +type I01 interface { + E0 + E1 +} + +func f01[T I01]() +var _ = f01[int] +var _ = f01[bool /* ERROR does not satisfy I0 */ ] +var _ = f01[string] +var _ = f01[float64 /* ERROR does not satisfy I0 */ ] + +type I012 interface { + E0 + E1 + E2 +} + +func f012[T I012]() +var _ = f012[int /* ERROR does not satisfy I012 */ ] +var _ = f012[bool /* ERROR does not satisfy I012 */ ] +var _ = f012[string /* ERROR does not satisfy I012 */ ] +var _ = f012[float64 /* ERROR does not satisfy I012 */ ] + +type I12 interface { + E1 + E2 +} + +func f12[T I12]() +var _ = f12[int /* ERROR does not satisfy I12 */ ] +var _ = f12[bool /* ERROR does not satisfy I12 */ ] +var _ = f12[string /* ERROR does not satisfy I12 */ ] +var _ = f12[float64] + +type I0_ interface { + E0 + type int +} + +func f0_[T I0_]() +var _ = f0_[int] +var _ = f0_[bool /* ERROR does not satisfy I0_ */ ] +var _ = f0_[string /* ERROR does not satisfy I0_ */ ] +var _ = f0_[float64 /* ERROR does not satisfy I0_ */ ] diff --git a/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 b/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 new file mode 100644 index 0000000000000000000000000000000000000000..badda01105bd46d48ece073837134c64b742dab1 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 @@ -0,0 +1,434 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// import "io" // for type assertion tests + +// The predeclared identifier "any" is only visible as a constraint +// in a type parameter list. +var _ any // ERROR undeclared +func _[_ any /* ok here */ , _ interface{any /* ERROR undeclared */ }](any /* ERROR undeclared */ ) { + var _ any /* ERROR undeclared */ +} + +func identity[T any](x T) T { return x } + +func _[_ any](x int) int +func _[T any](T /* ERROR redeclared */ T)() +func _[T, T /* ERROR redeclared */ any]() + +// Constraints (incl. any) may be parenthesized. +func _[_ (any)]() {} +func _[_ (interface{})]() {} + +func reverse[T any](list []T) []T { + rlist := make([]T, len(list)) + i := len(list) + for _, x := range list { + i-- + rlist[i] = x + } + return rlist +} + +var _ = reverse /* ERROR cannot use generic function reverse */ +var _ = reverse[int, float32 /* ERROR got 2 type arguments */ ] ([]int{1, 2, 3}) +var _ = reverse[int]([ /* ERROR cannot use */ ]float32{1, 2, 3}) +var f = reverse[chan int] +var _ = f(0 /* ERROR cannot use 0 .* as \[\]chan int */ ) + +func swap[A, B any](a A, b B) (B, A) { return b, a } + +var _ = swap /* ERROR single value is expected */ [int, float32](1, 2) +var f32, i = swap[int, float32](swap[float32, int](1, 2)) +var _ float32 = f32 +var _ int = i + +func swapswap[A, B any](a A, b B) (A, B) { + return swap[B, A](b, a) +} + +type F[A, B any] func(A, B) (B, A) + +func min[T interface{ type int }](x, y T) T { + if x < y { + return x + } + return y +} + +func _[T interface{type int, float32}](x, y T) bool { return x < y } +func _[T any](x, y T) bool { return x /* ERROR cannot compare */ < y } +func _[T interface{type int, float32, bool}](x, y T) bool { return x /* ERROR cannot compare */ < y } + +func _[T C1[T]](x, y T) bool { return x /* ERROR cannot compare */ < y } +func _[T C2[T]](x, y T) bool { return x < y } + +type C1[T any] interface{} +type C2[T any] interface{ type int, float32 } + +func new[T any]() *T { + var x T + return &x +} + +var _ = new /* ERROR cannot use generic function new */ +var _ *int = new[int]() + +func _[T any](map[T /* ERROR invalid map key type T \(missing comparable constraint\) */]int) // w/o constraint we don't know if T is comparable + +func f1[T1 any](struct{T1}) int +var _ = f1[int](struct{T1}{}) +type T1 = int + +func f2[t1 any](struct{t1; x float32}) int +var _ = f2[t1](struct{t1; x float32}{}) +type t1 = int + + +func f3[A, B, C any](A, struct{x B}, func(A, struct{x B}, *C)) int + +var _ = f3[int, rune, bool](1, struct{x rune}{}, nil) + +// indexing + +func _[T any] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] } +func _[T interface{ type int }] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] } +func _[T interface{ type string }] (x T, i int) { _ = x[i] } +func _[T interface{ type []int }] (x T, i int) { _ = x[i] } +func _[T interface{ type [10]int, *[20]int, map[int]int }] (x T, i int) { _ = x[i] } +func _[T interface{ type string, []byte }] (x T, i int) { _ = x[i] } +func _[T interface{ type []int, [1]rune }] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] } +func _[T interface{ type string, []rune }] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] } + +// indexing with various combinations of map types in type lists (see issue #42616) +func _[T interface{ type []E, map[int]E }, E any](x T, i int) { _ = x[i] } +func _[T interface{ type []E }, E any](x T, i int) { _ = &x[i] } +func _[T interface{ type map[int]E }, E any](x T, i int) { _, _ = x[i] } // comma-ok permitted +func _[T interface{ type []E, map[int]E }, E any](x T, i int) { _ = &x /* ERROR cannot take address */ [i] } +func _[T interface{ type []E, map[int]E, map[uint]E }, E any](x T, i int) { _ = x /* ERROR cannot index */ [i] } // different map element types +func _[T interface{ type []E, map[string]E }, E any](x T, i int) { _ = x[i /* ERROR cannot use i */ ] } + +// slicing +// TODO(gri) implement this + +func _[T interface{ type string }] (x T, i, j, k int) { _ = x /* ERROR invalid operation */ [i:j:k] } + +// len/cap built-ins + +func _[T any](x T) { _ = len(x /* ERROR invalid argument */ ) } +func _[T interface{ type int }](x T) { _ = len(x /* ERROR invalid argument */ ) } +func _[T interface{ type string, []byte, int }](x T) { _ = len(x /* ERROR invalid argument */ ) } +func _[T interface{ type string }](x T) { _ = len(x) } +func _[T interface{ type [10]int }](x T) { _ = len(x) } +func _[T interface{ type []byte }](x T) { _ = len(x) } +func _[T interface{ type map[int]int }](x T) { _ = len(x) } +func _[T interface{ type chan int }](x T) { _ = len(x) } +func _[T interface{ type string, []byte, chan int }](x T) { _ = len(x) } + +func _[T any](x T) { _ = cap(x /* ERROR invalid argument */ ) } +func _[T interface{ type int }](x T) { _ = cap(x /* ERROR invalid argument */ ) } +func _[T interface{ type string, []byte, int }](x T) { _ = cap(x /* ERROR invalid argument */ ) } +func _[T interface{ type string }](x T) { _ = cap(x /* ERROR invalid argument */ ) } +func _[T interface{ type [10]int }](x T) { _ = cap(x) } +func _[T interface{ type []byte }](x T) { _ = cap(x) } +func _[T interface{ type map[int]int }](x T) { _ = cap(x /* ERROR invalid argument */ ) } +func _[T interface{ type chan int }](x T) { _ = cap(x) } +func _[T interface{ type []byte, chan int }](x T) { _ = cap(x) } + +// range iteration + +func _[T interface{}](x T) { + for range x /* ERROR cannot range */ {} +} + +func _[T interface{ type string, []string }](x T) { + for range x {} + for i := range x { _ = i } + for i, _ := range x { _ = i } + for i, e := range x /* ERROR must have the same element type */ { _ = i } + for _, e := range x /* ERROR must have the same element type */ {} + var e rune + _ = e + for _, (e) = range x /* ERROR must have the same element type */ {} +} + + +func _[T interface{ type string, []rune, map[int]rune }](x T) { + for _, e := range x { _ = e } + for i, e := range x { _ = i; _ = e } +} + +func _[T interface{ type string, []rune, map[string]rune }](x T) { + for _, e := range x { _ = e } + for i, e := range x /* ERROR must have the same key type */ { _ = e } +} + +func _[T interface{ type string, chan int }](x T) { + for range x {} + for i := range x { _ = i } + for i, _ := range x { _ = i } // TODO(gri) should get an error here: channels only return one value +} + +func _[T interface{ type string, chan<-int }](x T) { + for i := range x /* ERROR send-only channel */ { _ = i } +} + +// type inference checks + +var _ = new() /* ERROR cannot infer T */ + +func f4[A, B, C any](A, B) C + +var _ = f4(1, 2) /* ERROR cannot infer C */ +var _ = f4[int, float32, complex128](1, 2) + +func f5[A, B, C any](A, []*B, struct{f []C}) int + +var _ = f5[int, float32, complex128](0, nil, struct{f []complex128}{}) +var _ = f5(0, nil, struct{f []complex128}{}) // ERROR cannot infer +var _ = f5(0, []*float32{new[float32]()}, struct{f []complex128}{}) + +func f6[A any](A, []A) int + +var _ = f6(0, nil) + +func f6nil[A any](A) int + +var _ = f6nil(nil) // ERROR cannot infer + +// type inference with variadic functions + +func f7[T any](...T) T + +var _ int = f7() /* ERROR cannot infer T */ +var _ int = f7(1) +var _ int = f7(1, 2) +var _ int = f7([]int{}...) +var _ int = f7 /* ERROR cannot use */ ([]float64{}...) +var _ float64 = f7([]float64{}...) +var _ = f7[float64](1, 2.3) +var _ = f7(float64(1), 2.3) +var _ = f7(1, 2.3 /* ERROR does not match */ ) +var _ = f7(1.2, 3 /* ERROR does not match */ ) + +func f8[A, B any](A, B, ...B) int + +var _ = f8(1) /* ERROR not enough arguments */ +var _ = f8(1, 2.3) +var _ = f8(1, 2.3, 3.4, 4.5) +var _ = f8(1, 2.3, 3.4, 4 /* ERROR does not match */ ) +var _ = f8[int, float64](1, 2.3, 3.4, 4) + +var _ = f8[int, float64](0, 0, nil...) // test case for #18268 + +// init functions cannot have type parameters + +func init() {} +func init[/* ERROR func init must have no type parameters */ _ any]() {} +func init[/* ERROR func init must have no type parameters */ P any]() {} + +type T struct {} + +func (T) m1() {} +// The type checker accepts method type parameters if configured accordingly. +func (T) m2[_ any]() {} +func (T) m3[P any]() {} + +// type inference across parameterized types + +type S1[P any] struct { f P } + +func f9[P any](x S1[P]) + +func _() { + f9[int](S1[int]{42}) + f9(S1[int]{42}) +} + +type S2[A, B, C any] struct{} + +func f10[X, Y, Z any](a S2[X, int, Z], b S2[X, Y, bool]) + +func _[P any]() { + f10[int, float32, string](S2[int, int, string]{}, S2[int, float32, bool]{}) + f10(S2[int, int, string]{}, S2[int, float32, bool]{}) + f10(S2[P, int, P]{}, S2[P, float32, bool]{}) +} + +// corner case for type inference +// (was bug: after instanting f11, the type-checker didn't mark f11 as non-generic) + +func f11[T any]() + +func _() { + f11[int]() +} + +// the previous example was extracted from + +func f12[T interface{m() T}]() + +type A[T any] T + +func (a A[T]) m() A[T] + +func _[T any]() { + f12[A[T]]() +} + +// method expressions + +func (_ S1[P]) m() + +func _() { + m := S1[int].m + m(struct { f int }{42}) +} + +func _[T any] (x T) { + m := S1[T].m + m(S1[T]{x}) +} + +// type parameters in methods (generalization) + +type R0 struct{} + +func (R0) _[T any](x T) +func (R0 /* ERROR invalid receiver */ ) _[R0 any]() // scope of type parameters starts at "func" + +type R1[A, B any] struct{} + +func (_ R1[A, B]) m0(A, B) +func (_ R1[A, B]) m1[T any](A, B, T) T +func (_ R1 /* ERROR not a generic type */ [R1, _]) _() +func (_ R1[A, B]) _[A /* ERROR redeclared */ any](B) + +func _() { + var r R1[int, string] + r.m1[rune](42, "foo", 'a') + r.m1[rune](42, "foo", 1.2 /* ERROR cannot use .* as rune .* \(truncated\) */) + r.m1(42, "foo", 1.2) // using type inference + var _ float64 = r.m1(42, "foo", 1.2) +} + +type I1[A any] interface { + m1(A) +} + +var _ I1[int] = r1[int]{} + +type r1[T any] struct{} + +func (_ r1[T]) m1(T) + +type I2[A, B any] interface { + m1(A) + m2(A) B +} + +var _ I2[int, float32] = R2[int, float32]{} + +type R2[P, Q any] struct{} + +func (_ R2[X, Y]) m1(X) +func (_ R2[X, Y]) m2(X) Y + +// type assertions and type switches over generic types +// NOTE: These are currently disabled because it's unclear what the correct +// approach is, and one can always work around by assigning the variable to +// an interface first. + +// // ReadByte1 corresponds to the ReadByte example in the draft design. +// func ReadByte1[T io.Reader](r T) (byte, error) { +// if br, ok := r.(io.ByteReader); ok { +// return br.ReadByte() +// } +// var b [1]byte +// _, err := r.Read(b[:]) +// return b[0], err +// } +// +// // ReadBytes2 is like ReadByte1 but uses a type switch instead. +// func ReadByte2[T io.Reader](r T) (byte, error) { +// switch br := r.(type) { +// case io.ByteReader: +// return br.ReadByte() +// } +// var b [1]byte +// _, err := r.Read(b[:]) +// return b[0], err +// } +// +// // type assertions and type switches over generic types are strict +// type I3 interface { +// m(int) +// } +// +// type I4 interface { +// m() int // different signature from I3.m +// } +// +// func _[T I3](x I3, p T) { +// // type assertions and type switches over interfaces are not strict +// _ = x.(I4) +// switch x.(type) { +// case I4: +// } +// +// // type assertions and type switches over generic types are strict +// _ = p /* ERROR cannot have dynamic type I4 */.(I4) +// switch p.(type) { +// case I4 /* ERROR cannot have dynamic type I4 */ : +// } +// } + +// type assertions and type switches over generic types lead to errors for now + +func _[T any](x T) { + _ = x /* ERROR not an interface */ .(int) + switch x /* ERROR not an interface */ .(type) { + } + + // work-around + var t interface{} = x + _ = t.(int) + switch t.(type) { + } +} + +func _[T interface{type int}](x T) { + _ = x /* ERROR not an interface */ .(int) + switch x /* ERROR not an interface */ .(type) { + } + + // work-around + var t interface{} = x + _ = t.(int) + switch t.(type) { + } +} + +// error messages related to type bounds mention those bounds +type C[P any] interface{} + +func _[P C[P]] (x P) { + x.m /* ERROR x.m undefined */ () +} + +type I interface {} + +func _[P I] (x P) { + x.m /* ERROR interface I has no method m */ () +} + +func _[P interface{}] (x P) { + x.m /* ERROR type bound for P has no method m */ () +} + +func _[P any] (x P) { + x.m /* ERROR type bound for P has no method m */ () +} diff --git a/src/cmd/compile/internal/types2/testdata/check/vardecl.src b/src/cmd/compile/internal/types2/testdata/check/vardecl.src new file mode 100644 index 0000000000000000000000000000000000000000..9e48cdf847740ea69125c7cbcab59d81ff03ea74 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/check/vardecl.src @@ -0,0 +1,214 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vardecl + +// Prerequisites. +import "math" +func f() {} +func g() (x, y int) { return } +var m map[string]int + +// Var decls must have a type or an initializer. +var _ int +var _, _ int + +var _ /* ERROR "expecting type" */ +var _, _ /* ERROR "expecting type" */ +var _, _, _ /* ERROR "expecting type" */ + +// The initializer must be an expression. +var _ = int /* ERROR "not an expression" */ +var _ = f /* ERROR "used as value" */ () + +// Identifier and expression arity must match. +var _, _ = 1, 2 +var _ = 1, 2 /* ERROR "extra init expr 2" */ +var _, _ = 1 /* ERROR "cannot initialize [0-9]+ variables with [0-9]+ values" */ +var _, _, _ /* ERROR "missing init expr for _" */ = 1, 2 + +var _ = g /* ERROR "2-valued g" */ () +var _, _ = g() +var _, _, _ = g /* ERROR "cannot initialize [0-9]+ variables with [0-9]+ values" */ () + +var _ = m["foo"] +var _, _ = m["foo"] +var _, _, _ = m /* ERROR "cannot initialize [0-9]+ variables with [0-9]+ values" */ ["foo"] + +var _, _ int = 1, 2 +var _ int = 1, 2 /* ERROR "extra init expr 2" */ +var _, _ int = 1 /* ERROR "cannot initialize [0-9]+ variables with [0-9]+ values" */ +var _, _, _ /* ERROR "missing init expr for _" */ int = 1, 2 + +var ( + _, _ = 1, 2 + _ = 1, 2 /* ERROR "extra init expr 2" */ + _, _ = 1 /* ERROR "cannot initialize [0-9]+ variables with [0-9]+ values" */ + _, _, _ /* ERROR "missing init expr for _" */ = 1, 2 + + _ = g /* ERROR "2-valued g" */ () + _, _ = g() + _, _, _ = g /* ERROR "cannot initialize [0-9]+ variables with [0-9]+ values" */ () + + _ = m["foo"] + _, _ = m["foo"] + _, _, _ = m /* ERROR "cannot initialize [0-9]+ variables with [0-9]+ values" */ ["foo"] + + _, _ int = 1, 2 + _ int = 1, 2 /* ERROR "extra init expr 2" */ + _, _ int = 1 /* ERROR "cannot initialize [0-9]+ variables with [0-9]+ values" */ + _, _, _ /* ERROR "missing init expr for _" */ int = 1, 2 +) + +// Variables declared in function bodies must be 'used'. +type T struct{} +func (r T) _(a, b, c int) (u, v, w int) { + var x1 /* ERROR "declared but not used" */ int + var x2 /* ERROR "declared but not used" */ int + x1 = 1 + (x2) = 2 + + y1 /* ERROR "declared but not used" */ := 1 + y2 /* ERROR "declared but not used" */ := 2 + y1 = 1 + (y1) = 2 + + { + var x1 /* ERROR "declared but not used" */ int + var x2 /* ERROR "declared but not used" */ int + x1 = 1 + (x2) = 2 + + y1 /* ERROR "declared but not used" */ := 1 + y2 /* ERROR "declared but not used" */ := 2 + y1 = 1 + (y1) = 2 + } + + if x /* ERROR "declared but not used" */ := 0; a < b {} + + switch x /* ERROR "declared but not used" */, y := 0, 1; a { + case 0: + _ = y + case 1: + x /* ERROR "declared but not used" */ := 0 + } + + var t interface{} + switch t /* ERROR "declared but not used" */ := t.(type) {} + + switch t /* ERROR "declared but not used" */ := t.(type) { + case int: + } + + switch t /* ERROR "declared but not used" */ := t.(type) { + case int: + case float32, complex64: + t = nil + } + + switch t := t.(type) { + case int: + case float32, complex64: + _ = t + } + + switch t := t.(type) { + case int: + case float32: + case string: + _ = func() string { + return t + } + } + + switch t := t; t /* ERROR "declared but not used" */ := t.(type) {} + + var z1 /* ERROR "declared but not used" */ int + var z2 int + _ = func(a, b, c int) (u, v, w int) { + z1 = a + (z1) = b + a = z2 + return + } + + var s []int + var i /* ERROR "declared but not used" */ , j int + for i, j = range s { + _ = j + } + + for i, j /* ERROR "declared but not used" */ := range s { + _ = func() int { + return i + } + } + return +} + +// Unused variables in function literals must lead to only one error (issue #22524). +func _() { + _ = func() { + var x /* ERROR declared but not used */ int + } +} + +// Invalid variable declarations must not lead to "declared but not used errors". +func _() { + var a x // ERROR undeclared name: x + var b = x // ERROR undeclared name: x + var c int = x // ERROR undeclared name: x + var d, e, f x /* ERROR x */ /* ERROR x */ /* ERROR x */ + var g, h, i = x, x, x /* ERROR x */ /* ERROR x */ /* ERROR x */ + var j, k, l float32 = x, x, x /* ERROR x */ /* ERROR x */ /* ERROR x */ + // but no "declared but not used" errors +} + +// Invalid (unused) expressions must not lead to spurious "declared but not used errors". +func _() { + var a, b, c int + var x, y int + x, y = a /* ERROR cannot assign [0-9]+ values to [0-9]+ variables */ , b, c + _ = x + _ = y +} + +func _() { + var x int + return x /* ERROR no result values expected */ + return math /* ERROR no result values expected */ .Sin(0) +} + +func _() int { + var x, y int + return /* ERROR wrong number of return values */ x, y +} + +// Short variable declarations must declare at least one new non-blank variable. +func _() { + _ := /* ERROR no new variables */ 0 + _, a := 0, 1 + _, a := /* ERROR no new variables */ 0, 1 + _, a, b := 0, 1, 2 + _, _, _ := /* ERROR no new variables */ 0, 1, 2 + + _ = a + _ = b +} + +// Test case for variables depending on function literals (see also #22992). +var A /* ERROR initialization cycle */ = func() int { return A }() + +func _() { + // The function literal below must not see a. + var a = func() int { return a /* ERROR "undeclared name" */ }() + var _ = func() int { return a }() + + // The function literal below must not see x, y, or z. + var x, y, z = 0, 1, func() int { return x /* ERROR "undeclared name" */ + y /* ERROR "undeclared name" */ + z /* ERROR "undeclared name" */ }() + _, _, _ = x, y, z +} + +// TODO(gri) consolidate other var decl checks in this file \ No newline at end of file diff --git a/src/cmd/compile/internal/types2/testdata/examples/functions.go2 b/src/cmd/compile/internal/types2/testdata/examples/functions.go2 new file mode 100644 index 0000000000000000000000000000000000000000..0c2a408f02162ee9bfa973fb75da2444a2d1e117 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/examples/functions.go2 @@ -0,0 +1,215 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file shows some examples of type-parameterized functions. + +package p + +// Reverse is a generic function that takes a []T argument and +// reverses that slice in place. +func Reverse[T any](list []T) { + i := 0 + j := len(list)-1 + for i < j { + list[i], list[j] = list[j], list[i] + i++ + j-- + } +} + +func _() { + // Reverse can be called with an explicit type argument. + Reverse[int](nil) + Reverse[string]([]string{"foo", "bar"}) + Reverse[struct{x, y int}]([]struct{x, y int}{{1, 2}, {2, 3}, {3, 4}}) + + // Since the type parameter is used for an incoming argument, + // it can be inferred from the provided argument's type. + Reverse([]string{"foo", "bar"}) + Reverse([]struct{x, y int}{{1, 2}, {2, 3}, {3, 4}}) + + // But the incoming argument must have a type, even if it's a + // default type. An untyped nil won't work. + // Reverse(nil) // this won't type-check + + // A typed nil will work, though. + Reverse([]int(nil)) +} + +// Certain functions, such as the built-in `new` could be written using +// type parameters. +func new[T any]() *T { + var x T + return &x +} + +// When calling our own `new`, we need to pass the type parameter +// explicitly since there is no (value) argument from which the +// result type could be inferred. We don't try to infer the +// result type from the assignment to keep things simple and +// easy to understand. +var _ = new[int]() +var _ *float64 = new[float64]() // the result type is indeed *float64 + +// A function may have multiple type parameters, of course. +func foo[A, B, C any](a A, b []B, c *C) B { + // do something here + return b[0] +} + +// As before, we can pass type parameters explicitly. +var s = foo[int, string, float64](1, []string{"first"}, new[float64]()) + +// Or we can use type inference. +var _ float64 = foo(42, []float64{1.0}, &s) + +// Type inference works in a straight-forward manner even +// for variadic functions. +func variadic[A, B any](A, B, ...B) int + +// var _ = variadic(1) // ERROR not enough arguments +var _ = variadic(1, 2.3) +var _ = variadic(1, 2.3, 3.4, 4.5) +var _ = variadic[int, float64](1, 2.3, 3.4, 4) + +// Type inference also works in recursive function calls where +// the inferred type is the type parameter of the caller. +func f1[T any](x T) { + f1(x) +} + +func f2a[T any](x, y T) { + f2a(x, y) +} + +func f2b[T any](x, y T) { + f2b(y, x) +} + +func g2a[P, Q any](x P, y Q) { + g2a(x, y) +} + +func g2b[P, Q any](x P, y Q) { + g2b(y, x) +} + +// Here's an example of a recursive function call with variadic +// arguments and type inference inferring the type parameter of +// the caller (i.e., itself). +func max[T interface{ type int }](x ...T) T { + var x0 T + if len(x) > 0 { + x0 = x[0] + } + if len(x) > 1 { + x1 := max(x[1:]...) + if x1 > x0 { + return x1 + } + } + return x0 +} + +// When inferring channel types, the channel direction is ignored +// for the purpose of type inference. Once the type has been in- +// fered, the usual parameter passing rules are applied. +// Thus even if a type can be inferred successfully, the function +// call may not be valid. + +func fboth[T any](chan T) +func frecv[T any](<-chan T) +func fsend[T any](chan<- T) + +func _() { + var both chan int + var recv <-chan int + var send chan<-int + + fboth(both) + fboth(recv /* ERROR cannot use */ ) + fboth(send /* ERROR cannot use */ ) + + frecv(both) + frecv(recv) + frecv(send /* ERROR cannot use */ ) + + fsend(both) + fsend(recv /* ERROR cannot use */) + fsend(send) +} + +func ffboth[T any](func(chan T)) +func ffrecv[T any](func(<-chan T)) +func ffsend[T any](func(chan<- T)) + +func _() { + var both func(chan int) + var recv func(<-chan int) + var send func(chan<- int) + + ffboth(both) + ffboth(recv /* ERROR cannot use */ ) + ffboth(send /* ERROR cannot use */ ) + + ffrecv(both /* ERROR cannot use */ ) + ffrecv(recv) + ffrecv(send /* ERROR cannot use */ ) + + ffsend(both /* ERROR cannot use */ ) + ffsend(recv /* ERROR cannot use */ ) + ffsend(send) +} + +// When inferring elements of unnamed composite parameter types, +// if the arguments are defined types, use their underlying types. +// Even though the matching types are not exactly structurally the +// same (one is a type literal, the other a named type), because +// assignment is permitted, parameter passing is permitted as well, +// so type inference should be able to handle these cases well. + +func g1[T any]([]T) +func g2[T any]([]T, T) +func g3[T any](*T, ...T) + +func _() { + type intSlize []int + g1([]int{}) + g1(intSlize{}) + g2(nil, 0) + + type myString string + var s1 string + g3(nil, "1", myString("2"), "3") + g3(&s1, "1", myString /* ERROR does not match */ ("2"), "3") + _ = s1 + + type myStruct struct{x int} + var s2 myStruct + g3(nil, struct{x int}{}, myStruct{}) + g3(&s2, struct{x int}{}, myStruct{}) + g3(nil, myStruct{}, struct{x int}{}) + g3(&s2, myStruct{}, struct{x int}{}) +} + +// Here's a realistic example. + +func append[T any](s []T, t ...T) []T + +func _() { + var f func() + type Funcs []func() + var funcs Funcs + _ = append(funcs, f) +} + +// Generic type declarations cannot have empty type parameter lists +// (that would indicate a slice type). Thus, generic functions cannot +// have empty type parameter lists, either. This is a syntax error. + +func h[] /* ERROR empty type parameter list */ () + +func _() { + h[] /* ERROR operand */ () +} diff --git a/src/cmd/compile/internal/types2/testdata/examples/inference.go2 b/src/cmd/compile/internal/types2/testdata/examples/inference.go2 new file mode 100644 index 0000000000000000000000000000000000000000..b47ce75805480539dcdf5fbef2ff350f6fc9cafe --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/examples/inference.go2 @@ -0,0 +1,101 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file shows some examples of type inference. + +package p + +type Ordered interface { + type int, float64, string +} + +func min[T Ordered](x, y T) T + +func _() { + // min can be called with explicit instantiation. + _ = min[int](1, 2) + + // Alternatively, the type argument can be inferred from + // one of the arguments. Untyped arguments will be considered + // last. + var x int + _ = min(x, x) + _ = min(x, 1) + _ = min(x, 1.0) + _ = min(1, 2) + _ = min(1, 2.3 /* ERROR default type float64 .* does not match */ ) + + var y float64 + _ = min(1, y) + _ = min(1.2, y) + _ = min(1.2, 3.4) + _ = min(1.2, 3 /* ERROR default type int .* does not match */ ) + + var s string + _ = min(s, "foo") + _ = min("foo", "bar") +} + +func mixed[T1, T2, T3 any](T1, T2, T3) + +func _() { + // mixed can be called with explicit instantiation. + mixed[int, string, bool](0, "", false) + + // Alternatively, partial type arguments may be provided + // (from left to right), and the other may be inferred. + mixed[int, string](0, "", false) + mixed[int](0, "", false) + mixed(0, "", false) + + // Provided type arguments always take precedence over + // inferred types. + mixed[int, string](1.1 /* ERROR cannot use 1.1 */ , "", false) +} + +func related1[Slice interface{type []Elem}, Elem any](s Slice, e Elem) + +func _() { + // related1 can be called with explicit instantiation. + var si []int + related1[[]int, int](si, 0) + + // Alternatively, the 2nd type argument can be inferred + // from the first one through constraint type inference. + var ss []string + _ = related1[[]string] + related1[[]string](ss, "foo") + + // A type argument inferred from another explicitly provided + // type argument overrides whatever value argument type is given. + related1[[]string](ss, 0 /* ERROR cannot use 0 */ ) + + // A type argument may be inferred from a value argument + // and then help infer another type argument via constraint + // type inference. + related1(si, 0) + related1(si, "foo" /* ERROR cannot use "foo" */ ) +} + +func related2[Elem any, Slice interface{type []Elem}](e Elem, s Slice) + +func _() { + // related2 can be called with explicit instantiation. + var si []int + related2[int, []int](0, si) + + // Alternatively, the 2nd type argument can be inferred + // from the first one through constraint type inference. + var ss []string + _ = related2[string] + related2[string]("foo", ss) + + // A type argument may be inferred from a value argument + // and then help infer another type argument via constraint + // type inference. Untyped arguments are always considered + // last. + related2(1.2, []float64{}) + related2(1.0, []int{}) + related2( /* ERROR does not satisfy */ float64(1.0), []int{}) // TODO(gri) fix error position +} diff --git a/src/cmd/compile/internal/types2/testdata/examples/methods.go2 b/src/cmd/compile/internal/types2/testdata/examples/methods.go2 new file mode 100644 index 0000000000000000000000000000000000000000..76c6539e1b7e7de019b7031025274eee082be8d7 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/examples/methods.go2 @@ -0,0 +1,96 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file shows some examples of methods on type-parameterized types. + +package p + +// Parameterized types may have methods. +type T1[A any] struct{ a A } + +// When declaring a method for a parameterized type, the "instantiated" +// receiver type acts as an implicit declaration of the type parameters +// for the receiver type. In the example below, method m1 on type T1 has +// the receiver type T1[A] which declares the type parameter A for use +// with this method. That is, within the method m1, A stands for the +// actual type argument provided to an instantiated T1. +func (t T1[A]) m1() A { return t.a } + +// For instance, if T1 is instantiated with the type int, the type +// parameter A in m1 assumes that type (int) as well and we can write +// code like this: +var x T1[int] +var _ int = x.m1() + +// Because the type parameter provided to a parameterized receiver type +// is declared through that receiver declaration, it must be an identifier. +// It cannot possibly be some other type because the receiver type is not +// instantiated with concrete types, it is standing for the parameterized +// receiver type. +func (t T1[[ /* ERROR must be an identifier */ ]int]) m2() {} + +// Note that using what looks like a predeclared identifier, say int, +// as type parameter in this situation is deceptive and considered bad +// style. In m3 below, int is the name of the local receiver type parameter +// and it shadows the predeclared identifier int which then cannot be used +// anymore as expected. +// This is no different from locally redelaring a predeclared identifier +// and usually should be avoided. There are some notable exceptions; e.g., +// sometimes it makes sense to use the identifier "copy" which happens to +// also be the name of a predeclared built-in function. +func (t T1[int]) m3() { var _ int = 42 /* ERROR cannot use 42 .* as int */ } + +// The names of the type parameters used in a parameterized receiver +// type don't have to match the type parameter names in the declaration +// of the type used for the receiver. In our example, even though T1 is +// declared with type parameter named A, methods using that receiver type +// are free to use their own name for that type parameter. That is, the +// name of type parameters is always local to the declaration where they +// are introduced. In our example we can write a method m2 and use the +// name X instead of A for the type parameter w/o any difference. +func (t T1[X]) m4() X { return t.a } + +// If the receiver type is parameterized, type parameters must always be +// provided: this simply follows from the general rule that a parameterized +// type must be instantiated before it can be used. A method receiver +// declaration using a parameterized receiver type is no exception. It is +// simply that such receiver type expressions perform two tasks simultaneously: +// they declare the (local) type parameters and then use them to instantiate +// the receiver type. Forgetting to provide a type parameter leads to an error. +func (t T1 /* ERROR generic type .* without instantiation */ ) m5() {} + +// However, sometimes we don't need the type parameter, and thus it is +// inconvenient to have to choose a name. Since the receiver type expression +// serves as a declaration for its type parameters, we are free to choose the +// blank identifier: +func (t T1[_]) m6() {} + +// Naturally, these rules apply to any number of type parameters on the receiver +// type. Here are some more complex examples. +type T2[A, B, C any] struct { + a A + b B + c C +} + +// Naming of the type parameters is local and has no semantic impact: +func (t T2[A, B, C]) m1() (A, B, C) { return t.a, t.b, t.c } +func (t T2[C, B, A]) m2() (C, B, A) { return t.a, t.b, t.c } +func (t T2[X, Y, Z]) m3() (X, Y, Z) { return t.a, t.b, t.c } + +// Type parameters may be left blank if they are not needed: +func (t T2[A, _, C]) m4() (A, C) { return t.a, t.c } +func (t T2[_, _, X]) m5() X { return t.c } +func (t T2[_, _, _]) m6() {} + +// As usual, blank names may be used for any object which we don't care about +// using later. For instance, we may write an unnamed method with a receiver +// that cannot be accessed: +func (_ T2[_, _, _]) _() int { return 42 } + +// Because a receiver parameter list is simply a parameter list, we can +// leave the receiver argument away for receiver types. +type T0 struct{} +func (T0) _() {} +func (T1[A]) _() {} diff --git a/src/cmd/compile/internal/types2/testdata/examples/types.go2 b/src/cmd/compile/internal/types2/testdata/examples/types.go2 new file mode 100644 index 0000000000000000000000000000000000000000..a7825ed2d9bf7c421c23c9fb78549ce889d2fe91 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/examples/types.go2 @@ -0,0 +1,279 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file shows some examples of generic types. + +package p + +// List is just what it says - a slice of E elements. +type List[E any] []E + +// A generic (parameterized) type must always be instantiated +// before it can be used to designate the type of a variable +// (including a struct field, or function parameter); though +// for the latter cases, the provided type may be another type +// parameter. So: +var _ List[byte] = []byte{} + +// A generic binary tree might be declared as follows. +type Tree[E any] struct { + left, right *Tree[E] + payload E +} + +// A simple instantiation of Tree: +var root1 Tree[int] + +// The actual type parameter provided may be a generic type itself: +var root2 Tree[List[int]] + +// A couple of more complex examples. +// We don't need extra parentheses around the element type of the slices on +// the right (unlike when we use ()'s rather than []'s for type parameters). +var _ List[List[int]] = []List[int]{} +var _ List[List[List[Tree[int]]]] = []List[List[Tree[int]]]{} + +// Type parameters act like type aliases when used in generic types +// in the sense that we can "emulate" a specific type instantiation +// with type aliases. +type T1[P any] struct { + f P +} + +type T2[P any] struct { + f struct { + g P + } +} + +var x1 T1[struct{ g int }] +var x2 T2[int] + +func _() { + // This assignment is invalid because the types of x1, x2 are T1(...) + // and T2(...) respectively, which are two different defined types. + x1 = x2 // ERROR assignment + + // This assignment is valid because the types of x1.f and x2.f are + // both struct { g int }; the type parameters act like type aliases + // and their actual names don't come into play here. + x1.f = x2.f +} + +// We can verify this behavior using type aliases instead: +type T1a struct { + f A1 +} +type A1 = struct { g int } + +type T2a struct { + f struct { + g A2 + } +} +type A2 = int + +var x1a T1a +var x2a T2a + +func _() { + x1a = x2a // ERROR assignment + x1a.f = x2a.f +} + +// Another interesting corner case are generic types that don't use +// their type arguments. For instance: +type T[P any] struct{} + +var xint T[int] +var xbool T[bool] + +// Are these two variables of the same type? After all, their underlying +// types are identical. We consider them to be different because each type +// instantiation creates a new named type, in this case T and T +// even if their underlying types are identical. This is sensible because +// we might still have methods that have different signatures or behave +// differently depending on the type arguments, and thus we can't possibly +// consider such types identical. Consequently: +func _() { + xint = xbool // ERROR assignment +} + +// Generic types cannot be used without instantiation. +var _ T // ERROR cannot use generic type T + +// In type context, generic (parameterized) types cannot be parenthesized before +// being instantiated. See also NOTES entry from 12/4/2019. +var _ (T /* ERROR cannot use generic type T */ )[ /* ERROR unexpected \[ */ int] + +// All types may be parameterized, including interfaces. +type I1[T any] interface{ + m1(T) +} + +// There is no such thing as a variadic generic type. +type _[T ... /* ERROR invalid use of ... */ interface{}] struct{} + +// Generic interfaces may be embedded as one would expect. +type I2 interface { + I1(int) // method! + I1[string] // embedded I1 +} + +func _() { + var x I2 + x.I1(0) + x.m1("foo") +} + +type I0 interface { + m0() +} + +type I3 interface { + I0 + I1[bool] + m(string) +} + +func _() { + var x I3 + x.m0() + x.m1(true) + x.m("foo") +} + +type _ struct { + ( /* ERROR cannot parenthesize */ int8) + ( /* ERROR cannot parenthesize */ *int16) + *( /* ERROR cannot parenthesize */ int32) + List[int] + + int8 /* ERROR int8 redeclared */ + * /* ERROR int16 redeclared */ int16 + List /* ERROR List redeclared */ [int] +} + +// It's possible to declare local types whose underlying types +// are type parameters. As with ordinary type definitions, the +// types underlying properties are "inherited" but the methods +// are not. +func _[T interface{ m(); type int }]() { + type L T + var x L + + // m is not defined on L (it is not "inherited" from + // its underlying type). + x.m /* ERROR x.m undefined */ () + + // But the properties of T, such that as that it supports + // the operations of the types given by its type bound, + // are also the properties of L. + x++ + _ = x - x + + // On the other hand, if we define a local alias for T, + // that alias stands for T as expected. + type A = T + var y A + y.m() + _ = y < 0 +} + +// As a special case, an explicit type argument may be omitted +// from a type parameter bound if the type bound expects exactly +// one type argument. In that case, the type argument is the +// respective type parameter to which the type bound applies. +// Note: We may not permit this syntactic sugar at first. +// Note: This is now disabled. All examples below are adjusted. +type Adder[T any] interface { + Add(T) T +} + +// We don't need to explicitly instantiate the Adder bound +// if we have exactly one type parameter. +func Sum[T Adder[T]](list []T) T { + var sum T + for _, x := range list { + sum = sum.Add(x) + } + return sum +} + +// Valid and invalid variations. +type B0 interface {} +type B1[_ any] interface{} +type B2[_, _ any] interface{} + +func _[T1 B0]() +func _[T1 B1[T1]]() +func _[T1 B2 /* ERROR cannot use generic type .* without instantiation */ ]() + +func _[T1, T2 B0]() +func _[T1 B1[T1], T2 B1[T2]]() +func _[T1, T2 B2 /* ERROR cannot use generic type .* without instantiation */ ]() + +func _[T1 B0, T2 B1[T2]]() // here B1 applies to T2 + +// When the type argument is left away, the type bound is +// instantiated for each type parameter with that type +// parameter. +// Note: We may not permit this syntactic sugar at first. +func _[A Adder[A], B Adder[B], C Adder[A]]() { + var a A // A's type bound is Adder[A] + a = a.Add(a) + var b B // B's type bound is Adder[B] + b = b.Add(b) + var c C // C's type bound is Adder[A] + a = c.Add(a) +} + +// The type of variables (incl. parameters and return values) cannot +// be an interface with type constraints or be/embed comparable. +type I interface { + type int +} + +var ( + _ interface /* ERROR contains type constraints */ {type int} + _ I /* ERROR contains type constraints */ +) + +func _(I /* ERROR contains type constraints */ ) +func _(x, y, z I /* ERROR contains type constraints */ ) +func _() I /* ERROR contains type constraints */ + +func _() { + var _ I /* ERROR contains type constraints */ +} + +type C interface { + comparable +} + +var _ comparable /* ERROR comparable */ +var _ C /* ERROR comparable */ + +func _(_ comparable /* ERROR comparable */ , _ C /* ERROR comparable */ ) + +func _() { + var _ comparable /* ERROR comparable */ + var _ C /* ERROR comparable */ +} + +// Type parameters are never const types, i.e., it's +// not possible to declare a constant of type parameter type. +// (If a type list contains just a single const type, we could +// allow it, but such type lists don't make much sense in the +// first place.) +func _[T interface { type int, float64 }]() { + // not valid + const _ = T /* ERROR not constant */ (0) + const _ T /* ERROR invalid constant type T */ = 1 + + // valid + var _ = T(0) + var _ T = 1 + _ = T(0) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue20583.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue20583.src new file mode 100644 index 0000000000000000000000000000000000000000..85f11ecd38367d87340b47f070cb80fc9f29d3ad --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue20583.src @@ -0,0 +1,12 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package issue20583 +const ( + _ = 6e886451608 /* ERROR malformed constant */ /2 + _ = 6e886451608i /* ERROR malformed constant */ /2 + _ = 0 * 1e+1000000000 // ERROR malformed constant + x = 1e100000000 + _ = x*x*x*x*x*x* /* ERROR not representable */ x +) diff --git a/src/go/types/fixedbugs/issue23203a.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue23203a.src similarity index 100% rename from src/go/types/fixedbugs/issue23203a.src rename to src/cmd/compile/internal/types2/testdata/fixedbugs/issue23203a.src diff --git a/src/go/types/fixedbugs/issue23203b.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue23203b.src similarity index 100% rename from src/go/types/fixedbugs/issue23203b.src rename to src/cmd/compile/internal/types2/testdata/fixedbugs/issue23203b.src diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue26390.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue26390.src new file mode 100644 index 0000000000000000000000000000000000000000..b8e67e9bddb6a8b2794c792dfaa73a5ff861098a --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue26390.src @@ -0,0 +1,11 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package issue26390 + +type A = T + +func (t *T) m() *A { return t } + +type T struct{} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue28251.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue28251.src new file mode 100644 index 0000000000000000000000000000000000000000..ef5e61df47f2c5e332a5db29e82f24bccadbd5e0 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue28251.src @@ -0,0 +1,65 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains test cases for various forms of +// method receiver declarations, per the spec clarification +// https://golang.org/cl/142757. + +package issue28251 + +// test case from issue28251 +type T struct{} + +type T0 = *T + +func (T0) m() {} + +func _() { (&T{}).m() } + +// various alternative forms +type ( + T1 = (((T))) +) + +func ((*(T1))) m1() {} +func _() { (T{}).m2() } +func _() { (&T{}).m2() } + +type ( + T2 = (((T3))) + T3 = T +) + +func (T2) m2() {} +func _() { (T{}).m2() } +func _() { (&T{}).m2() } + +type ( + T4 = ((*(T5))) + T5 = T +) + +func (T4) m4() {} +func _() { (T{}).m4 /* ERROR "cannot call pointer method m4 on T" */ () } +func _() { (&T{}).m4() } + +type ( + T6 = (((T7))) + T7 = (*(T8)) + T8 = T +) + +func (T6) m6() {} +func _() { (T{}).m6 /* ERROR "cannot call pointer method m6 on T" */ () } +func _() { (&T{}).m6() } + +type ( + T9 = *T10 + T10 = *T11 + T11 = T +) + +func (T9 /* ERROR invalid receiver type \*\*T */ ) m9() {} +func _() { (T{}).m9 /* ERROR has no field or method m9 */ () } +func _() { (&T{}).m9 /* ERROR has no field or method m9 */ () } diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39634.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39634.go2 new file mode 100644 index 0000000000000000000000000000000000000000..2c1299feb09519d7499d09d937abf4abfb3f00bd --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39634.go2 @@ -0,0 +1,91 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Examples adjusted to match new [T any] syntax for type parameters. +// Also, previously permitted empty type parameter lists and instantiations +// are now syntax errors. + +package p + +// crash 1 +type nt1[_ any]interface{g /* ERROR undeclared name */ } +type ph1[e nt1[e],g(d /* ERROR undeclared name */ )]s /* ERROR undeclared name */ +func(*ph1[e,e /* ERROR redeclared */ ])h(d /* ERROR undeclared name */ ) + +// crash 2 +// Disabled: empty []'s are now syntax errors. This example leads to too many follow-on errors. +// type Numeric2 interface{t2 /* ERROR not a type */ } +// func t2[T Numeric2](s[]T){0 /* ERROR not a type */ []{s /* ERROR cannot index */ [0][0]}} + +// crash 3 +type t3 *interface{ t3.p /* ERROR no field or method p */ } + +// crash 4 +type Numeric4 interface{t4 /* ERROR not a type */ } +func t4[T Numeric4](s[]T){if( /* ERROR non-boolean */ 0){*s /* ERROR cannot indirect */ [0]}} + +// crash 7 +type foo7 interface { bar() } +type x7[A any] struct{ foo7 } +func main7() { var _ foo7 = x7[int]{} } + +// crash 8 +type foo8[A any] interface { type A } +func bar8[A foo8[A]](a A) {} +func main8() {} + +// crash 9 +type foo9[A any] interface { type foo9 /* ERROR interface contains type constraints */ [A] } +func _() { var _ = new(foo9 /* ERROR interface contains type constraints */ [int]) } + +// crash 12 +var u /* ERROR cycle */ , i [func /* ERROR used as value */ /* ERROR used as value */ (u, c /* ERROR undeclared */ /* ERROR undeclared */ ) {}(0, len /* ERROR must be called */ /* ERROR must be called */ )]c /* ERROR undeclared */ /* ERROR undeclared */ + +// crash 15 +func y15() { var a /* ERROR declared but not used */ interface{ p() } = G15[string]{} } +type G15[X any] s /* ERROR undeclared name */ +func (G15 /* ERROR generic type .* without instantiation */ ) p() + +// crash 16 +type Foo16[T any] r16 /* ERROR not a type */ +func r16[T any]() Foo16[Foo16[T]] + +// crash 17 +type Y17 interface{ c() } +type Z17 interface { + c() Y17 + Y17 /* ERROR duplicate method */ +} +func F17[T Z17](T) + +// crash 18 +type o18[T any] []func(_ o18[[]_ /* ERROR cannot use _ */ ]) + +// crash 19 +type Z19 [][[]Z19{}[0][0]]c19 /* ERROR undeclared */ + +// crash 20 +type Z20 /* ERROR illegal cycle */ interface{ Z20 } +func F20[t Z20]() { F20(t /* ERROR invalid composite literal type */ {}) } + +// crash 21 +type Z21 /* ERROR illegal cycle */ interface{ Z21 } +func F21[T Z21]() { ( /* ERROR not used */ F21[Z21]) } + +// crash 24 +type T24[P any] P +func (r T24[P]) m() { T24 /* ERROR without instantiation */ .m() } + +// crash 25 +type T25[A any] int +func (t T25[A]) m1() {} +var x T25 /* ERROR without instantiation */ .m1 + +// crash 26 +type T26 = interface{ F26[ /* ERROR cannot have type parameters */ Z any]() } +func F26[Z any]() T26 { return F26 /* ERROR without instantiation */ /* ERROR missing method */ [] /* ERROR operand */ } + +// crash 27 +func e27[T any]() interface{ x27 /* ERROR not a type */ } +func x27() { e27( /* ERROR cannot infer T */ ) } \ No newline at end of file diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39664.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39664.go2 new file mode 100644 index 0000000000000000000000000000000000000000..3b3ec56980edda8c7bb56c0dabc5c28526fa78ec --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39664.go2 @@ -0,0 +1,15 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type T[_ any] struct {} + +func (T /* ERROR instantiation */ ) m() + +func _() { + var x interface { m() } + x = T[int]{} + _ = x +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39680.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39680.go2 new file mode 100644 index 0000000000000000000000000000000000000000..9bc26f35461345eb2f0d5728ba39fe793774723d --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39680.go2 @@ -0,0 +1,27 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +import "fmt" + +// Minimal test case. +func _[T interface{type T}](x T) T{ + return x +} + +// Test case from issue. +type constr[T any] interface { + type T +} + +func Print[T constr[T]](s []T) { + for _, v := range s { + fmt.Print(v) + } +} + +func f() { + Print([]string{"Hello, ", "playground\n"}) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39693.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39693.go2 new file mode 100644 index 0000000000000000000000000000000000000000..316ab1982e89cf3635b1b1b16b3356f70a4d2b4f --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39693.go2 @@ -0,0 +1,14 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type Number interface { + int /* ERROR int is not an interface */ + float64 /* ERROR float64 is not an interface */ +} + +func Add[T Number](a, b T) T { + return a /* ERROR not defined */ + b +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39699.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39699.go2 new file mode 100644 index 0000000000000000000000000000000000000000..75491e7e26f00cd5b1d0ce771d471724ae7cdf9e --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39699.go2 @@ -0,0 +1,29 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type T0 interface{ +} + +type T1 interface{ + type int +} + +type T2 interface{ + comparable +} + +type T3 interface { + T0 + T1 + T2 +} + +func _() { + _ = T0(0) + _ = T1 /* ERROR cannot use interface T1 in conversion */ (1) + _ = T2 /* ERROR cannot use interface T2 in conversion */ (2) + _ = T3 /* ERROR cannot use interface T3 in conversion */ (3) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39711.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39711.go2 new file mode 100644 index 0000000000000000000000000000000000000000..df621a4c1730db66648dd23c898a1238bdfce332 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39711.go2 @@ -0,0 +1,11 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// Do not report a duplicate type error for this type list. +// (Check types after interfaces have been completed.) +type _ interface { + type interface{ Error() string }, interface{ String() string } +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39723.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39723.go2 new file mode 100644 index 0000000000000000000000000000000000000000..55464e6b7759f09016050e541868a72b50e1dca1 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39723.go2 @@ -0,0 +1,9 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// A constraint must be an interface; it cannot +// be a type parameter, for instance. +func _[A interface{ type interface{} }, B A /* ERROR not an interface */ ]() diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39725.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39725.go2 new file mode 100644 index 0000000000000000000000000000000000000000..e19b6770bfe4bd21462434f97ace29659a02c4ed --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39725.go2 @@ -0,0 +1,16 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func f1[T1, T2 any](T1, T2, struct{a T1; b T2}) +func _() { + f1(42, string("foo"), struct /* ERROR does not match inferred type struct\{a int; b string\} */ {a, b int}{}) +} + +// simplified test case from issue +func f2[T any](_ []T, _ func(T)) +func _() { + f2([]string{}, func /* ERROR does not match inferred type func\(string\) */ (f []byte) {}) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39754.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39754.go2 new file mode 100644 index 0000000000000000000000000000000000000000..f70b8d0ce0388757004f3797bac5cae749429177 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39754.go2 @@ -0,0 +1,23 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type Optional[T any] struct {} + +func (_ Optional[T]) Val() (T, bool) + +type Box[T any] interface { + Val() (T, bool) +} + +func f[V interface{}, A, B Box[V]]() {} + +func _() { + f[int, Optional[int], Optional[int]]() + _ = f[int, Optional[int], Optional /* ERROR does not satisfy Box */ [string]] + // TODO(gri) Provide better position information here. + // See TODO in call.go, Checker.arguments. + f[int, Optional[int], Optional[string]]( /* ERROR does not satisfy Box */ ) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39755.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39755.go2 new file mode 100644 index 0000000000000000000000000000000000000000..b7ab68818e9587897e7f916730644a3dc1f0af8b --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39755.go2 @@ -0,0 +1,23 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _[T interface{type map[string]int}](x T) { + _ = x == nil +} + +// simplified test case from issue + +type PathParamsConstraint interface { + type map[string]string, []struct{key, value string} +} + +type PathParams[T PathParamsConstraint] struct { + t T +} + +func (pp *PathParams[T]) IsNil() bool { + return pp.t == nil // this must succeed +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39768.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39768.go2 new file mode 100644 index 0000000000000000000000000000000000000000..abac141d7f08dd359a26ba18c36b696d7970d40a --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39768.go2 @@ -0,0 +1,20 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type T[P any] P +type A = T +var x A[int] +var _ A /* ERROR cannot use generic type */ + +type B = T[int] +var y B = x +var _ B /* ERROR not a generic type */ [int] + +// test case from issue + +type Vector[T any] []T +type VectorAlias = Vector +var v Vector[int] diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39938.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39938.go2 new file mode 100644 index 0000000000000000000000000000000000000000..76e7e369ca12bb6718272a2de41dfc7f6617d848 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39938.go2 @@ -0,0 +1,50 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check "infinite expansion" cycle errors across instantiated types. + +package p + +type E0[P any] P +type E1[P any] *P +type E2[P any] struct{ P } +type E3[P any] struct{ *P } + +type T0 /* ERROR illegal cycle */ struct { + _ E0[T0] +} + +type T0_ /* ERROR illegal cycle */ struct { + E0[T0_] +} + +type T1 struct { + _ E1[T1] +} + +type T2 /* ERROR illegal cycle */ struct { + _ E2[T2] +} + +type T3 struct { + _ E3[T3] +} + +// some more complex cases + +type T4 /* ERROR illegal cycle */ struct { + _ E0[E2[T4]] +} + +type T5 struct { + _ E0[E2[E0[E1[E2[[10]T5]]]]] +} + +type T6 /* ERROR illegal cycle */ struct { + _ E0[[10]E2[E0[E2[E2[T6]]]]] +} + +type T7 struct { + _ E0[[]E2[E0[E2[E2[T6]]]]] +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39948.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39948.go2 new file mode 100644 index 0000000000000000000000000000000000000000..c2b460902cc643b5537834e489f3213f551fe6f7 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39948.go2 @@ -0,0 +1,9 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type T[P any] interface{ + P // ERROR P is a type parameter, not an interface +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39976.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39976.go2 new file mode 100644 index 0000000000000000000000000000000000000000..3db4eae0123930db81b5ff193e58ff802678878a --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39976.go2 @@ -0,0 +1,16 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type policy[K, V any] interface{} +type LRU[K, V any] struct{} + +func NewCache[K, V any](p policy[K, V]) + +func _() { + var lru LRU[int, string] + NewCache[int, string](&lru) + NewCache(& /* ERROR does not match policy\[K, V\] \(cannot infer K and V\) */ lru) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39982.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39982.go2 new file mode 100644 index 0000000000000000000000000000000000000000..9810b6386a9a9e847617ac0915f28dd42e8374d7 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue39982.go2 @@ -0,0 +1,36 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type ( + T[_ any] struct{} + S[_ any] struct { + data T[*T[int]] + } +) + +func _() { + _ = S[int]{ + data: T[*T[int]]{}, + } +} + +// full test case from issue + +type ( + Element[TElem any] struct{} + + entry[K comparable] struct{} + + Cache[K comparable] struct { + data map[K]*Element[*entry[K]] + } +) + +func _() { + _ = Cache[int]{ + data: make(map[int](*Element[*entry[int]])), + } +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40038.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40038.go2 new file mode 100644 index 0000000000000000000000000000000000000000..8948d61caa477fff40ac7f773946d0377df734e4 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40038.go2 @@ -0,0 +1,15 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type A[T any] int + +func (A[T]) m(A[T]) + +func f[P interface{m(P)}]() + +func _() { + _ = f[A[int]] +} \ No newline at end of file diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40056.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40056.go2 new file mode 100644 index 0000000000000000000000000000000000000000..747aab49dd1639b79bbf7eb0efcf84787b337c4c --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40056.go2 @@ -0,0 +1,15 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _() { + NewS( /* ERROR cannot infer T */ ) .M() +} + +type S struct {} + +func NewS[T any]() *S + +func (_ *S /* ERROR S is not a generic type */ [T]) M() diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40057.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40057.go2 new file mode 100644 index 0000000000000000000000000000000000000000..fdc8fb1c00ace942c75b7bcc411d776356d82ee5 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40057.go2 @@ -0,0 +1,17 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _() { + var x interface{} + switch t := x.(type) { + case S /* ERROR cannot use generic type */ : + t.m() + } +} + +type S[T any] struct {} + +func (_ S[T]) m() diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40301.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40301.go2 new file mode 100644 index 0000000000000000000000000000000000000000..5d97855f8a172ff414ae86c5112b500059a3142b --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40301.go2 @@ -0,0 +1,12 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +import "unsafe" + +func _[T any](x T) { + _ = unsafe /* ERROR undefined */ .Alignof(x) + _ = unsafe /* ERROR undefined */ .Sizeof(x) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40684.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40684.go2 new file mode 100644 index 0000000000000000000000000000000000000000..0269c3a62ce6d0d10d92f0f2cb4e0ce8e82129cf --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40684.go2 @@ -0,0 +1,15 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type T[_ any] int + +func f[_ any]() +func g[_, _ any]() + +func _() { + _ = f[T /* ERROR without instantiation */ ] + _ = g[T /* ERROR without instantiation */ , T /* ERROR without instantiation */ ] +} \ No newline at end of file diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue41124.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue41124.go2 new file mode 100644 index 0000000000000000000000000000000000000000..61f766bcbd7891ef16f8ca6396349e1ff43159f6 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue41124.go2 @@ -0,0 +1,91 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// Test case from issue. + +type Nat interface { + type Zero, Succ +} + +type Zero struct{} +type Succ struct{ + Nat // ERROR interface contains type constraints +} + +// Struct tests. + +type I1 interface { + comparable +} + +type I2 interface { + type int +} + +type I3 interface { + I1 + I2 +} + +type _ struct { + f I1 // ERROR interface is .* comparable +} + +type _ struct { + comparable // ERROR interface is .* comparable +} + +type _ struct{ + I1 // ERROR interface is .* comparable +} + +type _ struct{ + I2 // ERROR interface contains type constraints +} + +type _ struct{ + I3 // ERROR interface contains type constraints +} + +// General composite types. + +type ( + _ [10]I1 // ERROR interface is .* comparable + _ [10]I2 // ERROR interface contains type constraints + + _ []I1 // ERROR interface is .* comparable + _ []I2 // ERROR interface contains type constraints + + _ *I3 // ERROR interface contains type constraints + _ map[I1 /* ERROR interface is .* comparable */ ]I2 // ERROR interface contains type constraints + _ chan I3 // ERROR interface contains type constraints + _ func(I1 /* ERROR interface is .* comparable */ ) + _ func() I2 // ERROR interface contains type constraints +) + +// Other cases. + +var _ = [...]I3 /* ERROR interface contains type constraints */ {} + +func _(x interface{}) { + _ = x.(I3 /* ERROR interface contains type constraints */ ) +} + +type T1[_ any] struct{} +type T3[_, _, _ any] struct{} +var _ T1[I2 /* ERROR interface contains type constraints */ ] +var _ T3[int, I2 /* ERROR interface contains type constraints */ , float32] + +func f1[_ any]() int +var _ = f1[I2 /* ERROR interface contains type constraints */ ]() +func f3[_, _, _ any]() int +var _ = f3[int, I2 /* ERROR interface contains type constraints */ , float32]() + +func _(x interface{}) { + switch x.(type) { + case I2 /* ERROR interface contains type constraints */ : + } +} diff --git a/src/go/types/fixedbugs/issue42695.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42695.src similarity index 100% rename from src/go/types/fixedbugs/issue42695.src rename to src/cmd/compile/internal/types2/testdata/fixedbugs/issue42695.src diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42758.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42758.go2 new file mode 100644 index 0000000000000000000000000000000000000000..698cb8a16bad26194a40f5893e7a1d493b30083c --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42758.go2 @@ -0,0 +1,33 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _[T any](x interface{}){ + switch x.(type) { + case T: // ok to use a type parameter + case int: + } + + switch x.(type) { + case T: + case T /* ERROR duplicate case */ : + } +} + +type constraint interface { + type int +} + +func _[T constraint](x interface{}){ + switch x.(type) { + case T: // ok to use a type parameter even if type list contains int + case int: + } +} + +func _(x constraint /* ERROR contains type constraints */ ) { + switch x /* ERROR contains type constraints */ .(type) { + } +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42987.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42987.src new file mode 100644 index 0000000000000000000000000000000000000000..8aa3544272e7431b8238f55a04775d65ca1f127d --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42987.src @@ -0,0 +1,8 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check that there is only one error (no follow-on errors). + +package p +var _ = [ /* ERROR invalid use of .* array */ ...]byte("foo") diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43087.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43087.src new file mode 100644 index 0000000000000000000000000000000000000000..85d4450139ea5f867e8017a9818f5c4b26edfe0e --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43087.src @@ -0,0 +1,43 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _() { + a, b, b /* ERROR b repeated on left side of := */ := 1, 2, 3 + _ = a + _ = b +} + +func _() { + a, _, _ := 1, 2, 3 // multiple _'s ok + _ = a +} + +func _() { + var b int + a, b, b /* ERROR b repeated on left side of := */ := 1, 2, 3 + _ = a + _ = b +} + +func _() { + var a []int + a /* ERROR non-name .* on left side of := */ [0], b := 1, 2 + _ = a + _ = b +} + +func _() { + var a int + a, a /* ERROR a repeated on left side of := */ := 1, 2 + _ = a +} + +func _() { + var a, b int + a, b := /* ERROR no new variables on left side of := */ 1, 2 + _ = a + _ = b +} diff --git a/src/go/types/fixedbugs/issue43110.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43110.src similarity index 100% rename from src/go/types/fixedbugs/issue43110.src rename to src/cmd/compile/internal/types2/testdata/fixedbugs/issue43110.src diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43124.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43124.src new file mode 100644 index 0000000000000000000000000000000000000000..7e48c2211b2c42bac91738c238530b251e29db8a --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43124.src @@ -0,0 +1,16 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +var _ = int(0 /* ERROR invalid use of \.\.\. in type conversion */ ...) + +// test case from issue + +type M []string + +var ( + x = []string{"a", "b"} + _ = M(x /* ERROR invalid use of \.\.\. in type conversion */ ...) +) diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43125.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43125.src new file mode 100644 index 0000000000000000000000000000000000000000..c2bd970e251c246e015c945910af32fc458ced92 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43125.src @@ -0,0 +1,8 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +var _ = new(- /* ERROR not a type */ 1) +var _ = new(1 /* ERROR not a type */ + 1) diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43190.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43190.src new file mode 100644 index 0000000000000000000000000000000000000000..ae42719ad75c5eb0e7f01aaacdbd9c581a3802f3 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43190.src @@ -0,0 +1,19 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +import ; // ERROR missing import path +import +var /* ERROR missing import path */ _ int +import .; // ERROR missing import path + +import () +import (.) // ERROR missing import path +import ( + "fmt" + . +) // ERROR missing import path + +var _ = fmt.Println // avoid imported but not used error diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue44688.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue44688.go2 new file mode 100644 index 0000000000000000000000000000000000000000..512bfcc9223c824c84f960b9de2fb78286bab1d8 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue44688.go2 @@ -0,0 +1,83 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package P + +type A1[T any] struct{} + +func (*A1[T]) m1(T) {} + +type A2[T any] interface { + m2(T) +} + +type B1[T any] struct { + filler int + *A1[T] + A2[T] +} + +type B2[T any] interface { + A2[T] +} + +type C[T any] struct { + filler1 int + filler2 int + B1[T] +} + +type D[T any] struct { + filler1 int + filler2 int + filler3 int + C[T] +} + +func _() { + // calling embedded methods + var b1 B1[string] + + b1.A1.m1("") + b1.m1("") + + b1.A2.m2("") + b1.m2("") + + var b2 B2[string] + b2.m2("") + + // a deeper nesting + var d D[string] + d.m1("") + d.m2("") + + // calling method expressions + m1x := B1[string].m1 + m1x(b1, "") + m2x := B2[string].m2 + m2x(b2, "") + + // calling method values + m1v := b1.m1 + m1v("") + m2v := b1.m2 + m2v("") + b2v := b2.m2 + b2v("") +} + +// actual test case from issue + +type A[T any] struct{} + +func (*A[T]) f(T) {} + +type B[T any] struct{ A[T] } + +func _() { + var b B[string] + b.A.f("") + b.f("") +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue44799.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue44799.go2 new file mode 100644 index 0000000000000000000000000000000000000000..9e528a7475b30e02792def3c31274c37e852e618 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue44799.go2 @@ -0,0 +1,19 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func Map[F, T any](s []F, f func(F) T) []T { return nil } + +func Reduce[Elem1, Elem2 any](s []Elem1, initializer Elem2, f func(Elem2, Elem1) Elem2) Elem2 { var x Elem2; return x } + +func main() { + var s []int + var f1 func(int) float64 + var f2 func(float64, int) float64 + _ = Map[int](s, f1) + _ = Map(s, f1) + _ = Reduce[int](s, 0, f2) + _ = Reduce(s, 0, f2) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45548.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45548.go2 new file mode 100644 index 0000000000000000000000000000000000000000..b1e42497e8586c500d11985f0df6c70249927949 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45548.go2 @@ -0,0 +1,13 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func f[F interface{type *Q}, G interface{type *R}, Q, R any](q Q, r R) {} + +func _() { + f[*float64, *int](1, 2) + f[*float64](1, 2) + f(1, 2) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45635.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45635.go2 new file mode 100644 index 0000000000000000000000000000000000000000..65662cdc7662938f72e41f4770149277cbdaedda --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45635.go2 @@ -0,0 +1,32 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func main() { + some /* ERROR "undeclared name" */ [int, int]() +} + +type N[T any] struct{} + +var _ N[] /* ERROR expecting type */ + +type I interface { + type map[int]int, []int +} + +func _[T I](i, j int) { + var m map[int]int + _ = m[i, j /* ERROR more than one index */ ] + + var a [3]int + _ = a[i, j /* ERROR more than one index */ ] + + var s []int + _ = s[i, j /* ERROR more than one index */ ] + + var t T + // TODO(gri) fix multiple error below + _ = t[i, j /* ERROR more than one index */ /* ERROR more than one index */ ] +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45985.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45985.go2 new file mode 100644 index 0000000000000000000000000000000000000000..7678e348ef99ce2733eaa741fcd3e3b1d13e0e46 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45985.go2 @@ -0,0 +1,14 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package issue45985 + +// TODO(gri): this error should be on app[int] below. +func app[S /* ERROR "type S = S does not match" */ interface{ type []T }, T any](s S, e T) S { + return append(s, e) +} + +func _() { + _ = app[int] +} diff --git a/src/go/types/fixedbugs/issue6977.src b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue6977.src similarity index 100% rename from src/go/types/fixedbugs/issue6977.src rename to src/cmd/compile/internal/types2/testdata/fixedbugs/issue6977.src diff --git a/src/cmd/compile/internal/types2/testdata/manual.go2 b/src/cmd/compile/internal/types2/testdata/manual.go2 new file mode 100644 index 0000000000000000000000000000000000000000..efe13cf8bc56c170d2a232230521b79e995bdc1b --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/manual.go2 @@ -0,0 +1,8 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is tested when running "go test -run Manual" +// without source arguments. Use for one-off debugging. + +package p diff --git a/src/cmd/compile/internal/types2/type.go b/src/cmd/compile/internal/types2/type.go new file mode 100644 index 0000000000000000000000000000000000000000..e6c260ff6790c6663438ad62ce3e21b232712a1a --- /dev/null +++ b/src/cmd/compile/internal/types2/type.go @@ -0,0 +1,993 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "sync/atomic" +) + +// A Type represents a type of Go. +// All types implement the Type interface. +type Type interface { + // Underlying returns the underlying type of a type + // w/o following forwarding chains. Only used by + // client packages (here for backward-compatibility). + Underlying() Type + + // String returns a string representation of a type. + String() string +} + +// BasicKind describes the kind of basic type. +type BasicKind int + +const ( + Invalid BasicKind = iota // type is invalid + + // predeclared types + Bool + Int + Int8 + Int16 + Int32 + Int64 + Uint + Uint8 + Uint16 + Uint32 + Uint64 + Uintptr + Float32 + Float64 + Complex64 + Complex128 + String + UnsafePointer + + // types for untyped values + UntypedBool + UntypedInt + UntypedRune + UntypedFloat + UntypedComplex + UntypedString + UntypedNil + + // aliases + Byte = Uint8 + Rune = Int32 +) + +// BasicInfo is a set of flags describing properties of a basic type. +type BasicInfo int + +// Properties of basic types. +const ( + IsBoolean BasicInfo = 1 << iota + IsInteger + IsUnsigned + IsFloat + IsComplex + IsString + IsUntyped + + IsOrdered = IsInteger | IsFloat | IsString + IsNumeric = IsInteger | IsFloat | IsComplex + IsConstType = IsBoolean | IsNumeric | IsString +) + +// A Basic represents a basic type. +type Basic struct { + kind BasicKind + info BasicInfo + name string +} + +// Kind returns the kind of basic type b. +func (b *Basic) Kind() BasicKind { return b.kind } + +// Info returns information about properties of basic type b. +func (b *Basic) Info() BasicInfo { return b.info } + +// Name returns the name of basic type b. +func (b *Basic) Name() string { return b.name } + +// An Array represents an array type. +type Array struct { + len int64 + elem Type +} + +// NewArray returns a new array type for the given element type and length. +// A negative length indicates an unknown length. +func NewArray(elem Type, len int64) *Array { return &Array{len: len, elem: elem} } + +// Len returns the length of array a. +// A negative result indicates an unknown length. +func (a *Array) Len() int64 { return a.len } + +// Elem returns element type of array a. +func (a *Array) Elem() Type { return a.elem } + +// A Slice represents a slice type. +type Slice struct { + elem Type +} + +// NewSlice returns a new slice type for the given element type. +func NewSlice(elem Type) *Slice { return &Slice{elem: elem} } + +// Elem returns the element type of slice s. +func (s *Slice) Elem() Type { return s.elem } + +// A Struct represents a struct type. +type Struct struct { + fields []*Var + tags []string // field tags; nil if there are no tags +} + +// NewStruct returns a new struct with the given fields and corresponding field tags. +// If a field with index i has a tag, tags[i] must be that tag, but len(tags) may be +// only as long as required to hold the tag with the largest index i. Consequently, +// if no field has a tag, tags may be nil. +func NewStruct(fields []*Var, tags []string) *Struct { + var fset objset + for _, f := range fields { + if f.name != "_" && fset.insert(f) != nil { + panic("multiple fields with the same name") + } + } + if len(tags) > len(fields) { + panic("more tags than fields") + } + return &Struct{fields: fields, tags: tags} +} + +// NumFields returns the number of fields in the struct (including blank and embedded fields). +func (s *Struct) NumFields() int { return len(s.fields) } + +// Field returns the i'th field for 0 <= i < NumFields(). +func (s *Struct) Field(i int) *Var { return s.fields[i] } + +// Tag returns the i'th field tag for 0 <= i < NumFields(). +func (s *Struct) Tag(i int) string { + if i < len(s.tags) { + return s.tags[i] + } + return "" +} + +// A Pointer represents a pointer type. +type Pointer struct { + base Type // element type +} + +// NewPointer returns a new pointer type for the given element (base) type. +func NewPointer(elem Type) *Pointer { return &Pointer{base: elem} } + +// Elem returns the element type for the given pointer p. +func (p *Pointer) Elem() Type { return p.base } + +// A Tuple represents an ordered list of variables; a nil *Tuple is a valid (empty) tuple. +// Tuples are used as components of signatures and to represent the type of multiple +// assignments; they are not first class types of Go. +type Tuple struct { + vars []*Var +} + +// NewTuple returns a new tuple for the given variables. +func NewTuple(x ...*Var) *Tuple { + if len(x) > 0 { + return &Tuple{vars: x} + } + // TODO(gri) Don't represent empty tuples with a (*Tuple)(nil) pointer; + // it's too subtle and causes problems. + return nil +} + +// Len returns the number variables of tuple t. +func (t *Tuple) Len() int { + if t != nil { + return len(t.vars) + } + return 0 +} + +// At returns the i'th variable of tuple t. +func (t *Tuple) At(i int) *Var { return t.vars[i] } + +// A Signature represents a (non-builtin) function or method type. +// The receiver is ignored when comparing signatures for identity. +type Signature struct { + // We need to keep the scope in Signature (rather than passing it around + // and store it in the Func Object) because when type-checking a function + // literal we call the general type checker which returns a general Type. + // We then unpack the *Signature and use the scope for the literal body. + rparams []*TypeName // receiver type parameters from left to right; or nil + tparams []*TypeName // type parameters from left to right; or nil + scope *Scope // function scope, present for package-local signatures + recv *Var // nil if not a method + params *Tuple // (incoming) parameters from left to right; or nil + results *Tuple // (outgoing) results from left to right; or nil + variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only) +} + +// NewSignature returns a new function type for the given receiver, parameters, +// and results, either of which may be nil. If variadic is set, the function +// is variadic, it must have at least one parameter, and the last parameter +// must be of unnamed slice type. +func NewSignature(recv *Var, params, results *Tuple, variadic bool) *Signature { + if variadic { + n := params.Len() + if n == 0 { + panic("types2.NewSignature: variadic function must have at least one parameter") + } + if _, ok := params.At(n - 1).typ.(*Slice); !ok { + panic("types2.NewSignature: variadic parameter must be of unnamed slice type") + } + } + return &Signature{recv: recv, params: params, results: results, variadic: variadic} +} + +// Recv returns the receiver of signature s (if a method), or nil if a +// function. It is ignored when comparing signatures for identity. +// +// For an abstract method, Recv returns the enclosing interface either +// as a *Named or an *Interface. Due to embedding, an interface may +// contain methods whose receiver type is a different interface. +func (s *Signature) Recv() *Var { return s.recv } + +// TParams returns the type parameters of signature s, or nil. +func (s *Signature) TParams() []*TypeName { return s.tparams } + +// RParams returns the receiver type params of signature s, or nil. +func (s *Signature) RParams() []*TypeName { return s.rparams } + +// SetTParams sets the type parameters of signature s. +func (s *Signature) SetTParams(tparams []*TypeName) { s.tparams = tparams } + +// Params returns the parameters of signature s, or nil. +func (s *Signature) Params() *Tuple { return s.params } + +// Results returns the results of signature s, or nil. +func (s *Signature) Results() *Tuple { return s.results } + +// Variadic reports whether the signature s is variadic. +func (s *Signature) Variadic() bool { return s.variadic } + +// A Sum represents a set of possible types. +// Sums are currently used to represent type lists of interfaces +// and thus the underlying types of type parameters; they are not +// first class types of Go. +type Sum struct { + types []Type // types are unique +} + +// NewSum returns a new Sum type consisting of the provided +// types if there are more than one. If there is exactly one +// type, it returns that type. If the list of types is empty +// the result is nil. +func NewSum(types []Type) Type { + if len(types) == 0 { + return nil + } + + // What should happen if types contains a sum type? + // Do we flatten the types list? For now we check + // and panic. This should not be possible for the + // current use case of type lists. + // TODO(gri) Come up with the rules for sum types. + for _, t := range types { + if _, ok := t.(*Sum); ok { + panic("sum type contains sum type - unimplemented") + } + } + + if len(types) == 1 { + return types[0] + } + return &Sum{types: types} +} + +// is reports whether all types in t satisfy pred. +func (s *Sum) is(pred func(Type) bool) bool { + if s == nil { + return false + } + for _, t := range s.types { + if !pred(t) { + return false + } + } + return true +} + +// An Interface represents an interface type. +type Interface struct { + methods []*Func // ordered list of explicitly declared methods + types Type // (possibly a Sum) type declared with a type list (TODO(gri) need better field name) + embeddeds []Type // ordered list of explicitly embedded types + + allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset) + allTypes Type // intersection of all embedded and locally declared types (TODO(gri) need better field name) + + obj Object // type declaration defining this interface; or nil (for better error messages) +} + +// unpack unpacks a type into a list of types. +// TODO(gri) Try to eliminate the need for this function. +func unpack(typ Type) []Type { + if typ == nil { + return nil + } + if sum := asSum(typ); sum != nil { + return sum.types + } + return []Type{typ} +} + +// is reports whether interface t represents types that all satisfy pred. +func (t *Interface) is(pred func(Type) bool) bool { + if t.allTypes == nil { + return false // we must have at least one type! (was bug) + } + for _, t := range unpack(t.allTypes) { + if !pred(t) { + return false + } + } + return true +} + +// emptyInterface represents the empty (completed) interface +var emptyInterface = Interface{allMethods: markComplete} + +// markComplete is used to mark an empty interface as completely +// set up by setting the allMethods field to a non-nil empty slice. +var markComplete = make([]*Func, 0) + +// NewInterface returns a new (incomplete) interface for the given methods and embedded types. +// Each embedded type must have an underlying type of interface type. +// NewInterface takes ownership of the provided methods and may modify their types by setting +// missing receivers. To compute the method set of the interface, Complete must be called. +// +// Deprecated: Use NewInterfaceType instead which allows any (even non-defined) interface types +// to be embedded. This is necessary for interfaces that embed alias type names referring to +// non-defined (literal) interface types. +func NewInterface(methods []*Func, embeddeds []*Named) *Interface { + tnames := make([]Type, len(embeddeds)) + for i, t := range embeddeds { + tnames[i] = t + } + return NewInterfaceType(methods, tnames) +} + +// NewInterfaceType returns a new (incomplete) interface for the given methods and embedded types. +// Each embedded type must have an underlying type of interface type (this property is not +// verified for defined types, which may be in the process of being set up and which don't +// have a valid underlying type yet). +// NewInterfaceType takes ownership of the provided methods and may modify their types by setting +// missing receivers. To compute the method set of the interface, Complete must be called. +func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { + if len(methods) == 0 && len(embeddeds) == 0 { + return &emptyInterface + } + + // set method receivers if necessary + typ := new(Interface) + for _, m := range methods { + if sig := m.typ.(*Signature); sig.recv == nil { + sig.recv = NewVar(m.pos, m.pkg, "", typ) + } + } + + // All embedded types should be interfaces; however, defined types + // may not yet be fully resolved. Only verify that non-defined types + // are interfaces. This matches the behavior of the code before the + // fix for #25301 (issue #25596). + for _, t := range embeddeds { + if _, ok := t.(*Named); !ok && !IsInterface(t) { + panic("embedded type is not an interface") + } + } + + // sort for API stability + sortMethods(methods) + sortTypes(embeddeds) + + typ.methods = methods + typ.embeddeds = embeddeds + return typ +} + +// NumExplicitMethods returns the number of explicitly declared methods of interface t. +func (t *Interface) NumExplicitMethods() int { return len(t.methods) } + +// ExplicitMethod returns the i'th explicitly declared method of interface t for 0 <= i < t.NumExplicitMethods(). +// The methods are ordered by their unique Id. +func (t *Interface) ExplicitMethod(i int) *Func { return t.methods[i] } + +// NumEmbeddeds returns the number of embedded types in interface t. +func (t *Interface) NumEmbeddeds() int { return len(t.embeddeds) } + +// Embedded returns the i'th embedded defined (*Named) type of interface t for 0 <= i < t.NumEmbeddeds(). +// The result is nil if the i'th embedded type is not a defined type. +// +// Deprecated: Use EmbeddedType which is not restricted to defined (*Named) types. +func (t *Interface) Embedded(i int) *Named { tname, _ := t.embeddeds[i].(*Named); return tname } + +// EmbeddedType returns the i'th embedded type of interface t for 0 <= i < t.NumEmbeddeds(). +func (t *Interface) EmbeddedType(i int) Type { return t.embeddeds[i] } + +// NumMethods returns the total number of methods of interface t. +// The interface must have been completed. +func (t *Interface) NumMethods() int { t.assertCompleteness(); return len(t.allMethods) } + +func (t *Interface) assertCompleteness() { + if t.allMethods == nil { + panic("interface is incomplete") + } +} + +// Method returns the i'th method of interface t for 0 <= i < t.NumMethods(). +// The methods are ordered by their unique Id. +// The interface must have been completed. +func (t *Interface) Method(i int) *Func { t.assertCompleteness(); return t.allMethods[i] } + +// Empty reports whether t is the empty interface. +func (t *Interface) Empty() bool { + if t.allMethods != nil { + // interface is complete - quick test + // A non-nil allTypes may still be empty and represents the bottom type. + return len(t.allMethods) == 0 && t.allTypes == nil + } + return !t.iterate(func(t *Interface) bool { + return len(t.methods) > 0 || t.types != nil + }, nil) +} + +// HasTypeList reports whether interface t has a type list, possibly from an embedded type. +func (t *Interface) HasTypeList() bool { + if t.allMethods != nil { + // interface is complete - quick test + return t.allTypes != nil + } + + return t.iterate(func(t *Interface) bool { + return t.types != nil + }, nil) +} + +// IsComparable reports whether interface t is or embeds the predeclared interface "comparable". +func (t *Interface) IsComparable() bool { + if t.allMethods != nil { + // interface is complete - quick test + _, m := lookupMethod(t.allMethods, nil, "==") + return m != nil + } + + return t.iterate(func(t *Interface) bool { + _, m := lookupMethod(t.methods, nil, "==") + return m != nil + }, nil) +} + +// IsConstraint reports t.HasTypeList() || t.IsComparable(). +func (t *Interface) IsConstraint() bool { + if t.allMethods != nil { + // interface is complete - quick test + if t.allTypes != nil { + return true + } + _, m := lookupMethod(t.allMethods, nil, "==") + return m != nil + } + + return t.iterate(func(t *Interface) bool { + if t.types != nil { + return true + } + _, m := lookupMethod(t.methods, nil, "==") + return m != nil + }, nil) +} + +// iterate calls f with t and then with any embedded interface of t, recursively, until f returns true. +// iterate reports whether any call to f returned true. +func (t *Interface) iterate(f func(*Interface) bool, seen map[*Interface]bool) bool { + if f(t) { + return true + } + for _, e := range t.embeddeds { + // e should be an interface but be careful (it may be invalid) + if e := asInterface(e); e != nil { + // Cyclic interfaces such as "type E interface { E }" are not permitted + // but they are still constructed and we need to detect such cycles. + if seen[e] { + continue + } + if seen == nil { + seen = make(map[*Interface]bool) + } + seen[e] = true + if e.iterate(f, seen) { + return true + } + } + } + return false +} + +// isSatisfiedBy reports whether interface t's type list is satisfied by the type typ. +// If the type list is empty (absent), typ trivially satisfies the interface. +// TODO(gri) This is not a great name. Eventually, we should have a more comprehensive +// "implements" predicate. +func (t *Interface) isSatisfiedBy(typ Type) bool { + t.Complete() + if t.allTypes == nil { + return true + } + types := unpack(t.allTypes) + return includes(types, typ) || includes(types, under(typ)) +} + +// Complete computes the interface's method set. It must be called by users of +// NewInterfaceType and NewInterface after the interface's embedded types are +// fully defined and before using the interface type in any way other than to +// form other types. The interface must not contain duplicate methods or a +// panic occurs. Complete returns the receiver. +func (t *Interface) Complete() *Interface { + // TODO(gri) consolidate this method with Checker.completeInterface + if t.allMethods != nil { + return t + } + + t.allMethods = markComplete // avoid infinite recursion + + var todo []*Func + var methods []*Func + var seen objset + addMethod := func(m *Func, explicit bool) { + switch other := seen.insert(m); { + case other == nil: + methods = append(methods, m) + case explicit: + panic("duplicate method " + m.name) + default: + // check method signatures after all locally embedded interfaces are computed + todo = append(todo, m, other.(*Func)) + } + } + + for _, m := range t.methods { + addMethod(m, true) + } + + allTypes := t.types + + for _, typ := range t.embeddeds { + utyp := under(typ) + etyp := asInterface(utyp) + if etyp == nil { + if utyp != Typ[Invalid] { + panic(fmt.Sprintf("%s is not an interface", typ)) + } + continue + } + etyp.Complete() + for _, m := range etyp.allMethods { + addMethod(m, false) + } + allTypes = intersect(allTypes, etyp.allTypes) + } + + for i := 0; i < len(todo); i += 2 { + m := todo[i] + other := todo[i+1] + if !Identical(m.typ, other.typ) { + panic("duplicate method " + m.name) + } + } + + if methods != nil { + sortMethods(methods) + t.allMethods = methods + } + t.allTypes = allTypes + + return t +} + +// A Map represents a map type. +type Map struct { + key, elem Type +} + +// NewMap returns a new map for the given key and element types. +func NewMap(key, elem Type) *Map { + return &Map{key: key, elem: elem} +} + +// Key returns the key type of map m. +func (m *Map) Key() Type { return m.key } + +// Elem returns the element type of map m. +func (m *Map) Elem() Type { return m.elem } + +// A Chan represents a channel type. +type Chan struct { + dir ChanDir + elem Type +} + +// A ChanDir value indicates a channel direction. +type ChanDir int + +// The direction of a channel is indicated by one of these constants. +const ( + SendRecv ChanDir = iota + SendOnly + RecvOnly +) + +// NewChan returns a new channel type for the given direction and element type. +func NewChan(dir ChanDir, elem Type) *Chan { + return &Chan{dir: dir, elem: elem} +} + +// Dir returns the direction of channel c. +func (c *Chan) Dir() ChanDir { return c.dir } + +// Elem returns the element type of channel c. +func (c *Chan) Elem() Type { return c.elem } + +// TODO(gri) Clean up Named struct below; specifically the fromRHS field (can we use underlying?). + +// A Named represents a named (defined) type. +type Named struct { + check *Checker // for Named.under implementation + info typeInfo // for cycle detection + obj *TypeName // corresponding declared object + orig *Named // original, uninstantiated type + fromRHS Type // type (on RHS of declaration) this *Named type is derived from (for cycle reporting) + underlying Type // possibly a *Named during setup; never a *Named once set up completely + tparams []*TypeName // type parameters, or nil + targs []Type // type arguments (after instantiation), or nil + methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily +} + +// NewNamed returns a new named type for the given type name, underlying type, and associated methods. +// If the given type name obj doesn't have a type yet, its type is set to the returned named type. +// The underlying type must not be a *Named. +func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { + if _, ok := underlying.(*Named); ok { + panic("types2.NewNamed: underlying type must not be *Named") + } + return (*Checker)(nil).newNamed(obj, nil, underlying, nil, methods) +} + +// newNamed is like NewNamed but with a *Checker receiver and additional orig argument. +func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tparams []*TypeName, methods []*Func) *Named { + typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, tparams: tparams, methods: methods} + if typ.orig == nil { + typ.orig = typ + } + if obj.typ == nil { + obj.typ = typ + } + return typ +} + +// Obj returns the type name for the named type t. +func (t *Named) Obj() *TypeName { return t.obj } + +// Orig returns the original generic type an instantiated type is derived from. +// If t is not an instantiated type, the result is t. +func (t *Named) Orig() *Named { return t.orig } + +// TODO(gri) Come up with a better representation and API to distinguish +// between parameterized instantiated and non-instantiated types. + +// TParams returns the type parameters of the named type t, or nil. +// The result is non-nil for an (originally) parameterized type even if it is instantiated. +func (t *Named) TParams() []*TypeName { return t.tparams } + +// SetTParams sets the type parameters of the named type t. +func (t *Named) SetTParams(tparams []*TypeName) { t.tparams = tparams } + +// TArgs returns the type arguments after instantiation of the named type t, or nil if not instantiated. +func (t *Named) TArgs() []Type { return t.targs } + +// SetTArgs sets the type arguments of the named type t. +func (t *Named) SetTArgs(args []Type) { t.targs = args } + +// NumMethods returns the number of explicit methods whose receiver is named type t. +func (t *Named) NumMethods() int { return len(t.methods) } + +// Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). +func (t *Named) Method(i int) *Func { return t.methods[i] } + +// SetUnderlying sets the underlying type and marks t as complete. +func (t *Named) SetUnderlying(underlying Type) { + if underlying == nil { + panic("types2.Named.SetUnderlying: underlying type must not be nil") + } + if _, ok := underlying.(*Named); ok { + panic("types2.Named.SetUnderlying: underlying type must not be *Named") + } + t.underlying = underlying +} + +// AddMethod adds method m unless it is already in the method list. +func (t *Named) AddMethod(m *Func) { + if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 { + t.methods = append(t.methods, m) + } +} + +// Note: This is a uint32 rather than a uint64 because the +// respective 64 bit atomic instructions are not available +// on all platforms. +var lastId uint32 + +// nextId returns a value increasing monotonically by 1 with +// each call, starting with 1. It may be called concurrently. +func nextId() uint64 { return uint64(atomic.AddUint32(&lastId, 1)) } + +// A TypeParam represents a type parameter type. +type TypeParam struct { + check *Checker // for lazy type bound completion + id uint64 // unique id, for debugging only + obj *TypeName // corresponding type name + index int // type parameter index in source order, starting at 0 + bound Type // *Named or *Interface; underlying type is always *Interface +} + +// Obj returns the type name for the type parameter t. +func (t *TypeParam) Obj() *TypeName { return t.obj } + +// NewTypeParam returns a new TypeParam. +func (check *Checker) NewTypeParam(obj *TypeName, index int, bound Type) *TypeParam { + assert(bound != nil) + typ := &TypeParam{check: check, id: nextId(), obj: obj, index: index, bound: bound} + if obj.typ == nil { + obj.typ = typ + } + return typ +} + +func (t *TypeParam) Bound() *Interface { + iface := asInterface(t.bound) + // use the type bound position if we have one + pos := nopos + if n, _ := t.bound.(*Named); n != nil { + pos = n.obj.pos + } + // TODO(gri) switch this to an unexported method on Checker. + t.check.completeInterface(pos, iface) + return iface +} + +// optype returns a type's operational type. Except for +// type parameters, the operational type is the same +// as the underlying type (as returned by under). For +// Type parameters, the operational type is determined +// by the corresponding type bound's type list. The +// result may be the bottom or top type, but it is never +// the incoming type parameter. +func optype(typ Type) Type { + if t := asTypeParam(typ); t != nil { + // If the optype is typ, return the top type as we have + // no information. It also prevents infinite recursion + // via the asTypeParam converter function. This can happen + // for a type parameter list of the form: + // (type T interface { type T }). + // See also issue #39680. + if u := t.Bound().allTypes; u != nil && u != typ { + // u != typ and u is a type parameter => under(u) != typ, so this is ok + return under(u) + } + return theTop + } + return under(typ) +} + +// An instance represents an instantiated generic type syntactically +// (without expanding the instantiation). Type instances appear only +// during type-checking and are replaced by their fully instantiated +// (expanded) types before the end of type-checking. +type instance struct { + check *Checker // for lazy instantiation + pos syntax.Pos // position of type instantiation; for error reporting only + base *Named // parameterized type to be instantiated + targs []Type // type arguments + poslist []syntax.Pos // position of each targ; for error reporting only + value Type // base(targs...) after instantiation or Typ[Invalid]; nil if not yet set +} + +// expand returns the instantiated (= expanded) type of t. +// The result is either an instantiated *Named type, or +// Typ[Invalid] if there was an error. +func (t *instance) expand() Type { + v := t.value + if v == nil { + v = t.check.instantiate(t.pos, t.base, t.targs, t.poslist) + if v == nil { + v = Typ[Invalid] + } + t.value = v + } + // After instantiation we must have an invalid or a *Named type. + if debug && v != Typ[Invalid] { + _ = v.(*Named) + } + return v +} + +// expand expands a type instance into its instantiated +// type and leaves all other types alone. expand does +// not recurse. +func expand(typ Type) Type { + if t, _ := typ.(*instance); t != nil { + return t.expand() + } + return typ +} + +// expandf is set to expand. +// Call expandf when calling expand causes compile-time cycle error. +var expandf func(Type) Type + +func init() { expandf = expand } + +// bottom represents the bottom of the type lattice. +// It is the underlying type of a type parameter that +// cannot be satisfied by any type, usually because +// the intersection of type constraints left nothing). +type bottom struct{} + +// theBottom is the singleton bottom type. +var theBottom = &bottom{} + +// top represents the top of the type lattice. +// It is the underlying type of a type parameter that +// can be satisfied by any type (ignoring methods), +// usually because the type constraint has no type +// list. +type top struct{} + +// theTop is the singleton top type. +var theTop = &top{} + +// Type-specific implementations of Underlying. +func (t *Basic) Underlying() Type { return t } +func (t *Array) Underlying() Type { return t } +func (t *Slice) Underlying() Type { return t } +func (t *Struct) Underlying() Type { return t } +func (t *Pointer) Underlying() Type { return t } +func (t *Tuple) Underlying() Type { return t } +func (t *Signature) Underlying() Type { return t } +func (t *Sum) Underlying() Type { return t } +func (t *Interface) Underlying() Type { return t } +func (t *Map) Underlying() Type { return t } +func (t *Chan) Underlying() Type { return t } +func (t *Named) Underlying() Type { return t.underlying } +func (t *TypeParam) Underlying() Type { return t } +func (t *instance) Underlying() Type { return t } +func (t *bottom) Underlying() Type { return t } +func (t *top) Underlying() Type { return t } + +// Type-specific implementations of String. +func (t *Basic) String() string { return TypeString(t, nil) } +func (t *Array) String() string { return TypeString(t, nil) } +func (t *Slice) String() string { return TypeString(t, nil) } +func (t *Struct) String() string { return TypeString(t, nil) } +func (t *Pointer) String() string { return TypeString(t, nil) } +func (t *Tuple) String() string { return TypeString(t, nil) } +func (t *Signature) String() string { return TypeString(t, nil) } +func (t *Sum) String() string { return TypeString(t, nil) } +func (t *Interface) String() string { return TypeString(t, nil) } +func (t *Map) String() string { return TypeString(t, nil) } +func (t *Chan) String() string { return TypeString(t, nil) } +func (t *Named) String() string { return TypeString(t, nil) } +func (t *TypeParam) String() string { return TypeString(t, nil) } +func (t *instance) String() string { return TypeString(t, nil) } +func (t *bottom) String() string { return TypeString(t, nil) } +func (t *top) String() string { return TypeString(t, nil) } + +// under returns the true expanded underlying type. +// If it doesn't exist, the result is Typ[Invalid]. +// under must only be called when a type is known +// to be fully set up. +func under(t Type) Type { + // TODO(gri) is this correct for *Sum? + if n := asNamed(t); n != nil { + return n.under() + } + return t +} + +// Converters +// +// A converter must only be called when a type is +// known to be fully set up. A converter returns +// a type's operational type (see comment for optype) +// or nil if the type argument is not of the +// respective type. + +func asBasic(t Type) *Basic { + op, _ := optype(t).(*Basic) + return op +} + +func asArray(t Type) *Array { + op, _ := optype(t).(*Array) + return op +} + +func asSlice(t Type) *Slice { + op, _ := optype(t).(*Slice) + return op +} + +func asStruct(t Type) *Struct { + op, _ := optype(t).(*Struct) + return op +} + +func asPointer(t Type) *Pointer { + op, _ := optype(t).(*Pointer) + return op +} + +// asTuple is not needed - not provided + +func asSignature(t Type) *Signature { + op, _ := optype(t).(*Signature) + return op +} + +func asSum(t Type) *Sum { + op, _ := optype(t).(*Sum) + return op +} + +func asInterface(t Type) *Interface { + op, _ := optype(t).(*Interface) + return op +} + +func asMap(t Type) *Map { + op, _ := optype(t).(*Map) + return op +} + +func asChan(t Type) *Chan { + op, _ := optype(t).(*Chan) + return op +} + +// If the argument to asNamed and asTypeParam is of the respective types +// (possibly after expanding an instance type), these methods return that type. +// Otherwise the result is nil. + +func asNamed(t Type) *Named { + e, _ := expand(t).(*Named) + return e +} + +func asTypeParam(t Type) *TypeParam { + u, _ := under(t).(*TypeParam) + return u +} + +// Exported for the compiler. + +func AsPointer(t Type) *Pointer { return asPointer(t) } +func AsNamed(t Type) *Named { return asNamed(t) } +func AsSignature(t Type) *Signature { return asSignature(t) } +func AsInterface(t Type) *Interface { return asInterface(t) } diff --git a/src/cmd/compile/internal/types2/types_test.go b/src/cmd/compile/internal/types2/types_test.go new file mode 100644 index 0000000000000000000000000000000000000000..096402148d38ad481815ad916e00664dfd0419c2 --- /dev/null +++ b/src/cmd/compile/internal/types2/types_test.go @@ -0,0 +1,17 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import "sync/atomic" + +func init() { + acceptMethodTypeParams = true +} + +// Upon calling ResetId, nextId starts with 1 again. +// It may be called concurrently. This is only needed +// for tests where we may want to have a consistent +// numbering for each individual test case. +func ResetId() { atomic.StoreUint32(&lastId, 0) } diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go new file mode 100644 index 0000000000000000000000000000000000000000..40016697b7c90f99c6319f0dae6c9079f3db0767 --- /dev/null +++ b/src/cmd/compile/internal/types2/typestring.go @@ -0,0 +1,441 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements printing of types. + +package types2 + +import ( + "bytes" + "fmt" + "unicode/utf8" +) + +// A Qualifier controls how named package-level objects are printed in +// calls to TypeString, ObjectString, and SelectionString. +// +// These three formatting routines call the Qualifier for each +// package-level object O, and if the Qualifier returns a non-empty +// string p, the object is printed in the form p.O. +// If it returns an empty string, only the object name O is printed. +// +// Using a nil Qualifier is equivalent to using (*Package).Path: the +// object is qualified by the import path, e.g., "encoding/json.Marshal". +// +type Qualifier func(*Package) string + +// RelativeTo returns a Qualifier that fully qualifies members of +// all packages other than pkg. +func RelativeTo(pkg *Package) Qualifier { + if pkg == nil { + return nil + } + return func(other *Package) string { + if pkg == other { + return "" // same package; unqualified + } + return other.Path() + } +} + +// If gcCompatibilityMode is set, printing of types is modified +// to match the representation of some types in the gc compiler: +// +// - byte and rune lose their alias name and simply stand for +// uint8 and int32 respectively +// - embedded interfaces get flattened (the embedding info is lost, +// and certain recursive interface types cannot be printed anymore) +// +// This makes it easier to compare packages computed with the type- +// checker vs packages imported from gc export data. +// +// Caution: This flag affects all uses of WriteType, globally. +// It is only provided for testing in conjunction with +// gc-generated data. +// +// This flag is exported in the x/tools/go/types package. We don't +// need it at the moment in the std repo and so we don't export it +// anymore. We should eventually try to remove it altogether. +// TODO(gri) remove this +var gcCompatibilityMode bool + +// TypeString returns the string representation of typ. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func TypeString(typ Type, qf Qualifier) string { + var buf bytes.Buffer + WriteType(&buf, typ, qf) + return buf.String() +} + +// WriteType writes the string representation of typ to buf. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func WriteType(buf *bytes.Buffer, typ Type, qf Qualifier) { + writeType(buf, typ, qf, make([]Type, 0, 8)) +} + +// instanceMarker is the prefix for an instantiated type +// in "non-evaluated" instance form. +const instanceMarker = '#' + +func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) { + // Theoretically, this is a quadratic lookup algorithm, but in + // practice deeply nested composite types with unnamed component + // types are uncommon. This code is likely more efficient than + // using a map. + for _, t := range visited { + if t == typ { + fmt.Fprintf(buf, "○%T", goTypeName(typ)) // cycle to typ + return + } + } + visited = append(visited, typ) + + switch t := typ.(type) { + case nil: + buf.WriteString("") + + case *Basic: + // exported basic types go into package unsafe + // (currently this is just unsafe.Pointer) + if isExported(t.name) { + if obj, _ := Unsafe.scope.Lookup(t.name).(*TypeName); obj != nil { + writeTypeName(buf, obj, qf) + break + } + } + + if gcCompatibilityMode { + // forget the alias names + switch t.kind { + case Byte: + t = Typ[Uint8] + case Rune: + t = Typ[Int32] + } + } + buf.WriteString(t.name) + + case *Array: + fmt.Fprintf(buf, "[%d]", t.len) + writeType(buf, t.elem, qf, visited) + + case *Slice: + buf.WriteString("[]") + writeType(buf, t.elem, qf, visited) + + case *Struct: + buf.WriteString("struct{") + for i, f := range t.fields { + if i > 0 { + buf.WriteString("; ") + } + // This doesn't do the right thing for embedded type + // aliases where we should print the alias name, not + // the aliased type (see issue #44410). + if !f.embedded { + buf.WriteString(f.name) + buf.WriteByte(' ') + } + writeType(buf, f.typ, qf, visited) + if tag := t.Tag(i); tag != "" { + fmt.Fprintf(buf, " %q", tag) + } + } + buf.WriteByte('}') + + case *Pointer: + buf.WriteByte('*') + writeType(buf, t.base, qf, visited) + + case *Tuple: + writeTuple(buf, t, false, qf, visited) + + case *Signature: + buf.WriteString("func") + writeSignature(buf, t, qf, visited) + + case *Sum: + for i, t := range t.types { + if i > 0 { + buf.WriteString(", ") + } + writeType(buf, t, qf, visited) + } + + case *Interface: + // We write the source-level methods and embedded types rather + // than the actual method set since resolved method signatures + // may have non-printable cycles if parameters have embedded + // interface types that (directly or indirectly) embed the + // current interface. For instance, consider the result type + // of m: + // + // type T interface{ + // m() interface{ T } + // } + // + buf.WriteString("interface{") + empty := true + if gcCompatibilityMode { + // print flattened interface + // (useful to compare against gc-generated interfaces) + for i, m := range t.allMethods { + if i > 0 { + buf.WriteString("; ") + } + buf.WriteString(m.name) + writeSignature(buf, m.typ.(*Signature), qf, visited) + empty = false + } + if !empty && t.allTypes != nil { + buf.WriteString("; ") + } + if t.allTypes != nil { + buf.WriteString("type ") + writeType(buf, t.allTypes, qf, visited) + } + } else { + // print explicit interface methods and embedded types + for i, m := range t.methods { + if i > 0 { + buf.WriteString("; ") + } + buf.WriteString(m.name) + writeSignature(buf, m.typ.(*Signature), qf, visited) + empty = false + } + if !empty && t.types != nil { + buf.WriteString("; ") + } + if t.types != nil { + buf.WriteString("type ") + writeType(buf, t.types, qf, visited) + empty = false + } + if !empty && len(t.embeddeds) > 0 { + buf.WriteString("; ") + } + for i, typ := range t.embeddeds { + if i > 0 { + buf.WriteString("; ") + } + writeType(buf, typ, qf, visited) + empty = false + } + } + if t.allMethods == nil || len(t.methods) > len(t.allMethods) { + if !empty { + buf.WriteByte(' ') + } + buf.WriteString("/* incomplete */") + } + buf.WriteByte('}') + + case *Map: + buf.WriteString("map[") + writeType(buf, t.key, qf, visited) + buf.WriteByte(']') + writeType(buf, t.elem, qf, visited) + + case *Chan: + var s string + var parens bool + switch t.dir { + case SendRecv: + s = "chan " + // chan (<-chan T) requires parentheses + if c, _ := t.elem.(*Chan); c != nil && c.dir == RecvOnly { + parens = true + } + case SendOnly: + s = "chan<- " + case RecvOnly: + s = "<-chan " + default: + panic("unreachable") + } + buf.WriteString(s) + if parens { + buf.WriteByte('(') + } + writeType(buf, t.elem, qf, visited) + if parens { + buf.WriteByte(')') + } + + case *Named: + writeTypeName(buf, t.obj, qf) + if t.targs != nil { + // instantiated type + buf.WriteByte('[') + writeTypeList(buf, t.targs, qf, visited) + buf.WriteByte(']') + } else if t.tparams != nil { + // parameterized type + writeTParamList(buf, t.tparams, qf, visited) + } + + case *TypeParam: + s := "?" + if t.obj != nil { + s = t.obj.name + } + buf.WriteString(s + subscript(t.id)) + + case *instance: + buf.WriteByte(instanceMarker) // indicate "non-evaluated" syntactic instance + writeTypeName(buf, t.base.obj, qf) + buf.WriteByte('[') + writeTypeList(buf, t.targs, qf, visited) + buf.WriteByte(']') + + case *bottom: + buf.WriteString("⊥") + + case *top: + buf.WriteString("⊤") + + default: + // For externally defined implementations of Type. + buf.WriteString(t.String()) + } +} + +func writeTypeList(buf *bytes.Buffer, list []Type, qf Qualifier, visited []Type) { + for i, typ := range list { + if i > 0 { + buf.WriteString(", ") + } + writeType(buf, typ, qf, visited) + } +} + +func writeTParamList(buf *bytes.Buffer, list []*TypeName, qf Qualifier, visited []Type) { + buf.WriteString("[") + var prev Type + for i, p := range list { + // TODO(gri) support 'any' sugar here. + var b Type = &emptyInterface + if t, _ := p.typ.(*TypeParam); t != nil && t.bound != nil { + b = t.bound + } + if i > 0 { + if b != prev { + // type bound changed - write previous one before advancing + buf.WriteByte(' ') + writeType(buf, prev, qf, visited) + } + buf.WriteString(", ") + } + prev = b + + if t, _ := p.typ.(*TypeParam); t != nil { + writeType(buf, t, qf, visited) + } else { + buf.WriteString(p.name) + } + } + if prev != nil { + buf.WriteByte(' ') + writeType(buf, prev, qf, visited) + } + buf.WriteByte(']') +} + +func writeTypeName(buf *bytes.Buffer, obj *TypeName, qf Qualifier) { + s := "" + if obj != nil { + if obj.pkg != nil { + writePackage(buf, obj.pkg, qf) + } + // TODO(gri): function-local named types should be displayed + // differently from named types at package level to avoid + // ambiguity. + s = obj.name + } + buf.WriteString(s) +} + +func writeTuple(buf *bytes.Buffer, tup *Tuple, variadic bool, qf Qualifier, visited []Type) { + buf.WriteByte('(') + if tup != nil { + for i, v := range tup.vars { + if i > 0 { + buf.WriteString(", ") + } + if v.name != "" { + buf.WriteString(v.name) + buf.WriteByte(' ') + } + typ := v.typ + if variadic && i == len(tup.vars)-1 { + if s, ok := typ.(*Slice); ok { + buf.WriteString("...") + typ = s.elem + } else { + // special case: + // append(s, "foo"...) leads to signature func([]byte, string...) + if t := asBasic(typ); t == nil || t.kind != String { + panic("internal error: string type expected") + } + writeType(buf, typ, qf, visited) + buf.WriteString("...") + continue + } + } + writeType(buf, typ, qf, visited) + } + } + buf.WriteByte(')') +} + +// WriteSignature writes the representation of the signature sig to buf, +// without a leading "func" keyword. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func WriteSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier) { + writeSignature(buf, sig, qf, make([]Type, 0, 8)) +} + +func writeSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier, visited []Type) { + if sig.tparams != nil { + writeTParamList(buf, sig.tparams, qf, visited) + } + + writeTuple(buf, sig.params, sig.variadic, qf, visited) + + n := sig.results.Len() + if n == 0 { + // no result + return + } + + buf.WriteByte(' ') + if n == 1 && sig.results.vars[0].name == "" { + // single unnamed result + writeType(buf, sig.results.vars[0].typ, qf, visited) + return + } + + // multiple or named result(s) + writeTuple(buf, sig.results, false, qf, visited) +} + +// subscript returns the decimal (utf8) representation of x using subscript digits. +func subscript(x uint64) string { + const w = len("₀") // all digits 0...9 have the same utf8 width + var buf [32 * w]byte + i := len(buf) + for { + i -= w + utf8.EncodeRune(buf[i:], '₀'+rune(x%10)) // '₀' == U+2080 + x /= 10 + if x == 0 { + break + } + } + return string(buf[i:]) +} diff --git a/src/cmd/compile/internal/types2/typestring_test.go b/src/cmd/compile/internal/types2/typestring_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d98e9a5ade6e9bbe0a3c60b03d9dd472425c35a9 --- /dev/null +++ b/src/cmd/compile/internal/types2/typestring_test.go @@ -0,0 +1,220 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2_test + +import ( + "internal/testenv" + "testing" + + "cmd/compile/internal/syntax" + . "cmd/compile/internal/types2" +) + +const filename = "" + +func makePkg(src string) (*Package, error) { + file, err := parseSrc(filename, src) + if err != nil { + return nil, err + } + // use the package name as package path + conf := Config{Importer: defaultImporter()} + return conf.Check(file.PkgName.Value, []*syntax.File{file}, nil) +} + +type testEntry struct { + src, str string +} + +// dup returns a testEntry where both src and str are the same. +func dup(s string) testEntry { + return testEntry{s, s} +} + +// types that don't depend on any other type declarations +var independentTestTypes = []testEntry{ + // basic types + dup("int"), + dup("float32"), + dup("string"), + + // arrays + dup("[10]int"), + + // slices + dup("[]int"), + dup("[][]int"), + + // structs + dup("struct{}"), + dup("struct{x int}"), + {`struct { + x, y int + z float32 "foo" + }`, `struct{x int; y int; z float32 "foo"}`}, + {`struct { + string + elems []complex128 + }`, `struct{string; elems []complex128}`}, + + // pointers + dup("*int"), + dup("***struct{}"), + dup("*struct{a int; b float32}"), + + // functions + dup("func()"), + dup("func(x int)"), + {"func(x, y int)", "func(x int, y int)"}, + {"func(x, y int, z string)", "func(x int, y int, z string)"}, + dup("func(int)"), + {"func(int, string, byte)", "func(int, string, byte)"}, + + dup("func() int"), + {"func() (string)", "func() string"}, + dup("func() (u int)"), + {"func() (u, v int, w string)", "func() (u int, v int, w string)"}, + + dup("func(int) string"), + dup("func(x int) string"), + dup("func(x int) (u string)"), + {"func(x, y int) (u string)", "func(x int, y int) (u string)"}, + + dup("func(...int) string"), + dup("func(x ...int) string"), + dup("func(x ...int) (u string)"), + {"func(x int, y ...int) (u string)", "func(x int, y ...int) (u string)"}, + + // interfaces + dup("interface{}"), + dup("interface{m()}"), + dup(`interface{String() string; m(int) float32}`), + dup(`interface{type int, float32, complex128}`), + + // maps + dup("map[string]int"), + {"map[struct{x, y int}][]byte", "map[struct{x int; y int}][]byte"}, + + // channels + dup("chan<- chan int"), + dup("chan<- <-chan int"), + dup("<-chan <-chan int"), + dup("chan (<-chan int)"), + dup("chan<- func()"), + dup("<-chan []func() int"), +} + +// types that depend on other type declarations (src in TestTypes) +var dependentTestTypes = []testEntry{ + // interfaces + dup(`interface{io.Reader; io.Writer}`), + dup(`interface{m() int; io.Writer}`), + {`interface{m() interface{T}}`, `interface{m() interface{generic_p.T}}`}, +} + +func TestTypeString(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests []testEntry + tests = append(tests, independentTestTypes...) + tests = append(tests, dependentTestTypes...) + + for _, test := range tests { + src := `package generic_p; import "io"; type _ io.Writer; type T ` + test.src + pkg, err := makePkg(src) + if err != nil { + t.Errorf("%s: %s", src, err) + continue + } + typ := pkg.Scope().Lookup("T").Type().Underlying() + if got := typ.String(); got != test.str { + t.Errorf("%s: got %s, want %s", test.src, got, test.str) + } + } +} + +var nopos syntax.Pos + +func TestIncompleteInterfaces(t *testing.T) { + sig := NewSignature(nil, nil, nil, false) + m := NewFunc(nopos, nil, "m", sig) + for _, test := range []struct { + typ *Interface + want string + }{ + {new(Interface), "interface{/* incomplete */}"}, + {new(Interface).Complete(), "interface{}"}, + + {NewInterface(nil, nil), "interface{}"}, + {NewInterface(nil, nil).Complete(), "interface{}"}, + {NewInterface([]*Func{}, nil), "interface{}"}, + {NewInterface([]*Func{}, nil).Complete(), "interface{}"}, + {NewInterface(nil, []*Named{}), "interface{}"}, + {NewInterface(nil, []*Named{}).Complete(), "interface{}"}, + {NewInterface([]*Func{m}, nil), "interface{m() /* incomplete */}"}, + {NewInterface([]*Func{m}, nil).Complete(), "interface{m()}"}, + {NewInterface(nil, []*Named{newDefined(new(Interface).Complete())}), "interface{T /* incomplete */}"}, + {NewInterface(nil, []*Named{newDefined(new(Interface).Complete())}).Complete(), "interface{T}"}, + {NewInterface(nil, []*Named{newDefined(NewInterface([]*Func{m}, nil))}), "interface{T /* incomplete */}"}, + {NewInterface(nil, []*Named{newDefined(NewInterface([]*Func{m}, nil).Complete())}), "interface{T /* incomplete */}"}, + {NewInterface(nil, []*Named{newDefined(NewInterface([]*Func{m}, nil).Complete())}).Complete(), "interface{T}"}, + + {NewInterfaceType(nil, nil), "interface{}"}, + {NewInterfaceType(nil, nil).Complete(), "interface{}"}, + {NewInterfaceType([]*Func{}, nil), "interface{}"}, + {NewInterfaceType([]*Func{}, nil).Complete(), "interface{}"}, + {NewInterfaceType(nil, []Type{}), "interface{}"}, + {NewInterfaceType(nil, []Type{}).Complete(), "interface{}"}, + {NewInterfaceType([]*Func{m}, nil), "interface{m() /* incomplete */}"}, + {NewInterfaceType([]*Func{m}, nil).Complete(), "interface{m()}"}, + {NewInterfaceType(nil, []Type{new(Interface).Complete()}), "interface{interface{} /* incomplete */}"}, + {NewInterfaceType(nil, []Type{new(Interface).Complete()}).Complete(), "interface{interface{}}"}, + {NewInterfaceType(nil, []Type{NewInterfaceType([]*Func{m}, nil)}), "interface{interface{m() /* incomplete */} /* incomplete */}"}, + {NewInterfaceType(nil, []Type{NewInterfaceType([]*Func{m}, nil).Complete()}), "interface{interface{m()} /* incomplete */}"}, + {NewInterfaceType(nil, []Type{NewInterfaceType([]*Func{m}, nil).Complete()}).Complete(), "interface{interface{m()}}"}, + } { + got := test.typ.String() + if got != test.want { + t.Errorf("got: %s, want: %s", got, test.want) + } + } +} + +// newDefined creates a new defined type named T with the given underlying type. +// Helper function for use with TestIncompleteInterfaces only. +func newDefined(underlying Type) *Named { + tname := NewTypeName(nopos, nil, "T", nil) + return NewNamed(tname, underlying, nil) +} + +func TestQualifiedTypeString(t *testing.T) { + p, _ := pkgFor("p.go", "package p; type T int", nil) + q, _ := pkgFor("q.go", "package q", nil) + + pT := p.Scope().Lookup("T").Type() + for _, test := range []struct { + typ Type + this *Package + want string + }{ + {nil, nil, ""}, + {pT, nil, "p.T"}, + {pT, p, "T"}, + {pT, q, "p.T"}, + {NewPointer(pT), p, "*T"}, + {NewPointer(pT), q, "*p.T"}, + } { + qualifier := func(pkg *Package) string { + if pkg != test.this { + return pkg.Name() + } + return "" + } + if got := TypeString(test.typ, qualifier); got != test.want { + t.Errorf("TypeString(%s, %s) = %s, want %s", + test.this, test.typ, got, test.want) + } + } +} diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go new file mode 100644 index 0000000000000000000000000000000000000000..e64d804c30b8ccc59a9c5532d714a624109c0cca --- /dev/null +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -0,0 +1,1268 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements type-checking of identifiers and type expressions. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "go/constant" + "sort" + "strconv" + "strings" +) + +// Disabled by default, but enabled when running tests (via types_test.go). +var acceptMethodTypeParams bool + +// ident type-checks identifier e and initializes x with the value or type of e. +// If an error occurred, x.mode is set to invalid. +// For the meaning of def, see Checker.definedType, below. +// If wantType is set, the identifier e is expected to denote a type. +// +func (check *Checker) ident(x *operand, e *syntax.Name, def *Named, wantType bool) { + x.mode = invalid + x.expr = e + + // Note that we cannot use check.lookup here because the returned scope + // may be different from obj.Parent(). See also Scope.LookupParent doc. + scope, obj := check.scope.LookupParent(e.Value, check.pos) + if obj == nil { + if e.Value == "_" { + check.error(e, "cannot use _ as value or type") + } else { + if check.conf.CompilerErrorMessages { + check.errorf(e, "undefined: %s", e.Value) + } else { + check.errorf(e, "undeclared name: %s", e.Value) + } + } + return + } + check.recordUse(e, obj) + + // Type-check the object. + // Only call Checker.objDecl if the object doesn't have a type yet + // (in which case we must actually determine it) or the object is a + // TypeName and we also want a type (in which case we might detect + // a cycle which needs to be reported). Otherwise we can skip the + // call and avoid a possible cycle error in favor of the more + // informative "not a type/value" error that this function's caller + // will issue (see issue #25790). + typ := obj.Type() + if _, gotType := obj.(*TypeName); typ == nil || gotType && wantType { + check.objDecl(obj, def) + typ = obj.Type() // type must have been assigned by Checker.objDecl + } + assert(typ != nil) + + // The object may have been dot-imported. + // If so, mark the respective package as used. + // (This code is only needed for dot-imports. Without them, + // we only have to mark variables, see *Var case below). + if pkgName := check.dotImportMap[dotImportKey{scope, obj}]; pkgName != nil { + pkgName.used = true + } + + switch obj := obj.(type) { + case *PkgName: + check.errorf(e, "use of package %s not in selector", obj.name) + return + + case *Const: + check.addDeclDep(obj) + if typ == Typ[Invalid] { + return + } + if obj == universeIota { + if check.iota == nil { + check.error(e, "cannot use iota outside constant declaration") + return + } + x.val = check.iota + } else { + x.val = obj.val + } + assert(x.val != nil) + x.mode = constant_ + + case *TypeName: + x.mode = typexpr + + case *Var: + // It's ok to mark non-local variables, but ignore variables + // from other packages to avoid potential race conditions with + // dot-imported variables. + if obj.pkg == check.pkg { + obj.used = true + } + check.addDeclDep(obj) + if typ == Typ[Invalid] { + return + } + x.mode = variable + + case *Func: + check.addDeclDep(obj) + x.mode = value + + case *Builtin: + x.id = obj.id + x.mode = builtin + + case *Nil: + x.mode = nilvalue + + default: + unreachable() + } + + x.typ = typ +} + +// typ type-checks the type expression e and returns its type, or Typ[Invalid]. +// The type must not be an (uninstantiated) generic type. +func (check *Checker) typ(e syntax.Expr) Type { + return check.definedType(e, nil) +} + +// varType type-checks the type expression e and returns its type, or Typ[Invalid]. +// The type must not be an (uninstantiated) generic type and it must be ordinary +// (see ordinaryType). +func (check *Checker) varType(e syntax.Expr) Type { + typ := check.definedType(e, nil) + check.ordinaryType(syntax.StartPos(e), typ) + return typ +} + +// ordinaryType reports an error if typ is an interface type containing +// type lists or is (or embeds) the predeclared type comparable. +func (check *Checker) ordinaryType(pos syntax.Pos, typ Type) { + // We don't want to call under() (via Interface) or complete interfaces while we + // are in the middle of type-checking parameter declarations that might belong to + // interface methods. Delay this check to the end of type-checking. + check.later(func() { + if t := asInterface(typ); t != nil { + check.completeInterface(pos, t) // TODO(gri) is this the correct position? + if t.allTypes != nil { + check.softErrorf(pos, "interface contains type constraints (%s)", t.allTypes) + return + } + if t.IsComparable() { + check.softErrorf(pos, "interface is (or embeds) comparable") + } + } + }) +} + +// anyType type-checks the type expression e and returns its type, or Typ[Invalid]. +// The type may be generic or instantiated. +func (check *Checker) anyType(e syntax.Expr) Type { + typ := check.typInternal(e, nil) + assert(isTyped(typ)) + check.recordTypeAndValue(e, typexpr, typ, nil) + return typ +} + +// definedType is like typ but also accepts a type name def. +// If def != nil, e is the type specification for the defined type def, declared +// in a type declaration, and def.underlying will be set to the type of e before +// any components of e are type-checked. +// +func (check *Checker) definedType(e syntax.Expr, def *Named) Type { + typ := check.typInternal(e, def) + assert(isTyped(typ)) + if isGeneric(typ) { + check.errorf(e, "cannot use generic type %s without instantiation", typ) + typ = Typ[Invalid] + } + check.recordTypeAndValue(e, typexpr, typ, nil) + return typ +} + +// genericType is like typ but the type must be an (uninstantiated) generic type. +func (check *Checker) genericType(e syntax.Expr, reportErr bool) Type { + typ := check.typInternal(e, nil) + assert(isTyped(typ)) + if typ != Typ[Invalid] && !isGeneric(typ) { + if reportErr { + check.errorf(e, "%s is not a generic type", typ) + } + typ = Typ[Invalid] + } + // TODO(gri) what is the correct call below? + check.recordTypeAndValue(e, typexpr, typ, nil) + return typ +} + +// isubst returns an x with identifiers substituted per the substitution map smap. +// isubst only handles the case of (valid) method receiver type expressions correctly. +func isubst(x syntax.Expr, smap map[*syntax.Name]*syntax.Name) syntax.Expr { + switch n := x.(type) { + case *syntax.Name: + if alt := smap[n]; alt != nil { + return alt + } + // case *syntax.StarExpr: + // X := isubst(n.X, smap) + // if X != n.X { + // new := *n + // new.X = X + // return &new + // } + case *syntax.Operation: + if n.Op == syntax.Mul && n.Y == nil { + X := isubst(n.X, smap) + if X != n.X { + new := *n + new.X = X + return &new + } + } + case *syntax.IndexExpr: + Index := isubst(n.Index, smap) + if Index != n.Index { + new := *n + new.Index = Index + return &new + } + case *syntax.ListExpr: + var elems []syntax.Expr + for i, elem := range n.ElemList { + new := isubst(elem, smap) + if new != elem { + if elems == nil { + elems = make([]syntax.Expr, len(n.ElemList)) + copy(elems, n.ElemList) + } + elems[i] = new + } + } + if elems != nil { + new := *n + new.ElemList = elems + return &new + } + case *syntax.ParenExpr: + return isubst(n.X, smap) // no need to keep parentheses + default: + // Other receiver type expressions are invalid. + // It's fine to ignore those here as they will + // be checked elsewhere. + } + return x +} + +// funcType type-checks a function or method type. +func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams []*syntax.Field, ftyp *syntax.FuncType) { + check.openScope(ftyp, "function") + check.scope.isFunc = true + check.recordScope(ftyp, check.scope) + sig.scope = check.scope + defer check.closeScope() + + var recvTyp syntax.Expr // rewritten receiver type; valid if != nil + if recvPar != nil { + // collect generic receiver type parameters, if any + // - a receiver type parameter is like any other type parameter, except that it is declared implicitly + // - the receiver specification acts as local declaration for its type parameters, which may be blank + _, rname, rparams := check.unpackRecv(recvPar.Type, true) + if len(rparams) > 0 { + // Blank identifiers don't get declared and regular type-checking of the instantiated + // parameterized receiver type expression fails in Checker.collectParams of receiver. + // Identify blank type parameters and substitute each with a unique new identifier named + // "n_" (where n is the parameter index) and which cannot conflict with any user-defined + // name. + var smap map[*syntax.Name]*syntax.Name // substitution map from "_" to "!n" identifiers + for i, p := range rparams { + if p.Value == "_" { + new := *p + new.Value = fmt.Sprintf("%d_", i) + rparams[i] = &new // use n_ identifier instead of _ so it can be looked up + if smap == nil { + smap = make(map[*syntax.Name]*syntax.Name) + } + smap[p] = &new + } + } + if smap != nil { + // blank identifiers were found => use rewritten receiver type + recvTyp = isubst(recvPar.Type, smap) + } + // TODO(gri) rework declareTypeParams + sig.rparams = nil + for _, rparam := range rparams { + sig.rparams = check.declareTypeParam(sig.rparams, rparam) + } + // determine receiver type to get its type parameters + // and the respective type parameter bounds + var recvTParams []*TypeName + if rname != nil { + // recv should be a Named type (otherwise an error is reported elsewhere) + // Also: Don't report an error via genericType since it will be reported + // again when we type-check the signature. + // TODO(gri) maybe the receiver should be marked as invalid instead? + if recv := asNamed(check.genericType(rname, false)); recv != nil { + recvTParams = recv.tparams + } + } + // provide type parameter bounds + // - only do this if we have the right number (otherwise an error is reported elsewhere) + if len(sig.rparams) == len(recvTParams) { + // We have a list of *TypeNames but we need a list of Types. + list := make([]Type, len(sig.rparams)) + for i, t := range sig.rparams { + list[i] = t.typ + } + smap := makeSubstMap(recvTParams, list) + for i, tname := range sig.rparams { + bound := recvTParams[i].typ.(*TypeParam).bound + // bound is (possibly) parameterized in the context of the + // receiver type declaration. Substitute parameters for the + // current context. + // TODO(gri) should we assume now that bounds always exist? + // (no bound == empty interface) + if bound != nil { + bound = check.subst(tname.pos, bound, smap) + tname.typ.(*TypeParam).bound = bound + } + } + } + } + } + + if tparams != nil { + sig.tparams = check.collectTypeParams(tparams) + // Always type-check method type parameters but complain if they are not enabled. + // (A separate check is needed when type-checking interface method signatures because + // they don't have a receiver specification.) + if recvPar != nil && !acceptMethodTypeParams { + check.error(ftyp, "methods cannot have type parameters") + } + } + + // Value (non-type) parameters' scope starts in the function body. Use a temporary scope for their + // declarations and then squash that scope into the parent scope (and report any redeclarations at + // that time). + scope := NewScope(check.scope, nopos, nopos, "function body (temp. scope)") + var recvList []*Var // TODO(gri) remove the need for making a list here + if recvPar != nil { + recvList, _ = check.collectParams(scope, []*syntax.Field{recvPar}, recvTyp, false) // use rewritten receiver type, if any + } + params, variadic := check.collectParams(scope, ftyp.ParamList, nil, true) + results, _ := check.collectParams(scope, ftyp.ResultList, nil, false) + scope.Squash(func(obj, alt Object) { + var err error_ + err.errorf(obj, "%s redeclared in this block", obj.Name()) + err.recordAltDecl(alt) + check.report(&err) + }) + + if recvPar != nil { + // recv parameter list present (may be empty) + // spec: "The receiver is specified via an extra parameter section preceding the + // method name. That parameter section must declare a single parameter, the receiver." + var recv *Var + switch len(recvList) { + case 0: + // error reported by resolver + recv = NewParam(nopos, nil, "", Typ[Invalid]) // ignore recv below + default: + // more than one receiver + check.error(recvList[len(recvList)-1].Pos(), "method must have exactly one receiver") + fallthrough // continue with first receiver + case 1: + recv = recvList[0] + } + + // TODO(gri) We should delay rtyp expansion to when we actually need the + // receiver; thus all checks here should be delayed to later. + rtyp, _ := deref(recv.typ) + rtyp = expand(rtyp) + + // spec: "The receiver type must be of the form T or *T where T is a type name." + // (ignore invalid types - error was reported before) + if t := rtyp; t != Typ[Invalid] { + var err string + if T := asNamed(t); T != nil { + // spec: "The type denoted by T is called the receiver base type; it must not + // be a pointer or interface type and it must be declared in the same package + // as the method." + if T.obj.pkg != check.pkg { + err = "type not defined in this package" + if check.conf.CompilerErrorMessages { + check.errorf(recv.pos, "cannot define new methods on non-local type %s", recv.typ) + err = "" + } + } else { + switch u := optype(T).(type) { + case *Basic: + // unsafe.Pointer is treated like a regular pointer + if u.kind == UnsafePointer { + err = "unsafe.Pointer" + } + case *Pointer, *Interface: + err = "pointer or interface type" + } + } + } else if T := asBasic(t); T != nil { + err = "basic or unnamed type" + if check.conf.CompilerErrorMessages { + check.errorf(recv.pos, "cannot define new methods on non-local type %s", recv.typ) + err = "" + } + } else { + check.errorf(recv.pos, "invalid receiver type %s", recv.typ) + } + if err != "" { + check.errorf(recv.pos, "invalid receiver type %s (%s)", recv.typ, err) + // ok to continue + } + } + sig.recv = recv + } + + sig.params = NewTuple(params...) + sig.results = NewTuple(results...) + sig.variadic = variadic +} + +// goTypeName returns the Go type name for typ and +// removes any occurrences of "types2." from that name. +func goTypeName(typ Type) string { + return strings.Replace(fmt.Sprintf("%T", typ), "types2.", "", -1) // strings.ReplaceAll is not available in Go 1.4 +} + +// typInternal drives type checking of types. +// Must only be called by definedType or genericType. +// +func (check *Checker) typInternal(e0 syntax.Expr, def *Named) (T Type) { + if check.conf.Trace { + check.trace(e0.Pos(), "type %s", e0) + check.indent++ + defer func() { + check.indent-- + var under Type + if T != nil { + // Calling under() here may lead to endless instantiations. + // Test case: type T[P any] *T[P] + // TODO(gri) investigate if that's a bug or to be expected + // (see also analogous comment in Checker.instantiate). + under = T.Underlying() + } + if T == under { + check.trace(e0.Pos(), "=> %s // %s", T, goTypeName(T)) + } else { + check.trace(e0.Pos(), "=> %s (under = %s) // %s", T, under, goTypeName(T)) + } + }() + } + + switch e := e0.(type) { + case *syntax.BadExpr: + // ignore - error reported before + + case *syntax.Name: + var x operand + check.ident(&x, e, def, true) + + switch x.mode { + case typexpr: + typ := x.typ + def.setUnderlying(typ) + return typ + case invalid: + // ignore - error reported before + case novalue: + check.errorf(&x, "%s used as type", &x) + default: + check.errorf(&x, "%s is not a type", &x) + } + + case *syntax.SelectorExpr: + var x operand + check.selector(&x, e) + + switch x.mode { + case typexpr: + typ := x.typ + def.setUnderlying(typ) + return typ + case invalid: + // ignore - error reported before + case novalue: + check.errorf(&x, "%s used as type", &x) + default: + check.errorf(&x, "%s is not a type", &x) + } + + case *syntax.IndexExpr: + return check.instantiatedType(e.X, unpackExpr(e.Index), def) + + case *syntax.ParenExpr: + // Generic types must be instantiated before they can be used in any form. + // Consequently, generic types cannot be parenthesized. + return check.definedType(e.X, def) + + case *syntax.ArrayType: + typ := new(Array) + def.setUnderlying(typ) + if e.Len != nil { + typ.len = check.arrayLength(e.Len) + } else { + // [...]array + check.error(e, "invalid use of [...] array (outside a composite literal)") + typ.len = -1 + } + typ.elem = check.varType(e.Elem) + if typ.len >= 0 { + return typ + } + // report error if we encountered [...] + + case *syntax.SliceType: + typ := new(Slice) + def.setUnderlying(typ) + typ.elem = check.varType(e.Elem) + return typ + + case *syntax.DotsType: + // dots are handled explicitly where they are legal + // (array composite literals and parameter lists) + check.error(e, "invalid use of '...'") + check.use(e.Elem) + + case *syntax.StructType: + typ := new(Struct) + def.setUnderlying(typ) + check.structType(typ, e) + return typ + + case *syntax.Operation: + if e.Op == syntax.Mul && e.Y == nil { + typ := new(Pointer) + def.setUnderlying(typ) + typ.base = check.varType(e.X) + return typ + } + + check.errorf(e0, "%s is not a type", e0) + check.use(e0) + + case *syntax.FuncType: + typ := new(Signature) + def.setUnderlying(typ) + check.funcType(typ, nil, nil, e) + return typ + + case *syntax.InterfaceType: + typ := new(Interface) + def.setUnderlying(typ) + if def != nil { + typ.obj = def.obj + } + check.interfaceType(typ, e, def) + return typ + + case *syntax.MapType: + typ := new(Map) + def.setUnderlying(typ) + + typ.key = check.varType(e.Key) + typ.elem = check.varType(e.Value) + + // spec: "The comparison operators == and != must be fully defined + // for operands of the key type; thus the key type must not be a + // function, map, or slice." + // + // Delay this check because it requires fully setup types; + // it is safe to continue in any case (was issue 6667). + check.later(func() { + if !Comparable(typ.key) { + var why string + if asTypeParam(typ.key) != nil { + why = " (missing comparable constraint)" + } + check.errorf(e.Key, "invalid map key type %s%s", typ.key, why) + } + }) + + return typ + + case *syntax.ChanType: + typ := new(Chan) + def.setUnderlying(typ) + + dir := SendRecv + switch e.Dir { + case 0: + // nothing to do + case syntax.SendOnly: + dir = SendOnly + case syntax.RecvOnly: + dir = RecvOnly + default: + check.errorf(e, invalidAST+"unknown channel direction %d", e.Dir) + // ok to continue + } + + typ.dir = dir + typ.elem = check.varType(e.Elem) + return typ + + default: + check.errorf(e0, "%s is not a type", e0) + check.use(e0) + } + + typ := Typ[Invalid] + def.setUnderlying(typ) + return typ +} + +// typeOrNil type-checks the type expression (or nil value) e +// and returns the type of e, or nil. If e is a type, it must +// not be an (uninstantiated) generic type. +// If e is neither a type nor nil, typeOrNil returns Typ[Invalid]. +// TODO(gri) should we also disallow non-var types? +func (check *Checker) typOrNil(e syntax.Expr) Type { + var x operand + check.rawExpr(&x, e, nil) + switch x.mode { + case invalid: + // ignore - error reported before + case novalue: + check.errorf(&x, "%s used as type", &x) + case typexpr: + check.instantiatedOperand(&x) + return x.typ + case nilvalue: + return nil + default: + check.errorf(&x, "%s is not a type", &x) + } + return Typ[Invalid] +} + +func (check *Checker) instantiatedType(x syntax.Expr, targs []syntax.Expr, def *Named) Type { + b := check.genericType(x, true) // TODO(gri) what about cycles? + if b == Typ[Invalid] { + return b // error already reported + } + base := asNamed(b) + if base == nil { + unreachable() // should have been caught by genericType + } + + // create a new type instance rather than instantiate the type + // TODO(gri) should do argument number check here rather than + // when instantiating the type? + typ := new(instance) + def.setUnderlying(typ) + + typ.check = check + typ.pos = x.Pos() + typ.base = base + + // evaluate arguments (always) + typ.targs = check.typeList(targs) + if typ.targs == nil { + def.setUnderlying(Typ[Invalid]) // avoid later errors due to lazy instantiation + return Typ[Invalid] + } + + // determine argument positions (for error reporting) + typ.poslist = make([]syntax.Pos, len(targs)) + for i, arg := range targs { + typ.poslist[i] = syntax.StartPos(arg) + } + + // make sure we check instantiation works at least once + // and that the resulting type is valid + check.later(func() { + t := typ.expand() + check.validType(t, nil) + }) + + return typ +} + +// arrayLength type-checks the array length expression e +// and returns the constant length >= 0, or a value < 0 +// to indicate an error (and thus an unknown length). +func (check *Checker) arrayLength(e syntax.Expr) int64 { + var x operand + check.expr(&x, e) + if x.mode != constant_ { + if x.mode != invalid { + check.errorf(&x, "array length %s must be constant", &x) + } + return -1 + } + if isUntyped(x.typ) || isInteger(x.typ) { + if val := constant.ToInt(x.val); val.Kind() == constant.Int { + if representableConst(val, check, Typ[Int], nil) { + if n, ok := constant.Int64Val(val); ok && n >= 0 { + return n + } + check.errorf(&x, "invalid array length %s", &x) + return -1 + } + } + } + check.errorf(&x, "array length %s must be integer", &x) + return -1 +} + +// typeList provides the list of types corresponding to the incoming expression list. +// If an error occurred, the result is nil, but all list elements were type-checked. +func (check *Checker) typeList(list []syntax.Expr) []Type { + res := make([]Type, len(list)) // res != nil even if len(list) == 0 + for i, x := range list { + t := check.varType(x) + if t == Typ[Invalid] { + res = nil + } + if res != nil { + res[i] = t + } + } + return res +} + +// collectParams declares the parameters of list in scope and returns the corresponding +// variable list. If type0 != nil, it is used instead of the first type in list. +func (check *Checker) collectParams(scope *Scope, list []*syntax.Field, type0 syntax.Expr, variadicOk bool) (params []*Var, variadic bool) { + if list == nil { + return + } + + var named, anonymous bool + + var typ Type + var prev syntax.Expr + for i, field := range list { + ftype := field.Type + // type-check type of grouped fields only once + if ftype != prev { + prev = ftype + if i == 0 && type0 != nil { + ftype = type0 + } + if t, _ := ftype.(*syntax.DotsType); t != nil { + ftype = t.Elem + if variadicOk && i == len(list)-1 { + variadic = true + } else { + check.softErrorf(t, "can only use ... with final parameter in list") + // ignore ... and continue + } + } + typ = check.varType(ftype) + } + // The parser ensures that f.Tag is nil and we don't + // care if a constructed AST contains a non-nil tag. + if field.Name != nil { + // named parameter + name := field.Name.Value + if name == "" { + check.error(field.Name, invalidAST+"anonymous parameter") + // ok to continue + } + par := NewParam(field.Name.Pos(), check.pkg, name, typ) + check.declare(scope, field.Name, par, scope.pos) + params = append(params, par) + named = true + } else { + // anonymous parameter + par := NewParam(ftype.Pos(), check.pkg, "", typ) + check.recordImplicit(field, par) + params = append(params, par) + anonymous = true + } + } + + if named && anonymous { + check.error(list[0], invalidAST+"list contains both named and anonymous parameters") + // ok to continue + } + + // For a variadic function, change the last parameter's type from T to []T. + // Since we type-checked T rather than ...T, we also need to retro-actively + // record the type for ...T. + if variadic { + last := params[len(params)-1] + last.typ = &Slice{elem: last.typ} + check.recordTypeAndValue(list[len(list)-1].Type, typexpr, last.typ, nil) + } + + return +} + +func (check *Checker) declareInSet(oset *objset, pos syntax.Pos, obj Object) bool { + if alt := oset.insert(obj); alt != nil { + var err error_ + err.errorf(pos, "%s redeclared", obj.Name()) + err.recordAltDecl(alt) + check.report(&err) + return false + } + return true +} + +func (check *Checker) interfaceType(ityp *Interface, iface *syntax.InterfaceType, def *Named) { + var tname *syntax.Name // most recent "type" name + var types []syntax.Expr + for _, f := range iface.MethodList { + if f.Name != nil { + // We have a method with name f.Name, or a type + // of a type list (f.Name.Value == "type"). + name := f.Name.Value + if name == "_" { + if check.conf.CompilerErrorMessages { + check.error(f.Name, "methods must have a unique non-blank name") + } else { + check.error(f.Name, "invalid method name _") + } + continue // ignore + } + + if name == "type" { + // Always collect all type list entries, even from + // different type lists, under the assumption that + // the author intended to include all types. + types = append(types, f.Type) + if tname != nil && tname != f.Name { + check.error(f.Name, "cannot have multiple type lists in an interface") + } + tname = f.Name + continue + } + + typ := check.typ(f.Type) + sig, _ := typ.(*Signature) + if sig == nil { + if typ != Typ[Invalid] { + check.errorf(f.Type, invalidAST+"%s is not a method signature", typ) + } + continue // ignore + } + + // Always type-check method type parameters but complain if they are not enabled. + // (This extra check is needed here because interface method signatures don't have + // a receiver specification.) + if sig.tparams != nil && !acceptMethodTypeParams { + check.error(f.Type, "methods cannot have type parameters") + } + + // use named receiver type if available (for better error messages) + var recvTyp Type = ityp + if def != nil { + recvTyp = def + } + sig.recv = NewVar(f.Name.Pos(), check.pkg, "", recvTyp) + + m := NewFunc(f.Name.Pos(), check.pkg, name, sig) + check.recordDef(f.Name, m) + ityp.methods = append(ityp.methods, m) + } else { + // We have an embedded type. completeInterface will + // eventually verify that we have an interface. + ityp.embeddeds = append(ityp.embeddeds, check.typ(f.Type)) + check.posMap[ityp] = append(check.posMap[ityp], f.Type.Pos()) + } + } + + // type constraints + ityp.types = NewSum(check.collectTypeConstraints(iface.Pos(), types)) + + if len(ityp.methods) == 0 && ityp.types == nil && len(ityp.embeddeds) == 0 { + // empty interface + ityp.allMethods = markComplete + return + } + + // sort for API stability + sortMethods(ityp.methods) + sortTypes(ityp.embeddeds) + + check.later(func() { check.completeInterface(iface.Pos(), ityp) }) +} + +func (check *Checker) completeInterface(pos syntax.Pos, ityp *Interface) { + if ityp.allMethods != nil { + return + } + + // completeInterface may be called via the LookupFieldOrMethod, + // MissingMethod, Identical, or IdenticalIgnoreTags external API + // in which case check will be nil. In this case, type-checking + // must be finished and all interfaces should have been completed. + if check == nil { + panic("internal error: incomplete interface") + } + + if check.conf.Trace { + // Types don't generally have position information. + // If we don't have a valid pos provided, try to use + // one close enough. + if !pos.IsKnown() && len(ityp.methods) > 0 { + pos = ityp.methods[0].pos + } + + check.trace(pos, "complete %s", ityp) + check.indent++ + defer func() { + check.indent-- + check.trace(pos, "=> %s (methods = %v, types = %v)", ityp, ityp.allMethods, ityp.allTypes) + }() + } + + // An infinitely expanding interface (due to a cycle) is detected + // elsewhere (Checker.validType), so here we simply assume we only + // have valid interfaces. Mark the interface as complete to avoid + // infinite recursion if the validType check occurs later for some + // reason. + ityp.allMethods = markComplete + + // Methods of embedded interfaces are collected unchanged; i.e., the identity + // of a method I.m's Func Object of an interface I is the same as that of + // the method m in an interface that embeds interface I. On the other hand, + // if a method is embedded via multiple overlapping embedded interfaces, we + // don't provide a guarantee which "original m" got chosen for the embedding + // interface. See also issue #34421. + // + // If we don't care to provide this identity guarantee anymore, instead of + // reusing the original method in embeddings, we can clone the method's Func + // Object and give it the position of a corresponding embedded interface. Then + // we can get rid of the mpos map below and simply use the cloned method's + // position. + + var seen objset + var methods []*Func + mpos := make(map[*Func]syntax.Pos) // method specification or method embedding position, for good error messages + addMethod := func(pos syntax.Pos, m *Func, explicit bool) { + switch other := seen.insert(m); { + case other == nil: + methods = append(methods, m) + mpos[m] = pos + case explicit: + var err error_ + err.errorf(pos, "duplicate method %s", m.name) + err.errorf(mpos[other.(*Func)], "other declaration of %s", m.name) + check.report(&err) + default: + // We have a duplicate method name in an embedded (not explicitly declared) method. + // Check method signatures after all types are computed (issue #33656). + // If we're pre-go1.14 (overlapping embeddings are not permitted), report that + // error here as well (even though we could do it eagerly) because it's the same + // error message. + check.later(func() { + if !check.allowVersion(m.pkg, 1, 14) || !check.identical(m.typ, other.Type()) { + var err error_ + err.errorf(pos, "duplicate method %s", m.name) + err.errorf(mpos[other.(*Func)], "other declaration of %s", m.name) + check.report(&err) + } + }) + } + } + + for _, m := range ityp.methods { + addMethod(m.pos, m, true) + } + + // collect types + allTypes := ityp.types + + posList := check.posMap[ityp] + for i, typ := range ityp.embeddeds { + pos := posList[i] // embedding position + utyp := under(typ) + etyp := asInterface(utyp) + if etyp == nil { + if utyp != Typ[Invalid] { + var format string + if _, ok := utyp.(*TypeParam); ok { + format = "%s is a type parameter, not an interface" + } else { + format = "%s is not an interface" + } + check.errorf(pos, format, typ) + } + continue + } + check.completeInterface(pos, etyp) + for _, m := range etyp.allMethods { + addMethod(pos, m, false) // use embedding position pos rather than m.pos + } + allTypes = intersect(allTypes, etyp.allTypes) + } + + if methods != nil { + sortMethods(methods) + ityp.allMethods = methods + } + ityp.allTypes = allTypes +} + +// intersect computes the intersection of the types x and y. +// Note: A incomming nil type stands for the top type. A top +// type result is returned as nil. +func intersect(x, y Type) (r Type) { + defer func() { + if r == theTop { + r = nil + } + }() + + switch { + case x == theBottom || y == theBottom: + return theBottom + case x == nil || x == theTop: + return y + case y == nil || x == theTop: + return x + } + + xtypes := unpack(x) + ytypes := unpack(y) + // Compute the list rtypes which includes only + // types that are in both xtypes and ytypes. + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix this + var rtypes []Type + for _, x := range xtypes { + if includes(ytypes, x) { + rtypes = append(rtypes, x) + } + } + + if rtypes == nil { + return theBottom + } + return NewSum(rtypes) +} + +func sortTypes(list []Type) { + sort.Stable(byUniqueTypeName(list)) +} + +// byUniqueTypeName named type lists can be sorted by their unique type names. +type byUniqueTypeName []Type + +func (a byUniqueTypeName) Len() int { return len(a) } +func (a byUniqueTypeName) Less(i, j int) bool { return sortName(a[i]) < sortName(a[j]) } +func (a byUniqueTypeName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func sortName(t Type) string { + if named := asNamed(t); named != nil { + return named.obj.Id() + } + return "" +} + +func sortMethods(list []*Func) { + sort.Sort(byUniqueMethodName(list)) +} + +func assertSortedMethods(list []*Func) { + if !debug { + panic("internal error: assertSortedMethods called outside debug mode") + } + if !sort.IsSorted(byUniqueMethodName(list)) { + panic("internal error: methods not sorted") + } +} + +// byUniqueMethodName method lists can be sorted by their unique method names. +type byUniqueMethodName []*Func + +func (a byUniqueMethodName) Len() int { return len(a) } +func (a byUniqueMethodName) Less(i, j int) bool { return a[i].less(a[j]) } +func (a byUniqueMethodName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func (check *Checker) tag(t *syntax.BasicLit) string { + // If t.Bad, an error was reported during parsing. + if t != nil && !t.Bad { + if t.Kind == syntax.StringLit { + if val, err := strconv.Unquote(t.Value); err == nil { + return val + } + } + check.errorf(t, invalidAST+"incorrect tag syntax: %q", t.Value) + } + return "" +} + +func (check *Checker) structType(styp *Struct, e *syntax.StructType) { + if e.FieldList == nil { + return + } + + // struct fields and tags + var fields []*Var + var tags []string + + // for double-declaration checks + var fset objset + + // current field typ and tag + var typ Type + var tag string + add := func(ident *syntax.Name, embedded bool, pos syntax.Pos) { + if tag != "" && tags == nil { + tags = make([]string, len(fields)) + } + if tags != nil { + tags = append(tags, tag) + } + + name := ident.Value + fld := NewField(pos, check.pkg, name, typ, embedded) + // spec: "Within a struct, non-blank field names must be unique." + if name == "_" || check.declareInSet(&fset, pos, fld) { + fields = append(fields, fld) + check.recordDef(ident, fld) + } + } + + // addInvalid adds an embedded field of invalid type to the struct for + // fields with errors; this keeps the number of struct fields in sync + // with the source as long as the fields are _ or have different names + // (issue #25627). + addInvalid := func(ident *syntax.Name, pos syntax.Pos) { + typ = Typ[Invalid] + tag = "" + add(ident, true, pos) + } + + var prev syntax.Expr + for i, f := range e.FieldList { + // Fields declared syntactically with the same type (e.g.: a, b, c T) + // share the same type expression. Only check type if it's a new type. + if i == 0 || f.Type != prev { + typ = check.varType(f.Type) + prev = f.Type + } + tag = "" + if i < len(e.TagList) { + tag = check.tag(e.TagList[i]) + } + if f.Name != nil { + // named field + add(f.Name, false, f.Name.Pos()) + } else { + // embedded field + // spec: "An embedded type must be specified as a type name T or as a + // pointer to a non-interface type name *T, and T itself may not be a + // pointer type." + pos := syntax.StartPos(f.Type) + name := embeddedFieldIdent(f.Type) + if name == nil { + check.errorf(pos, "invalid embedded field type %s", f.Type) + name = &syntax.Name{Value: "_"} // TODO(gri) need to set position to pos + addInvalid(name, pos) + continue + } + add(name, true, pos) + + // Because we have a name, typ must be of the form T or *T, where T is the name + // of a (named or alias) type, and t (= deref(typ)) must be the type of T. + // We must delay this check to the end because we don't want to instantiate + // (via under(t)) a possibly incomplete type. + embeddedTyp := typ // for closure below + embeddedPos := pos + check.later(func() { + t, isPtr := deref(embeddedTyp) + switch t := optype(t).(type) { + case *Basic: + if t == Typ[Invalid] { + // error was reported before + return + } + // unsafe.Pointer is treated like a regular pointer + if t.kind == UnsafePointer { + check.error(embeddedPos, "embedded field type cannot be unsafe.Pointer") + } + case *Pointer: + check.error(embeddedPos, "embedded field type cannot be a pointer") + case *Interface: + if isPtr { + check.error(embeddedPos, "embedded field type cannot be a pointer to an interface") + } + } + }) + } + } + + styp.fields = fields + styp.tags = tags +} + +func embeddedFieldIdent(e syntax.Expr) *syntax.Name { + switch e := e.(type) { + case *syntax.Name: + return e + case *syntax.Operation: + if base := ptrBase(e); base != nil { + // *T is valid, but **T is not + if op, _ := base.(*syntax.Operation); op == nil || ptrBase(op) == nil { + return embeddedFieldIdent(e.X) + } + } + case *syntax.SelectorExpr: + return e.Sel + case *syntax.IndexExpr: + return embeddedFieldIdent(e.X) + } + return nil // invalid embedded field +} + +func (check *Checker) collectTypeConstraints(pos syntax.Pos, types []syntax.Expr) []Type { + list := make([]Type, 0, len(types)) // assume all types are correct + for _, texpr := range types { + if texpr == nil { + check.error(pos, invalidAST+"missing type constraint") + continue + } + list = append(list, check.varType(texpr)) + } + + // Ensure that each type is only present once in the type list. Types may be + // interfaces, which may not be complete yet. It's ok to do this check at the + // end because it's not a requirement for correctness of the code. + // Note: This is a quadratic algorithm, but type lists tend to be short. + check.later(func() { + for i, t := range list { + if t := asInterface(t); t != nil { + check.completeInterface(types[i].Pos(), t) + } + if includes(list[:i], t) { + check.softErrorf(types[i], "duplicate type %s in type list", t) + } + } + }) + + return list +} + +// includes reports whether typ is in list +func includes(list []Type, typ Type) bool { + for _, e := range list { + if Identical(typ, e) { + return true + } + } + return false +} + +func ptrBase(x *syntax.Operation) syntax.Expr { + if x.Op == syntax.Mul && x.Y == nil { + return x.X + } + return nil +} diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go new file mode 100644 index 0000000000000000000000000000000000000000..e1832bbb2a36232cd00a04e2e195ae51711938fe --- /dev/null +++ b/src/cmd/compile/internal/types2/unify.go @@ -0,0 +1,469 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements type unification. + +package types2 + +import "bytes" + +// The unifier maintains two separate sets of type parameters x and y +// which are used to resolve type parameters in the x and y arguments +// provided to the unify call. For unidirectional unification, only +// one of these sets (say x) is provided, and then type parameters are +// only resolved for the x argument passed to unify, not the y argument +// (even if that also contains possibly the same type parameters). This +// is crucial to infer the type parameters of self-recursive calls: +// +// func f[P any](a P) { f(a) } +// +// For the call f(a) we want to infer that the type argument for P is P. +// During unification, the parameter type P must be resolved to the type +// parameter P ("x" side), but the argument type P must be left alone so +// that unification resolves the type parameter P to P. +// +// For bidirection unification, both sets are provided. This enables +// unification to go from argument to parameter type and vice versa. +// For constraint type inference, we use bidirectional unification +// where both the x and y type parameters are identical. This is done +// by setting up one of them (using init) and then assigning its value +// to the other. + +// A unifier maintains the current type parameters for x and y +// and the respective types inferred for each type parameter. +// A unifier is created by calling newUnifier. +type unifier struct { + check *Checker + exact bool + x, y tparamsList // x and y must initialized via tparamsList.init + types []Type // inferred types, shared by x and y +} + +// newUnifier returns a new unifier. +// If exact is set, unification requires unified types to match +// exactly. If exact is not set, a named type's underlying type +// is considered if unification would fail otherwise, and the +// direction of channels is ignored. +func newUnifier(check *Checker, exact bool) *unifier { + u := &unifier{check: check, exact: exact} + u.x.unifier = u + u.y.unifier = u + return u +} + +// unify attempts to unify x and y and reports whether it succeeded. +func (u *unifier) unify(x, y Type) bool { + return u.nify(x, y, nil) +} + +// A tparamsList describes a list of type parameters and the types inferred for them. +type tparamsList struct { + unifier *unifier + tparams []*TypeName + // For each tparams element, there is a corresponding type slot index in indices. + // index < 0: unifier.types[-index-1] == nil + // index == 0: no type slot allocated yet + // index > 0: unifier.types[index-1] == typ + // Joined tparams elements share the same type slot and thus have the same index. + // By using a negative index for nil types we don't need to check unifier.types + // to see if we have a type or not. + indices []int // len(d.indices) == len(d.tparams) +} + +// String returns a string representation for a tparamsList. For debugging. +func (d *tparamsList) String() string { + var buf bytes.Buffer + buf.WriteByte('[') + for i, tname := range d.tparams { + if i > 0 { + buf.WriteString(", ") + } + writeType(&buf, tname.typ, nil, nil) + buf.WriteString(": ") + writeType(&buf, d.at(i), nil, nil) + } + buf.WriteByte(']') + return buf.String() +} + +// init initializes d with the given type parameters. +// The type parameters must be in the order in which they appear in their declaration +// (this ensures that the tparams indices match the respective type parameter index). +func (d *tparamsList) init(tparams []*TypeName) { + if len(tparams) == 0 { + return + } + if debug { + for i, tpar := range tparams { + assert(i == tpar.typ.(*TypeParam).index) + } + } + d.tparams = tparams + d.indices = make([]int, len(tparams)) +} + +// join unifies the i'th type parameter of x with the j'th type parameter of y. +// If both type parameters already have a type associated with them and they are +// not joined, join fails and return false. +func (u *unifier) join(i, j int) bool { + ti := u.x.indices[i] + tj := u.y.indices[j] + switch { + case ti == 0 && tj == 0: + // Neither type parameter has a type slot associated with them. + // Allocate a new joined nil type slot (negative index). + u.types = append(u.types, nil) + u.x.indices[i] = -len(u.types) + u.y.indices[j] = -len(u.types) + case ti == 0: + // The type parameter for x has no type slot yet. Use slot of y. + u.x.indices[i] = tj + case tj == 0: + // The type parameter for y has no type slot yet. Use slot of x. + u.y.indices[j] = ti + + // Both type parameters have a slot: ti != 0 && tj != 0. + case ti == tj: + // Both type parameters already share the same slot. Nothing to do. + break + case ti > 0 && tj > 0: + // Both type parameters have (possibly different) inferred types. Cannot join. + return false + case ti > 0: + // Only the type parameter for x has an inferred type. Use x slot for y. + u.y.setIndex(j, ti) + // This case is handled like the default case. + // case tj > 0: + // // Only the type parameter for y has an inferred type. Use y slot for x. + // u.x.setIndex(i, tj) + default: + // Neither type parameter has an inferred type. Use y slot for x + // (or x slot for y, it doesn't matter). + u.x.setIndex(i, tj) + } + return true +} + +// If typ is a type parameter of d, index returns the type parameter index. +// Otherwise, the result is < 0. +func (d *tparamsList) index(typ Type) int { + if t, ok := typ.(*TypeParam); ok { + if i := t.index; i < len(d.tparams) && d.tparams[i].typ == t { + return i + } + } + return -1 +} + +// setIndex sets the type slot index for the i'th type parameter +// (and all its joined parameters) to tj. The type parameter +// must have a (possibly nil) type slot associated with it. +func (d *tparamsList) setIndex(i, tj int) { + ti := d.indices[i] + assert(ti != 0 && tj != 0) + for k, tk := range d.indices { + if tk == ti { + d.indices[k] = tj + } + } +} + +// at returns the type set for the i'th type parameter; or nil. +func (d *tparamsList) at(i int) Type { + if ti := d.indices[i]; ti > 0 { + return d.unifier.types[ti-1] + } + return nil +} + +// set sets the type typ for the i'th type parameter; +// typ must not be nil and it must not have been set before. +func (d *tparamsList) set(i int, typ Type) { + assert(typ != nil) + u := d.unifier + switch ti := d.indices[i]; { + case ti < 0: + u.types[-ti-1] = typ + d.setIndex(i, -ti) + case ti == 0: + u.types = append(u.types, typ) + d.indices[i] = len(u.types) + default: + panic("type already set") + } +} + +// types returns the list of inferred types (via unification) for the type parameters +// described by d, and an index. If all types were inferred, the returned index is < 0. +// Otherwise, it is the index of the first type parameter which couldn't be inferred; +// i.e., for which list[index] is nil. +func (d *tparamsList) types() (list []Type, index int) { + list = make([]Type, len(d.tparams)) + index = -1 + for i := range d.tparams { + t := d.at(i) + list[i] = t + if index < 0 && t == nil { + index = i + } + } + return +} + +func (u *unifier) nifyEq(x, y Type, p *ifacePair) bool { + return x == y || u.nify(x, y, p) +} + +// nify implements the core unification algorithm which is an +// adapted version of Checker.identical0. For changes to that +// code the corresponding changes should be made here. +// Must not be called directly from outside the unifier. +func (u *unifier) nify(x, y Type, p *ifacePair) bool { + // types must be expanded for comparison + x = expand(x) + y = expand(y) + + if !u.exact { + // If exact unification is known to fail because we attempt to + // match a type name against an unnamed type literal, consider + // the underlying type of the named type. + // (Subtle: We use isNamed to include any type with a name (incl. + // basic types and type parameters. We use asNamed because we only + // want *Named types.) + switch { + case !isNamed(x) && y != nil && asNamed(y) != nil: + return u.nify(x, under(y), p) + case x != nil && asNamed(x) != nil && !isNamed(y): + return u.nify(under(x), y, p) + } + } + + // Cases where at least one of x or y is a type parameter. + switch i, j := u.x.index(x), u.y.index(y); { + case i >= 0 && j >= 0: + // both x and y are type parameters + if u.join(i, j) { + return true + } + // both x and y have an inferred type - they must match + return u.nifyEq(u.x.at(i), u.y.at(j), p) + + case i >= 0: + // x is a type parameter, y is not + if tx := u.x.at(i); tx != nil { + return u.nifyEq(tx, y, p) + } + // otherwise, infer type from y + u.x.set(i, y) + return true + + case j >= 0: + // y is a type parameter, x is not + if ty := u.y.at(j); ty != nil { + return u.nifyEq(x, ty, p) + } + // otherwise, infer type from x + u.y.set(j, x) + return true + } + + // For type unification, do not shortcut (x == y) for identical + // types. Instead keep comparing them element-wise to unify the + // matching (and equal type parameter types). A simple test case + // where this matters is: func f[P any](a P) { f(a) } . + + switch x := x.(type) { + case *Basic: + // Basic types are singletons except for the rune and byte + // aliases, thus we cannot solely rely on the x == y check + // above. See also comment in TypeName.IsAlias. + if y, ok := y.(*Basic); ok { + return x.kind == y.kind + } + + case *Array: + // Two array types are identical if they have identical element types + // and the same array length. + if y, ok := y.(*Array); ok { + // If one or both array lengths are unknown (< 0) due to some error, + // assume they are the same to avoid spurious follow-on errors. + return (x.len < 0 || y.len < 0 || x.len == y.len) && u.nify(x.elem, y.elem, p) + } + + case *Slice: + // Two slice types are identical if they have identical element types. + if y, ok := y.(*Slice); ok { + return u.nify(x.elem, y.elem, p) + } + + case *Struct: + // Two struct types are identical if they have the same sequence of fields, + // and if corresponding fields have the same names, and identical types, + // and identical tags. Two embedded fields are considered to have the same + // name. Lower-case field names from different packages are always different. + if y, ok := y.(*Struct); ok { + if x.NumFields() == y.NumFields() { + for i, f := range x.fields { + g := y.fields[i] + if f.embedded != g.embedded || + x.Tag(i) != y.Tag(i) || + !f.sameId(g.pkg, g.name) || + !u.nify(f.typ, g.typ, p) { + return false + } + } + return true + } + } + + case *Pointer: + // Two pointer types are identical if they have identical base types. + if y, ok := y.(*Pointer); ok { + return u.nify(x.base, y.base, p) + } + + case *Tuple: + // Two tuples types are identical if they have the same number of elements + // and corresponding elements have identical types. + if y, ok := y.(*Tuple); ok { + if x.Len() == y.Len() { + if x != nil { + for i, v := range x.vars { + w := y.vars[i] + if !u.nify(v.typ, w.typ, p) { + return false + } + } + } + return true + } + } + + case *Signature: + // Two function types are identical if they have the same number of parameters + // and result values, corresponding parameter and result types are identical, + // and either both functions are variadic or neither is. Parameter and result + // names are not required to match. + // TODO(gri) handle type parameters or document why we can ignore them. + if y, ok := y.(*Signature); ok { + return x.variadic == y.variadic && + u.nify(x.params, y.params, p) && + u.nify(x.results, y.results, p) + } + + case *Sum: + // This should not happen with the current internal use of sum types. + panic("type inference across sum types not implemented") + + case *Interface: + // Two interface types are identical if they have the same set of methods with + // the same names and identical function types. Lower-case method names from + // different packages are always different. The order of the methods is irrelevant. + if y, ok := y.(*Interface); ok { + // If identical0 is called (indirectly) via an external API entry point + // (such as Identical, IdenticalIgnoreTags, etc.), check is nil. But in + // that case, interfaces are expected to be complete and lazy completion + // here is not needed. + if u.check != nil { + u.check.completeInterface(nopos, x) + u.check.completeInterface(nopos, y) + } + a := x.allMethods + b := y.allMethods + if len(a) == len(b) { + // Interface types are the only types where cycles can occur + // that are not "terminated" via named types; and such cycles + // can only be created via method parameter types that are + // anonymous interfaces (directly or indirectly) embedding + // the current interface. Example: + // + // type T interface { + // m() interface{T} + // } + // + // If two such (differently named) interfaces are compared, + // endless recursion occurs if the cycle is not detected. + // + // If x and y were compared before, they must be equal + // (if they were not, the recursion would have stopped); + // search the ifacePair stack for the same pair. + // + // This is a quadratic algorithm, but in practice these stacks + // are extremely short (bounded by the nesting depth of interface + // type declarations that recur via parameter types, an extremely + // rare occurrence). An alternative implementation might use a + // "visited" map, but that is probably less efficient overall. + q := &ifacePair{x, y, p} + for p != nil { + if p.identical(q) { + return true // same pair was compared before + } + p = p.prev + } + if debug { + assertSortedMethods(a) + assertSortedMethods(b) + } + for i, f := range a { + g := b[i] + if f.Id() != g.Id() || !u.nify(f.typ, g.typ, q) { + return false + } + } + return true + } + } + + case *Map: + // Two map types are identical if they have identical key and value types. + if y, ok := y.(*Map); ok { + return u.nify(x.key, y.key, p) && u.nify(x.elem, y.elem, p) + } + + case *Chan: + // Two channel types are identical if they have identical value types. + if y, ok := y.(*Chan); ok { + return (!u.exact || x.dir == y.dir) && u.nify(x.elem, y.elem, p) + } + + case *Named: + // Two named types are identical if their type names originate + // in the same type declaration. + // if y, ok := y.(*Named); ok { + // return x.obj == y.obj + // } + if y, ok := y.(*Named); ok { + // TODO(gri) This is not always correct: two types may have the same names + // in the same package if one of them is nested in a function. + // Extremely unlikely but we need an always correct solution. + if x.obj.pkg == y.obj.pkg && x.obj.name == y.obj.name { + assert(len(x.targs) == len(y.targs)) + for i, x := range x.targs { + if !u.nify(x, y.targs[i], p) { + return false + } + } + return true + } + } + + case *TypeParam: + // Two type parameters (which are not part of the type parameters of the + // enclosing type as those are handled in the beginning of this function) + // are identical if they originate in the same declaration. + return x == y + + // case *instance: + // unreachable since types are expanded + + case nil: + // avoid a crash in case of nil type + + default: + u.check.dump("### u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams) + unreachable() + } + + return false +} diff --git a/src/cmd/compile/internal/types2/universe.go b/src/cmd/compile/internal/types2/universe.go new file mode 100644 index 0000000000000000000000000000000000000000..76d4e55e84b59127dd4d3b5d9335a37c8369d0a8 --- /dev/null +++ b/src/cmd/compile/internal/types2/universe.go @@ -0,0 +1,282 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file sets up the universe scope and the unsafe package. + +package types2 + +import ( + "go/constant" + "strings" +) + +// The Universe scope contains all predeclared objects of Go. +// It is the outermost scope of any chain of nested scopes. +var Universe *Scope + +// The Unsafe package is the package returned by an importer +// for the import path "unsafe". +var Unsafe *Package + +var ( + universeIota *Const + universeByte *Basic // uint8 alias, but has name "byte" + universeRune *Basic // int32 alias, but has name "rune" + universeAny *Interface + universeError *Named +) + +// Typ contains the predeclared *Basic types indexed by their +// corresponding BasicKind. +// +// The *Basic type for Typ[Byte] will have the name "uint8". +// Use Universe.Lookup("byte").Type() to obtain the specific +// alias basic type named "byte" (and analogous for "rune"). +var Typ = [...]*Basic{ + Invalid: {Invalid, 0, "invalid type"}, + + Bool: {Bool, IsBoolean, "bool"}, + Int: {Int, IsInteger, "int"}, + Int8: {Int8, IsInteger, "int8"}, + Int16: {Int16, IsInteger, "int16"}, + Int32: {Int32, IsInteger, "int32"}, + Int64: {Int64, IsInteger, "int64"}, + Uint: {Uint, IsInteger | IsUnsigned, "uint"}, + Uint8: {Uint8, IsInteger | IsUnsigned, "uint8"}, + Uint16: {Uint16, IsInteger | IsUnsigned, "uint16"}, + Uint32: {Uint32, IsInteger | IsUnsigned, "uint32"}, + Uint64: {Uint64, IsInteger | IsUnsigned, "uint64"}, + Uintptr: {Uintptr, IsInteger | IsUnsigned, "uintptr"}, + Float32: {Float32, IsFloat, "float32"}, + Float64: {Float64, IsFloat, "float64"}, + Complex64: {Complex64, IsComplex, "complex64"}, + Complex128: {Complex128, IsComplex, "complex128"}, + String: {String, IsString, "string"}, + UnsafePointer: {UnsafePointer, 0, "Pointer"}, + + UntypedBool: {UntypedBool, IsBoolean | IsUntyped, "untyped bool"}, + UntypedInt: {UntypedInt, IsInteger | IsUntyped, "untyped int"}, + UntypedRune: {UntypedRune, IsInteger | IsUntyped, "untyped rune"}, + UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, "untyped float"}, + UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, "untyped complex"}, + UntypedString: {UntypedString, IsString | IsUntyped, "untyped string"}, + UntypedNil: {UntypedNil, IsUntyped, "untyped nil"}, +} + +var aliases = [...]*Basic{ + {Byte, IsInteger | IsUnsigned, "byte"}, + {Rune, IsInteger, "rune"}, +} + +func defPredeclaredTypes() { + for _, t := range Typ { + def(NewTypeName(nopos, nil, t.name, t)) + } + for _, t := range aliases { + def(NewTypeName(nopos, nil, t.name, t)) + } + + // any + // (Predeclared and entered into universe scope so we do all the + // usual checks; but removed again from scope later since it's + // only visible as constraint in a type parameter list.) + def(NewTypeName(nopos, nil, "any", &emptyInterface)) + + // Error has a nil package in its qualified name since it is in no package + { + res := NewVar(nopos, nil, "", Typ[String]) + sig := &Signature{results: NewTuple(res)} + err := NewFunc(nopos, nil, "Error", sig) + typ := &Named{underlying: NewInterfaceType([]*Func{err}, nil).Complete()} + sig.recv = NewVar(nopos, nil, "", typ) + def(NewTypeName(nopos, nil, "error", typ)) + } +} + +var predeclaredConsts = [...]struct { + name string + kind BasicKind + val constant.Value +}{ + {"true", UntypedBool, constant.MakeBool(true)}, + {"false", UntypedBool, constant.MakeBool(false)}, + {"iota", UntypedInt, constant.MakeInt64(0)}, +} + +func defPredeclaredConsts() { + for _, c := range predeclaredConsts { + def(NewConst(nopos, nil, c.name, Typ[c.kind], c.val)) + } +} + +func defPredeclaredNil() { + def(&Nil{object{name: "nil", typ: Typ[UntypedNil], color_: black}}) +} + +// A builtinId is the id of a builtin function. +type builtinId int + +const ( + // universe scope + _Append builtinId = iota + _Cap + _Close + _Complex + _Copy + _Delete + _Imag + _Len + _Make + _New + _Panic + _Print + _Println + _Real + _Recover + + // package unsafe + _Add + _Alignof + _Offsetof + _Sizeof + _Slice + + // testing support + _Assert + _Trace +) + +var predeclaredFuncs = [...]struct { + name string + nargs int + variadic bool + kind exprKind +}{ + _Append: {"append", 1, true, expression}, + _Cap: {"cap", 1, false, expression}, + _Close: {"close", 1, false, statement}, + _Complex: {"complex", 2, false, expression}, + _Copy: {"copy", 2, false, statement}, + _Delete: {"delete", 2, false, statement}, + _Imag: {"imag", 1, false, expression}, + _Len: {"len", 1, false, expression}, + _Make: {"make", 1, true, expression}, + _New: {"new", 1, false, expression}, + _Panic: {"panic", 1, false, statement}, + _Print: {"print", 0, true, statement}, + _Println: {"println", 0, true, statement}, + _Real: {"real", 1, false, expression}, + _Recover: {"recover", 0, false, statement}, + + _Add: {"Add", 2, false, expression}, + _Alignof: {"Alignof", 1, false, expression}, + _Offsetof: {"Offsetof", 1, false, expression}, + _Sizeof: {"Sizeof", 1, false, expression}, + _Slice: {"Slice", 2, false, expression}, + + _Assert: {"assert", 1, false, statement}, + _Trace: {"trace", 0, true, statement}, +} + +func defPredeclaredFuncs() { + for i := range predeclaredFuncs { + id := builtinId(i) + if id == _Assert || id == _Trace { + continue // only define these in testing environment + } + def(newBuiltin(id)) + } +} + +// DefPredeclaredTestFuncs defines the assert and trace built-ins. +// These built-ins are intended for debugging and testing of this +// package only. +func DefPredeclaredTestFuncs() { + if Universe.Lookup("assert") != nil { + return // already defined + } + def(newBuiltin(_Assert)) + def(newBuiltin(_Trace)) +} + +func defPredeclaredComparable() { + // The "comparable" interface can be imagined as defined like + // + // type comparable interface { + // == () untyped bool + // != () untyped bool + // } + // + // == and != cannot be user-declared but we can declare + // a magic method == and check for its presence when needed. + + // Define interface { == () }. We don't care about the signature + // for == so leave it empty except for the receiver, which is + // set up later to match the usual interface method assumptions. + sig := new(Signature) + eql := NewFunc(nopos, nil, "==", sig) + iface := NewInterfaceType([]*Func{eql}, nil).Complete() + + // set up the defined type for the interface + obj := NewTypeName(nopos, nil, "comparable", nil) + named := NewNamed(obj, iface, nil) + obj.color_ = black + sig.recv = NewVar(nopos, nil, "", named) // complete == signature + + def(obj) +} + +func init() { + Universe = NewScope(nil, nopos, nopos, "universe") + Unsafe = NewPackage("unsafe", "unsafe") + Unsafe.complete = true + + defPredeclaredTypes() + defPredeclaredConsts() + defPredeclaredNil() + defPredeclaredFuncs() + defPredeclaredComparable() + + universeIota = Universe.Lookup("iota").(*Const) + universeByte = Universe.Lookup("byte").(*TypeName).typ.(*Basic) + universeRune = Universe.Lookup("rune").(*TypeName).typ.(*Basic) + universeAny = Universe.Lookup("any").(*TypeName).typ.(*Interface) + universeError = Universe.Lookup("error").(*TypeName).typ.(*Named) + + // "any" is only visible as constraint in a type parameter list + delete(Universe.elems, "any") +} + +// Objects with names containing blanks are internal and not entered into +// a scope. Objects with exported names are inserted in the unsafe package +// scope; other objects are inserted in the universe scope. +// +func def(obj Object) { + assert(obj.color() == black) + name := obj.Name() + if strings.Contains(name, " ") { + return // nothing to do + } + // fix Obj link for named types + if typ := asNamed(obj.Type()); typ != nil { + typ.obj = obj.(*TypeName) + } + // exported identifiers go into package unsafe + scope := Universe + if obj.Exported() { + scope = Unsafe.scope + // set Pkg field + switch obj := obj.(type) { + case *TypeName: + obj.pkg = Unsafe + case *Builtin: + obj.pkg = Unsafe + default: + unreachable() + } + } + if scope.Insert(obj) != nil { + panic("internal error: double declaration") + } +} diff --git a/src/cmd/compile/internal/types2/version.go b/src/cmd/compile/internal/types2/version.go new file mode 100644 index 0000000000000000000000000000000000000000..d9d18b6f7a7f487e88320324fd7f457b5704b023 --- /dev/null +++ b/src/cmd/compile/internal/types2/version.go @@ -0,0 +1,81 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "regexp" + "strconv" + "strings" +) + +// langCompat reports an error if the representation of a numeric +// literal is not compatible with the current language version. +func (check *Checker) langCompat(lit *syntax.BasicLit) { + s := lit.Value + if len(s) <= 2 || check.allowVersion(check.pkg, 1, 13) { + return + } + // len(s) > 2 + if strings.Contains(s, "_") { + check.error(lit, "underscores in numeric literals requires go1.13 or later") + return + } + if s[0] != '0' { + return + } + radix := s[1] + if radix == 'b' || radix == 'B' { + check.error(lit, "binary literals requires go1.13 or later") + return + } + if radix == 'o' || radix == 'O' { + check.error(lit, "0o/0O-style octal literals requires go1.13 or later") + return + } + if lit.Kind != syntax.IntLit && (radix == 'x' || radix == 'X') { + check.error(lit, "hexadecimal floating-point literals requires go1.13 or later") + } +} + +// allowVersion reports whether the given package +// is allowed to use version major.minor. +func (check *Checker) allowVersion(pkg *Package, major, minor int) bool { + // We assume that imported packages have all been checked, + // so we only have to check for the local package. + if pkg != check.pkg { + return true + } + ma, mi := check.version.major, check.version.minor + return ma == 0 && mi == 0 || ma > major || ma == major && mi >= minor +} + +type version struct { + major, minor int +} + +// parseGoVersion parses a Go version string (such as "go1.12") +// and returns the version, or an error. If s is the empty +// string, the version is 0.0. +func parseGoVersion(s string) (v version, err error) { + if s == "" { + return + } + matches := goVersionRx.FindStringSubmatch(s) + if matches == nil { + err = fmt.Errorf(`should be something like "go1.12"`) + return + } + v.major, err = strconv.Atoi(matches[1]) + if err != nil { + return + } + v.minor, err = strconv.Atoi(matches[2]) + return +} + +// goVersionRx matches a Go version string, e.g. "go1.12". +var goVersionRx = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`) diff --git a/src/cmd/compile/internal/walk/assign.go b/src/cmd/compile/internal/walk/assign.go new file mode 100644 index 0000000000000000000000000000000000000000..6d697a53ae3aaa14442a21f9f4e15880bbfe75c2 --- /dev/null +++ b/src/cmd/compile/internal/walk/assign.go @@ -0,0 +1,718 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +// walkAssign walks an OAS (AssignExpr) or OASOP (AssignOpExpr) node. +func walkAssign(init *ir.Nodes, n ir.Node) ir.Node { + init.Append(ir.TakeInit(n)...) + + var left, right ir.Node + switch n.Op() { + case ir.OAS: + n := n.(*ir.AssignStmt) + left, right = n.X, n.Y + case ir.OASOP: + n := n.(*ir.AssignOpStmt) + left, right = n.X, n.Y + } + + // Recognize m[k] = append(m[k], ...) so we can reuse + // the mapassign call. + var mapAppend *ir.CallExpr + if left.Op() == ir.OINDEXMAP && right.Op() == ir.OAPPEND { + left := left.(*ir.IndexExpr) + mapAppend = right.(*ir.CallExpr) + if !ir.SameSafeExpr(left, mapAppend.Args[0]) { + base.Fatalf("not same expressions: %v != %v", left, mapAppend.Args[0]) + } + } + + left = walkExpr(left, init) + left = safeExpr(left, init) + if mapAppend != nil { + mapAppend.Args[0] = left + } + + if n.Op() == ir.OASOP { + // Rewrite x op= y into x = x op y. + n = ir.NewAssignStmt(base.Pos, left, typecheck.Expr(ir.NewBinaryExpr(base.Pos, n.(*ir.AssignOpStmt).AsOp, left, right))) + } else { + n.(*ir.AssignStmt).X = left + } + as := n.(*ir.AssignStmt) + + if oaslit(as, init) { + return ir.NewBlockStmt(as.Pos(), nil) + } + + if as.Y == nil { + // TODO(austin): Check all "implicit zeroing" + return as + } + + if !base.Flag.Cfg.Instrumenting && ir.IsZero(as.Y) { + return as + } + + switch as.Y.Op() { + default: + as.Y = walkExpr(as.Y, init) + + case ir.ORECV: + // x = <-c; as.Left is x, as.Right.Left is c. + // order.stmt made sure x is addressable. + recv := as.Y.(*ir.UnaryExpr) + recv.X = walkExpr(recv.X, init) + + n1 := typecheck.NodAddr(as.X) + r := recv.X // the channel + return mkcall1(chanfn("chanrecv1", 2, r.Type()), nil, init, r, n1) + + case ir.OAPPEND: + // x = append(...) + call := as.Y.(*ir.CallExpr) + if call.Type().Elem().NotInHeap() { + base.Errorf("%v can't be allocated in Go; it is incomplete (or unallocatable)", call.Type().Elem()) + } + var r ir.Node + switch { + case isAppendOfMake(call): + // x = append(y, make([]T, y)...) + r = extendSlice(call, init) + case call.IsDDD: + r = appendSlice(call, init) // also works for append(slice, string). + default: + r = walkAppend(call, init, as) + } + as.Y = r + if r.Op() == ir.OAPPEND { + // Left in place for back end. + // Do not add a new write barrier. + // Set up address of type for back end. + r.(*ir.CallExpr).X = reflectdata.TypePtr(r.Type().Elem()) + return as + } + // Otherwise, lowered for race detector. + // Treat as ordinary assignment. + } + + if as.X != nil && as.Y != nil { + return convas(as, init) + } + return as +} + +// walkAssignDotType walks an OAS2DOTTYPE node. +func walkAssignDotType(n *ir.AssignListStmt, init *ir.Nodes) ir.Node { + walkExprListSafe(n.Lhs, init) + n.Rhs[0] = walkExpr(n.Rhs[0], init) + return n +} + +// walkAssignFunc walks an OAS2FUNC node. +func walkAssignFunc(init *ir.Nodes, n *ir.AssignListStmt) ir.Node { + init.Append(ir.TakeInit(n)...) + + r := n.Rhs[0] + walkExprListSafe(n.Lhs, init) + r = walkExpr(r, init) + + if ir.IsIntrinsicCall(r.(*ir.CallExpr)) { + n.Rhs = []ir.Node{r} + return n + } + init.Append(r) + + ll := ascompatet(n.Lhs, r.Type()) + return ir.NewBlockStmt(src.NoXPos, ll) +} + +// walkAssignList walks an OAS2 node. +func walkAssignList(init *ir.Nodes, n *ir.AssignListStmt) ir.Node { + init.Append(ir.TakeInit(n)...) + return ir.NewBlockStmt(src.NoXPos, ascompatee(ir.OAS, n.Lhs, n.Rhs)) +} + +// walkAssignMapRead walks an OAS2MAPR node. +func walkAssignMapRead(init *ir.Nodes, n *ir.AssignListStmt) ir.Node { + init.Append(ir.TakeInit(n)...) + + r := n.Rhs[0].(*ir.IndexExpr) + walkExprListSafe(n.Lhs, init) + r.X = walkExpr(r.X, init) + r.Index = walkExpr(r.Index, init) + t := r.X.Type() + + fast := mapfast(t) + key := mapKeyArg(fast, r, r.Index) + + // from: + // a,b = m[i] + // to: + // var,b = mapaccess2*(t, m, i) + // a = *var + a := n.Lhs[0] + + var call *ir.CallExpr + if w := t.Elem().Width; w <= zeroValSize { + fn := mapfn(mapaccess2[fast], t, false) + call = mkcall1(fn, fn.Type().Results(), init, reflectdata.TypePtr(t), r.X, key) + } else { + fn := mapfn("mapaccess2_fat", t, true) + z := reflectdata.ZeroAddr(w) + call = mkcall1(fn, fn.Type().Results(), init, reflectdata.TypePtr(t), r.X, key, z) + } + + // mapaccess2* returns a typed bool, but due to spec changes, + // the boolean result of i.(T) is now untyped so we make it the + // same type as the variable on the lhs. + if ok := n.Lhs[1]; !ir.IsBlank(ok) && ok.Type().IsBoolean() { + call.Type().Field(1).Type = ok.Type() + } + n.Rhs = []ir.Node{call} + n.SetOp(ir.OAS2FUNC) + + // don't generate a = *var if a is _ + if ir.IsBlank(a) { + return walkExpr(typecheck.Stmt(n), init) + } + + var_ := typecheck.Temp(types.NewPtr(t.Elem())) + var_.SetTypecheck(1) + var_.MarkNonNil() // mapaccess always returns a non-nil pointer + + n.Lhs[0] = var_ + init.Append(walkExpr(n, init)) + + as := ir.NewAssignStmt(base.Pos, a, ir.NewStarExpr(base.Pos, var_)) + return walkExpr(typecheck.Stmt(as), init) +} + +// walkAssignRecv walks an OAS2RECV node. +func walkAssignRecv(init *ir.Nodes, n *ir.AssignListStmt) ir.Node { + init.Append(ir.TakeInit(n)...) + + r := n.Rhs[0].(*ir.UnaryExpr) // recv + walkExprListSafe(n.Lhs, init) + r.X = walkExpr(r.X, init) + var n1 ir.Node + if ir.IsBlank(n.Lhs[0]) { + n1 = typecheck.NodNil() + } else { + n1 = typecheck.NodAddr(n.Lhs[0]) + } + fn := chanfn("chanrecv2", 2, r.X.Type()) + ok := n.Lhs[1] + call := mkcall1(fn, types.Types[types.TBOOL], init, r.X, n1) + return typecheck.Stmt(ir.NewAssignStmt(base.Pos, ok, call)) +} + +// walkReturn walks an ORETURN node. +func walkReturn(n *ir.ReturnStmt) ir.Node { + fn := ir.CurFunc + + fn.NumReturns++ + if len(n.Results) == 0 { + return n + } + + results := fn.Type().Results().FieldSlice() + dsts := make([]ir.Node, len(results)) + for i, v := range results { + // TODO(mdempsky): typecheck should have already checked the result variables. + dsts[i] = typecheck.AssignExpr(v.Nname.(*ir.Name)) + } + + n.Results = ascompatee(n.Op(), dsts, n.Results) + return n +} + +// check assign type list to +// an expression list. called in +// expr-list = func() +func ascompatet(nl ir.Nodes, nr *types.Type) []ir.Node { + if len(nl) != nr.NumFields() { + base.Fatalf("ascompatet: assignment count mismatch: %d = %d", len(nl), nr.NumFields()) + } + + var nn ir.Nodes + for i, l := range nl { + if ir.IsBlank(l) { + continue + } + r := nr.Field(i) + + // Order should have created autotemps of the appropriate type for + // us to store results into. + if tmp, ok := l.(*ir.Name); !ok || !tmp.AutoTemp() || !types.Identical(tmp.Type(), r.Type) { + base.FatalfAt(l.Pos(), "assigning %v to %+v", r.Type, l) + } + + res := ir.NewResultExpr(base.Pos, nil, types.BADWIDTH) + res.Index = int64(i) + res.SetType(r.Type) + res.SetTypecheck(1) + + nn.Append(ir.NewAssignStmt(base.Pos, l, res)) + } + return nn +} + +// check assign expression list to +// an expression list. called in +// expr-list = expr-list +func ascompatee(op ir.Op, nl, nr []ir.Node) []ir.Node { + // cannot happen: should have been rejected during type checking + if len(nl) != len(nr) { + base.Fatalf("assignment operands mismatch: %+v / %+v", ir.Nodes(nl), ir.Nodes(nr)) + } + + var assigned ir.NameSet + var memWrite, deferResultWrite bool + + // affected reports whether expression n could be affected by + // the assignments applied so far. + affected := func(n ir.Node) bool { + if deferResultWrite { + return true + } + return ir.Any(n, func(n ir.Node) bool { + if n.Op() == ir.ONAME && assigned.Has(n.(*ir.Name)) { + return true + } + if memWrite && readsMemory(n) { + return true + } + return false + }) + } + + // If a needed expression may be affected by an + // earlier assignment, make an early copy of that + // expression and use the copy instead. + var early ir.Nodes + save := func(np *ir.Node) { + if n := *np; affected(n) { + *np = copyExpr(n, n.Type(), &early) + } + } + + var late ir.Nodes + for i, lorig := range nl { + l, r := lorig, nr[i] + + // Do not generate 'x = x' during return. See issue 4014. + if op == ir.ORETURN && ir.SameSafeExpr(l, r) { + continue + } + + // Save subexpressions needed on left side. + // Drill through non-dereferences. + for { + // If an expression has init statements, they must be evaluated + // before any of its saved sub-operands (#45706). + // TODO(mdempsky): Disallow init statements on lvalues. + init := ir.TakeInit(l) + walkStmtList(init) + early.Append(init...) + + switch ll := l.(type) { + case *ir.IndexExpr: + if ll.X.Type().IsArray() { + save(&ll.Index) + l = ll.X + continue + } + case *ir.ParenExpr: + l = ll.X + continue + case *ir.SelectorExpr: + if ll.Op() == ir.ODOT { + l = ll.X + continue + } + } + break + } + + var name *ir.Name + switch l.Op() { + default: + base.Fatalf("unexpected lvalue %v", l.Op()) + case ir.ONAME: + name = l.(*ir.Name) + case ir.OINDEX, ir.OINDEXMAP: + l := l.(*ir.IndexExpr) + save(&l.X) + save(&l.Index) + case ir.ODEREF: + l := l.(*ir.StarExpr) + save(&l.X) + case ir.ODOTPTR: + l := l.(*ir.SelectorExpr) + save(&l.X) + } + + // Save expression on right side. + save(&r) + + appendWalkStmt(&late, convas(ir.NewAssignStmt(base.Pos, lorig, r), &late)) + + // Check for reasons why we may need to compute later expressions + // before this assignment happens. + + if name == nil { + // Not a direct assignment to a declared variable. + // Conservatively assume any memory access might alias. + memWrite = true + continue + } + + if name.Class == ir.PPARAMOUT && ir.CurFunc.HasDefer() { + // Assignments to a result parameter in a function with defers + // becomes visible early if evaluation of any later expression + // panics (#43835). + deferResultWrite = true + continue + } + + if sym := types.OrigSym(name.Sym()); sym == nil || sym.IsBlank() { + // We can ignore assignments to blank or anonymous result parameters. + // These can't appear in expressions anyway. + continue + } + + if name.Addrtaken() || !name.OnStack() { + // Global variable, heap escaped, or just addrtaken. + // Conservatively assume any memory access might alias. + memWrite = true + continue + } + + // Local, non-addrtaken variable. + // Assignments can only alias with direct uses of this variable. + assigned.Add(name) + } + + early.Append(late.Take()...) + return early +} + +// readsMemory reports whether the evaluation n directly reads from +// memory that might be written to indirectly. +func readsMemory(n ir.Node) bool { + switch n.Op() { + case ir.ONAME: + n := n.(*ir.Name) + if n.Class == ir.PFUNC { + return false + } + return n.Addrtaken() || !n.OnStack() + + case ir.OADD, + ir.OAND, + ir.OANDAND, + ir.OANDNOT, + ir.OBITNOT, + ir.OCONV, + ir.OCONVIFACE, + ir.OCONVNOP, + ir.ODIV, + ir.ODOT, + ir.ODOTTYPE, + ir.OLITERAL, + ir.OLSH, + ir.OMOD, + ir.OMUL, + ir.ONEG, + ir.ONIL, + ir.OOR, + ir.OOROR, + ir.OPAREN, + ir.OPLUS, + ir.ORSH, + ir.OSUB, + ir.OXOR: + return false + } + + // Be conservative. + return true +} + +// expand append(l1, l2...) to +// init { +// s := l1 +// n := len(s) + len(l2) +// // Compare as uint so growslice can panic on overflow. +// if uint(n) > uint(cap(s)) { +// s = growslice(s, n) +// } +// s = s[:n] +// memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T)) +// } +// s +// +// l2 is allowed to be a string. +func appendSlice(n *ir.CallExpr, init *ir.Nodes) ir.Node { + walkAppendArgs(n, init) + + l1 := n.Args[0] + l2 := n.Args[1] + l2 = cheapExpr(l2, init) + n.Args[1] = l2 + + var nodes ir.Nodes + + // var s []T + s := typecheck.Temp(l1.Type()) + nodes.Append(ir.NewAssignStmt(base.Pos, s, l1)) // s = l1 + + elemtype := s.Type().Elem() + + // n := len(s) + len(l2) + nn := typecheck.Temp(types.Types[types.TINT]) + nodes.Append(ir.NewAssignStmt(base.Pos, nn, ir.NewBinaryExpr(base.Pos, ir.OADD, ir.NewUnaryExpr(base.Pos, ir.OLEN, s), ir.NewUnaryExpr(base.Pos, ir.OLEN, l2)))) + + // if uint(n) > uint(cap(s)) + nif := ir.NewIfStmt(base.Pos, nil, nil, nil) + nuint := typecheck.Conv(nn, types.Types[types.TUINT]) + scapuint := typecheck.Conv(ir.NewUnaryExpr(base.Pos, ir.OCAP, s), types.Types[types.TUINT]) + nif.Cond = ir.NewBinaryExpr(base.Pos, ir.OGT, nuint, scapuint) + + // instantiate growslice(typ *type, []any, int) []any + fn := typecheck.LookupRuntime("growslice") + fn = typecheck.SubstArgTypes(fn, elemtype, elemtype) + + // s = growslice(T, s, n) + nif.Body = []ir.Node{ir.NewAssignStmt(base.Pos, s, mkcall1(fn, s.Type(), nif.PtrInit(), reflectdata.TypePtr(elemtype), s, nn))} + nodes.Append(nif) + + // s = s[:n] + nt := ir.NewSliceExpr(base.Pos, ir.OSLICE, s, nil, nn, nil) + nt.SetBounded(true) + nodes.Append(ir.NewAssignStmt(base.Pos, s, nt)) + + var ncopy ir.Node + if elemtype.HasPointers() { + // copy(s[len(l1):], l2) + slice := ir.NewSliceExpr(base.Pos, ir.OSLICE, s, ir.NewUnaryExpr(base.Pos, ir.OLEN, l1), nil, nil) + slice.SetType(s.Type()) + + ir.CurFunc.SetWBPos(n.Pos()) + + // instantiate typedslicecopy(typ *type, dstPtr *any, dstLen int, srcPtr *any, srcLen int) int + fn := typecheck.LookupRuntime("typedslicecopy") + fn = typecheck.SubstArgTypes(fn, l1.Type().Elem(), l2.Type().Elem()) + ptr1, len1 := backingArrayPtrLen(cheapExpr(slice, &nodes)) + ptr2, len2 := backingArrayPtrLen(l2) + ncopy = mkcall1(fn, types.Types[types.TINT], &nodes, reflectdata.TypePtr(elemtype), ptr1, len1, ptr2, len2) + } else if base.Flag.Cfg.Instrumenting && !base.Flag.CompilingRuntime { + // rely on runtime to instrument: + // copy(s[len(l1):], l2) + // l2 can be a slice or string. + slice := ir.NewSliceExpr(base.Pos, ir.OSLICE, s, ir.NewUnaryExpr(base.Pos, ir.OLEN, l1), nil, nil) + slice.SetType(s.Type()) + + ptr1, len1 := backingArrayPtrLen(cheapExpr(slice, &nodes)) + ptr2, len2 := backingArrayPtrLen(l2) + + fn := typecheck.LookupRuntime("slicecopy") + fn = typecheck.SubstArgTypes(fn, ptr1.Type().Elem(), ptr2.Type().Elem()) + ncopy = mkcall1(fn, types.Types[types.TINT], &nodes, ptr1, len1, ptr2, len2, ir.NewInt(elemtype.Width)) + } else { + // memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T)) + ix := ir.NewIndexExpr(base.Pos, s, ir.NewUnaryExpr(base.Pos, ir.OLEN, l1)) + ix.SetBounded(true) + addr := typecheck.NodAddr(ix) + + sptr := ir.NewUnaryExpr(base.Pos, ir.OSPTR, l2) + + nwid := cheapExpr(typecheck.Conv(ir.NewUnaryExpr(base.Pos, ir.OLEN, l2), types.Types[types.TUINTPTR]), &nodes) + nwid = ir.NewBinaryExpr(base.Pos, ir.OMUL, nwid, ir.NewInt(elemtype.Width)) + + // instantiate func memmove(to *any, frm *any, length uintptr) + fn := typecheck.LookupRuntime("memmove") + fn = typecheck.SubstArgTypes(fn, elemtype, elemtype) + ncopy = mkcall1(fn, nil, &nodes, addr, sptr, nwid) + } + ln := append(nodes, ncopy) + + typecheck.Stmts(ln) + walkStmtList(ln) + init.Append(ln...) + return s +} + +// isAppendOfMake reports whether n is of the form append(x, make([]T, y)...). +// isAppendOfMake assumes n has already been typechecked. +func isAppendOfMake(n ir.Node) bool { + if base.Flag.N != 0 || base.Flag.Cfg.Instrumenting { + return false + } + + if n.Typecheck() == 0 { + base.Fatalf("missing typecheck: %+v", n) + } + + if n.Op() != ir.OAPPEND { + return false + } + call := n.(*ir.CallExpr) + if !call.IsDDD || len(call.Args) != 2 || call.Args[1].Op() != ir.OMAKESLICE { + return false + } + + mk := call.Args[1].(*ir.MakeExpr) + if mk.Cap != nil { + return false + } + + // y must be either an integer constant or the largest possible positive value + // of variable y needs to fit into an uint. + + // typecheck made sure that constant arguments to make are not negative and fit into an int. + + // The care of overflow of the len argument to make will be handled by an explicit check of int(len) < 0 during runtime. + y := mk.Len + if !ir.IsConst(y, constant.Int) && y.Type().Size() > types.Types[types.TUINT].Size() { + return false + } + + return true +} + +// extendSlice rewrites append(l1, make([]T, l2)...) to +// init { +// if l2 >= 0 { // Empty if block here for more meaningful node.SetLikely(true) +// } else { +// panicmakeslicelen() +// } +// s := l1 +// n := len(s) + l2 +// // Compare n and s as uint so growslice can panic on overflow of len(s) + l2. +// // cap is a positive int and n can become negative when len(s) + l2 +// // overflows int. Interpreting n when negative as uint makes it larger +// // than cap(s). growslice will check the int n arg and panic if n is +// // negative. This prevents the overflow from being undetected. +// if uint(n) > uint(cap(s)) { +// s = growslice(T, s, n) +// } +// s = s[:n] +// lptr := &l1[0] +// sptr := &s[0] +// if lptr == sptr || !T.HasPointers() { +// // growslice did not clear the whole underlying array (or did not get called) +// hp := &s[len(l1)] +// hn := l2 * sizeof(T) +// memclr(hp, hn) +// } +// } +// s +func extendSlice(n *ir.CallExpr, init *ir.Nodes) ir.Node { + // isAppendOfMake made sure all possible positive values of l2 fit into an uint. + // The case of l2 overflow when converting from e.g. uint to int is handled by an explicit + // check of l2 < 0 at runtime which is generated below. + l2 := typecheck.Conv(n.Args[1].(*ir.MakeExpr).Len, types.Types[types.TINT]) + l2 = typecheck.Expr(l2) + n.Args[1] = l2 // walkAppendArgs expects l2 in n.List.Second(). + + walkAppendArgs(n, init) + + l1 := n.Args[0] + l2 = n.Args[1] // re-read l2, as it may have been updated by walkAppendArgs + + var nodes []ir.Node + + // if l2 >= 0 (likely happens), do nothing + nifneg := ir.NewIfStmt(base.Pos, ir.NewBinaryExpr(base.Pos, ir.OGE, l2, ir.NewInt(0)), nil, nil) + nifneg.Likely = true + + // else panicmakeslicelen() + nifneg.Else = []ir.Node{mkcall("panicmakeslicelen", nil, init)} + nodes = append(nodes, nifneg) + + // s := l1 + s := typecheck.Temp(l1.Type()) + nodes = append(nodes, ir.NewAssignStmt(base.Pos, s, l1)) + + elemtype := s.Type().Elem() + + // n := len(s) + l2 + nn := typecheck.Temp(types.Types[types.TINT]) + nodes = append(nodes, ir.NewAssignStmt(base.Pos, nn, ir.NewBinaryExpr(base.Pos, ir.OADD, ir.NewUnaryExpr(base.Pos, ir.OLEN, s), l2))) + + // if uint(n) > uint(cap(s)) + nuint := typecheck.Conv(nn, types.Types[types.TUINT]) + capuint := typecheck.Conv(ir.NewUnaryExpr(base.Pos, ir.OCAP, s), types.Types[types.TUINT]) + nif := ir.NewIfStmt(base.Pos, ir.NewBinaryExpr(base.Pos, ir.OGT, nuint, capuint), nil, nil) + + // instantiate growslice(typ *type, old []any, newcap int) []any + fn := typecheck.LookupRuntime("growslice") + fn = typecheck.SubstArgTypes(fn, elemtype, elemtype) + + // s = growslice(T, s, n) + nif.Body = []ir.Node{ir.NewAssignStmt(base.Pos, s, mkcall1(fn, s.Type(), nif.PtrInit(), reflectdata.TypePtr(elemtype), s, nn))} + nodes = append(nodes, nif) + + // s = s[:n] + nt := ir.NewSliceExpr(base.Pos, ir.OSLICE, s, nil, nn, nil) + nt.SetBounded(true) + nodes = append(nodes, ir.NewAssignStmt(base.Pos, s, nt)) + + // lptr := &l1[0] + l1ptr := typecheck.Temp(l1.Type().Elem().PtrTo()) + tmp := ir.NewUnaryExpr(base.Pos, ir.OSPTR, l1) + nodes = append(nodes, ir.NewAssignStmt(base.Pos, l1ptr, tmp)) + + // sptr := &s[0] + sptr := typecheck.Temp(elemtype.PtrTo()) + tmp = ir.NewUnaryExpr(base.Pos, ir.OSPTR, s) + nodes = append(nodes, ir.NewAssignStmt(base.Pos, sptr, tmp)) + + // hp := &s[len(l1)] + ix := ir.NewIndexExpr(base.Pos, s, ir.NewUnaryExpr(base.Pos, ir.OLEN, l1)) + ix.SetBounded(true) + hp := typecheck.ConvNop(typecheck.NodAddr(ix), types.Types[types.TUNSAFEPTR]) + + // hn := l2 * sizeof(elem(s)) + hn := typecheck.Conv(ir.NewBinaryExpr(base.Pos, ir.OMUL, l2, ir.NewInt(elemtype.Width)), types.Types[types.TUINTPTR]) + + clrname := "memclrNoHeapPointers" + hasPointers := elemtype.HasPointers() + if hasPointers { + clrname = "memclrHasPointers" + ir.CurFunc.SetWBPos(n.Pos()) + } + + var clr ir.Nodes + clrfn := mkcall(clrname, nil, &clr, hp, hn) + clr.Append(clrfn) + + if hasPointers { + // if l1ptr == sptr + nifclr := ir.NewIfStmt(base.Pos, ir.NewBinaryExpr(base.Pos, ir.OEQ, l1ptr, sptr), nil, nil) + nifclr.Body = clr + nodes = append(nodes, nifclr) + } else { + nodes = append(nodes, clr...) + } + + typecheck.Stmts(nodes) + walkStmtList(nodes) + init.Append(nodes...) + return s +} diff --git a/src/cmd/compile/internal/walk/builtin.go b/src/cmd/compile/internal/walk/builtin.go new file mode 100644 index 0000000000000000000000000000000000000000..14efc05e32753727502e42f4e805ebe134522125 --- /dev/null +++ b/src/cmd/compile/internal/walk/builtin.go @@ -0,0 +1,718 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "fmt" + "go/constant" + "go/token" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/escape" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" +) + +// Rewrite append(src, x, y, z) so that any side effects in +// x, y, z (including runtime panics) are evaluated in +// initialization statements before the append. +// For normal code generation, stop there and leave the +// rest to cgen_append. +// +// For race detector, expand append(src, a [, b]* ) to +// +// init { +// s := src +// const argc = len(args) - 1 +// if cap(s) - len(s) < argc { +// s = growslice(s, len(s)+argc) +// } +// n := len(s) +// s = s[:n+argc] +// s[n] = a +// s[n+1] = b +// ... +// } +// s +func walkAppend(n *ir.CallExpr, init *ir.Nodes, dst ir.Node) ir.Node { + if !ir.SameSafeExpr(dst, n.Args[0]) { + n.Args[0] = safeExpr(n.Args[0], init) + n.Args[0] = walkExpr(n.Args[0], init) + } + walkExprListSafe(n.Args[1:], init) + + nsrc := n.Args[0] + + // walkExprListSafe will leave OINDEX (s[n]) alone if both s + // and n are name or literal, but those may index the slice we're + // modifying here. Fix explicitly. + // Using cheapExpr also makes sure that the evaluation + // of all arguments (and especially any panics) happen + // before we begin to modify the slice in a visible way. + ls := n.Args[1:] + for i, n := range ls { + n = cheapExpr(n, init) + if !types.Identical(n.Type(), nsrc.Type().Elem()) { + n = typecheck.AssignConv(n, nsrc.Type().Elem(), "append") + n = walkExpr(n, init) + } + ls[i] = n + } + + argc := len(n.Args) - 1 + if argc < 1 { + return nsrc + } + + // General case, with no function calls left as arguments. + // Leave for gen, except that instrumentation requires old form. + if !base.Flag.Cfg.Instrumenting || base.Flag.CompilingRuntime { + return n + } + + var l []ir.Node + + ns := typecheck.Temp(nsrc.Type()) + l = append(l, ir.NewAssignStmt(base.Pos, ns, nsrc)) // s = src + + na := ir.NewInt(int64(argc)) // const argc + nif := ir.NewIfStmt(base.Pos, nil, nil, nil) // if cap(s) - len(s) < argc + nif.Cond = ir.NewBinaryExpr(base.Pos, ir.OLT, ir.NewBinaryExpr(base.Pos, ir.OSUB, ir.NewUnaryExpr(base.Pos, ir.OCAP, ns), ir.NewUnaryExpr(base.Pos, ir.OLEN, ns)), na) + + fn := typecheck.LookupRuntime("growslice") // growslice(, old []T, mincap int) (ret []T) + fn = typecheck.SubstArgTypes(fn, ns.Type().Elem(), ns.Type().Elem()) + + nif.Body = []ir.Node{ir.NewAssignStmt(base.Pos, ns, mkcall1(fn, ns.Type(), nif.PtrInit(), reflectdata.TypePtr(ns.Type().Elem()), ns, + ir.NewBinaryExpr(base.Pos, ir.OADD, ir.NewUnaryExpr(base.Pos, ir.OLEN, ns), na)))} + + l = append(l, nif) + + nn := typecheck.Temp(types.Types[types.TINT]) + l = append(l, ir.NewAssignStmt(base.Pos, nn, ir.NewUnaryExpr(base.Pos, ir.OLEN, ns))) // n = len(s) + + slice := ir.NewSliceExpr(base.Pos, ir.OSLICE, ns, nil, ir.NewBinaryExpr(base.Pos, ir.OADD, nn, na), nil) // ...s[:n+argc] + slice.SetBounded(true) + l = append(l, ir.NewAssignStmt(base.Pos, ns, slice)) // s = s[:n+argc] + + ls = n.Args[1:] + for i, n := range ls { + ix := ir.NewIndexExpr(base.Pos, ns, nn) // s[n] ... + ix.SetBounded(true) + l = append(l, ir.NewAssignStmt(base.Pos, ix, n)) // s[n] = arg + if i+1 < len(ls) { + l = append(l, ir.NewAssignStmt(base.Pos, nn, ir.NewBinaryExpr(base.Pos, ir.OADD, nn, ir.NewInt(1)))) // n = n + 1 + } + } + + typecheck.Stmts(l) + walkStmtList(l) + init.Append(l...) + return ns +} + +// walkClose walks an OCLOSE node. +func walkClose(n *ir.UnaryExpr, init *ir.Nodes) ir.Node { + // cannot use chanfn - closechan takes any, not chan any + fn := typecheck.LookupRuntime("closechan") + fn = typecheck.SubstArgTypes(fn, n.X.Type()) + return mkcall1(fn, nil, init, n.X) +} + +// Lower copy(a, b) to a memmove call or a runtime call. +// +// init { +// n := len(a) +// if n > len(b) { n = len(b) } +// if a.ptr != b.ptr { memmove(a.ptr, b.ptr, n*sizeof(elem(a))) } +// } +// n; +// +// Also works if b is a string. +// +func walkCopy(n *ir.BinaryExpr, init *ir.Nodes, runtimecall bool) ir.Node { + if n.X.Type().Elem().HasPointers() { + ir.CurFunc.SetWBPos(n.Pos()) + fn := writebarrierfn("typedslicecopy", n.X.Type().Elem(), n.Y.Type().Elem()) + n.X = cheapExpr(n.X, init) + ptrL, lenL := backingArrayPtrLen(n.X) + n.Y = cheapExpr(n.Y, init) + ptrR, lenR := backingArrayPtrLen(n.Y) + return mkcall1(fn, n.Type(), init, reflectdata.TypePtr(n.X.Type().Elem()), ptrL, lenL, ptrR, lenR) + } + + if runtimecall { + // rely on runtime to instrument: + // copy(n.Left, n.Right) + // n.Right can be a slice or string. + + n.X = cheapExpr(n.X, init) + ptrL, lenL := backingArrayPtrLen(n.X) + n.Y = cheapExpr(n.Y, init) + ptrR, lenR := backingArrayPtrLen(n.Y) + + fn := typecheck.LookupRuntime("slicecopy") + fn = typecheck.SubstArgTypes(fn, ptrL.Type().Elem(), ptrR.Type().Elem()) + + return mkcall1(fn, n.Type(), init, ptrL, lenL, ptrR, lenR, ir.NewInt(n.X.Type().Elem().Width)) + } + + n.X = walkExpr(n.X, init) + n.Y = walkExpr(n.Y, init) + nl := typecheck.Temp(n.X.Type()) + nr := typecheck.Temp(n.Y.Type()) + var l []ir.Node + l = append(l, ir.NewAssignStmt(base.Pos, nl, n.X)) + l = append(l, ir.NewAssignStmt(base.Pos, nr, n.Y)) + + nfrm := ir.NewUnaryExpr(base.Pos, ir.OSPTR, nr) + nto := ir.NewUnaryExpr(base.Pos, ir.OSPTR, nl) + + nlen := typecheck.Temp(types.Types[types.TINT]) + + // n = len(to) + l = append(l, ir.NewAssignStmt(base.Pos, nlen, ir.NewUnaryExpr(base.Pos, ir.OLEN, nl))) + + // if n > len(frm) { n = len(frm) } + nif := ir.NewIfStmt(base.Pos, nil, nil, nil) + + nif.Cond = ir.NewBinaryExpr(base.Pos, ir.OGT, nlen, ir.NewUnaryExpr(base.Pos, ir.OLEN, nr)) + nif.Body.Append(ir.NewAssignStmt(base.Pos, nlen, ir.NewUnaryExpr(base.Pos, ir.OLEN, nr))) + l = append(l, nif) + + // if to.ptr != frm.ptr { memmove( ... ) } + ne := ir.NewIfStmt(base.Pos, ir.NewBinaryExpr(base.Pos, ir.ONE, nto, nfrm), nil, nil) + ne.Likely = true + l = append(l, ne) + + fn := typecheck.LookupRuntime("memmove") + fn = typecheck.SubstArgTypes(fn, nl.Type().Elem(), nl.Type().Elem()) + nwid := ir.Node(typecheck.Temp(types.Types[types.TUINTPTR])) + setwid := ir.NewAssignStmt(base.Pos, nwid, typecheck.Conv(nlen, types.Types[types.TUINTPTR])) + ne.Body.Append(setwid) + nwid = ir.NewBinaryExpr(base.Pos, ir.OMUL, nwid, ir.NewInt(nl.Type().Elem().Width)) + call := mkcall1(fn, nil, init, nto, nfrm, nwid) + ne.Body.Append(call) + + typecheck.Stmts(l) + walkStmtList(l) + init.Append(l...) + return nlen +} + +// walkDelete walks an ODELETE node. +func walkDelete(init *ir.Nodes, n *ir.CallExpr) ir.Node { + init.Append(ir.TakeInit(n)...) + map_ := n.Args[0] + key := n.Args[1] + map_ = walkExpr(map_, init) + key = walkExpr(key, init) + + t := map_.Type() + fast := mapfast(t) + key = mapKeyArg(fast, n, key) + return mkcall1(mapfndel(mapdelete[fast], t), nil, init, reflectdata.TypePtr(t), map_, key) +} + +// walkLenCap walks an OLEN or OCAP node. +func walkLenCap(n *ir.UnaryExpr, init *ir.Nodes) ir.Node { + if isRuneCount(n) { + // Replace len([]rune(string)) with runtime.countrunes(string). + return mkcall("countrunes", n.Type(), init, typecheck.Conv(n.X.(*ir.ConvExpr).X, types.Types[types.TSTRING])) + } + + n.X = walkExpr(n.X, init) + + // replace len(*[10]int) with 10. + // delayed until now to preserve side effects. + t := n.X.Type() + + if t.IsPtr() { + t = t.Elem() + } + if t.IsArray() { + safeExpr(n.X, init) + con := typecheck.OrigInt(n, t.NumElem()) + con.SetTypecheck(1) + return con + } + return n +} + +// walkMakeChan walks an OMAKECHAN node. +func walkMakeChan(n *ir.MakeExpr, init *ir.Nodes) ir.Node { + // When size fits into int, use makechan instead of + // makechan64, which is faster and shorter on 32 bit platforms. + size := n.Len + fnname := "makechan64" + argtype := types.Types[types.TINT64] + + // Type checking guarantees that TIDEAL size is positive and fits in an int. + // The case of size overflow when converting TUINT or TUINTPTR to TINT + // will be handled by the negative range checks in makechan during runtime. + if size.Type().IsKind(types.TIDEAL) || size.Type().Size() <= types.Types[types.TUINT].Size() { + fnname = "makechan" + argtype = types.Types[types.TINT] + } + + return mkcall1(chanfn(fnname, 1, n.Type()), n.Type(), init, reflectdata.TypePtr(n.Type()), typecheck.Conv(size, argtype)) +} + +// walkMakeMap walks an OMAKEMAP node. +func walkMakeMap(n *ir.MakeExpr, init *ir.Nodes) ir.Node { + t := n.Type() + hmapType := reflectdata.MapType(t) + hint := n.Len + + // var h *hmap + var h ir.Node + if n.Esc() == ir.EscNone { + // Allocate hmap on stack. + + // var hv hmap + // h = &hv + h = stackTempAddr(init, hmapType) + + // Allocate one bucket pointed to by hmap.buckets on stack if hint + // is not larger than BUCKETSIZE. In case hint is larger than + // BUCKETSIZE runtime.makemap will allocate the buckets on the heap. + // Maximum key and elem size is 128 bytes, larger objects + // are stored with an indirection. So max bucket size is 2048+eps. + if !ir.IsConst(hint, constant.Int) || + constant.Compare(hint.Val(), token.LEQ, constant.MakeInt64(reflectdata.BUCKETSIZE)) { + + // In case hint is larger than BUCKETSIZE runtime.makemap + // will allocate the buckets on the heap, see #20184 + // + // if hint <= BUCKETSIZE { + // var bv bmap + // b = &bv + // h.buckets = b + // } + + nif := ir.NewIfStmt(base.Pos, ir.NewBinaryExpr(base.Pos, ir.OLE, hint, ir.NewInt(reflectdata.BUCKETSIZE)), nil, nil) + nif.Likely = true + + // var bv bmap + // b = &bv + b := stackTempAddr(&nif.Body, reflectdata.MapBucketType(t)) + + // h.buckets = b + bsym := hmapType.Field(5).Sym // hmap.buckets see reflect.go:hmap + na := ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, h, bsym), b) + nif.Body.Append(na) + appendWalkStmt(init, nif) + } + } + + if ir.IsConst(hint, constant.Int) && constant.Compare(hint.Val(), token.LEQ, constant.MakeInt64(reflectdata.BUCKETSIZE)) { + // Handling make(map[any]any) and + // make(map[any]any, hint) where hint <= BUCKETSIZE + // special allows for faster map initialization and + // improves binary size by using calls with fewer arguments. + // For hint <= BUCKETSIZE overLoadFactor(hint, 0) is false + // and no buckets will be allocated by makemap. Therefore, + // no buckets need to be allocated in this code path. + if n.Esc() == ir.EscNone { + // Only need to initialize h.hash0 since + // hmap h has been allocated on the stack already. + // h.hash0 = fastrand() + rand := mkcall("fastrand", types.Types[types.TUINT32], init) + hashsym := hmapType.Field(4).Sym // hmap.hash0 see reflect.go:hmap + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, h, hashsym), rand)) + return typecheck.ConvNop(h, t) + } + // Call runtime.makehmap to allocate an + // hmap on the heap and initialize hmap's hash0 field. + fn := typecheck.LookupRuntime("makemap_small") + fn = typecheck.SubstArgTypes(fn, t.Key(), t.Elem()) + return mkcall1(fn, n.Type(), init) + } + + if n.Esc() != ir.EscNone { + h = typecheck.NodNil() + } + // Map initialization with a variable or large hint is + // more complicated. We therefore generate a call to + // runtime.makemap to initialize hmap and allocate the + // map buckets. + + // When hint fits into int, use makemap instead of + // makemap64, which is faster and shorter on 32 bit platforms. + fnname := "makemap64" + argtype := types.Types[types.TINT64] + + // Type checking guarantees that TIDEAL hint is positive and fits in an int. + // See checkmake call in TMAP case of OMAKE case in OpSwitch in typecheck1 function. + // The case of hint overflow when converting TUINT or TUINTPTR to TINT + // will be handled by the negative range checks in makemap during runtime. + if hint.Type().IsKind(types.TIDEAL) || hint.Type().Size() <= types.Types[types.TUINT].Size() { + fnname = "makemap" + argtype = types.Types[types.TINT] + } + + fn := typecheck.LookupRuntime(fnname) + fn = typecheck.SubstArgTypes(fn, hmapType, t.Key(), t.Elem()) + return mkcall1(fn, n.Type(), init, reflectdata.TypePtr(n.Type()), typecheck.Conv(hint, argtype), h) +} + +// walkMakeSlice walks an OMAKESLICE node. +func walkMakeSlice(n *ir.MakeExpr, init *ir.Nodes) ir.Node { + l := n.Len + r := n.Cap + if r == nil { + r = safeExpr(l, init) + l = r + } + t := n.Type() + if t.Elem().NotInHeap() { + base.Errorf("%v can't be allocated in Go; it is incomplete (or unallocatable)", t.Elem()) + } + if n.Esc() == ir.EscNone { + if why := escape.HeapAllocReason(n); why != "" { + base.Fatalf("%v has EscNone, but %v", n, why) + } + // var arr [r]T + // n = arr[:l] + i := typecheck.IndexConst(r) + if i < 0 { + base.Fatalf("walkExpr: invalid index %v", r) + } + + // cap is constrained to [0,2^31) or [0,2^63) depending on whether + // we're in 32-bit or 64-bit systems. So it's safe to do: + // + // if uint64(len) > cap { + // if len < 0 { panicmakeslicelen() } + // panicmakeslicecap() + // } + nif := ir.NewIfStmt(base.Pos, ir.NewBinaryExpr(base.Pos, ir.OGT, typecheck.Conv(l, types.Types[types.TUINT64]), ir.NewInt(i)), nil, nil) + niflen := ir.NewIfStmt(base.Pos, ir.NewBinaryExpr(base.Pos, ir.OLT, l, ir.NewInt(0)), nil, nil) + niflen.Body = []ir.Node{mkcall("panicmakeslicelen", nil, init)} + nif.Body.Append(niflen, mkcall("panicmakeslicecap", nil, init)) + init.Append(typecheck.Stmt(nif)) + + t = types.NewArray(t.Elem(), i) // [r]T + var_ := typecheck.Temp(t) + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, var_, nil)) // zero temp + r := ir.NewSliceExpr(base.Pos, ir.OSLICE, var_, nil, l, nil) // arr[:l] + // The conv is necessary in case n.Type is named. + return walkExpr(typecheck.Expr(typecheck.Conv(r, n.Type())), init) + } + + // n escapes; set up a call to makeslice. + // When len and cap can fit into int, use makeslice instead of + // makeslice64, which is faster and shorter on 32 bit platforms. + + len, cap := l, r + + fnname := "makeslice64" + argtype := types.Types[types.TINT64] + + // Type checking guarantees that TIDEAL len/cap are positive and fit in an int. + // The case of len or cap overflow when converting TUINT or TUINTPTR to TINT + // will be handled by the negative range checks in makeslice during runtime. + if (len.Type().IsKind(types.TIDEAL) || len.Type().Size() <= types.Types[types.TUINT].Size()) && + (cap.Type().IsKind(types.TIDEAL) || cap.Type().Size() <= types.Types[types.TUINT].Size()) { + fnname = "makeslice" + argtype = types.Types[types.TINT] + } + fn := typecheck.LookupRuntime(fnname) + ptr := mkcall1(fn, types.Types[types.TUNSAFEPTR], init, reflectdata.TypePtr(t.Elem()), typecheck.Conv(len, argtype), typecheck.Conv(cap, argtype)) + ptr.MarkNonNil() + len = typecheck.Conv(len, types.Types[types.TINT]) + cap = typecheck.Conv(cap, types.Types[types.TINT]) + sh := ir.NewSliceHeaderExpr(base.Pos, t, ptr, len, cap) + return walkExpr(typecheck.Expr(sh), init) +} + +// walkMakeSliceCopy walks an OMAKESLICECOPY node. +func walkMakeSliceCopy(n *ir.MakeExpr, init *ir.Nodes) ir.Node { + if n.Esc() == ir.EscNone { + base.Fatalf("OMAKESLICECOPY with EscNone: %v", n) + } + + t := n.Type() + if t.Elem().NotInHeap() { + base.Errorf("%v can't be allocated in Go; it is incomplete (or unallocatable)", t.Elem()) + } + + length := typecheck.Conv(n.Len, types.Types[types.TINT]) + copylen := ir.NewUnaryExpr(base.Pos, ir.OLEN, n.Cap) + copyptr := ir.NewUnaryExpr(base.Pos, ir.OSPTR, n.Cap) + + if !t.Elem().HasPointers() && n.Bounded() { + // When len(to)==len(from) and elements have no pointers: + // replace make+copy with runtime.mallocgc+runtime.memmove. + + // We do not check for overflow of len(to)*elem.Width here + // since len(from) is an existing checked slice capacity + // with same elem.Width for the from slice. + size := ir.NewBinaryExpr(base.Pos, ir.OMUL, typecheck.Conv(length, types.Types[types.TUINTPTR]), typecheck.Conv(ir.NewInt(t.Elem().Width), types.Types[types.TUINTPTR])) + + // instantiate mallocgc(size uintptr, typ *byte, needszero bool) unsafe.Pointer + fn := typecheck.LookupRuntime("mallocgc") + ptr := mkcall1(fn, types.Types[types.TUNSAFEPTR], init, size, typecheck.NodNil(), ir.NewBool(false)) + ptr.MarkNonNil() + sh := ir.NewSliceHeaderExpr(base.Pos, t, ptr, length, length) + + s := typecheck.Temp(t) + r := typecheck.Stmt(ir.NewAssignStmt(base.Pos, s, sh)) + r = walkExpr(r, init) + init.Append(r) + + // instantiate memmove(to *any, frm *any, size uintptr) + fn = typecheck.LookupRuntime("memmove") + fn = typecheck.SubstArgTypes(fn, t.Elem(), t.Elem()) + ncopy := mkcall1(fn, nil, init, ir.NewUnaryExpr(base.Pos, ir.OSPTR, s), copyptr, size) + init.Append(walkExpr(typecheck.Stmt(ncopy), init)) + + return s + } + // Replace make+copy with runtime.makeslicecopy. + // instantiate makeslicecopy(typ *byte, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer + fn := typecheck.LookupRuntime("makeslicecopy") + ptr := mkcall1(fn, types.Types[types.TUNSAFEPTR], init, reflectdata.TypePtr(t.Elem()), length, copylen, typecheck.Conv(copyptr, types.Types[types.TUNSAFEPTR])) + ptr.MarkNonNil() + sh := ir.NewSliceHeaderExpr(base.Pos, t, ptr, length, length) + return walkExpr(typecheck.Expr(sh), init) +} + +// walkNew walks an ONEW node. +func walkNew(n *ir.UnaryExpr, init *ir.Nodes) ir.Node { + t := n.Type().Elem() + if t.NotInHeap() { + base.Errorf("%v can't be allocated in Go; it is incomplete (or unallocatable)", n.Type().Elem()) + } + if n.Esc() == ir.EscNone { + if t.Size() > ir.MaxImplicitStackVarSize { + base.Fatalf("large ONEW with EscNone: %v", n) + } + return stackTempAddr(init, t) + } + types.CalcSize(t) + n.MarkNonNil() + return n +} + +// generate code for print +func walkPrint(nn *ir.CallExpr, init *ir.Nodes) ir.Node { + // Hoist all the argument evaluation up before the lock. + walkExprListCheap(nn.Args, init) + + // For println, add " " between elements and "\n" at the end. + if nn.Op() == ir.OPRINTN { + s := nn.Args + t := make([]ir.Node, 0, len(s)*2) + for i, n := range s { + if i != 0 { + t = append(t, ir.NewString(" ")) + } + t = append(t, n) + } + t = append(t, ir.NewString("\n")) + nn.Args = t + } + + // Collapse runs of constant strings. + s := nn.Args + t := make([]ir.Node, 0, len(s)) + for i := 0; i < len(s); { + var strs []string + for i < len(s) && ir.IsConst(s[i], constant.String) { + strs = append(strs, ir.StringVal(s[i])) + i++ + } + if len(strs) > 0 { + t = append(t, ir.NewString(strings.Join(strs, ""))) + } + if i < len(s) { + t = append(t, s[i]) + i++ + } + } + nn.Args = t + + calls := []ir.Node{mkcall("printlock", nil, init)} + for i, n := range nn.Args { + if n.Op() == ir.OLITERAL { + if n.Type() == types.UntypedRune { + n = typecheck.DefaultLit(n, types.RuneType) + } + + switch n.Val().Kind() { + case constant.Int: + n = typecheck.DefaultLit(n, types.Types[types.TINT64]) + + case constant.Float: + n = typecheck.DefaultLit(n, types.Types[types.TFLOAT64]) + } + } + + if n.Op() != ir.OLITERAL && n.Type() != nil && n.Type().Kind() == types.TIDEAL { + n = typecheck.DefaultLit(n, types.Types[types.TINT64]) + } + n = typecheck.DefaultLit(n, nil) + nn.Args[i] = n + if n.Type() == nil || n.Type().Kind() == types.TFORW { + continue + } + + var on *ir.Name + switch n.Type().Kind() { + case types.TINTER: + if n.Type().IsEmptyInterface() { + on = typecheck.LookupRuntime("printeface") + } else { + on = typecheck.LookupRuntime("printiface") + } + on = typecheck.SubstArgTypes(on, n.Type()) // any-1 + case types.TPTR: + if n.Type().Elem().NotInHeap() { + on = typecheck.LookupRuntime("printuintptr") + n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n) + n.SetType(types.Types[types.TUNSAFEPTR]) + n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n) + n.SetType(types.Types[types.TUINTPTR]) + break + } + fallthrough + case types.TCHAN, types.TMAP, types.TFUNC, types.TUNSAFEPTR: + on = typecheck.LookupRuntime("printpointer") + on = typecheck.SubstArgTypes(on, n.Type()) // any-1 + case types.TSLICE: + on = typecheck.LookupRuntime("printslice") + on = typecheck.SubstArgTypes(on, n.Type()) // any-1 + case types.TUINT, types.TUINT8, types.TUINT16, types.TUINT32, types.TUINT64, types.TUINTPTR: + if types.IsRuntimePkg(n.Type().Sym().Pkg) && n.Type().Sym().Name == "hex" { + on = typecheck.LookupRuntime("printhex") + } else { + on = typecheck.LookupRuntime("printuint") + } + case types.TINT, types.TINT8, types.TINT16, types.TINT32, types.TINT64: + on = typecheck.LookupRuntime("printint") + case types.TFLOAT32, types.TFLOAT64: + on = typecheck.LookupRuntime("printfloat") + case types.TCOMPLEX64, types.TCOMPLEX128: + on = typecheck.LookupRuntime("printcomplex") + case types.TBOOL: + on = typecheck.LookupRuntime("printbool") + case types.TSTRING: + cs := "" + if ir.IsConst(n, constant.String) { + cs = ir.StringVal(n) + } + switch cs { + case " ": + on = typecheck.LookupRuntime("printsp") + case "\n": + on = typecheck.LookupRuntime("printnl") + default: + on = typecheck.LookupRuntime("printstring") + } + default: + badtype(ir.OPRINT, n.Type(), nil) + continue + } + + r := ir.NewCallExpr(base.Pos, ir.OCALL, on, nil) + if params := on.Type().Params().FieldSlice(); len(params) > 0 { + t := params[0].Type + if !types.Identical(t, n.Type()) { + n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n) + n.SetType(t) + } + r.Args.Append(n) + } + calls = append(calls, r) + } + + calls = append(calls, mkcall("printunlock", nil, init)) + + typecheck.Stmts(calls) + walkExprList(calls, init) + + r := ir.NewBlockStmt(base.Pos, nil) + r.List = calls + return walkStmt(typecheck.Stmt(r)) +} + +// walkRecover walks an ORECOVER node. +func walkRecover(nn *ir.CallExpr, init *ir.Nodes) ir.Node { + // Call gorecover with the FP of this frame. + // FP is equal to caller's SP plus FixedFrameSize(). + var fp ir.Node = mkcall("getcallersp", types.Types[types.TUINTPTR], init) + if off := base.Ctxt.FixedFrameSize(); off != 0 { + fp = ir.NewBinaryExpr(fp.Pos(), ir.OADD, fp, ir.NewInt(off)) + } + fp = ir.NewConvExpr(fp.Pos(), ir.OCONVNOP, types.NewPtr(types.Types[types.TINT32]), fp) + return mkcall("gorecover", nn.Type(), init, fp) +} + +func walkUnsafeSlice(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { + ptr := safeExpr(n.X, init) + len := safeExpr(n.Y, init) + + fnname := "unsafeslice64" + lenType := types.Types[types.TINT64] + + // Type checking guarantees that TIDEAL len/cap are positive and fit in an int. + // The case of len or cap overflow when converting TUINT or TUINTPTR to TINT + // will be handled by the negative range checks in unsafeslice during runtime. + if ir.ShouldCheckPtr(ir.CurFunc, 1) { + fnname = "unsafeslicecheckptr" + // for simplicity, unsafeslicecheckptr always uses int64 + } else if len.Type().IsKind(types.TIDEAL) || len.Type().Size() <= types.Types[types.TUINT].Size() { + fnname = "unsafeslice" + lenType = types.Types[types.TINT] + } + + t := n.Type() + + // Call runtime.unsafeslice{,64,checkptr} to check ptr and len. + fn := typecheck.LookupRuntime(fnname) + init.Append(mkcall1(fn, nil, init, reflectdata.TypePtr(t.Elem()), typecheck.Conv(ptr, types.Types[types.TUNSAFEPTR]), typecheck.Conv(len, lenType))) + + h := ir.NewSliceHeaderExpr(n.Pos(), t, + typecheck.Conv(ptr, types.Types[types.TUNSAFEPTR]), + typecheck.Conv(len, types.Types[types.TINT]), + typecheck.Conv(len, types.Types[types.TINT])) + return walkExpr(typecheck.Expr(h), init) +} + +func badtype(op ir.Op, tl, tr *types.Type) { + var s string + if tl != nil { + s += fmt.Sprintf("\n\t%v", tl) + } + if tr != nil { + s += fmt.Sprintf("\n\t%v", tr) + } + + // common mistake: *struct and *interface. + if tl != nil && tr != nil && tl.IsPtr() && tr.IsPtr() { + if tl.Elem().IsStruct() && tr.Elem().IsInterface() { + s += "\n\t(*struct vs *interface)" + } else if tl.Elem().IsInterface() && tr.Elem().IsStruct() { + s += "\n\t(*interface vs *struct)" + } + } + + base.Errorf("illegal types for operand: %v%s", op, s) +} + +func writebarrierfn(name string, l *types.Type, r *types.Type) ir.Node { + fn := typecheck.LookupRuntime(name) + fn = typecheck.SubstArgTypes(fn, l, r) + return fn +} + +// isRuneCount reports whether n is of the form len([]rune(string)). +// These are optimized into a call to runtime.countrunes. +func isRuneCount(n ir.Node) bool { + return base.Flag.N == 0 && !base.Flag.Cfg.Instrumenting && n.Op() == ir.OLEN && n.(*ir.UnaryExpr).X.Op() == ir.OSTR2RUNES +} diff --git a/src/cmd/compile/internal/walk/closure.go b/src/cmd/compile/internal/walk/closure.go new file mode 100644 index 0000000000000000000000000000000000000000..2194e1c5b0c7a3fd9f5ed01a57bb1e8aeb613238 --- /dev/null +++ b/src/cmd/compile/internal/walk/closure.go @@ -0,0 +1,207 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +// directClosureCall rewrites a direct call of a function literal into +// a normal function call with closure variables passed as arguments. +// This avoids allocation of a closure object. +// +// For illustration, the following call: +// +// func(a int) { +// println(byval) +// byref++ +// }(42) +// +// becomes: +// +// func(byval int, &byref *int, a int) { +// println(byval) +// (*&byref)++ +// }(byval, &byref, 42) +func directClosureCall(n *ir.CallExpr) { + clo := n.X.(*ir.ClosureExpr) + clofn := clo.Func + + if ir.IsTrivialClosure(clo) { + return // leave for walkClosure to handle + } + + // If wrapGoDefer() in the order phase has flagged this call, + // avoid eliminating the closure even if there is a direct call to + // (the closure is needed to simplify the register ABI). See + // wrapGoDefer for more details. + if n.PreserveClosure { + return + } + + // We are going to insert captured variables before input args. + var params []*types.Field + var decls []*ir.Name + for _, v := range clofn.ClosureVars { + if !v.Byval() { + // If v of type T is captured by reference, + // we introduce function param &v *T + // and v remains PAUTOHEAP with &v heapaddr + // (accesses will implicitly deref &v). + + addr := ir.NewNameAt(clofn.Pos(), typecheck.Lookup("&"+v.Sym().Name)) + addr.Curfn = clofn + addr.SetType(types.NewPtr(v.Type())) + v.Heapaddr = addr + v = addr + } + + v.Class = ir.PPARAM + decls = append(decls, v) + + fld := types.NewField(src.NoXPos, v.Sym(), v.Type()) + fld.Nname = v + params = append(params, fld) + } + + // f is ONAME of the actual function. + f := clofn.Nname + typ := f.Type() + + // Create new function type with parameters prepended, and + // then update type and declarations. + typ = types.NewSignature(typ.Pkg(), nil, nil, append(params, typ.Params().FieldSlice()...), typ.Results().FieldSlice()) + f.SetType(typ) + clofn.Dcl = append(decls, clofn.Dcl...) + + // Rewrite call. + n.X = f + n.Args.Prepend(closureArgs(clo)...) + + // Update the call expression's type. We need to do this + // because typecheck gave it the result type of the OCLOSURE + // node, but we only rewrote the ONAME node's type. Logically, + // they're the same, but the stack offsets probably changed. + if typ.NumResults() == 1 { + n.SetType(typ.Results().Field(0).Type) + } else { + n.SetType(typ.Results()) + } + + // Add to Closures for enqueueFunc. It's no longer a proper + // closure, but we may have already skipped over it in the + // functions list as a non-trivial closure, so this just + // ensures it's compiled. + ir.CurFunc.Closures = append(ir.CurFunc.Closures, clofn) +} + +func walkClosure(clo *ir.ClosureExpr, init *ir.Nodes) ir.Node { + clofn := clo.Func + + // If no closure vars, don't bother wrapping. + if ir.IsTrivialClosure(clo) { + if base.Debug.Closure > 0 { + base.WarnfAt(clo.Pos(), "closure converted to global") + } + return clofn.Nname + } + + // The closure is not trivial or directly called, so it's going to stay a closure. + ir.ClosureDebugRuntimeCheck(clo) + clofn.SetNeedctxt(true) + ir.CurFunc.Closures = append(ir.CurFunc.Closures, clofn) + + typ := typecheck.ClosureType(clo) + + clos := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(typ), nil) + clos.SetEsc(clo.Esc()) + clos.List = append([]ir.Node{ir.NewUnaryExpr(base.Pos, ir.OCFUNC, clofn.Nname)}, closureArgs(clo)...) + + addr := typecheck.NodAddr(clos) + addr.SetEsc(clo.Esc()) + + // Force type conversion from *struct to the func type. + cfn := typecheck.ConvNop(addr, clo.Type()) + + // non-escaping temp to use, if any. + if x := clo.Prealloc; x != nil { + if !types.Identical(typ, x.Type()) { + panic("closure type does not match order's assigned type") + } + addr.Prealloc = x + clo.Prealloc = nil + } + + return walkExpr(cfn, init) +} + +// closureArgs returns a slice of expressions that an be used to +// initialize the given closure's free variables. These correspond +// one-to-one with the variables in clo.Func.ClosureVars, and will be +// either an ONAME node (if the variable is captured by value) or an +// OADDR-of-ONAME node (if not). +func closureArgs(clo *ir.ClosureExpr) []ir.Node { + fn := clo.Func + + args := make([]ir.Node, len(fn.ClosureVars)) + for i, v := range fn.ClosureVars { + var outer ir.Node + outer = v.Outer + if !v.Byval() { + outer = typecheck.NodAddrAt(fn.Pos(), outer) + } + args[i] = typecheck.Expr(outer) + } + return args +} + +func walkCallPart(n *ir.SelectorExpr, init *ir.Nodes) ir.Node { + // Create closure in the form of a composite literal. + // For x.M with receiver (x) type T, the generated code looks like: + // + // clos = &struct{F uintptr; R T}{T.M·f, x} + // + // Like walkClosure above. + + if n.X.Type().IsInterface() { + // Trigger panic for method on nil interface now. + // Otherwise it happens in the wrapper and is confusing. + n.X = cheapExpr(n.X, init) + n.X = walkExpr(n.X, nil) + + tab := typecheck.Expr(ir.NewUnaryExpr(base.Pos, ir.OITAB, n.X)) + + c := ir.NewUnaryExpr(base.Pos, ir.OCHECKNIL, tab) + c.SetTypecheck(1) + init.Append(c) + } + + typ := typecheck.PartialCallType(n) + + clos := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(typ), nil) + clos.SetEsc(n.Esc()) + clos.List = []ir.Node{ir.NewUnaryExpr(base.Pos, ir.OCFUNC, typecheck.MethodValueWrapper(n).Nname), n.X} + + addr := typecheck.NodAddr(clos) + addr.SetEsc(n.Esc()) + + // Force type conversion from *struct to the func type. + cfn := typecheck.ConvNop(addr, n.Type()) + + // non-escaping temp to use, if any. + if x := n.Prealloc; x != nil { + if !types.Identical(typ, x.Type()) { + panic("partial call type does not match order's assigned type") + } + addr.Prealloc = x + n.Prealloc = nil + } + + return walkExpr(cfn, init) +} diff --git a/src/cmd/compile/internal/walk/compare.go b/src/cmd/compile/internal/walk/compare.go new file mode 100644 index 0000000000000000000000000000000000000000..b18615f61a3e3b4d0ca988193c1114d3b6d1fb86 --- /dev/null +++ b/src/cmd/compile/internal/walk/compare.go @@ -0,0 +1,508 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "encoding/binary" + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/sys" +) + +// The result of walkCompare MUST be assigned back to n, e.g. +// n.Left = walkCompare(n.Left, init) +func walkCompare(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { + if n.X.Type().IsInterface() && n.Y.Type().IsInterface() && n.X.Op() != ir.ONIL && n.Y.Op() != ir.ONIL { + return walkCompareInterface(n, init) + } + + if n.X.Type().IsString() && n.Y.Type().IsString() { + return walkCompareString(n, init) + } + + n.X = walkExpr(n.X, init) + n.Y = walkExpr(n.Y, init) + + // Given mixed interface/concrete comparison, + // rewrite into types-equal && data-equal. + // This is efficient, avoids allocations, and avoids runtime calls. + if n.X.Type().IsInterface() != n.Y.Type().IsInterface() { + // Preserve side-effects in case of short-circuiting; see #32187. + l := cheapExpr(n.X, init) + r := cheapExpr(n.Y, init) + // Swap so that l is the interface value and r is the concrete value. + if n.Y.Type().IsInterface() { + l, r = r, l + } + + // Handle both == and !=. + eq := n.Op() + andor := ir.OOROR + if eq == ir.OEQ { + andor = ir.OANDAND + } + // Check for types equal. + // For empty interface, this is: + // l.tab == type(r) + // For non-empty interface, this is: + // l.tab != nil && l.tab._type == type(r) + var eqtype ir.Node + tab := ir.NewUnaryExpr(base.Pos, ir.OITAB, l) + rtyp := reflectdata.TypePtr(r.Type()) + if l.Type().IsEmptyInterface() { + tab.SetType(types.NewPtr(types.Types[types.TUINT8])) + tab.SetTypecheck(1) + eqtype = ir.NewBinaryExpr(base.Pos, eq, tab, rtyp) + } else { + nonnil := ir.NewBinaryExpr(base.Pos, brcom(eq), typecheck.NodNil(), tab) + match := ir.NewBinaryExpr(base.Pos, eq, itabType(tab), rtyp) + eqtype = ir.NewLogicalExpr(base.Pos, andor, nonnil, match) + } + // Check for data equal. + eqdata := ir.NewBinaryExpr(base.Pos, eq, ifaceData(n.Pos(), l, r.Type()), r) + // Put it all together. + expr := ir.NewLogicalExpr(base.Pos, andor, eqtype, eqdata) + return finishCompare(n, expr, init) + } + + // Must be comparison of array or struct. + // Otherwise back end handles it. + // While we're here, decide whether to + // inline or call an eq alg. + t := n.X.Type() + var inline bool + + maxcmpsize := int64(4) + unalignedLoad := canMergeLoads() + if unalignedLoad { + // Keep this low enough to generate less code than a function call. + maxcmpsize = 2 * int64(ssagen.Arch.LinkArch.RegSize) + } + + switch t.Kind() { + default: + if base.Debug.Libfuzzer != 0 && t.IsInteger() { + n.X = cheapExpr(n.X, init) + n.Y = cheapExpr(n.Y, init) + + // If exactly one comparison operand is + // constant, invoke the constcmp functions + // instead, and arrange for the constant + // operand to be the first argument. + l, r := n.X, n.Y + if r.Op() == ir.OLITERAL { + l, r = r, l + } + constcmp := l.Op() == ir.OLITERAL && r.Op() != ir.OLITERAL + + var fn string + var paramType *types.Type + switch t.Size() { + case 1: + fn = "libfuzzerTraceCmp1" + if constcmp { + fn = "libfuzzerTraceConstCmp1" + } + paramType = types.Types[types.TUINT8] + case 2: + fn = "libfuzzerTraceCmp2" + if constcmp { + fn = "libfuzzerTraceConstCmp2" + } + paramType = types.Types[types.TUINT16] + case 4: + fn = "libfuzzerTraceCmp4" + if constcmp { + fn = "libfuzzerTraceConstCmp4" + } + paramType = types.Types[types.TUINT32] + case 8: + fn = "libfuzzerTraceCmp8" + if constcmp { + fn = "libfuzzerTraceConstCmp8" + } + paramType = types.Types[types.TUINT64] + default: + base.Fatalf("unexpected integer size %d for %v", t.Size(), t) + } + init.Append(mkcall(fn, nil, init, tracecmpArg(l, paramType, init), tracecmpArg(r, paramType, init))) + } + return n + case types.TARRAY: + // We can compare several elements at once with 2/4/8 byte integer compares + inline = t.NumElem() <= 1 || (types.IsSimple[t.Elem().Kind()] && (t.NumElem() <= 4 || t.Elem().Width*t.NumElem() <= maxcmpsize)) + case types.TSTRUCT: + inline = t.NumComponents(types.IgnoreBlankFields) <= 4 + } + + cmpl := n.X + for cmpl != nil && cmpl.Op() == ir.OCONVNOP { + cmpl = cmpl.(*ir.ConvExpr).X + } + cmpr := n.Y + for cmpr != nil && cmpr.Op() == ir.OCONVNOP { + cmpr = cmpr.(*ir.ConvExpr).X + } + + // Chose not to inline. Call equality function directly. + if !inline { + // eq algs take pointers; cmpl and cmpr must be addressable + if !ir.IsAddressable(cmpl) || !ir.IsAddressable(cmpr) { + base.Fatalf("arguments of comparison must be lvalues - %v %v", cmpl, cmpr) + } + + fn, needsize := eqFor(t) + call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil) + call.Args.Append(typecheck.NodAddr(cmpl)) + call.Args.Append(typecheck.NodAddr(cmpr)) + if needsize { + call.Args.Append(ir.NewInt(t.Width)) + } + res := ir.Node(call) + if n.Op() != ir.OEQ { + res = ir.NewUnaryExpr(base.Pos, ir.ONOT, res) + } + return finishCompare(n, res, init) + } + + // inline: build boolean expression comparing element by element + andor := ir.OANDAND + if n.Op() == ir.ONE { + andor = ir.OOROR + } + var expr ir.Node + compare := func(el, er ir.Node) { + a := ir.NewBinaryExpr(base.Pos, n.Op(), el, er) + if expr == nil { + expr = a + } else { + expr = ir.NewLogicalExpr(base.Pos, andor, expr, a) + } + } + cmpl = safeExpr(cmpl, init) + cmpr = safeExpr(cmpr, init) + if t.IsStruct() { + for _, f := range t.Fields().Slice() { + sym := f.Sym + if sym.IsBlank() { + continue + } + compare( + ir.NewSelectorExpr(base.Pos, ir.OXDOT, cmpl, sym), + ir.NewSelectorExpr(base.Pos, ir.OXDOT, cmpr, sym), + ) + } + } else { + step := int64(1) + remains := t.NumElem() * t.Elem().Width + combine64bit := unalignedLoad && types.RegSize == 8 && t.Elem().Width <= 4 && t.Elem().IsInteger() + combine32bit := unalignedLoad && t.Elem().Width <= 2 && t.Elem().IsInteger() + combine16bit := unalignedLoad && t.Elem().Width == 1 && t.Elem().IsInteger() + for i := int64(0); remains > 0; { + var convType *types.Type + switch { + case remains >= 8 && combine64bit: + convType = types.Types[types.TINT64] + step = 8 / t.Elem().Width + case remains >= 4 && combine32bit: + convType = types.Types[types.TUINT32] + step = 4 / t.Elem().Width + case remains >= 2 && combine16bit: + convType = types.Types[types.TUINT16] + step = 2 / t.Elem().Width + default: + step = 1 + } + if step == 1 { + compare( + ir.NewIndexExpr(base.Pos, cmpl, ir.NewInt(i)), + ir.NewIndexExpr(base.Pos, cmpr, ir.NewInt(i)), + ) + i++ + remains -= t.Elem().Width + } else { + elemType := t.Elem().ToUnsigned() + cmplw := ir.Node(ir.NewIndexExpr(base.Pos, cmpl, ir.NewInt(i))) + cmplw = typecheck.Conv(cmplw, elemType) // convert to unsigned + cmplw = typecheck.Conv(cmplw, convType) // widen + cmprw := ir.Node(ir.NewIndexExpr(base.Pos, cmpr, ir.NewInt(i))) + cmprw = typecheck.Conv(cmprw, elemType) + cmprw = typecheck.Conv(cmprw, convType) + // For code like this: uint32(s[0]) | uint32(s[1])<<8 | uint32(s[2])<<16 ... + // ssa will generate a single large load. + for offset := int64(1); offset < step; offset++ { + lb := ir.Node(ir.NewIndexExpr(base.Pos, cmpl, ir.NewInt(i+offset))) + lb = typecheck.Conv(lb, elemType) + lb = typecheck.Conv(lb, convType) + lb = ir.NewBinaryExpr(base.Pos, ir.OLSH, lb, ir.NewInt(8*t.Elem().Width*offset)) + cmplw = ir.NewBinaryExpr(base.Pos, ir.OOR, cmplw, lb) + rb := ir.Node(ir.NewIndexExpr(base.Pos, cmpr, ir.NewInt(i+offset))) + rb = typecheck.Conv(rb, elemType) + rb = typecheck.Conv(rb, convType) + rb = ir.NewBinaryExpr(base.Pos, ir.OLSH, rb, ir.NewInt(8*t.Elem().Width*offset)) + cmprw = ir.NewBinaryExpr(base.Pos, ir.OOR, cmprw, rb) + } + compare(cmplw, cmprw) + i += step + remains -= step * t.Elem().Width + } + } + } + if expr == nil { + expr = ir.NewBool(n.Op() == ir.OEQ) + // We still need to use cmpl and cmpr, in case they contain + // an expression which might panic. See issue 23837. + t := typecheck.Temp(cmpl.Type()) + a1 := typecheck.Stmt(ir.NewAssignStmt(base.Pos, t, cmpl)) + a2 := typecheck.Stmt(ir.NewAssignStmt(base.Pos, t, cmpr)) + init.Append(a1, a2) + } + return finishCompare(n, expr, init) +} + +func walkCompareInterface(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { + n.Y = cheapExpr(n.Y, init) + n.X = cheapExpr(n.X, init) + eqtab, eqdata := reflectdata.EqInterface(n.X, n.Y) + var cmp ir.Node + if n.Op() == ir.OEQ { + cmp = ir.NewLogicalExpr(base.Pos, ir.OANDAND, eqtab, eqdata) + } else { + eqtab.SetOp(ir.ONE) + cmp = ir.NewLogicalExpr(base.Pos, ir.OOROR, eqtab, ir.NewUnaryExpr(base.Pos, ir.ONOT, eqdata)) + } + return finishCompare(n, cmp, init) +} + +func walkCompareString(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { + // Rewrite comparisons to short constant strings as length+byte-wise comparisons. + var cs, ncs ir.Node // const string, non-const string + switch { + case ir.IsConst(n.X, constant.String) && ir.IsConst(n.Y, constant.String): + // ignore; will be constant evaluated + case ir.IsConst(n.X, constant.String): + cs = n.X + ncs = n.Y + case ir.IsConst(n.Y, constant.String): + cs = n.Y + ncs = n.X + } + if cs != nil { + cmp := n.Op() + // Our comparison below assumes that the non-constant string + // is on the left hand side, so rewrite "" cmp x to x cmp "". + // See issue 24817. + if ir.IsConst(n.X, constant.String) { + cmp = brrev(cmp) + } + + // maxRewriteLen was chosen empirically. + // It is the value that minimizes cmd/go file size + // across most architectures. + // See the commit description for CL 26758 for details. + maxRewriteLen := 6 + // Some architectures can load unaligned byte sequence as 1 word. + // So we can cover longer strings with the same amount of code. + canCombineLoads := canMergeLoads() + combine64bit := false + if canCombineLoads { + // Keep this low enough to generate less code than a function call. + maxRewriteLen = 2 * ssagen.Arch.LinkArch.RegSize + combine64bit = ssagen.Arch.LinkArch.RegSize >= 8 + } + + var and ir.Op + switch cmp { + case ir.OEQ: + and = ir.OANDAND + case ir.ONE: + and = ir.OOROR + default: + // Don't do byte-wise comparisons for <, <=, etc. + // They're fairly complicated. + // Length-only checks are ok, though. + maxRewriteLen = 0 + } + if s := ir.StringVal(cs); len(s) <= maxRewriteLen { + if len(s) > 0 { + ncs = safeExpr(ncs, init) + } + r := ir.Node(ir.NewBinaryExpr(base.Pos, cmp, ir.NewUnaryExpr(base.Pos, ir.OLEN, ncs), ir.NewInt(int64(len(s))))) + remains := len(s) + for i := 0; remains > 0; { + if remains == 1 || !canCombineLoads { + cb := ir.NewInt(int64(s[i])) + ncb := ir.NewIndexExpr(base.Pos, ncs, ir.NewInt(int64(i))) + r = ir.NewLogicalExpr(base.Pos, and, r, ir.NewBinaryExpr(base.Pos, cmp, ncb, cb)) + remains-- + i++ + continue + } + var step int + var convType *types.Type + switch { + case remains >= 8 && combine64bit: + convType = types.Types[types.TINT64] + step = 8 + case remains >= 4: + convType = types.Types[types.TUINT32] + step = 4 + case remains >= 2: + convType = types.Types[types.TUINT16] + step = 2 + } + ncsubstr := typecheck.Conv(ir.NewIndexExpr(base.Pos, ncs, ir.NewInt(int64(i))), convType) + csubstr := int64(s[i]) + // Calculate large constant from bytes as sequence of shifts and ors. + // Like this: uint32(s[0]) | uint32(s[1])<<8 | uint32(s[2])<<16 ... + // ssa will combine this into a single large load. + for offset := 1; offset < step; offset++ { + b := typecheck.Conv(ir.NewIndexExpr(base.Pos, ncs, ir.NewInt(int64(i+offset))), convType) + b = ir.NewBinaryExpr(base.Pos, ir.OLSH, b, ir.NewInt(int64(8*offset))) + ncsubstr = ir.NewBinaryExpr(base.Pos, ir.OOR, ncsubstr, b) + csubstr |= int64(s[i+offset]) << uint8(8*offset) + } + csubstrPart := ir.NewInt(csubstr) + // Compare "step" bytes as once + r = ir.NewLogicalExpr(base.Pos, and, r, ir.NewBinaryExpr(base.Pos, cmp, csubstrPart, ncsubstr)) + remains -= step + i += step + } + return finishCompare(n, r, init) + } + } + + var r ir.Node + if n.Op() == ir.OEQ || n.Op() == ir.ONE { + // prepare for rewrite below + n.X = cheapExpr(n.X, init) + n.Y = cheapExpr(n.Y, init) + eqlen, eqmem := reflectdata.EqString(n.X, n.Y) + // quick check of len before full compare for == or !=. + // memequal then tests equality up to length len. + if n.Op() == ir.OEQ { + // len(left) == len(right) && memequal(left, right, len) + r = ir.NewLogicalExpr(base.Pos, ir.OANDAND, eqlen, eqmem) + } else { + // len(left) != len(right) || !memequal(left, right, len) + eqlen.SetOp(ir.ONE) + r = ir.NewLogicalExpr(base.Pos, ir.OOROR, eqlen, ir.NewUnaryExpr(base.Pos, ir.ONOT, eqmem)) + } + } else { + // sys_cmpstring(s1, s2) :: 0 + r = mkcall("cmpstring", types.Types[types.TINT], init, typecheck.Conv(n.X, types.Types[types.TSTRING]), typecheck.Conv(n.Y, types.Types[types.TSTRING])) + r = ir.NewBinaryExpr(base.Pos, n.Op(), r, ir.NewInt(0)) + } + + return finishCompare(n, r, init) +} + +// The result of finishCompare MUST be assigned back to n, e.g. +// n.Left = finishCompare(n.Left, x, r, init) +func finishCompare(n *ir.BinaryExpr, r ir.Node, init *ir.Nodes) ir.Node { + r = typecheck.Expr(r) + r = typecheck.Conv(r, n.Type()) + r = walkExpr(r, init) + return r +} + +func eqFor(t *types.Type) (n ir.Node, needsize bool) { + // Should only arrive here with large memory or + // a struct/array containing a non-memory field/element. + // Small memory is handled inline, and single non-memory + // is handled by walkCompare. + switch a, _ := types.AlgType(t); a { + case types.AMEM: + n := typecheck.LookupRuntime("memequal") + n = typecheck.SubstArgTypes(n, t, t) + return n, true + case types.ASPECIAL: + sym := reflectdata.TypeSymPrefix(".eq", t) + // TODO(austin): This creates an ir.Name with a nil Func. + n := typecheck.NewName(sym) + ir.MarkFunc(n) + n.SetType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{ + types.NewField(base.Pos, nil, types.NewPtr(t)), + types.NewField(base.Pos, nil, types.NewPtr(t)), + }, []*types.Field{ + types.NewField(base.Pos, nil, types.Types[types.TBOOL]), + })) + return n, false + } + base.Fatalf("eqFor %v", t) + return nil, false +} + +// brcom returns !(op). +// For example, brcom(==) is !=. +func brcom(op ir.Op) ir.Op { + switch op { + case ir.OEQ: + return ir.ONE + case ir.ONE: + return ir.OEQ + case ir.OLT: + return ir.OGE + case ir.OGT: + return ir.OLE + case ir.OLE: + return ir.OGT + case ir.OGE: + return ir.OLT + } + base.Fatalf("brcom: no com for %v\n", op) + return op +} + +// brrev returns reverse(op). +// For example, Brrev(<) is >. +func brrev(op ir.Op) ir.Op { + switch op { + case ir.OEQ: + return ir.OEQ + case ir.ONE: + return ir.ONE + case ir.OLT: + return ir.OGT + case ir.OGT: + return ir.OLT + case ir.OLE: + return ir.OGE + case ir.OGE: + return ir.OLE + } + base.Fatalf("brrev: no rev for %v\n", op) + return op +} + +func tracecmpArg(n ir.Node, t *types.Type, init *ir.Nodes) ir.Node { + // Ugly hack to avoid "constant -1 overflows uintptr" errors, etc. + if n.Op() == ir.OLITERAL && n.Type().IsSigned() && ir.Int64Val(n) < 0 { + n = copyExpr(n, n.Type(), init) + } + + return typecheck.Conv(n, t) +} + +// canMergeLoads reports whether the backend optimization passes for +// the current architecture can combine adjacent loads into a single +// larger, possibly unaligned, load. Note that currently the +// optimizations must be able to handle little endian byte order. +func canMergeLoads() bool { + switch ssagen.Arch.LinkArch.Family { + case sys.ARM64, sys.AMD64, sys.I386, sys.S390X: + return true + case sys.PPC64: + // Load combining only supported on ppc64le. + return ssagen.Arch.LinkArch.ByteOrder == binary.LittleEndian + } + return false +} diff --git a/src/cmd/compile/internal/walk/complit.go b/src/cmd/compile/internal/walk/complit.go new file mode 100644 index 0000000000000000000000000000000000000000..abd920d64612ff720dbfc27e6b37d6ce32f64e7f --- /dev/null +++ b/src/cmd/compile/internal/walk/complit.go @@ -0,0 +1,670 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/staticdata" + "cmd/compile/internal/staticinit" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" +) + +// walkCompLit walks a composite literal node: +// OARRAYLIT, OSLICELIT, OMAPLIT, OSTRUCTLIT (all CompLitExpr), or OPTRLIT (AddrExpr). +func walkCompLit(n ir.Node, init *ir.Nodes) ir.Node { + if isStaticCompositeLiteral(n) && !ssagen.TypeOK(n.Type()) { + n := n.(*ir.CompLitExpr) // not OPTRLIT + // n can be directly represented in the read-only data section. + // Make direct reference to the static data. See issue 12841. + vstat := readonlystaticname(n.Type()) + fixedlit(inInitFunction, initKindStatic, n, vstat, init) + return typecheck.Expr(vstat) + } + var_ := typecheck.Temp(n.Type()) + anylit(n, var_, init) + return var_ +} + +// initContext is the context in which static data is populated. +// It is either in an init function or in any other function. +// Static data populated in an init function will be written either +// zero times (as a readonly, static data symbol) or +// one time (during init function execution). +// Either way, there is no opportunity for races or further modification, +// so the data can be written to a (possibly readonly) data symbol. +// Static data populated in any other function needs to be local to +// that function to allow multiple instances of that function +// to execute concurrently without clobbering each others' data. +type initContext uint8 + +const ( + inInitFunction initContext = iota + inNonInitFunction +) + +func (c initContext) String() string { + if c == inInitFunction { + return "inInitFunction" + } + return "inNonInitFunction" +} + +// readonlystaticname returns a name backed by a read-only static data symbol. +func readonlystaticname(t *types.Type) *ir.Name { + n := staticinit.StaticName(t) + n.MarkReadonly() + n.Linksym().Set(obj.AttrContentAddressable, true) + n.Linksym().Set(obj.AttrLocal, true) + return n +} + +func isSimpleName(nn ir.Node) bool { + if nn.Op() != ir.ONAME || ir.IsBlank(nn) { + return false + } + n := nn.(*ir.Name) + return n.OnStack() +} + +func litas(l ir.Node, r ir.Node, init *ir.Nodes) { + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, l, r)) +} + +// initGenType is a bitmap indicating the types of generation that will occur for a static value. +type initGenType uint8 + +const ( + initDynamic initGenType = 1 << iota // contains some dynamic values, for which init code will be generated + initConst // contains some constant values, which may be written into data symbols +) + +// getdyn calculates the initGenType for n. +// If top is false, getdyn is recursing. +func getdyn(n ir.Node, top bool) initGenType { + switch n.Op() { + default: + if ir.IsConstNode(n) { + return initConst + } + return initDynamic + + case ir.OSLICELIT: + n := n.(*ir.CompLitExpr) + if !top { + return initDynamic + } + if n.Len/4 > int64(len(n.List)) { + // <25% of entries have explicit values. + // Very rough estimation, it takes 4 bytes of instructions + // to initialize 1 byte of result. So don't use a static + // initializer if the dynamic initialization code would be + // smaller than the static value. + // See issue 23780. + return initDynamic + } + + case ir.OARRAYLIT, ir.OSTRUCTLIT: + } + lit := n.(*ir.CompLitExpr) + + var mode initGenType + for _, n1 := range lit.List { + switch n1.Op() { + case ir.OKEY: + n1 = n1.(*ir.KeyExpr).Value + case ir.OSTRUCTKEY: + n1 = n1.(*ir.StructKeyExpr).Value + } + mode |= getdyn(n1, false) + if mode == initDynamic|initConst { + break + } + } + return mode +} + +// isStaticCompositeLiteral reports whether n is a compile-time constant. +func isStaticCompositeLiteral(n ir.Node) bool { + switch n.Op() { + case ir.OSLICELIT: + return false + case ir.OARRAYLIT: + n := n.(*ir.CompLitExpr) + for _, r := range n.List { + if r.Op() == ir.OKEY { + r = r.(*ir.KeyExpr).Value + } + if !isStaticCompositeLiteral(r) { + return false + } + } + return true + case ir.OSTRUCTLIT: + n := n.(*ir.CompLitExpr) + for _, r := range n.List { + r := r.(*ir.StructKeyExpr) + if !isStaticCompositeLiteral(r.Value) { + return false + } + } + return true + case ir.OLITERAL, ir.ONIL: + return true + case ir.OCONVIFACE: + // See staticassign's OCONVIFACE case for comments. + n := n.(*ir.ConvExpr) + val := ir.Node(n) + for val.Op() == ir.OCONVIFACE { + val = val.(*ir.ConvExpr).X + } + if val.Type().IsInterface() { + return val.Op() == ir.ONIL + } + if types.IsDirectIface(val.Type()) && val.Op() == ir.ONIL { + return true + } + return isStaticCompositeLiteral(val) + } + return false +} + +// initKind is a kind of static initialization: static, dynamic, or local. +// Static initialization represents literals and +// literal components of composite literals. +// Dynamic initialization represents non-literals and +// non-literal components of composite literals. +// LocalCode initialization represents initialization +// that occurs purely in generated code local to the function of use. +// Initialization code is sometimes generated in passes, +// first static then dynamic. +type initKind uint8 + +const ( + initKindStatic initKind = iota + 1 + initKindDynamic + initKindLocalCode +) + +// fixedlit handles struct, array, and slice literals. +// TODO: expand documentation. +func fixedlit(ctxt initContext, kind initKind, n *ir.CompLitExpr, var_ ir.Node, init *ir.Nodes) { + isBlank := var_ == ir.BlankNode + var splitnode func(ir.Node) (a ir.Node, value ir.Node) + switch n.Op() { + case ir.OARRAYLIT, ir.OSLICELIT: + var k int64 + splitnode = func(r ir.Node) (ir.Node, ir.Node) { + if r.Op() == ir.OKEY { + kv := r.(*ir.KeyExpr) + k = typecheck.IndexConst(kv.Key) + if k < 0 { + base.Fatalf("fixedlit: invalid index %v", kv.Key) + } + r = kv.Value + } + a := ir.NewIndexExpr(base.Pos, var_, ir.NewInt(k)) + k++ + if isBlank { + return ir.BlankNode, r + } + return a, r + } + case ir.OSTRUCTLIT: + splitnode = func(rn ir.Node) (ir.Node, ir.Node) { + r := rn.(*ir.StructKeyExpr) + if r.Field.IsBlank() || isBlank { + return ir.BlankNode, r.Value + } + ir.SetPos(r) + return ir.NewSelectorExpr(base.Pos, ir.ODOT, var_, r.Field), r.Value + } + default: + base.Fatalf("fixedlit bad op: %v", n.Op()) + } + + for _, r := range n.List { + a, value := splitnode(r) + if a == ir.BlankNode && !staticinit.AnySideEffects(value) { + // Discard. + continue + } + + switch value.Op() { + case ir.OSLICELIT: + value := value.(*ir.CompLitExpr) + if (kind == initKindStatic && ctxt == inNonInitFunction) || (kind == initKindDynamic && ctxt == inInitFunction) { + slicelit(ctxt, value, a, init) + continue + } + + case ir.OARRAYLIT, ir.OSTRUCTLIT: + value := value.(*ir.CompLitExpr) + fixedlit(ctxt, kind, value, a, init) + continue + } + + islit := ir.IsConstNode(value) + if (kind == initKindStatic && !islit) || (kind == initKindDynamic && islit) { + continue + } + + // build list of assignments: var[index] = expr + ir.SetPos(a) + as := ir.NewAssignStmt(base.Pos, a, value) + as = typecheck.Stmt(as).(*ir.AssignStmt) + switch kind { + case initKindStatic: + genAsStatic(as) + case initKindDynamic, initKindLocalCode: + a = orderStmtInPlace(as, map[string][]*ir.Name{}) + a = walkStmt(a) + init.Append(a) + default: + base.Fatalf("fixedlit: bad kind %d", kind) + } + + } +} + +func isSmallSliceLit(n *ir.CompLitExpr) bool { + if n.Op() != ir.OSLICELIT { + return false + } + + return n.Type().Elem().Width == 0 || n.Len <= ir.MaxSmallArraySize/n.Type().Elem().Width +} + +func slicelit(ctxt initContext, n *ir.CompLitExpr, var_ ir.Node, init *ir.Nodes) { + // make an array type corresponding the number of elements we have + t := types.NewArray(n.Type().Elem(), n.Len) + types.CalcSize(t) + + if ctxt == inNonInitFunction { + // put everything into static array + vstat := staticinit.StaticName(t) + + fixedlit(ctxt, initKindStatic, n, vstat, init) + fixedlit(ctxt, initKindDynamic, n, vstat, init) + + // copy static to slice + var_ = typecheck.AssignExpr(var_) + name, offset, ok := staticinit.StaticLoc(var_) + if !ok || name.Class != ir.PEXTERN { + base.Fatalf("slicelit: %v", var_) + } + staticdata.InitSlice(name, offset, vstat.Linksym(), t.NumElem()) + return + } + + // recipe for var = []t{...} + // 1. make a static array + // var vstat [...]t + // 2. assign (data statements) the constant part + // vstat = constpart{} + // 3. make an auto pointer to array and allocate heap to it + // var vauto *[...]t = new([...]t) + // 4. copy the static array to the auto array + // *vauto = vstat + // 5. for each dynamic part assign to the array + // vauto[i] = dynamic part + // 6. assign slice of allocated heap to var + // var = vauto[:] + // + // an optimization is done if there is no constant part + // 3. var vauto *[...]t = new([...]t) + // 5. vauto[i] = dynamic part + // 6. var = vauto[:] + + // if the literal contains constants, + // make static initialized array (1),(2) + var vstat ir.Node + + mode := getdyn(n, true) + if mode&initConst != 0 && !isSmallSliceLit(n) { + if ctxt == inInitFunction { + vstat = readonlystaticname(t) + } else { + vstat = staticinit.StaticName(t) + } + fixedlit(ctxt, initKindStatic, n, vstat, init) + } + + // make new auto *array (3 declare) + vauto := typecheck.Temp(types.NewPtr(t)) + + // set auto to point at new temp or heap (3 assign) + var a ir.Node + if x := n.Prealloc; x != nil { + // temp allocated during order.go for dddarg + if !types.Identical(t, x.Type()) { + panic("dotdotdot base type does not match order's assigned type") + } + a = initStackTemp(init, x, vstat) + } else if n.Esc() == ir.EscNone { + a = initStackTemp(init, typecheck.Temp(t), vstat) + } else { + a = ir.NewUnaryExpr(base.Pos, ir.ONEW, ir.TypeNode(t)) + } + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, vauto, a)) + + if vstat != nil && n.Prealloc == nil && n.Esc() != ir.EscNone { + // If we allocated on the heap with ONEW, copy the static to the + // heap (4). We skip this for stack temporaries, because + // initStackTemp already handled the copy. + a = ir.NewStarExpr(base.Pos, vauto) + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, a, vstat)) + } + + // put dynamics into array (5) + var index int64 + for _, value := range n.List { + if value.Op() == ir.OKEY { + kv := value.(*ir.KeyExpr) + index = typecheck.IndexConst(kv.Key) + if index < 0 { + base.Fatalf("slicelit: invalid index %v", kv.Key) + } + value = kv.Value + } + a := ir.NewIndexExpr(base.Pos, vauto, ir.NewInt(index)) + a.SetBounded(true) + index++ + + // TODO need to check bounds? + + switch value.Op() { + case ir.OSLICELIT: + break + + case ir.OARRAYLIT, ir.OSTRUCTLIT: + value := value.(*ir.CompLitExpr) + k := initKindDynamic + if vstat == nil { + // Generate both static and dynamic initializations. + // See issue #31987. + k = initKindLocalCode + } + fixedlit(ctxt, k, value, a, init) + continue + } + + if vstat != nil && ir.IsConstNode(value) { // already set by copy from static value + continue + } + + // build list of vauto[c] = expr + ir.SetPos(value) + as := typecheck.Stmt(ir.NewAssignStmt(base.Pos, a, value)) + as = orderStmtInPlace(as, map[string][]*ir.Name{}) + as = walkStmt(as) + init.Append(as) + } + + // make slice out of heap (6) + a = ir.NewAssignStmt(base.Pos, var_, ir.NewSliceExpr(base.Pos, ir.OSLICE, vauto, nil, nil, nil)) + + a = typecheck.Stmt(a) + a = orderStmtInPlace(a, map[string][]*ir.Name{}) + a = walkStmt(a) + init.Append(a) +} + +func maplit(n *ir.CompLitExpr, m ir.Node, init *ir.Nodes) { + // make the map var + a := ir.NewCallExpr(base.Pos, ir.OMAKE, nil, nil) + a.SetEsc(n.Esc()) + a.Args = []ir.Node{ir.TypeNode(n.Type()), ir.NewInt(int64(len(n.List)))} + litas(m, a, init) + + entries := n.List + + // The order pass already removed any dynamic (runtime-computed) entries. + // All remaining entries are static. Double-check that. + for _, r := range entries { + r := r.(*ir.KeyExpr) + if !isStaticCompositeLiteral(r.Key) || !isStaticCompositeLiteral(r.Value) { + base.Fatalf("maplit: entry is not a literal: %v", r) + } + } + + if len(entries) > 25 { + // For a large number of entries, put them in an array and loop. + + // build types [count]Tindex and [count]Tvalue + tk := types.NewArray(n.Type().Key(), int64(len(entries))) + te := types.NewArray(n.Type().Elem(), int64(len(entries))) + + tk.SetNoalg(true) + te.SetNoalg(true) + + types.CalcSize(tk) + types.CalcSize(te) + + // make and initialize static arrays + vstatk := readonlystaticname(tk) + vstate := readonlystaticname(te) + + datak := ir.NewCompLitExpr(base.Pos, ir.OARRAYLIT, nil, nil) + datae := ir.NewCompLitExpr(base.Pos, ir.OARRAYLIT, nil, nil) + for _, r := range entries { + r := r.(*ir.KeyExpr) + datak.List.Append(r.Key) + datae.List.Append(r.Value) + } + fixedlit(inInitFunction, initKindStatic, datak, vstatk, init) + fixedlit(inInitFunction, initKindStatic, datae, vstate, init) + + // loop adding structure elements to map + // for i = 0; i < len(vstatk); i++ { + // map[vstatk[i]] = vstate[i] + // } + i := typecheck.Temp(types.Types[types.TINT]) + rhs := ir.NewIndexExpr(base.Pos, vstate, i) + rhs.SetBounded(true) + + kidx := ir.NewIndexExpr(base.Pos, vstatk, i) + kidx.SetBounded(true) + lhs := ir.NewIndexExpr(base.Pos, m, kidx) + + zero := ir.NewAssignStmt(base.Pos, i, ir.NewInt(0)) + cond := ir.NewBinaryExpr(base.Pos, ir.OLT, i, ir.NewInt(tk.NumElem())) + incr := ir.NewAssignStmt(base.Pos, i, ir.NewBinaryExpr(base.Pos, ir.OADD, i, ir.NewInt(1))) + + var body ir.Node = ir.NewAssignStmt(base.Pos, lhs, rhs) + body = typecheck.Stmt(body) // typechecker rewrites OINDEX to OINDEXMAP + body = orderStmtInPlace(body, map[string][]*ir.Name{}) + + loop := ir.NewForStmt(base.Pos, nil, cond, incr, nil) + loop.Body = []ir.Node{body} + *loop.PtrInit() = []ir.Node{zero} + + appendWalkStmt(init, loop) + return + } + // For a small number of entries, just add them directly. + + // Build list of var[c] = expr. + // Use temporaries so that mapassign1 can have addressable key, elem. + // TODO(josharian): avoid map key temporaries for mapfast_* assignments with literal keys. + tmpkey := typecheck.Temp(m.Type().Key()) + tmpelem := typecheck.Temp(m.Type().Elem()) + + for _, r := range entries { + r := r.(*ir.KeyExpr) + index, elem := r.Key, r.Value + + ir.SetPos(index) + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, tmpkey, index)) + + ir.SetPos(elem) + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, tmpelem, elem)) + + ir.SetPos(tmpelem) + var a ir.Node = ir.NewAssignStmt(base.Pos, ir.NewIndexExpr(base.Pos, m, tmpkey), tmpelem) + a = typecheck.Stmt(a) // typechecker rewrites OINDEX to OINDEXMAP + a = orderStmtInPlace(a, map[string][]*ir.Name{}) + appendWalkStmt(init, a) + } + + appendWalkStmt(init, ir.NewUnaryExpr(base.Pos, ir.OVARKILL, tmpkey)) + appendWalkStmt(init, ir.NewUnaryExpr(base.Pos, ir.OVARKILL, tmpelem)) +} + +func anylit(n ir.Node, var_ ir.Node, init *ir.Nodes) { + t := n.Type() + switch n.Op() { + default: + base.Fatalf("anylit: not lit, op=%v node=%v", n.Op(), n) + + case ir.ONAME: + n := n.(*ir.Name) + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, var_, n)) + + case ir.OMETHEXPR: + n := n.(*ir.SelectorExpr) + anylit(n.FuncName(), var_, init) + + case ir.OPTRLIT: + n := n.(*ir.AddrExpr) + if !t.IsPtr() { + base.Fatalf("anylit: not ptr") + } + + var r ir.Node + if n.Prealloc != nil { + // n.Prealloc is stack temporary used as backing store. + r = initStackTemp(init, n.Prealloc, nil) + } else { + r = ir.NewUnaryExpr(base.Pos, ir.ONEW, ir.TypeNode(n.X.Type())) + r.SetEsc(n.Esc()) + } + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, var_, r)) + + var_ = ir.NewStarExpr(base.Pos, var_) + var_ = typecheck.AssignExpr(var_) + anylit(n.X, var_, init) + + case ir.OSTRUCTLIT, ir.OARRAYLIT: + n := n.(*ir.CompLitExpr) + if !t.IsStruct() && !t.IsArray() { + base.Fatalf("anylit: not struct/array") + } + + if isSimpleName(var_) && len(n.List) > 4 { + // lay out static data + vstat := readonlystaticname(t) + + ctxt := inInitFunction + if n.Op() == ir.OARRAYLIT { + ctxt = inNonInitFunction + } + fixedlit(ctxt, initKindStatic, n, vstat, init) + + // copy static to var + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, var_, vstat)) + + // add expressions to automatic + fixedlit(inInitFunction, initKindDynamic, n, var_, init) + break + } + + var components int64 + if n.Op() == ir.OARRAYLIT { + components = t.NumElem() + } else { + components = int64(t.NumFields()) + } + // initialization of an array or struct with unspecified components (missing fields or arrays) + if isSimpleName(var_) || int64(len(n.List)) < components { + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, var_, nil)) + } + + fixedlit(inInitFunction, initKindLocalCode, n, var_, init) + + case ir.OSLICELIT: + n := n.(*ir.CompLitExpr) + slicelit(inInitFunction, n, var_, init) + + case ir.OMAPLIT: + n := n.(*ir.CompLitExpr) + if !t.IsMap() { + base.Fatalf("anylit: not map") + } + maplit(n, var_, init) + } +} + +// oaslit handles special composite literal assignments. +// It returns true if n's effects have been added to init, +// in which case n should be dropped from the program by the caller. +func oaslit(n *ir.AssignStmt, init *ir.Nodes) bool { + if n.X == nil || n.Y == nil { + // not a special composite literal assignment + return false + } + if n.X.Type() == nil || n.Y.Type() == nil { + // not a special composite literal assignment + return false + } + if !isSimpleName(n.X) { + // not a special composite literal assignment + return false + } + x := n.X.(*ir.Name) + if !types.Identical(n.X.Type(), n.Y.Type()) { + // not a special composite literal assignment + return false + } + + switch n.Y.Op() { + default: + // not a special composite literal assignment + return false + + case ir.OSTRUCTLIT, ir.OARRAYLIT, ir.OSLICELIT, ir.OMAPLIT: + if ir.Any(n.Y, func(y ir.Node) bool { return ir.Uses(y, x) }) { + // not a special composite literal assignment + return false + } + anylit(n.Y, n.X, init) + } + + return true +} + +func genAsStatic(as *ir.AssignStmt) { + if as.X.Type() == nil { + base.Fatalf("genAsStatic as.Left not typechecked") + } + + name, offset, ok := staticinit.StaticLoc(as.X) + if !ok || (name.Class != ir.PEXTERN && as.X != ir.BlankNode) { + base.Fatalf("genAsStatic: lhs %v", as.X) + } + + switch r := as.Y; r.Op() { + case ir.OLITERAL: + staticdata.InitConst(name, offset, r, int(r.Type().Width)) + return + case ir.OMETHEXPR: + r := r.(*ir.SelectorExpr) + staticdata.InitAddr(name, offset, staticdata.FuncLinksym(r.FuncName())) + return + case ir.ONAME: + r := r.(*ir.Name) + if r.Offset_ != 0 { + base.Fatalf("genAsStatic %+v", as) + } + if r.Class == ir.PFUNC { + staticdata.InitAddr(name, offset, staticdata.FuncLinksym(r)) + return + } + } + base.Fatalf("genAsStatic: rhs %v", as.Y) +} diff --git a/src/cmd/compile/internal/walk/convert.go b/src/cmd/compile/internal/walk/convert.go new file mode 100644 index 0000000000000000000000000000000000000000..26e17a126f22151a542fecf9026002846baa82ca --- /dev/null +++ b/src/cmd/compile/internal/walk/convert.go @@ -0,0 +1,510 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "encoding/binary" + "go/constant" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/sys" +) + +// walkConv walks an OCONV or OCONVNOP (but not OCONVIFACE) node. +func walkConv(n *ir.ConvExpr, init *ir.Nodes) ir.Node { + n.X = walkExpr(n.X, init) + if n.Op() == ir.OCONVNOP && n.Type() == n.X.Type() { + return n.X + } + if n.Op() == ir.OCONVNOP && ir.ShouldCheckPtr(ir.CurFunc, 1) { + if n.Type().IsPtr() && n.X.Type().IsUnsafePtr() { // unsafe.Pointer to *T + return walkCheckPtrAlignment(n, init, nil) + } + if n.Type().IsUnsafePtr() && n.X.Type().IsUintptr() { // uintptr to unsafe.Pointer + return walkCheckPtrArithmetic(n, init) + } + } + param, result := rtconvfn(n.X.Type(), n.Type()) + if param == types.Txxx { + return n + } + fn := types.BasicTypeNames[param] + "to" + types.BasicTypeNames[result] + return typecheck.Conv(mkcall(fn, types.Types[result], init, typecheck.Conv(n.X, types.Types[param])), n.Type()) +} + +// walkConvInterface walks an OCONVIFACE node. +func walkConvInterface(n *ir.ConvExpr, init *ir.Nodes) ir.Node { + n.X = walkExpr(n.X, init) + + fromType := n.X.Type() + toType := n.Type() + + if !fromType.IsInterface() && !ir.IsBlank(ir.CurFunc.Nname) { // skip unnamed functions (func _()) + reflectdata.MarkTypeUsedInInterface(fromType, ir.CurFunc.LSym) + } + + // typeword generates the type word of the interface value. + typeword := func() ir.Node { + if toType.IsEmptyInterface() { + return reflectdata.TypePtr(fromType) + } + return reflectdata.ITabAddr(fromType, toType) + } + + // Optimize convT2E or convT2I as a two-word copy when T is pointer-shaped. + if types.IsDirectIface(fromType) { + l := ir.NewBinaryExpr(base.Pos, ir.OEFACE, typeword(), n.X) + l.SetType(toType) + l.SetTypecheck(n.Typecheck()) + return l + } + + // Optimize convT2{E,I} for many cases in which T is not pointer-shaped, + // by using an existing addressable value identical to n.Left + // or creating one on the stack. + var value ir.Node + switch { + case fromType.Size() == 0: + // n.Left is zero-sized. Use zerobase. + cheapExpr(n.X, init) // Evaluate n.Left for side-effects. See issue 19246. + value = ir.NewLinksymExpr(base.Pos, ir.Syms.Zerobase, types.Types[types.TUINTPTR]) + case fromType.IsBoolean() || (fromType.Size() == 1 && fromType.IsInteger()): + // n.Left is a bool/byte. Use staticuint64s[n.Left * 8] on little-endian + // and staticuint64s[n.Left * 8 + 7] on big-endian. + n.X = cheapExpr(n.X, init) + // byteindex widens n.Left so that the multiplication doesn't overflow. + index := ir.NewBinaryExpr(base.Pos, ir.OLSH, byteindex(n.X), ir.NewInt(3)) + if ssagen.Arch.LinkArch.ByteOrder == binary.BigEndian { + index = ir.NewBinaryExpr(base.Pos, ir.OADD, index, ir.NewInt(7)) + } + // The actual type is [256]uint64, but we use [256*8]uint8 so we can address + // individual bytes. + staticuint64s := ir.NewLinksymExpr(base.Pos, ir.Syms.Staticuint64s, types.NewArray(types.Types[types.TUINT8], 256*8)) + xe := ir.NewIndexExpr(base.Pos, staticuint64s, index) + xe.SetBounded(true) + value = xe + case n.X.Op() == ir.ONAME && n.X.(*ir.Name).Class == ir.PEXTERN && n.X.(*ir.Name).Readonly(): + // n.Left is a readonly global; use it directly. + value = n.X + case !fromType.IsInterface() && n.Esc() == ir.EscNone && fromType.Width <= 1024: + // n.Left does not escape. Use a stack temporary initialized to n.Left. + value = typecheck.Temp(fromType) + init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, value, n.X))) + } + + if value != nil { + // Value is identical to n.Left. + // Construct the interface directly: {type/itab, &value}. + l := ir.NewBinaryExpr(base.Pos, ir.OEFACE, typeword(), typecheck.Expr(typecheck.NodAddr(value))) + l.SetType(toType) + l.SetTypecheck(n.Typecheck()) + return l + } + + // Implement interface to empty interface conversion. + // tmp = i.itab + // if tmp != nil { + // tmp = tmp.type + // } + // e = iface{tmp, i.data} + if toType.IsEmptyInterface() && fromType.IsInterface() && !fromType.IsEmptyInterface() { + // Evaluate the input interface. + c := typecheck.Temp(fromType) + init.Append(ir.NewAssignStmt(base.Pos, c, n.X)) + + // Get the itab out of the interface. + tmp := typecheck.Temp(types.NewPtr(types.Types[types.TUINT8])) + init.Append(ir.NewAssignStmt(base.Pos, tmp, typecheck.Expr(ir.NewUnaryExpr(base.Pos, ir.OITAB, c)))) + + // Get the type out of the itab. + nif := ir.NewIfStmt(base.Pos, typecheck.Expr(ir.NewBinaryExpr(base.Pos, ir.ONE, tmp, typecheck.NodNil())), nil, nil) + nif.Body = []ir.Node{ir.NewAssignStmt(base.Pos, tmp, itabType(tmp))} + init.Append(nif) + + // Build the result. + e := ir.NewBinaryExpr(base.Pos, ir.OEFACE, tmp, ifaceData(n.Pos(), c, types.NewPtr(types.Types[types.TUINT8]))) + e.SetType(toType) // assign type manually, typecheck doesn't understand OEFACE. + e.SetTypecheck(1) + return e + } + + fnname, argType, needsaddr := convFuncName(fromType, toType) + + if !needsaddr && !fromType.IsInterface() { + // Use a specialized conversion routine that only returns a data pointer. + // ptr = convT2X(val) + // e = iface{typ/tab, ptr} + fn := typecheck.LookupRuntime(fnname) + types.CalcSize(fromType) + + arg := n.X + switch { + case fromType == argType: + // already in the right type, nothing to do + case fromType.Kind() == argType.Kind(), + fromType.IsPtrShaped() && argType.IsPtrShaped(): + // can directly convert (e.g. named type to underlying type, or one pointer to another) + arg = ir.NewConvExpr(n.Pos(), ir.OCONVNOP, argType, arg) + case fromType.IsInteger() && argType.IsInteger(): + // can directly convert (e.g. int32 to uint32) + arg = ir.NewConvExpr(n.Pos(), ir.OCONV, argType, arg) + default: + // unsafe cast through memory + arg = copyExpr(arg, arg.Type(), init) + var addr ir.Node = typecheck.NodAddr(arg) + addr = ir.NewConvExpr(n.Pos(), ir.OCONVNOP, argType.PtrTo(), addr) + arg = ir.NewStarExpr(n.Pos(), addr) + arg.SetType(argType) + } + + call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil) + call.Args = []ir.Node{arg} + e := ir.NewBinaryExpr(base.Pos, ir.OEFACE, typeword(), safeExpr(walkExpr(typecheck.Expr(call), init), init)) + e.SetType(toType) + e.SetTypecheck(1) + return e + } + + var tab ir.Node + if fromType.IsInterface() { + // convI2I + tab = reflectdata.TypePtr(toType) + } else { + // convT2x + tab = typeword() + } + + v := n.X + if needsaddr { + // Types of large or unknown size are passed by reference. + // Orderexpr arranged for n.Left to be a temporary for all + // the conversions it could see. Comparison of an interface + // with a non-interface, especially in a switch on interface value + // with non-interface cases, is not visible to order.stmt, so we + // have to fall back on allocating a temp here. + if !ir.IsAddressable(v) { + v = copyExpr(v, v.Type(), init) + } + v = typecheck.NodAddr(v) + } + + types.CalcSize(fromType) + fn := typecheck.LookupRuntime(fnname) + fn = typecheck.SubstArgTypes(fn, fromType, toType) + types.CalcSize(fn.Type()) + call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil) + call.Args = []ir.Node{tab, v} + return walkExpr(typecheck.Expr(call), init) +} + +// walkBytesRunesToString walks an OBYTES2STR or ORUNES2STR node. +func walkBytesRunesToString(n *ir.ConvExpr, init *ir.Nodes) ir.Node { + a := typecheck.NodNil() + if n.Esc() == ir.EscNone { + // Create temporary buffer for string on stack. + a = stackBufAddr(tmpstringbufsize, types.Types[types.TUINT8]) + } + if n.Op() == ir.ORUNES2STR { + // slicerunetostring(*[32]byte, []rune) string + return mkcall("slicerunetostring", n.Type(), init, a, n.X) + } + // slicebytetostring(*[32]byte, ptr *byte, n int) string + n.X = cheapExpr(n.X, init) + ptr, len := backingArrayPtrLen(n.X) + return mkcall("slicebytetostring", n.Type(), init, a, ptr, len) +} + +// walkBytesToStringTemp walks an OBYTES2STRTMP node. +func walkBytesToStringTemp(n *ir.ConvExpr, init *ir.Nodes) ir.Node { + n.X = walkExpr(n.X, init) + if !base.Flag.Cfg.Instrumenting { + // Let the backend handle OBYTES2STRTMP directly + // to avoid a function call to slicebytetostringtmp. + return n + } + // slicebytetostringtmp(ptr *byte, n int) string + n.X = cheapExpr(n.X, init) + ptr, len := backingArrayPtrLen(n.X) + return mkcall("slicebytetostringtmp", n.Type(), init, ptr, len) +} + +// walkRuneToString walks an ORUNESTR node. +func walkRuneToString(n *ir.ConvExpr, init *ir.Nodes) ir.Node { + a := typecheck.NodNil() + if n.Esc() == ir.EscNone { + a = stackBufAddr(4, types.Types[types.TUINT8]) + } + // intstring(*[4]byte, rune) + return mkcall("intstring", n.Type(), init, a, typecheck.Conv(n.X, types.Types[types.TINT64])) +} + +// walkStringToBytes walks an OSTR2BYTES node. +func walkStringToBytes(n *ir.ConvExpr, init *ir.Nodes) ir.Node { + s := n.X + if ir.IsConst(s, constant.String) { + sc := ir.StringVal(s) + + // Allocate a [n]byte of the right size. + t := types.NewArray(types.Types[types.TUINT8], int64(len(sc))) + var a ir.Node + if n.Esc() == ir.EscNone && len(sc) <= int(ir.MaxImplicitStackVarSize) { + a = stackBufAddr(t.NumElem(), t.Elem()) + } else { + types.CalcSize(t) + a = ir.NewUnaryExpr(base.Pos, ir.ONEW, nil) + a.SetType(types.NewPtr(t)) + a.SetTypecheck(1) + a.MarkNonNil() + } + p := typecheck.Temp(t.PtrTo()) // *[n]byte + init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, p, a))) + + // Copy from the static string data to the [n]byte. + if len(sc) > 0 { + as := ir.NewAssignStmt(base.Pos, ir.NewStarExpr(base.Pos, p), ir.NewStarExpr(base.Pos, typecheck.ConvNop(ir.NewUnaryExpr(base.Pos, ir.OSPTR, s), t.PtrTo()))) + appendWalkStmt(init, as) + } + + // Slice the [n]byte to a []byte. + slice := ir.NewSliceExpr(n.Pos(), ir.OSLICEARR, p, nil, nil, nil) + slice.SetType(n.Type()) + slice.SetTypecheck(1) + return walkExpr(slice, init) + } + + a := typecheck.NodNil() + if n.Esc() == ir.EscNone { + // Create temporary buffer for slice on stack. + a = stackBufAddr(tmpstringbufsize, types.Types[types.TUINT8]) + } + // stringtoslicebyte(*32[byte], string) []byte + return mkcall("stringtoslicebyte", n.Type(), init, a, typecheck.Conv(s, types.Types[types.TSTRING])) +} + +// walkStringToBytesTemp walks an OSTR2BYTESTMP node. +func walkStringToBytesTemp(n *ir.ConvExpr, init *ir.Nodes) ir.Node { + // []byte(string) conversion that creates a slice + // referring to the actual string bytes. + // This conversion is handled later by the backend and + // is only for use by internal compiler optimizations + // that know that the slice won't be mutated. + // The only such case today is: + // for i, c := range []byte(string) + n.X = walkExpr(n.X, init) + return n +} + +// walkStringToRunes walks an OSTR2RUNES node. +func walkStringToRunes(n *ir.ConvExpr, init *ir.Nodes) ir.Node { + a := typecheck.NodNil() + if n.Esc() == ir.EscNone { + // Create temporary buffer for slice on stack. + a = stackBufAddr(tmpstringbufsize, types.Types[types.TINT32]) + } + // stringtoslicerune(*[32]rune, string) []rune + return mkcall("stringtoslicerune", n.Type(), init, a, typecheck.Conv(n.X, types.Types[types.TSTRING])) +} + +// convFuncName builds the runtime function name for interface conversion. +// It also returns the argument type that the runtime function takes, and +// whether the function expects the data by address. +// Not all names are possible. For example, we never generate convE2E or convE2I. +func convFuncName(from, to *types.Type) (fnname string, argType *types.Type, needsaddr bool) { + tkind := to.Tie() + switch from.Tie() { + case 'I': + if tkind == 'I' { + return "convI2I", types.Types[types.TINTER], false + } + case 'T': + switch { + case from.Size() == 2 && from.Align == 2: + return "convT16", types.Types[types.TUINT16], false + case from.Size() == 4 && from.Align == 4 && !from.HasPointers(): + return "convT32", types.Types[types.TUINT32], false + case from.Size() == 8 && from.Align == types.Types[types.TUINT64].Align && !from.HasPointers(): + return "convT64", types.Types[types.TUINT64], false + } + if sc := from.SoleComponent(); sc != nil { + switch { + case sc.IsString(): + return "convTstring", types.Types[types.TSTRING], false + case sc.IsSlice(): + return "convTslice", types.NewSlice(types.Types[types.TUINT8]), false // the element type doesn't matter + } + } + + switch tkind { + case 'E': + if !from.HasPointers() { + return "convT2Enoptr", types.Types[types.TUNSAFEPTR], true + } + return "convT2E", types.Types[types.TUNSAFEPTR], true + case 'I': + if !from.HasPointers() { + return "convT2Inoptr", types.Types[types.TUNSAFEPTR], true + } + return "convT2I", types.Types[types.TUNSAFEPTR], true + } + } + base.Fatalf("unknown conv func %c2%c", from.Tie(), to.Tie()) + panic("unreachable") +} + +// rtconvfn returns the parameter and result types that will be used by a +// runtime function to convert from type src to type dst. The runtime function +// name can be derived from the names of the returned types. +// +// If no such function is necessary, it returns (Txxx, Txxx). +func rtconvfn(src, dst *types.Type) (param, result types.Kind) { + if ssagen.Arch.SoftFloat { + return types.Txxx, types.Txxx + } + + switch ssagen.Arch.LinkArch.Family { + case sys.ARM, sys.MIPS: + if src.IsFloat() { + switch dst.Kind() { + case types.TINT64, types.TUINT64: + return types.TFLOAT64, dst.Kind() + } + } + if dst.IsFloat() { + switch src.Kind() { + case types.TINT64, types.TUINT64: + return src.Kind(), types.TFLOAT64 + } + } + + case sys.I386: + if src.IsFloat() { + switch dst.Kind() { + case types.TINT64, types.TUINT64: + return types.TFLOAT64, dst.Kind() + case types.TUINT32, types.TUINT, types.TUINTPTR: + return types.TFLOAT64, types.TUINT32 + } + } + if dst.IsFloat() { + switch src.Kind() { + case types.TINT64, types.TUINT64: + return src.Kind(), types.TFLOAT64 + case types.TUINT32, types.TUINT, types.TUINTPTR: + return types.TUINT32, types.TFLOAT64 + } + } + } + return types.Txxx, types.Txxx +} + +// byteindex converts n, which is byte-sized, to an int used to index into an array. +// We cannot use conv, because we allow converting bool to int here, +// which is forbidden in user code. +func byteindex(n ir.Node) ir.Node { + // We cannot convert from bool to int directly. + // While converting from int8 to int is possible, it would yield + // the wrong result for negative values. + // Reinterpreting the value as an unsigned byte solves both cases. + if !types.Identical(n.Type(), types.Types[types.TUINT8]) { + n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n) + n.SetType(types.Types[types.TUINT8]) + n.SetTypecheck(1) + } + n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n) + n.SetType(types.Types[types.TINT]) + n.SetTypecheck(1) + return n +} + +func walkCheckPtrAlignment(n *ir.ConvExpr, init *ir.Nodes, count ir.Node) ir.Node { + if !n.Type().IsPtr() { + base.Fatalf("expected pointer type: %v", n.Type()) + } + elem := n.Type().Elem() + if count != nil { + if !elem.IsArray() { + base.Fatalf("expected array type: %v", elem) + } + elem = elem.Elem() + } + + size := elem.Size() + if elem.Alignment() == 1 && (size == 0 || size == 1 && count == nil) { + return n + } + + if count == nil { + count = ir.NewInt(1) + } + + n.X = cheapExpr(n.X, init) + init.Append(mkcall("checkptrAlignment", nil, init, typecheck.ConvNop(n.X, types.Types[types.TUNSAFEPTR]), reflectdata.TypePtr(elem), typecheck.Conv(count, types.Types[types.TUINTPTR]))) + return n +} + +func walkCheckPtrArithmetic(n *ir.ConvExpr, init *ir.Nodes) ir.Node { + // Calling cheapExpr(n, init) below leads to a recursive call to + // walkExpr, which leads us back here again. Use n.Checkptr to + // prevent infinite loops. + if n.CheckPtr() { + return n + } + n.SetCheckPtr(true) + defer n.SetCheckPtr(false) + + // TODO(mdempsky): Make stricter. We only need to exempt + // reflect.Value.Pointer and reflect.Value.UnsafeAddr. + switch n.X.Op() { + case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER: + return n + } + + if n.X.Op() == ir.ODOTPTR && ir.IsReflectHeaderDataField(n.X) { + return n + } + + // Find original unsafe.Pointer operands involved in this + // arithmetic expression. + // + // "It is valid both to add and to subtract offsets from a + // pointer in this way. It is also valid to use &^ to round + // pointers, usually for alignment." + var originals []ir.Node + var walk func(n ir.Node) + walk = func(n ir.Node) { + switch n.Op() { + case ir.OADD: + n := n.(*ir.BinaryExpr) + walk(n.X) + walk(n.Y) + case ir.OSUB, ir.OANDNOT: + n := n.(*ir.BinaryExpr) + walk(n.X) + case ir.OCONVNOP: + n := n.(*ir.ConvExpr) + if n.X.Type().IsUnsafePtr() { + n.X = cheapExpr(n.X, init) + originals = append(originals, typecheck.ConvNop(n.X, types.Types[types.TUNSAFEPTR])) + } + } + } + walk(n.X) + + cheap := cheapExpr(n, init) + + slice := typecheck.MakeDotArgs(types.NewSlice(types.Types[types.TUNSAFEPTR]), originals) + slice.SetEsc(ir.EscNone) + + init.Append(mkcall("checkptrArithmetic", nil, init, typecheck.ConvNop(cheap, types.Types[types.TUNSAFEPTR]), slice)) + // TODO(khr): Mark backing store of slice as dead. This will allow us to reuse + // the backing store for multiple calls to checkptrArithmetic. + + return cheap +} diff --git a/src/cmd/compile/internal/walk/expr.go b/src/cmd/compile/internal/walk/expr.go new file mode 100644 index 0000000000000000000000000000000000000000..2fb907710bbbda09f6cdae70ba6a2fd734d84175 --- /dev/null +++ b/src/cmd/compile/internal/walk/expr.go @@ -0,0 +1,1030 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "fmt" + "go/constant" + "internal/buildcfg" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/staticdata" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" +) + +// The result of walkExpr MUST be assigned back to n, e.g. +// n.Left = walkExpr(n.Left, init) +func walkExpr(n ir.Node, init *ir.Nodes) ir.Node { + if n == nil { + return n + } + + if n, ok := n.(ir.InitNode); ok && init == n.PtrInit() { + // not okay to use n->ninit when walking n, + // because we might replace n with some other node + // and would lose the init list. + base.Fatalf("walkExpr init == &n->ninit") + } + + if len(n.Init()) != 0 { + walkStmtList(n.Init()) + init.Append(ir.TakeInit(n)...) + } + + lno := ir.SetPos(n) + + if base.Flag.LowerW > 1 { + ir.Dump("before walk expr", n) + } + + if n.Typecheck() != 1 { + base.Fatalf("missed typecheck: %+v", n) + } + + if n.Type().IsUntyped() { + base.Fatalf("expression has untyped type: %+v", n) + } + + n = walkExpr1(n, init) + + // Eagerly compute sizes of all expressions for the back end. + if typ := n.Type(); typ != nil && typ.Kind() != types.TBLANK && !typ.IsFuncArgStruct() { + types.CheckSize(typ) + } + if n, ok := n.(*ir.Name); ok && n.Heapaddr != nil { + types.CheckSize(n.Heapaddr.Type()) + } + if ir.IsConst(n, constant.String) { + // Emit string symbol now to avoid emitting + // any concurrently during the backend. + _ = staticdata.StringSym(n.Pos(), constant.StringVal(n.Val())) + } + + if base.Flag.LowerW != 0 && n != nil { + ir.Dump("after walk expr", n) + } + + base.Pos = lno + return n +} + +func walkExpr1(n ir.Node, init *ir.Nodes) ir.Node { + switch n.Op() { + default: + ir.Dump("walk", n) + base.Fatalf("walkExpr: switch 1 unknown op %+v", n.Op()) + panic("unreachable") + + case ir.ONONAME, ir.OGETG: + return n + + case ir.OTYPE, ir.ONAME, ir.OLITERAL, ir.ONIL, ir.OLINKSYMOFFSET: + // TODO(mdempsky): Just return n; see discussion on CL 38655. + // Perhaps refactor to use Node.mayBeShared for these instead. + // If these return early, make sure to still call + // StringSym for constant strings. + return n + + case ir.OMETHEXPR: + // TODO(mdempsky): Do this right after type checking. + n := n.(*ir.SelectorExpr) + return n.FuncName() + + case ir.ONOT, ir.ONEG, ir.OPLUS, ir.OBITNOT, ir.OREAL, ir.OIMAG, ir.OSPTR, ir.OITAB, ir.OIDATA: + n := n.(*ir.UnaryExpr) + n.X = walkExpr(n.X, init) + return n + + case ir.ODOTMETH, ir.ODOTINTER: + n := n.(*ir.SelectorExpr) + n.X = walkExpr(n.X, init) + return n + + case ir.OADDR: + n := n.(*ir.AddrExpr) + n.X = walkExpr(n.X, init) + return n + + case ir.ODEREF: + n := n.(*ir.StarExpr) + n.X = walkExpr(n.X, init) + return n + + case ir.OEFACE, ir.OAND, ir.OANDNOT, ir.OSUB, ir.OMUL, ir.OADD, ir.OOR, ir.OXOR, ir.OLSH, ir.ORSH, + ir.OUNSAFEADD: + n := n.(*ir.BinaryExpr) + n.X = walkExpr(n.X, init) + n.Y = walkExpr(n.Y, init) + return n + + case ir.OUNSAFESLICE: + n := n.(*ir.BinaryExpr) + return walkUnsafeSlice(n, init) + + case ir.ODOT, ir.ODOTPTR: + n := n.(*ir.SelectorExpr) + return walkDot(n, init) + + case ir.ODOTTYPE, ir.ODOTTYPE2: + n := n.(*ir.TypeAssertExpr) + return walkDotType(n, init) + + case ir.OLEN, ir.OCAP: + n := n.(*ir.UnaryExpr) + return walkLenCap(n, init) + + case ir.OCOMPLEX: + n := n.(*ir.BinaryExpr) + n.X = walkExpr(n.X, init) + n.Y = walkExpr(n.Y, init) + return n + + case ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE: + n := n.(*ir.BinaryExpr) + return walkCompare(n, init) + + case ir.OANDAND, ir.OOROR: + n := n.(*ir.LogicalExpr) + return walkLogical(n, init) + + case ir.OPRINT, ir.OPRINTN: + return walkPrint(n.(*ir.CallExpr), init) + + case ir.OPANIC: + n := n.(*ir.UnaryExpr) + return mkcall("gopanic", nil, init, n.X) + + case ir.ORECOVER: + return walkRecover(n.(*ir.CallExpr), init) + + case ir.OCFUNC: + return n + + case ir.OCALLINTER, ir.OCALLFUNC, ir.OCALLMETH: + n := n.(*ir.CallExpr) + return walkCall(n, init) + + case ir.OAS, ir.OASOP: + return walkAssign(init, n) + + case ir.OAS2: + n := n.(*ir.AssignListStmt) + return walkAssignList(init, n) + + // a,b,... = fn() + case ir.OAS2FUNC: + n := n.(*ir.AssignListStmt) + return walkAssignFunc(init, n) + + // x, y = <-c + // order.stmt made sure x is addressable or blank. + case ir.OAS2RECV: + n := n.(*ir.AssignListStmt) + return walkAssignRecv(init, n) + + // a,b = m[i] + case ir.OAS2MAPR: + n := n.(*ir.AssignListStmt) + return walkAssignMapRead(init, n) + + case ir.ODELETE: + n := n.(*ir.CallExpr) + return walkDelete(init, n) + + case ir.OAS2DOTTYPE: + n := n.(*ir.AssignListStmt) + return walkAssignDotType(n, init) + + case ir.OCONVIFACE: + n := n.(*ir.ConvExpr) + return walkConvInterface(n, init) + + case ir.OCONV, ir.OCONVNOP: + n := n.(*ir.ConvExpr) + return walkConv(n, init) + + case ir.OSLICE2ARRPTR: + n := n.(*ir.ConvExpr) + n.X = walkExpr(n.X, init) + return n + + case ir.ODIV, ir.OMOD: + n := n.(*ir.BinaryExpr) + return walkDivMod(n, init) + + case ir.OINDEX: + n := n.(*ir.IndexExpr) + return walkIndex(n, init) + + case ir.OINDEXMAP: + n := n.(*ir.IndexExpr) + return walkIndexMap(n, init) + + case ir.ORECV: + base.Fatalf("walkExpr ORECV") // should see inside OAS only + panic("unreachable") + + case ir.OSLICEHEADER: + n := n.(*ir.SliceHeaderExpr) + return walkSliceHeader(n, init) + + case ir.OSLICE, ir.OSLICEARR, ir.OSLICESTR, ir.OSLICE3, ir.OSLICE3ARR: + n := n.(*ir.SliceExpr) + return walkSlice(n, init) + + case ir.ONEW: + n := n.(*ir.UnaryExpr) + return walkNew(n, init) + + case ir.OADDSTR: + return walkAddString(n.(*ir.AddStringExpr), init) + + case ir.OAPPEND: + // order should make sure we only see OAS(node, OAPPEND), which we handle above. + base.Fatalf("append outside assignment") + panic("unreachable") + + case ir.OCOPY: + return walkCopy(n.(*ir.BinaryExpr), init, base.Flag.Cfg.Instrumenting && !base.Flag.CompilingRuntime) + + case ir.OCLOSE: + n := n.(*ir.UnaryExpr) + return walkClose(n, init) + + case ir.OMAKECHAN: + n := n.(*ir.MakeExpr) + return walkMakeChan(n, init) + + case ir.OMAKEMAP: + n := n.(*ir.MakeExpr) + return walkMakeMap(n, init) + + case ir.OMAKESLICE: + n := n.(*ir.MakeExpr) + return walkMakeSlice(n, init) + + case ir.OMAKESLICECOPY: + n := n.(*ir.MakeExpr) + return walkMakeSliceCopy(n, init) + + case ir.ORUNESTR: + n := n.(*ir.ConvExpr) + return walkRuneToString(n, init) + + case ir.OBYTES2STR, ir.ORUNES2STR: + n := n.(*ir.ConvExpr) + return walkBytesRunesToString(n, init) + + case ir.OBYTES2STRTMP: + n := n.(*ir.ConvExpr) + return walkBytesToStringTemp(n, init) + + case ir.OSTR2BYTES: + n := n.(*ir.ConvExpr) + return walkStringToBytes(n, init) + + case ir.OSTR2BYTESTMP: + n := n.(*ir.ConvExpr) + return walkStringToBytesTemp(n, init) + + case ir.OSTR2RUNES: + n := n.(*ir.ConvExpr) + return walkStringToRunes(n, init) + + case ir.OARRAYLIT, ir.OSLICELIT, ir.OMAPLIT, ir.OSTRUCTLIT, ir.OPTRLIT: + return walkCompLit(n, init) + + case ir.OSEND: + n := n.(*ir.SendStmt) + return walkSend(n, init) + + case ir.OCLOSURE: + return walkClosure(n.(*ir.ClosureExpr), init) + + case ir.OCALLPART: + return walkCallPart(n.(*ir.SelectorExpr), init) + } + + // No return! Each case must return (or panic), + // to avoid confusion about what gets returned + // in the presence of type assertions. +} + +// walk the whole tree of the body of an +// expression or simple statement. +// the types expressions are calculated. +// compile-time constants are evaluated. +// complex side effects like statements are appended to init +func walkExprList(s []ir.Node, init *ir.Nodes) { + for i := range s { + s[i] = walkExpr(s[i], init) + } +} + +func walkExprListCheap(s []ir.Node, init *ir.Nodes) { + for i, n := range s { + s[i] = cheapExpr(n, init) + s[i] = walkExpr(s[i], init) + } +} + +func walkExprListSafe(s []ir.Node, init *ir.Nodes) { + for i, n := range s { + s[i] = safeExpr(n, init) + s[i] = walkExpr(s[i], init) + } +} + +// return side-effect free and cheap n, appending side effects to init. +// result may not be assignable. +func cheapExpr(n ir.Node, init *ir.Nodes) ir.Node { + switch n.Op() { + case ir.ONAME, ir.OLITERAL, ir.ONIL: + return n + } + + return copyExpr(n, n.Type(), init) +} + +// return side effect-free n, appending side effects to init. +// result is assignable if n is. +func safeExpr(n ir.Node, init *ir.Nodes) ir.Node { + if n == nil { + return nil + } + + if len(n.Init()) != 0 { + walkStmtList(n.Init()) + init.Append(ir.TakeInit(n)...) + } + + switch n.Op() { + case ir.ONAME, ir.OLITERAL, ir.ONIL, ir.OLINKSYMOFFSET: + return n + + case ir.OLEN, ir.OCAP: + n := n.(*ir.UnaryExpr) + l := safeExpr(n.X, init) + if l == n.X { + return n + } + a := ir.Copy(n).(*ir.UnaryExpr) + a.X = l + return walkExpr(typecheck.Expr(a), init) + + case ir.ODOT, ir.ODOTPTR: + n := n.(*ir.SelectorExpr) + l := safeExpr(n.X, init) + if l == n.X { + return n + } + a := ir.Copy(n).(*ir.SelectorExpr) + a.X = l + return walkExpr(typecheck.Expr(a), init) + + case ir.ODEREF: + n := n.(*ir.StarExpr) + l := safeExpr(n.X, init) + if l == n.X { + return n + } + a := ir.Copy(n).(*ir.StarExpr) + a.X = l + return walkExpr(typecheck.Expr(a), init) + + case ir.OINDEX, ir.OINDEXMAP: + n := n.(*ir.IndexExpr) + l := safeExpr(n.X, init) + r := safeExpr(n.Index, init) + if l == n.X && r == n.Index { + return n + } + a := ir.Copy(n).(*ir.IndexExpr) + a.X = l + a.Index = r + return walkExpr(typecheck.Expr(a), init) + + case ir.OSTRUCTLIT, ir.OARRAYLIT, ir.OSLICELIT: + n := n.(*ir.CompLitExpr) + if isStaticCompositeLiteral(n) { + return n + } + } + + // make a copy; must not be used as an lvalue + if ir.IsAddressable(n) { + base.Fatalf("missing lvalue case in safeExpr: %v", n) + } + return cheapExpr(n, init) +} + +func copyExpr(n ir.Node, t *types.Type, init *ir.Nodes) ir.Node { + l := typecheck.Temp(t) + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, l, n)) + return l +} + +func walkAddString(n *ir.AddStringExpr, init *ir.Nodes) ir.Node { + c := len(n.List) + + if c < 2 { + base.Fatalf("walkAddString count %d too small", c) + } + + buf := typecheck.NodNil() + if n.Esc() == ir.EscNone { + sz := int64(0) + for _, n1 := range n.List { + if n1.Op() == ir.OLITERAL { + sz += int64(len(ir.StringVal(n1))) + } + } + + // Don't allocate the buffer if the result won't fit. + if sz < tmpstringbufsize { + // Create temporary buffer for result string on stack. + buf = stackBufAddr(tmpstringbufsize, types.Types[types.TUINT8]) + } + } + + // build list of string arguments + args := []ir.Node{buf} + for _, n2 := range n.List { + args = append(args, typecheck.Conv(n2, types.Types[types.TSTRING])) + } + + var fn string + if c <= 5 { + // small numbers of strings use direct runtime helpers. + // note: order.expr knows this cutoff too. + fn = fmt.Sprintf("concatstring%d", c) + } else { + // large numbers of strings are passed to the runtime as a slice. + fn = "concatstrings" + + t := types.NewSlice(types.Types[types.TSTRING]) + // args[1:] to skip buf arg + slice := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(t), args[1:]) + slice.Prealloc = n.Prealloc + args = []ir.Node{buf, slice} + slice.SetEsc(ir.EscNone) + } + + cat := typecheck.LookupRuntime(fn) + r := ir.NewCallExpr(base.Pos, ir.OCALL, cat, nil) + r.Args = args + r1 := typecheck.Expr(r) + r1 = walkExpr(r1, init) + r1.SetType(n.Type()) + + return r1 +} + +// walkCall walks an OCALLFUNC, OCALLINTER, or OCALLMETH node. +func walkCall(n *ir.CallExpr, init *ir.Nodes) ir.Node { + if n.Op() == ir.OCALLINTER || n.Op() == ir.OCALLMETH { + // We expect both interface call reflect.Type.Method and concrete + // call reflect.(*rtype).Method. + usemethod(n) + } + if n.Op() == ir.OCALLINTER { + reflectdata.MarkUsedIfaceMethod(n) + } + + if n.Op() == ir.OCALLFUNC && n.X.Op() == ir.OCLOSURE { + directClosureCall(n) + } + + if isFuncPCIntrinsic(n) { + // For internal/abi.FuncPCABIxxx(fn), if fn is a defined function, rewrite + // it to the address of the function of the ABI fn is defined. + name := n.X.(*ir.Name).Sym().Name + arg := n.Args[0] + var wantABI obj.ABI + switch name { + case "FuncPCABI0": + wantABI = obj.ABI0 + case "FuncPCABIInternal": + wantABI = obj.ABIInternal + } + if isIfaceOfFunc(arg) { + fn := arg.(*ir.ConvExpr).X.(*ir.Name) + abi := fn.Func.ABI + if abi != wantABI { + base.ErrorfAt(n.Pos(), "internal/abi.%s expects an %v function, %s is defined as %v", name, wantABI, fn.Sym().Name, abi) + } + var e ir.Node = ir.NewLinksymExpr(n.Pos(), fn.Sym().LinksymABI(abi), types.Types[types.TUINTPTR]) + e = ir.NewAddrExpr(n.Pos(), e) + e.SetType(types.Types[types.TUINTPTR].PtrTo()) + e = ir.NewConvExpr(n.Pos(), ir.OCONVNOP, n.Type(), e) + return e + } + // fn is not a defined function. It must be ABIInternal. + // Read the address from func value, i.e. *(*uintptr)(idata(fn)). + if wantABI != obj.ABIInternal { + base.ErrorfAt(n.Pos(), "internal/abi.%s does not accept func expression, which is ABIInternal", name) + } + arg = walkExpr(arg, init) + var e ir.Node = ir.NewUnaryExpr(n.Pos(), ir.OIDATA, arg) + e.SetType(n.Type().PtrTo()) + e = ir.NewStarExpr(n.Pos(), e) + e.SetType(n.Type()) + return e + } + + walkCall1(n, init) + return n +} + +func walkCall1(n *ir.CallExpr, init *ir.Nodes) { + if n.Walked() { + return // already walked + } + n.SetWalked(true) + + // If this is a method call t.M(...), + // rewrite into a function call T.M(t, ...). + // TODO(mdempsky): Do this right after type checking. + if n.Op() == ir.OCALLMETH { + withRecv := make([]ir.Node, len(n.Args)+1) + dot := n.X.(*ir.SelectorExpr) + withRecv[0] = dot.X + copy(withRecv[1:], n.Args) + n.Args = withRecv + + dot = ir.NewSelectorExpr(dot.Pos(), ir.OXDOT, ir.TypeNode(dot.X.Type()), dot.Selection.Sym) + + n.SetOp(ir.OCALLFUNC) + n.X = typecheck.Expr(dot) + } + + args := n.Args + params := n.X.Type().Params() + + n.X = walkExpr(n.X, init) + walkExprList(args, init) + + for i, arg := range args { + // Validate argument and parameter types match. + param := params.Field(i) + if !types.Identical(arg.Type(), param.Type) { + base.FatalfAt(n.Pos(), "assigning %L to parameter %v (type %v)", arg, param.Sym, param.Type) + } + + // For any argument whose evaluation might require a function call, + // store that argument into a temporary variable, + // to prevent that calls from clobbering arguments already on the stack. + if mayCall(arg) { + // assignment of arg to Temp + tmp := typecheck.Temp(param.Type) + init.Append(convas(typecheck.Stmt(ir.NewAssignStmt(base.Pos, tmp, arg)).(*ir.AssignStmt), init)) + // replace arg with temp + args[i] = tmp + } + } + + n.Args = args +} + +// walkDivMod walks an ODIV or OMOD node. +func walkDivMod(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { + n.X = walkExpr(n.X, init) + n.Y = walkExpr(n.Y, init) + + // rewrite complex div into function call. + et := n.X.Type().Kind() + + if types.IsComplex[et] && n.Op() == ir.ODIV { + t := n.Type() + call := mkcall("complex128div", types.Types[types.TCOMPLEX128], init, typecheck.Conv(n.X, types.Types[types.TCOMPLEX128]), typecheck.Conv(n.Y, types.Types[types.TCOMPLEX128])) + return typecheck.Conv(call, t) + } + + // Nothing to do for float divisions. + if types.IsFloat[et] { + return n + } + + // rewrite 64-bit div and mod on 32-bit architectures. + // TODO: Remove this code once we can introduce + // runtime calls late in SSA processing. + if types.RegSize < 8 && (et == types.TINT64 || et == types.TUINT64) { + if n.Y.Op() == ir.OLITERAL { + // Leave div/mod by constant powers of 2 or small 16-bit constants. + // The SSA backend will handle those. + switch et { + case types.TINT64: + c := ir.Int64Val(n.Y) + if c < 0 { + c = -c + } + if c != 0 && c&(c-1) == 0 { + return n + } + case types.TUINT64: + c := ir.Uint64Val(n.Y) + if c < 1<<16 { + return n + } + if c != 0 && c&(c-1) == 0 { + return n + } + } + } + var fn string + if et == types.TINT64 { + fn = "int64" + } else { + fn = "uint64" + } + if n.Op() == ir.ODIV { + fn += "div" + } else { + fn += "mod" + } + return mkcall(fn, n.Type(), init, typecheck.Conv(n.X, types.Types[et]), typecheck.Conv(n.Y, types.Types[et])) + } + return n +} + +// walkDot walks an ODOT or ODOTPTR node. +func walkDot(n *ir.SelectorExpr, init *ir.Nodes) ir.Node { + usefield(n) + n.X = walkExpr(n.X, init) + return n +} + +// walkDotType walks an ODOTTYPE or ODOTTYPE2 node. +func walkDotType(n *ir.TypeAssertExpr, init *ir.Nodes) ir.Node { + n.X = walkExpr(n.X, init) + // Set up interface type addresses for back end. + if !n.Type().IsInterface() && !n.X.Type().IsEmptyInterface() { + n.Itab = reflectdata.ITabAddr(n.Type(), n.X.Type()) + } + return n +} + +// walkIndex walks an OINDEX node. +func walkIndex(n *ir.IndexExpr, init *ir.Nodes) ir.Node { + n.X = walkExpr(n.X, init) + + // save the original node for bounds checking elision. + // If it was a ODIV/OMOD walk might rewrite it. + r := n.Index + + n.Index = walkExpr(n.Index, init) + + // if range of type cannot exceed static array bound, + // disable bounds check. + if n.Bounded() { + return n + } + t := n.X.Type() + if t != nil && t.IsPtr() { + t = t.Elem() + } + if t.IsArray() { + n.SetBounded(bounded(r, t.NumElem())) + if base.Flag.LowerM != 0 && n.Bounded() && !ir.IsConst(n.Index, constant.Int) { + base.Warn("index bounds check elided") + } + if ir.IsSmallIntConst(n.Index) && !n.Bounded() { + base.Errorf("index out of bounds") + } + } else if ir.IsConst(n.X, constant.String) { + n.SetBounded(bounded(r, int64(len(ir.StringVal(n.X))))) + if base.Flag.LowerM != 0 && n.Bounded() && !ir.IsConst(n.Index, constant.Int) { + base.Warn("index bounds check elided") + } + if ir.IsSmallIntConst(n.Index) && !n.Bounded() { + base.Errorf("index out of bounds") + } + } + + if ir.IsConst(n.Index, constant.Int) { + if v := n.Index.Val(); constant.Sign(v) < 0 || ir.ConstOverflow(v, types.Types[types.TINT]) { + base.Errorf("index out of bounds") + } + } + return n +} + +// mapKeyArg returns an expression for key that is suitable to be passed +// as the key argument for mapaccess and mapdelete functions. +// n is is the map indexing or delete Node (to provide Pos). +// Note: this is not used for mapassign, which does distinguish pointer vs. +// integer key. +func mapKeyArg(fast int, n, key ir.Node) ir.Node { + switch fast { + case mapslow: + // standard version takes key by reference. + // order.expr made sure key is addressable. + return typecheck.NodAddr(key) + case mapfast32ptr: + // mapaccess and mapdelete don't distinguish pointer vs. integer key. + return ir.NewConvExpr(n.Pos(), ir.OCONVNOP, types.Types[types.TUINT32], key) + case mapfast64ptr: + // mapaccess and mapdelete don't distinguish pointer vs. integer key. + return ir.NewConvExpr(n.Pos(), ir.OCONVNOP, types.Types[types.TUINT64], key) + default: + // fast version takes key by value. + return key + } +} + +// walkIndexMap walks an OINDEXMAP node. +func walkIndexMap(n *ir.IndexExpr, init *ir.Nodes) ir.Node { + // Replace m[k] with *map{access1,assign}(maptype, m, &k) + n.X = walkExpr(n.X, init) + n.Index = walkExpr(n.Index, init) + map_ := n.X + key := n.Index + t := map_.Type() + var call *ir.CallExpr + if n.Assigned { + // This m[k] expression is on the left-hand side of an assignment. + fast := mapfast(t) + if fast == mapslow { + // standard version takes key by reference. + // order.expr made sure key is addressable. + key = typecheck.NodAddr(key) + } + call = mkcall1(mapfn(mapassign[fast], t, false), nil, init, reflectdata.TypePtr(t), map_, key) + } else { + // m[k] is not the target of an assignment. + fast := mapfast(t) + key = mapKeyArg(fast, n, key) + if w := t.Elem().Width; w <= zeroValSize { + call = mkcall1(mapfn(mapaccess1[fast], t, false), types.NewPtr(t.Elem()), init, reflectdata.TypePtr(t), map_, key) + } else { + z := reflectdata.ZeroAddr(w) + call = mkcall1(mapfn("mapaccess1_fat", t, true), types.NewPtr(t.Elem()), init, reflectdata.TypePtr(t), map_, key, z) + } + } + call.SetType(types.NewPtr(t.Elem())) + call.MarkNonNil() // mapaccess1* and mapassign always return non-nil pointers. + star := ir.NewStarExpr(base.Pos, call) + star.SetType(t.Elem()) + star.SetTypecheck(1) + return star +} + +// walkLogical walks an OANDAND or OOROR node. +func walkLogical(n *ir.LogicalExpr, init *ir.Nodes) ir.Node { + n.X = walkExpr(n.X, init) + + // cannot put side effects from n.Right on init, + // because they cannot run before n.Left is checked. + // save elsewhere and store on the eventual n.Right. + var ll ir.Nodes + + n.Y = walkExpr(n.Y, &ll) + n.Y = ir.InitExpr(ll, n.Y) + return n +} + +// walkSend walks an OSEND node. +func walkSend(n *ir.SendStmt, init *ir.Nodes) ir.Node { + n1 := n.Value + n1 = typecheck.AssignConv(n1, n.Chan.Type().Elem(), "chan send") + n1 = walkExpr(n1, init) + n1 = typecheck.NodAddr(n1) + return mkcall1(chanfn("chansend1", 2, n.Chan.Type()), nil, init, n.Chan, n1) +} + +// walkSlice walks an OSLICE, OSLICEARR, OSLICESTR, OSLICE3, or OSLICE3ARR node. +func walkSlice(n *ir.SliceExpr, init *ir.Nodes) ir.Node { + + checkSlice := ir.ShouldCheckPtr(ir.CurFunc, 1) && n.Op() == ir.OSLICE3ARR && n.X.Op() == ir.OCONVNOP && n.X.(*ir.ConvExpr).X.Type().IsUnsafePtr() + if checkSlice { + conv := n.X.(*ir.ConvExpr) + conv.X = walkExpr(conv.X, init) + } else { + n.X = walkExpr(n.X, init) + } + + n.Low = walkExpr(n.Low, init) + if n.Low != nil && ir.IsZero(n.Low) { + // Reduce x[0:j] to x[:j] and x[0:j:k] to x[:j:k]. + n.Low = nil + } + n.High = walkExpr(n.High, init) + n.Max = walkExpr(n.Max, init) + if checkSlice { + n.X = walkCheckPtrAlignment(n.X.(*ir.ConvExpr), init, n.Max) + } + + if n.Op().IsSlice3() { + if n.Max != nil && n.Max.Op() == ir.OCAP && ir.SameSafeExpr(n.X, n.Max.(*ir.UnaryExpr).X) { + // Reduce x[i:j:cap(x)] to x[i:j]. + if n.Op() == ir.OSLICE3 { + n.SetOp(ir.OSLICE) + } else { + n.SetOp(ir.OSLICEARR) + } + return reduceSlice(n) + } + return n + } + return reduceSlice(n) +} + +// walkSliceHeader walks an OSLICEHEADER node. +func walkSliceHeader(n *ir.SliceHeaderExpr, init *ir.Nodes) ir.Node { + n.Ptr = walkExpr(n.Ptr, init) + n.Len = walkExpr(n.Len, init) + n.Cap = walkExpr(n.Cap, init) + return n +} + +// TODO(josharian): combine this with its caller and simplify +func reduceSlice(n *ir.SliceExpr) ir.Node { + if n.High != nil && n.High.Op() == ir.OLEN && ir.SameSafeExpr(n.X, n.High.(*ir.UnaryExpr).X) { + // Reduce x[i:len(x)] to x[i:]. + n.High = nil + } + if (n.Op() == ir.OSLICE || n.Op() == ir.OSLICESTR) && n.Low == nil && n.High == nil { + // Reduce x[:] to x. + if base.Debug.Slice > 0 { + base.Warn("slice: omit slice operation") + } + return n.X + } + return n +} + +// return 1 if integer n must be in range [0, max), 0 otherwise +func bounded(n ir.Node, max int64) bool { + if n.Type() == nil || !n.Type().IsInteger() { + return false + } + + sign := n.Type().IsSigned() + bits := int32(8 * n.Type().Width) + + if ir.IsSmallIntConst(n) { + v := ir.Int64Val(n) + return 0 <= v && v < max + } + + switch n.Op() { + case ir.OAND, ir.OANDNOT: + n := n.(*ir.BinaryExpr) + v := int64(-1) + switch { + case ir.IsSmallIntConst(n.X): + v = ir.Int64Val(n.X) + case ir.IsSmallIntConst(n.Y): + v = ir.Int64Val(n.Y) + if n.Op() == ir.OANDNOT { + v = ^v + if !sign { + v &= 1< 0 && v >= 2 { + bits-- + v >>= 1 + } + } + + case ir.ORSH: + n := n.(*ir.BinaryExpr) + if !sign && ir.IsSmallIntConst(n.Y) { + v := ir.Int64Val(n.Y) + if v > int64(bits) { + return true + } + bits -= int32(v) + } + } + + if !sign && bits <= 62 && 1< 1 { + s := fmt.Sprintf("\nbefore order %v", fn.Sym()) + ir.DumpList(s, fn.Body) + } + + orderBlock(&fn.Body, map[string][]*ir.Name{}) +} + +// append typechecks stmt and appends it to out. +func (o *orderState) append(stmt ir.Node) { + o.out = append(o.out, typecheck.Stmt(stmt)) +} + +// newTemp allocates a new temporary with the given type, +// pushes it onto the temp stack, and returns it. +// If clear is true, newTemp emits code to zero the temporary. +func (o *orderState) newTemp(t *types.Type, clear bool) *ir.Name { + var v *ir.Name + // Note: LongString is close to the type equality we want, + // but not exactly. We still need to double-check with types.Identical. + key := t.LongString() + a := o.free[key] + for i, n := range a { + if types.Identical(t, n.Type()) { + v = a[i] + a[i] = a[len(a)-1] + a = a[:len(a)-1] + o.free[key] = a + break + } + } + if v == nil { + v = typecheck.Temp(t) + } + if clear { + o.append(ir.NewAssignStmt(base.Pos, v, nil)) + } + + o.temp = append(o.temp, v) + return v +} + +// copyExpr behaves like newTemp but also emits +// code to initialize the temporary to the value n. +func (o *orderState) copyExpr(n ir.Node) *ir.Name { + return o.copyExpr1(n, false) +} + +// copyExprClear is like copyExpr but clears the temp before assignment. +// It is provided for use when the evaluation of tmp = n turns into +// a function call that is passed a pointer to the temporary as the output space. +// If the call blocks before tmp has been written, +// the garbage collector will still treat the temporary as live, +// so we must zero it before entering that call. +// Today, this only happens for channel receive operations. +// (The other candidate would be map access, but map access +// returns a pointer to the result data instead of taking a pointer +// to be filled in.) +func (o *orderState) copyExprClear(n ir.Node) *ir.Name { + return o.copyExpr1(n, true) +} + +func (o *orderState) copyExpr1(n ir.Node, clear bool) *ir.Name { + t := n.Type() + v := o.newTemp(t, clear) + o.append(ir.NewAssignStmt(base.Pos, v, n)) + return v +} + +// cheapExpr returns a cheap version of n. +// The definition of cheap is that n is a variable or constant. +// If not, cheapExpr allocates a new tmp, emits tmp = n, +// and then returns tmp. +func (o *orderState) cheapExpr(n ir.Node) ir.Node { + if n == nil { + return nil + } + + switch n.Op() { + case ir.ONAME, ir.OLITERAL, ir.ONIL: + return n + case ir.OLEN, ir.OCAP: + n := n.(*ir.UnaryExpr) + l := o.cheapExpr(n.X) + if l == n.X { + return n + } + a := ir.SepCopy(n).(*ir.UnaryExpr) + a.X = l + return typecheck.Expr(a) + } + + return o.copyExpr(n) +} + +// safeExpr returns a safe version of n. +// The definition of safe is that n can appear multiple times +// without violating the semantics of the original program, +// and that assigning to the safe version has the same effect +// as assigning to the original n. +// +// The intended use is to apply to x when rewriting x += y into x = x + y. +func (o *orderState) safeExpr(n ir.Node) ir.Node { + switch n.Op() { + case ir.ONAME, ir.OLITERAL, ir.ONIL: + return n + + case ir.OLEN, ir.OCAP: + n := n.(*ir.UnaryExpr) + l := o.safeExpr(n.X) + if l == n.X { + return n + } + a := ir.SepCopy(n).(*ir.UnaryExpr) + a.X = l + return typecheck.Expr(a) + + case ir.ODOT: + n := n.(*ir.SelectorExpr) + l := o.safeExpr(n.X) + if l == n.X { + return n + } + a := ir.SepCopy(n).(*ir.SelectorExpr) + a.X = l + return typecheck.Expr(a) + + case ir.ODOTPTR: + n := n.(*ir.SelectorExpr) + l := o.cheapExpr(n.X) + if l == n.X { + return n + } + a := ir.SepCopy(n).(*ir.SelectorExpr) + a.X = l + return typecheck.Expr(a) + + case ir.ODEREF: + n := n.(*ir.StarExpr) + l := o.cheapExpr(n.X) + if l == n.X { + return n + } + a := ir.SepCopy(n).(*ir.StarExpr) + a.X = l + return typecheck.Expr(a) + + case ir.OINDEX, ir.OINDEXMAP: + n := n.(*ir.IndexExpr) + var l ir.Node + if n.X.Type().IsArray() { + l = o.safeExpr(n.X) + } else { + l = o.cheapExpr(n.X) + } + r := o.cheapExpr(n.Index) + if l == n.X && r == n.Index { + return n + } + a := ir.SepCopy(n).(*ir.IndexExpr) + a.X = l + a.Index = r + return typecheck.Expr(a) + + default: + base.Fatalf("order.safeExpr %v", n.Op()) + return nil // not reached + } +} + +// isaddrokay reports whether it is okay to pass n's address to runtime routines. +// Taking the address of a variable makes the liveness and optimization analyses +// lose track of where the variable's lifetime ends. To avoid hurting the analyses +// of ordinary stack variables, those are not 'isaddrokay'. Temporaries are okay, +// because we emit explicit VARKILL instructions marking the end of those +// temporaries' lifetimes. +func isaddrokay(n ir.Node) bool { + return ir.IsAddressable(n) && (n.Op() != ir.ONAME || n.(*ir.Name).Class == ir.PEXTERN || ir.IsAutoTmp(n)) +} + +// addrTemp ensures that n is okay to pass by address to runtime routines. +// If the original argument n is not okay, addrTemp creates a tmp, emits +// tmp = n, and then returns tmp. +// The result of addrTemp MUST be assigned back to n, e.g. +// n.Left = o.addrTemp(n.Left) +func (o *orderState) addrTemp(n ir.Node) ir.Node { + if n.Op() == ir.OLITERAL || n.Op() == ir.ONIL { + // TODO: expand this to all static composite literal nodes? + n = typecheck.DefaultLit(n, nil) + types.CalcSize(n.Type()) + vstat := readonlystaticname(n.Type()) + var s staticinit.Schedule + s.StaticAssign(vstat, 0, n, n.Type()) + if s.Out != nil { + base.Fatalf("staticassign of const generated code: %+v", n) + } + vstat = typecheck.Expr(vstat).(*ir.Name) + return vstat + } + if isaddrokay(n) { + return n + } + return o.copyExpr(n) +} + +// mapKeyTemp prepares n to be a key in a map runtime call and returns n. +// It should only be used for map runtime calls which have *_fast* versions. +func (o *orderState) mapKeyTemp(t *types.Type, n ir.Node) ir.Node { + // Most map calls need to take the address of the key. + // Exception: map*_fast* calls. See golang.org/issue/19015. + alg := mapfast(t) + if alg == mapslow { + return o.addrTemp(n) + } + var kt *types.Type + switch alg { + case mapfast32: + kt = types.Types[types.TUINT32] + case mapfast64: + kt = types.Types[types.TUINT64] + case mapfast32ptr, mapfast64ptr: + kt = types.Types[types.TUNSAFEPTR] + case mapfaststr: + kt = types.Types[types.TSTRING] + } + nt := n.Type() + switch { + case nt == kt: + return n + case nt.Kind() == kt.Kind(), nt.IsPtrShaped() && kt.IsPtrShaped(): + // can directly convert (e.g. named type to underlying type, or one pointer to another) + return typecheck.Expr(ir.NewConvExpr(n.Pos(), ir.OCONVNOP, kt, n)) + case nt.IsInteger() && kt.IsInteger(): + // can directly convert (e.g. int32 to uint32) + if n.Op() == ir.OLITERAL && nt.IsSigned() { + // avoid constant overflow error + n = ir.NewConstExpr(constant.MakeUint64(uint64(ir.Int64Val(n))), n) + n.SetType(kt) + return n + } + return typecheck.Expr(ir.NewConvExpr(n.Pos(), ir.OCONV, kt, n)) + default: + // Unsafe cast through memory. + // We'll need to do a load with type kt. Create a temporary of type kt to + // ensure sufficient alignment. nt may be under-aligned. + if kt.Align < nt.Align { + base.Fatalf("mapKeyTemp: key type is not sufficiently aligned, kt=%v nt=%v", kt, nt) + } + tmp := o.newTemp(kt, true) + // *(*nt)(&tmp) = n + var e ir.Node = typecheck.NodAddr(tmp) + e = ir.NewConvExpr(n.Pos(), ir.OCONVNOP, nt.PtrTo(), e) + e = ir.NewStarExpr(n.Pos(), e) + o.append(ir.NewAssignStmt(base.Pos, e, n)) + return tmp + } +} + +// mapKeyReplaceStrConv replaces OBYTES2STR by OBYTES2STRTMP +// in n to avoid string allocations for keys in map lookups. +// Returns a bool that signals if a modification was made. +// +// For: +// x = m[string(k)] +// x = m[T1{... Tn{..., string(k), ...}] +// where k is []byte, T1 to Tn is a nesting of struct and array literals, +// the allocation of backing bytes for the string can be avoided +// by reusing the []byte backing array. These are special cases +// for avoiding allocations when converting byte slices to strings. +// It would be nice to handle these generally, but because +// []byte keys are not allowed in maps, the use of string(k) +// comes up in important cases in practice. See issue 3512. +func mapKeyReplaceStrConv(n ir.Node) bool { + var replaced bool + switch n.Op() { + case ir.OBYTES2STR: + n := n.(*ir.ConvExpr) + n.SetOp(ir.OBYTES2STRTMP) + replaced = true + case ir.OSTRUCTLIT: + n := n.(*ir.CompLitExpr) + for _, elem := range n.List { + elem := elem.(*ir.StructKeyExpr) + if mapKeyReplaceStrConv(elem.Value) { + replaced = true + } + } + case ir.OARRAYLIT: + n := n.(*ir.CompLitExpr) + for _, elem := range n.List { + if elem.Op() == ir.OKEY { + elem = elem.(*ir.KeyExpr).Value + } + if mapKeyReplaceStrConv(elem) { + replaced = true + } + } + } + return replaced +} + +type ordermarker int + +// markTemp returns the top of the temporary variable stack. +func (o *orderState) markTemp() ordermarker { + return ordermarker(len(o.temp)) +} + +// popTemp pops temporaries off the stack until reaching the mark, +// which must have been returned by markTemp. +func (o *orderState) popTemp(mark ordermarker) { + for _, n := range o.temp[mark:] { + key := n.Type().LongString() + o.free[key] = append(o.free[key], n) + } + o.temp = o.temp[:mark] +} + +// cleanTempNoPop emits VARKILL instructions to *out +// for each temporary above the mark on the temporary stack. +// It does not pop the temporaries from the stack. +func (o *orderState) cleanTempNoPop(mark ordermarker) []ir.Node { + var out []ir.Node + for i := len(o.temp) - 1; i >= int(mark); i-- { + n := o.temp[i] + out = append(out, typecheck.Stmt(ir.NewUnaryExpr(base.Pos, ir.OVARKILL, n))) + } + return out +} + +// cleanTemp emits VARKILL instructions for each temporary above the +// mark on the temporary stack and removes them from the stack. +func (o *orderState) cleanTemp(top ordermarker) { + o.out = append(o.out, o.cleanTempNoPop(top)...) + o.popTemp(top) +} + +// stmtList orders each of the statements in the list. +func (o *orderState) stmtList(l ir.Nodes) { + s := l + for i := range s { + orderMakeSliceCopy(s[i:]) + o.stmt(s[i]) + } +} + +// orderMakeSliceCopy matches the pattern: +// m = OMAKESLICE([]T, x); OCOPY(m, s) +// and rewrites it to: +// m = OMAKESLICECOPY([]T, x, s); nil +func orderMakeSliceCopy(s []ir.Node) { + if base.Flag.N != 0 || base.Flag.Cfg.Instrumenting { + return + } + if len(s) < 2 || s[0] == nil || s[0].Op() != ir.OAS || s[1] == nil || s[1].Op() != ir.OCOPY { + return + } + + as := s[0].(*ir.AssignStmt) + cp := s[1].(*ir.BinaryExpr) + if as.Y == nil || as.Y.Op() != ir.OMAKESLICE || ir.IsBlank(as.X) || + as.X.Op() != ir.ONAME || cp.X.Op() != ir.ONAME || cp.Y.Op() != ir.ONAME || + as.X.Name() != cp.X.Name() || cp.X.Name() == cp.Y.Name() { + // The line above this one is correct with the differing equality operators: + // we want as.X and cp.X to be the same name, + // but we want the initial data to be coming from a different name. + return + } + + mk := as.Y.(*ir.MakeExpr) + if mk.Esc() == ir.EscNone || mk.Len == nil || mk.Cap != nil { + return + } + mk.SetOp(ir.OMAKESLICECOPY) + mk.Cap = cp.Y + // Set bounded when m = OMAKESLICE([]T, len(s)); OCOPY(m, s) + mk.SetBounded(mk.Len.Op() == ir.OLEN && ir.SameSafeExpr(mk.Len.(*ir.UnaryExpr).X, cp.Y)) + as.Y = typecheck.Expr(mk) + s[1] = nil // remove separate copy call +} + +// edge inserts coverage instrumentation for libfuzzer. +func (o *orderState) edge() { + if base.Debug.Libfuzzer == 0 { + return + } + + // Create a new uint8 counter to be allocated in section + // __libfuzzer_extra_counters. + counter := staticinit.StaticName(types.Types[types.TUINT8]) + counter.SetLibfuzzerExtraCounter(true) + + // counter += 1 + incr := ir.NewAssignOpStmt(base.Pos, ir.OADD, counter, ir.NewInt(1)) + o.append(incr) +} + +// orderBlock orders the block of statements in n into a new slice, +// and then replaces the old slice in n with the new slice. +// free is a map that can be used to obtain temporary variables by type. +func orderBlock(n *ir.Nodes, free map[string][]*ir.Name) { + var order orderState + order.free = free + mark := order.markTemp() + order.edge() + order.stmtList(*n) + order.cleanTemp(mark) + *n = order.out +} + +// exprInPlace orders the side effects in *np and +// leaves them as the init list of the final *np. +// The result of exprInPlace MUST be assigned back to n, e.g. +// n.Left = o.exprInPlace(n.Left) +func (o *orderState) exprInPlace(n ir.Node) ir.Node { + var order orderState + order.free = o.free + n = order.expr(n, nil) + n = ir.InitExpr(order.out, n) + + // insert new temporaries from order + // at head of outer list. + o.temp = append(o.temp, order.temp...) + return n +} + +// orderStmtInPlace orders the side effects of the single statement *np +// and replaces it with the resulting statement list. +// The result of orderStmtInPlace MUST be assigned back to n, e.g. +// n.Left = orderStmtInPlace(n.Left) +// free is a map that can be used to obtain temporary variables by type. +func orderStmtInPlace(n ir.Node, free map[string][]*ir.Name) ir.Node { + var order orderState + order.free = free + mark := order.markTemp() + order.stmt(n) + order.cleanTemp(mark) + return ir.NewBlockStmt(src.NoXPos, order.out) +} + +// init moves n's init list to o.out. +func (o *orderState) init(n ir.Node) { + if ir.MayBeShared(n) { + // For concurrency safety, don't mutate potentially shared nodes. + // First, ensure that no work is required here. + if len(n.Init()) > 0 { + base.Fatalf("order.init shared node with ninit") + } + return + } + o.stmtList(ir.TakeInit(n)) +} + +// call orders the call expression n. +// n.Op is OCALLMETH/OCALLFUNC/OCALLINTER or a builtin like OCOPY. +func (o *orderState) call(nn ir.Node) { + if len(nn.Init()) > 0 { + // Caller should have already called o.init(nn). + base.Fatalf("%v with unexpected ninit", nn.Op()) + } + + // Builtin functions. + if nn.Op() != ir.OCALLFUNC && nn.Op() != ir.OCALLMETH && nn.Op() != ir.OCALLINTER { + switch n := nn.(type) { + default: + base.Fatalf("unexpected call: %+v", n) + case *ir.UnaryExpr: + n.X = o.expr(n.X, nil) + case *ir.ConvExpr: + n.X = o.expr(n.X, nil) + case *ir.BinaryExpr: + n.X = o.expr(n.X, nil) + n.Y = o.expr(n.Y, nil) + case *ir.MakeExpr: + n.Len = o.expr(n.Len, nil) + n.Cap = o.expr(n.Cap, nil) + case *ir.CallExpr: + o.exprList(n.Args) + } + return + } + + n := nn.(*ir.CallExpr) + typecheck.FixVariadicCall(n) + + if isFuncPCIntrinsic(n) && isIfaceOfFunc(n.Args[0]) { + // For internal/abi.FuncPCABIxxx(fn), if fn is a defined function, + // do not introduce temporaries here, so it is easier to rewrite it + // to symbol address reference later in walk. + return + } + + n.X = o.expr(n.X, nil) + o.exprList(n.Args) + + if n.Op() == ir.OCALLINTER { + return + } + keepAlive := func(arg ir.Node) { + // If the argument is really a pointer being converted to uintptr, + // arrange for the pointer to be kept alive until the call returns, + // by copying it into a temp and marking that temp + // still alive when we pop the temp stack. + if arg.Op() == ir.OCONVNOP { + arg := arg.(*ir.ConvExpr) + if arg.X.Type().IsUnsafePtr() { + x := o.copyExpr(arg.X) + arg.X = x + x.SetAddrtaken(true) // ensure SSA keeps the x variable + n.KeepAlive = append(n.KeepAlive, x) + } + } + } + + // Check for "unsafe-uintptr" tag provided by escape analysis. + for i, param := range n.X.Type().Params().FieldSlice() { + if param.Note == escape.UnsafeUintptrNote || param.Note == escape.UintptrEscapesNote { + if arg := n.Args[i]; arg.Op() == ir.OSLICELIT { + arg := arg.(*ir.CompLitExpr) + for _, elt := range arg.List { + keepAlive(elt) + } + } else { + keepAlive(arg) + } + } + } +} + +// mapAssign appends n to o.out. +func (o *orderState) mapAssign(n ir.Node) { + switch n.Op() { + default: + base.Fatalf("order.mapAssign %v", n.Op()) + + case ir.OAS: + n := n.(*ir.AssignStmt) + if n.X.Op() == ir.OINDEXMAP { + n.Y = o.safeMapRHS(n.Y) + } + o.out = append(o.out, n) + case ir.OASOP: + n := n.(*ir.AssignOpStmt) + if n.X.Op() == ir.OINDEXMAP { + n.Y = o.safeMapRHS(n.Y) + } + o.out = append(o.out, n) + } +} + +func (o *orderState) safeMapRHS(r ir.Node) ir.Node { + // Make sure we evaluate the RHS before starting the map insert. + // We need to make sure the RHS won't panic. See issue 22881. + if r.Op() == ir.OAPPEND { + r := r.(*ir.CallExpr) + s := r.Args[1:] + for i, n := range s { + s[i] = o.cheapExpr(n) + } + return r + } + return o.cheapExpr(r) +} + +// stmt orders the statement n, appending to o.out. +// Temporaries created during the statement are cleaned +// up using VARKILL instructions as possible. +func (o *orderState) stmt(n ir.Node) { + if n == nil { + return + } + + lno := ir.SetPos(n) + o.init(n) + + switch n.Op() { + default: + base.Fatalf("order.stmt %v", n.Op()) + + case ir.OVARKILL, ir.OVARLIVE, ir.OINLMARK: + o.out = append(o.out, n) + + case ir.OAS: + n := n.(*ir.AssignStmt) + t := o.markTemp() + n.X = o.expr(n.X, nil) + n.Y = o.expr(n.Y, n.X) + o.mapAssign(n) + o.cleanTemp(t) + + case ir.OASOP: + n := n.(*ir.AssignOpStmt) + t := o.markTemp() + n.X = o.expr(n.X, nil) + n.Y = o.expr(n.Y, nil) + + if base.Flag.Cfg.Instrumenting || n.X.Op() == ir.OINDEXMAP && (n.AsOp == ir.ODIV || n.AsOp == ir.OMOD) { + // Rewrite m[k] op= r into m[k] = m[k] op r so + // that we can ensure that if op panics + // because r is zero, the panic happens before + // the map assignment. + // DeepCopy is a big hammer here, but safeExpr + // makes sure there is nothing too deep being copied. + l1 := o.safeExpr(n.X) + l2 := ir.DeepCopy(src.NoXPos, l1) + if l2.Op() == ir.OINDEXMAP { + l2 := l2.(*ir.IndexExpr) + l2.Assigned = false + } + l2 = o.copyExpr(l2) + r := o.expr(typecheck.Expr(ir.NewBinaryExpr(n.Pos(), n.AsOp, l2, n.Y)), nil) + as := typecheck.Stmt(ir.NewAssignStmt(n.Pos(), l1, r)) + o.mapAssign(as) + o.cleanTemp(t) + return + } + + o.mapAssign(n) + o.cleanTemp(t) + + case ir.OAS2: + n := n.(*ir.AssignListStmt) + t := o.markTemp() + o.exprList(n.Lhs) + o.exprList(n.Rhs) + o.out = append(o.out, n) + o.cleanTemp(t) + + // Special: avoid copy of func call n.Right + case ir.OAS2FUNC: + n := n.(*ir.AssignListStmt) + t := o.markTemp() + o.exprList(n.Lhs) + o.init(n.Rhs[0]) + o.call(n.Rhs[0]) + o.as2func(n) + o.cleanTemp(t) + + // Special: use temporary variables to hold result, + // so that runtime can take address of temporary. + // No temporary for blank assignment. + // + // OAS2MAPR: make sure key is addressable if needed, + // and make sure OINDEXMAP is not copied out. + case ir.OAS2DOTTYPE, ir.OAS2RECV, ir.OAS2MAPR: + n := n.(*ir.AssignListStmt) + t := o.markTemp() + o.exprList(n.Lhs) + + switch r := n.Rhs[0]; r.Op() { + case ir.ODOTTYPE2: + r := r.(*ir.TypeAssertExpr) + r.X = o.expr(r.X, nil) + case ir.ORECV: + r := r.(*ir.UnaryExpr) + r.X = o.expr(r.X, nil) + case ir.OINDEXMAP: + r := r.(*ir.IndexExpr) + r.X = o.expr(r.X, nil) + r.Index = o.expr(r.Index, nil) + // See similar conversion for OINDEXMAP below. + _ = mapKeyReplaceStrConv(r.Index) + r.Index = o.mapKeyTemp(r.X.Type(), r.Index) + default: + base.Fatalf("order.stmt: %v", r.Op()) + } + + o.as2ok(n) + o.cleanTemp(t) + + // Special: does not save n onto out. + case ir.OBLOCK: + n := n.(*ir.BlockStmt) + o.stmtList(n.List) + + // Special: n->left is not an expression; save as is. + case ir.OBREAK, + ir.OCONTINUE, + ir.ODCL, + ir.ODCLCONST, + ir.ODCLTYPE, + ir.OFALL, + ir.OGOTO, + ir.OLABEL, + ir.OTAILCALL: + o.out = append(o.out, n) + + // Special: handle call arguments. + case ir.OCALLFUNC, ir.OCALLINTER, ir.OCALLMETH: + n := n.(*ir.CallExpr) + t := o.markTemp() + o.call(n) + o.out = append(o.out, n) + o.cleanTemp(t) + + case ir.OCLOSE, ir.ORECV: + n := n.(*ir.UnaryExpr) + t := o.markTemp() + n.X = o.expr(n.X, nil) + o.out = append(o.out, n) + o.cleanTemp(t) + + case ir.OCOPY: + n := n.(*ir.BinaryExpr) + t := o.markTemp() + n.X = o.expr(n.X, nil) + n.Y = o.expr(n.Y, nil) + o.out = append(o.out, n) + o.cleanTemp(t) + + case ir.OPRINT, ir.OPRINTN, ir.ORECOVER: + n := n.(*ir.CallExpr) + t := o.markTemp() + o.exprList(n.Args) + o.out = append(o.out, n) + o.cleanTemp(t) + + // Special: order arguments to inner call but not call itself. + case ir.ODEFER, ir.OGO: + n := n.(*ir.GoDeferStmt) + t := o.markTemp() + o.init(n.Call) + o.call(n.Call) + if n.Call.Op() == ir.ORECOVER { + // Special handling of "defer recover()". We need to evaluate the FP + // argument before wrapping. + var init ir.Nodes + n.Call = walkRecover(n.Call.(*ir.CallExpr), &init) + o.stmtList(init) + } + if buildcfg.Experiment.RegabiDefer { + o.wrapGoDefer(n) + } + o.out = append(o.out, n) + o.cleanTemp(t) + + case ir.ODELETE: + n := n.(*ir.CallExpr) + t := o.markTemp() + n.Args[0] = o.expr(n.Args[0], nil) + n.Args[1] = o.expr(n.Args[1], nil) + n.Args[1] = o.mapKeyTemp(n.Args[0].Type(), n.Args[1]) + o.out = append(o.out, n) + o.cleanTemp(t) + + // Clean temporaries from condition evaluation at + // beginning of loop body and after for statement. + case ir.OFOR: + n := n.(*ir.ForStmt) + t := o.markTemp() + n.Cond = o.exprInPlace(n.Cond) + n.Body.Prepend(o.cleanTempNoPop(t)...) + orderBlock(&n.Body, o.free) + n.Post = orderStmtInPlace(n.Post, o.free) + o.out = append(o.out, n) + o.cleanTemp(t) + + // Clean temporaries from condition at + // beginning of both branches. + case ir.OIF: + n := n.(*ir.IfStmt) + t := o.markTemp() + n.Cond = o.exprInPlace(n.Cond) + n.Body.Prepend(o.cleanTempNoPop(t)...) + n.Else.Prepend(o.cleanTempNoPop(t)...) + o.popTemp(t) + orderBlock(&n.Body, o.free) + orderBlock(&n.Else, o.free) + o.out = append(o.out, n) + + case ir.OPANIC: + n := n.(*ir.UnaryExpr) + t := o.markTemp() + n.X = o.expr(n.X, nil) + if !n.X.Type().IsEmptyInterface() { + base.FatalfAt(n.Pos(), "bad argument to panic: %L", n.X) + } + o.out = append(o.out, n) + o.cleanTemp(t) + + case ir.ORANGE: + // n.Right is the expression being ranged over. + // order it, and then make a copy if we need one. + // We almost always do, to ensure that we don't + // see any value changes made during the loop. + // Usually the copy is cheap (e.g., array pointer, + // chan, slice, string are all tiny). + // The exception is ranging over an array value + // (not a slice, not a pointer to array), + // which must make a copy to avoid seeing updates made during + // the range body. Ranging over an array value is uncommon though. + + // Mark []byte(str) range expression to reuse string backing storage. + // It is safe because the storage cannot be mutated. + n := n.(*ir.RangeStmt) + if n.X.Op() == ir.OSTR2BYTES { + n.X.(*ir.ConvExpr).SetOp(ir.OSTR2BYTESTMP) + } + + t := o.markTemp() + n.X = o.expr(n.X, nil) + + orderBody := true + xt := typecheck.RangeExprType(n.X.Type()) + switch xt.Kind() { + default: + base.Fatalf("order.stmt range %v", n.Type()) + + case types.TARRAY, types.TSLICE: + if n.Value == nil || ir.IsBlank(n.Value) { + // for i := range x will only use x once, to compute len(x). + // No need to copy it. + break + } + fallthrough + + case types.TCHAN, types.TSTRING: + // chan, string, slice, array ranges use value multiple times. + // make copy. + r := n.X + + if r.Type().IsString() && r.Type() != types.Types[types.TSTRING] { + r = ir.NewConvExpr(base.Pos, ir.OCONV, nil, r) + r.SetType(types.Types[types.TSTRING]) + r = typecheck.Expr(r) + } + + n.X = o.copyExpr(r) + + case types.TMAP: + if isMapClear(n) { + // Preserve the body of the map clear pattern so it can + // be detected during walk. The loop body will not be used + // when optimizing away the range loop to a runtime call. + orderBody = false + break + } + + // copy the map value in case it is a map literal. + // TODO(rsc): Make tmp = literal expressions reuse tmp. + // For maps tmp is just one word so it hardly matters. + r := n.X + n.X = o.copyExpr(r) + + // n.Prealloc is the temp for the iterator. + // MapIterType contains pointers and needs to be zeroed. + n.Prealloc = o.newTemp(reflectdata.MapIterType(xt), true) + } + n.Key = o.exprInPlace(n.Key) + n.Value = o.exprInPlace(n.Value) + if orderBody { + orderBlock(&n.Body, o.free) + } + o.out = append(o.out, n) + o.cleanTemp(t) + + case ir.ORETURN: + n := n.(*ir.ReturnStmt) + o.exprList(n.Results) + o.out = append(o.out, n) + + // Special: clean case temporaries in each block entry. + // Select must enter one of its blocks, so there is no + // need for a cleaning at the end. + // Doubly special: evaluation order for select is stricter + // than ordinary expressions. Even something like p.c + // has to be hoisted into a temporary, so that it cannot be + // reordered after the channel evaluation for a different + // case (if p were nil, then the timing of the fault would + // give this away). + case ir.OSELECT: + n := n.(*ir.SelectStmt) + t := o.markTemp() + for _, ncas := range n.Cases { + r := ncas.Comm + ir.SetPos(ncas) + + // Append any new body prologue to ninit. + // The next loop will insert ninit into nbody. + if len(ncas.Init()) != 0 { + base.Fatalf("order select ninit") + } + if r == nil { + continue + } + switch r.Op() { + default: + ir.Dump("select case", r) + base.Fatalf("unknown op in select %v", r.Op()) + + case ir.OSELRECV2: + // case x, ok = <-c + r := r.(*ir.AssignListStmt) + recv := r.Rhs[0].(*ir.UnaryExpr) + recv.X = o.expr(recv.X, nil) + if !ir.IsAutoTmp(recv.X) { + recv.X = o.copyExpr(recv.X) + } + init := ir.TakeInit(r) + + colas := r.Def + do := func(i int, t *types.Type) { + n := r.Lhs[i] + if ir.IsBlank(n) { + return + } + // If this is case x := <-ch or case x, y := <-ch, the case has + // the ODCL nodes to declare x and y. We want to delay that + // declaration (and possible allocation) until inside the case body. + // Delete the ODCL nodes here and recreate them inside the body below. + if colas { + if len(init) > 0 && init[0].Op() == ir.ODCL && init[0].(*ir.Decl).X == n { + init = init[1:] + } + dcl := typecheck.Stmt(ir.NewDecl(base.Pos, ir.ODCL, n.(*ir.Name))) + ncas.PtrInit().Append(dcl) + } + tmp := o.newTemp(t, t.HasPointers()) + as := typecheck.Stmt(ir.NewAssignStmt(base.Pos, n, typecheck.Conv(tmp, n.Type()))) + ncas.PtrInit().Append(as) + r.Lhs[i] = tmp + } + do(0, recv.X.Type().Elem()) + do(1, types.Types[types.TBOOL]) + if len(init) != 0 { + ir.DumpList("ninit", r.Init()) + base.Fatalf("ninit on select recv") + } + orderBlock(ncas.PtrInit(), o.free) + + case ir.OSEND: + r := r.(*ir.SendStmt) + if len(r.Init()) != 0 { + ir.DumpList("ninit", r.Init()) + base.Fatalf("ninit on select send") + } + + // case c <- x + // r->left is c, r->right is x, both are always evaluated. + r.Chan = o.expr(r.Chan, nil) + + if !ir.IsAutoTmp(r.Chan) { + r.Chan = o.copyExpr(r.Chan) + } + r.Value = o.expr(r.Value, nil) + if !ir.IsAutoTmp(r.Value) { + r.Value = o.copyExpr(r.Value) + } + } + } + // Now that we have accumulated all the temporaries, clean them. + // Also insert any ninit queued during the previous loop. + // (The temporary cleaning must follow that ninit work.) + for _, cas := range n.Cases { + orderBlock(&cas.Body, o.free) + cas.Body.Prepend(o.cleanTempNoPop(t)...) + + // TODO(mdempsky): Is this actually necessary? + // walkSelect appears to walk Ninit. + cas.Body.Prepend(ir.TakeInit(cas)...) + } + + o.out = append(o.out, n) + o.popTemp(t) + + // Special: value being sent is passed as a pointer; make it addressable. + case ir.OSEND: + n := n.(*ir.SendStmt) + t := o.markTemp() + n.Chan = o.expr(n.Chan, nil) + n.Value = o.expr(n.Value, nil) + if base.Flag.Cfg.Instrumenting { + // Force copying to the stack so that (chan T)(nil) <- x + // is still instrumented as a read of x. + n.Value = o.copyExpr(n.Value) + } else { + n.Value = o.addrTemp(n.Value) + } + o.out = append(o.out, n) + o.cleanTemp(t) + + // TODO(rsc): Clean temporaries more aggressively. + // Note that because walkSwitch will rewrite some of the + // switch into a binary search, this is not as easy as it looks. + // (If we ran that code here we could invoke order.stmt on + // the if-else chain instead.) + // For now just clean all the temporaries at the end. + // In practice that's fine. + case ir.OSWITCH: + n := n.(*ir.SwitchStmt) + if base.Debug.Libfuzzer != 0 && !hasDefaultCase(n) { + // Add empty "default:" case for instrumentation. + n.Cases = append(n.Cases, ir.NewCaseStmt(base.Pos, nil, nil)) + } + + t := o.markTemp() + n.Tag = o.expr(n.Tag, nil) + for _, ncas := range n.Cases { + o.exprListInPlace(ncas.List) + orderBlock(&ncas.Body, o.free) + } + + o.out = append(o.out, n) + o.cleanTemp(t) + } + + base.Pos = lno +} + +func hasDefaultCase(n *ir.SwitchStmt) bool { + for _, ncas := range n.Cases { + if len(ncas.List) == 0 { + return true + } + } + return false +} + +// exprList orders the expression list l into o. +func (o *orderState) exprList(l ir.Nodes) { + s := l + for i := range s { + s[i] = o.expr(s[i], nil) + } +} + +// exprListInPlace orders the expression list l but saves +// the side effects on the individual expression ninit lists. +func (o *orderState) exprListInPlace(l ir.Nodes) { + s := l + for i := range s { + s[i] = o.exprInPlace(s[i]) + } +} + +func (o *orderState) exprNoLHS(n ir.Node) ir.Node { + return o.expr(n, nil) +} + +// expr orders a single expression, appending side +// effects to o.out as needed. +// If this is part of an assignment lhs = *np, lhs is given. +// Otherwise lhs == nil. (When lhs != nil it may be possible +// to avoid copying the result of the expression to a temporary.) +// The result of expr MUST be assigned back to n, e.g. +// n.Left = o.expr(n.Left, lhs) +func (o *orderState) expr(n, lhs ir.Node) ir.Node { + if n == nil { + return n + } + lno := ir.SetPos(n) + n = o.expr1(n, lhs) + base.Pos = lno + return n +} + +func (o *orderState) expr1(n, lhs ir.Node) ir.Node { + o.init(n) + + switch n.Op() { + default: + if o.edit == nil { + o.edit = o.exprNoLHS // create closure once + } + ir.EditChildren(n, o.edit) + return n + + // Addition of strings turns into a function call. + // Allocate a temporary to hold the strings. + // Fewer than 5 strings use direct runtime helpers. + case ir.OADDSTR: + n := n.(*ir.AddStringExpr) + o.exprList(n.List) + + if len(n.List) > 5 { + t := types.NewArray(types.Types[types.TSTRING], int64(len(n.List))) + n.Prealloc = o.newTemp(t, false) + } + + // Mark string(byteSlice) arguments to reuse byteSlice backing + // buffer during conversion. String concatenation does not + // memorize the strings for later use, so it is safe. + // However, we can do it only if there is at least one non-empty string literal. + // Otherwise if all other arguments are empty strings, + // concatstrings will return the reference to the temp string + // to the caller. + hasbyte := false + + haslit := false + for _, n1 := range n.List { + hasbyte = hasbyte || n1.Op() == ir.OBYTES2STR + haslit = haslit || n1.Op() == ir.OLITERAL && len(ir.StringVal(n1)) != 0 + } + + if haslit && hasbyte { + for _, n2 := range n.List { + if n2.Op() == ir.OBYTES2STR { + n2 := n2.(*ir.ConvExpr) + n2.SetOp(ir.OBYTES2STRTMP) + } + } + } + return n + + case ir.OINDEXMAP: + n := n.(*ir.IndexExpr) + n.X = o.expr(n.X, nil) + n.Index = o.expr(n.Index, nil) + needCopy := false + + if !n.Assigned { + // Enforce that any []byte slices we are not copying + // can not be changed before the map index by forcing + // the map index to happen immediately following the + // conversions. See copyExpr a few lines below. + needCopy = mapKeyReplaceStrConv(n.Index) + + if base.Flag.Cfg.Instrumenting { + // Race detector needs the copy. + needCopy = true + } + } + + // key must be addressable + n.Index = o.mapKeyTemp(n.X.Type(), n.Index) + if needCopy { + return o.copyExpr(n) + } + return n + + // concrete type (not interface) argument might need an addressable + // temporary to pass to the runtime conversion routine. + case ir.OCONVIFACE: + n := n.(*ir.ConvExpr) + n.X = o.expr(n.X, nil) + if n.X.Type().IsInterface() { + return n + } + if _, _, needsaddr := convFuncName(n.X.Type(), n.Type()); needsaddr || isStaticCompositeLiteral(n.X) { + // Need a temp if we need to pass the address to the conversion function. + // We also process static composite literal node here, making a named static global + // whose address we can put directly in an interface (see OCONVIFACE case in walk). + n.X = o.addrTemp(n.X) + } + return n + + case ir.OCONVNOP: + n := n.(*ir.ConvExpr) + if n.Type().IsKind(types.TUNSAFEPTR) && n.X.Type().IsKind(types.TUINTPTR) && (n.X.Op() == ir.OCALLFUNC || n.X.Op() == ir.OCALLINTER || n.X.Op() == ir.OCALLMETH) { + call := n.X.(*ir.CallExpr) + // When reordering unsafe.Pointer(f()) into a separate + // statement, the conversion and function call must stay + // together. See golang.org/issue/15329. + o.init(call) + o.call(call) + if lhs == nil || lhs.Op() != ir.ONAME || base.Flag.Cfg.Instrumenting { + return o.copyExpr(n) + } + } else { + n.X = o.expr(n.X, nil) + } + return n + + case ir.OANDAND, ir.OOROR: + // ... = LHS && RHS + // + // var r bool + // r = LHS + // if r { // or !r, for OROR + // r = RHS + // } + // ... = r + + n := n.(*ir.LogicalExpr) + r := o.newTemp(n.Type(), false) + + // Evaluate left-hand side. + lhs := o.expr(n.X, nil) + o.out = append(o.out, typecheck.Stmt(ir.NewAssignStmt(base.Pos, r, lhs))) + + // Evaluate right-hand side, save generated code. + saveout := o.out + o.out = nil + t := o.markTemp() + o.edge() + rhs := o.expr(n.Y, nil) + o.out = append(o.out, typecheck.Stmt(ir.NewAssignStmt(base.Pos, r, rhs))) + o.cleanTemp(t) + gen := o.out + o.out = saveout + + // If left-hand side doesn't cause a short-circuit, issue right-hand side. + nif := ir.NewIfStmt(base.Pos, r, nil, nil) + if n.Op() == ir.OANDAND { + nif.Body = gen + } else { + nif.Else = gen + } + o.out = append(o.out, nif) + return r + + case ir.OCALLFUNC, + ir.OCALLINTER, + ir.OCALLMETH, + ir.OCAP, + ir.OCOMPLEX, + ir.OCOPY, + ir.OIMAG, + ir.OLEN, + ir.OMAKECHAN, + ir.OMAKEMAP, + ir.OMAKESLICE, + ir.OMAKESLICECOPY, + ir.ONEW, + ir.OREAL, + ir.ORECOVER, + ir.OSTR2BYTES, + ir.OSTR2BYTESTMP, + ir.OSTR2RUNES: + + if isRuneCount(n) { + // len([]rune(s)) is rewritten to runtime.countrunes(s) later. + conv := n.(*ir.UnaryExpr).X.(*ir.ConvExpr) + conv.X = o.expr(conv.X, nil) + } else { + o.call(n) + } + + if lhs == nil || lhs.Op() != ir.ONAME || base.Flag.Cfg.Instrumenting { + return o.copyExpr(n) + } + return n + + case ir.OAPPEND: + // Check for append(x, make([]T, y)...) . + n := n.(*ir.CallExpr) + if isAppendOfMake(n) { + n.Args[0] = o.expr(n.Args[0], nil) // order x + mk := n.Args[1].(*ir.MakeExpr) + mk.Len = o.expr(mk.Len, nil) // order y + } else { + o.exprList(n.Args) + } + + if lhs == nil || lhs.Op() != ir.ONAME && !ir.SameSafeExpr(lhs, n.Args[0]) { + return o.copyExpr(n) + } + return n + + case ir.OSLICE, ir.OSLICEARR, ir.OSLICESTR, ir.OSLICE3, ir.OSLICE3ARR: + n := n.(*ir.SliceExpr) + n.X = o.expr(n.X, nil) + n.Low = o.cheapExpr(o.expr(n.Low, nil)) + n.High = o.cheapExpr(o.expr(n.High, nil)) + n.Max = o.cheapExpr(o.expr(n.Max, nil)) + if lhs == nil || lhs.Op() != ir.ONAME && !ir.SameSafeExpr(lhs, n.X) { + return o.copyExpr(n) + } + return n + + case ir.OCLOSURE: + n := n.(*ir.ClosureExpr) + if n.Transient() && len(n.Func.ClosureVars) > 0 { + n.Prealloc = o.newTemp(typecheck.ClosureType(n), false) + } + return n + + case ir.OCALLPART: + n := n.(*ir.SelectorExpr) + n.X = o.expr(n.X, nil) + if n.Transient() { + t := typecheck.PartialCallType(n) + n.Prealloc = o.newTemp(t, false) + } + return n + + case ir.OSLICELIT: + n := n.(*ir.CompLitExpr) + o.exprList(n.List) + if n.Transient() { + t := types.NewArray(n.Type().Elem(), n.Len) + n.Prealloc = o.newTemp(t, false) + } + return n + + case ir.ODOTTYPE, ir.ODOTTYPE2: + n := n.(*ir.TypeAssertExpr) + n.X = o.expr(n.X, nil) + if !types.IsDirectIface(n.Type()) || base.Flag.Cfg.Instrumenting { + return o.copyExprClear(n) + } + return n + + case ir.ORECV: + n := n.(*ir.UnaryExpr) + n.X = o.expr(n.X, nil) + return o.copyExprClear(n) + + case ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE: + n := n.(*ir.BinaryExpr) + n.X = o.expr(n.X, nil) + n.Y = o.expr(n.Y, nil) + + t := n.X.Type() + switch { + case t.IsString(): + // Mark string(byteSlice) arguments to reuse byteSlice backing + // buffer during conversion. String comparison does not + // memorize the strings for later use, so it is safe. + if n.X.Op() == ir.OBYTES2STR { + n.X.(*ir.ConvExpr).SetOp(ir.OBYTES2STRTMP) + } + if n.Y.Op() == ir.OBYTES2STR { + n.Y.(*ir.ConvExpr).SetOp(ir.OBYTES2STRTMP) + } + + case t.IsStruct() || t.IsArray(): + // for complex comparisons, we need both args to be + // addressable so we can pass them to the runtime. + n.X = o.addrTemp(n.X) + n.Y = o.addrTemp(n.Y) + } + return n + + case ir.OMAPLIT: + // Order map by converting: + // map[int]int{ + // a(): b(), + // c(): d(), + // e(): f(), + // } + // to + // m := map[int]int{} + // m[a()] = b() + // m[c()] = d() + // m[e()] = f() + // Then order the result. + // Without this special case, order would otherwise compute all + // the keys and values before storing any of them to the map. + // See issue 26552. + n := n.(*ir.CompLitExpr) + entries := n.List + statics := entries[:0] + var dynamics []*ir.KeyExpr + for _, r := range entries { + r := r.(*ir.KeyExpr) + + if !isStaticCompositeLiteral(r.Key) || !isStaticCompositeLiteral(r.Value) { + dynamics = append(dynamics, r) + continue + } + + // Recursively ordering some static entries can change them to dynamic; + // e.g., OCONVIFACE nodes. See #31777. + r = o.expr(r, nil).(*ir.KeyExpr) + if !isStaticCompositeLiteral(r.Key) || !isStaticCompositeLiteral(r.Value) { + dynamics = append(dynamics, r) + continue + } + + statics = append(statics, r) + } + n.List = statics + + if len(dynamics) == 0 { + return n + } + + // Emit the creation of the map (with all its static entries). + m := o.newTemp(n.Type(), false) + as := ir.NewAssignStmt(base.Pos, m, n) + typecheck.Stmt(as) + o.stmt(as) + + // Emit eval+insert of dynamic entries, one at a time. + for _, r := range dynamics { + as := ir.NewAssignStmt(base.Pos, ir.NewIndexExpr(base.Pos, m, r.Key), r.Value) + typecheck.Stmt(as) // Note: this converts the OINDEX to an OINDEXMAP + o.stmt(as) + } + return m + } + + // No return - type-assertions above. Each case must return for itself. +} + +// as2func orders OAS2FUNC nodes. It creates temporaries to ensure left-to-right assignment. +// The caller should order the right-hand side of the assignment before calling order.as2func. +// It rewrites, +// a, b, a = ... +// as +// tmp1, tmp2, tmp3 = ... +// a, b, a = tmp1, tmp2, tmp3 +// This is necessary to ensure left to right assignment order. +func (o *orderState) as2func(n *ir.AssignListStmt) { + results := n.Rhs[0].Type() + as := ir.NewAssignListStmt(n.Pos(), ir.OAS2, nil, nil) + for i, nl := range n.Lhs { + if !ir.IsBlank(nl) { + typ := results.Field(i).Type + tmp := o.newTemp(typ, typ.HasPointers()) + n.Lhs[i] = tmp + as.Lhs = append(as.Lhs, nl) + as.Rhs = append(as.Rhs, tmp) + } + } + + o.out = append(o.out, n) + o.stmt(typecheck.Stmt(as)) +} + +// as2ok orders OAS2XXX with ok. +// Just like as2func, this also adds temporaries to ensure left-to-right assignment. +func (o *orderState) as2ok(n *ir.AssignListStmt) { + as := ir.NewAssignListStmt(n.Pos(), ir.OAS2, nil, nil) + + do := func(i int, typ *types.Type) { + if nl := n.Lhs[i]; !ir.IsBlank(nl) { + var tmp ir.Node = o.newTemp(typ, typ.HasPointers()) + n.Lhs[i] = tmp + as.Lhs = append(as.Lhs, nl) + if i == 1 { + // The "ok" result is an untyped boolean according to the Go + // spec. We need to explicitly convert it to the LHS type in + // case the latter is a defined boolean type (#8475). + tmp = typecheck.Conv(tmp, nl.Type()) + } + as.Rhs = append(as.Rhs, tmp) + } + } + + do(0, n.Rhs[0].Type()) + do(1, types.Types[types.TBOOL]) + + o.out = append(o.out, n) + o.stmt(typecheck.Stmt(as)) +} + +var wrapGoDefer_prgen int + +// wrapGoDefer wraps the target of a "go" or "defer" statement with a +// new "function with no arguments" closure. Specifically, it converts +// +// defer f(x, y) +// +// to +// +// x1, y1 := x, y +// defer func() { f(x1, y1) }() +// +// This is primarily to enable a quicker bringup of defers under the +// new register ABI; by doing this conversion, we can simplify the +// code in the runtime that invokes defers on the panic path. +func (o *orderState) wrapGoDefer(n *ir.GoDeferStmt) { + call := n.Call + + var callX ir.Node // thing being called + var callArgs []ir.Node // call arguments + var keepAlive []*ir.Name // KeepAlive list from call, if present + + // A helper to recreate the call within the closure. + var mkNewCall func(pos src.XPos, op ir.Op, fun ir.Node, args []ir.Node) ir.Node + + // Defer calls come in many shapes and sizes; not all of them + // are ir.CallExpr's. Examine the type to see what we're dealing with. + switch x := call.(type) { + case *ir.CallExpr: + callX = x.X + callArgs = x.Args + keepAlive = x.KeepAlive + mkNewCall = func(pos src.XPos, op ir.Op, fun ir.Node, args []ir.Node) ir.Node { + newcall := ir.NewCallExpr(pos, op, fun, args) + newcall.IsDDD = x.IsDDD + return ir.Node(newcall) + } + case *ir.UnaryExpr: // ex: OCLOSE + callArgs = []ir.Node{x.X} + mkNewCall = func(pos src.XPos, op ir.Op, fun ir.Node, args []ir.Node) ir.Node { + if len(args) != 1 { + panic("internal error, expecting single arg") + } + return ir.Node(ir.NewUnaryExpr(pos, op, args[0])) + } + case *ir.BinaryExpr: // ex: OCOPY + callArgs = []ir.Node{x.X, x.Y} + mkNewCall = func(pos src.XPos, op ir.Op, fun ir.Node, args []ir.Node) ir.Node { + if len(args) != 2 { + panic("internal error, expecting two args") + } + return ir.Node(ir.NewBinaryExpr(pos, op, args[0], args[1])) + } + default: + panic("unhandled op") + } + + // No need to wrap if called func has no args, no receiver, and no results. + // However in the case of "defer func() { ... }()" we need to + // protect against the possibility of directClosureCall rewriting + // things so that the call does have arguments. + // + // Do wrap method calls (OCALLMETH, OCALLINTER), because it has + // a receiver. + // + // Also do wrap builtin functions, because they may be expanded to + // calls with arguments (e.g. ORECOVER). + // + // TODO: maybe not wrap if the called function has no arguments and + // only in-register results? + if len(callArgs) == 0 && call.Op() == ir.OCALLFUNC && callX.Type().NumResults() == 0 { + if c, ok := call.(*ir.CallExpr); ok && callX != nil && callX.Op() == ir.OCLOSURE { + cloFunc := callX.(*ir.ClosureExpr).Func + cloFunc.SetClosureCalled(false) + c.PreserveClosure = true + } + return + } + + if c, ok := call.(*ir.CallExpr); ok { + // To simplify things, turn f(a, b, []T{c, d, e}...) back + // into f(a, b, c, d, e) -- when the final call is run through the + // type checker below, it will rebuild the proper slice literal. + undoVariadic(c) + callX = c.X + callArgs = c.Args + } + + // This is set to true if the closure we're generating escapes + // (needs heap allocation). + cloEscapes := func() bool { + if n.Op() == ir.OGO { + // For "go", assume that all closures escape. + return true + } + // For defer, just use whatever result escape analysis + // has determined for the defer. + return n.Esc() != ir.EscNever + }() + + // A helper for making a copy of an argument. Note that it is + // not safe to use o.copyExpr(arg) if we're putting a + // reference to the temp into the closure (as opposed to + // copying it in by value), since in the by-reference case we + // need a temporary whose lifetime extends to the end of the + // function (as opposed to being local to the current block or + // statement being ordered). + mkArgCopy := func(arg ir.Node) *ir.Name { + t := arg.Type() + byval := t.Size() <= 128 || cloEscapes + var argCopy *ir.Name + if byval { + argCopy = o.copyExpr(arg) + } else { + argCopy = typecheck.Temp(t) + o.append(ir.NewAssignStmt(base.Pos, argCopy, arg)) + } + // The value of 128 below is meant to be consistent with code + // in escape analysis that picks byval/byaddr based on size. + argCopy.SetByval(byval) + return argCopy + } + + // getUnsafeArg looks for an unsafe.Pointer arg that has been + // previously captured into the call's keepalive list, returning + // the name node for it if found. + getUnsafeArg := func(arg ir.Node) *ir.Name { + // Look for uintptr(unsafe.Pointer(name)) + if arg.Op() != ir.OCONVNOP { + return nil + } + if !arg.Type().IsUintptr() { + return nil + } + if !arg.(*ir.ConvExpr).X.Type().IsUnsafePtr() { + return nil + } + arg = arg.(*ir.ConvExpr).X + argname, ok := arg.(*ir.Name) + if !ok { + return nil + } + for i := range keepAlive { + if argname == keepAlive[i] { + return argname + } + } + return nil + } + + // Copy the arguments to the function into temps. + // + // For calls with uintptr(unsafe.Pointer(...)) args that are being + // kept alive (see code in (*orderState).call that does this), use + // the existing arg copy instead of creating a new copy. + unsafeArgs := make([]*ir.Name, len(callArgs)) + origArgs := callArgs + var newNames []*ir.Name + for i := range callArgs { + arg := callArgs[i] + var argname *ir.Name + unsafeArgName := getUnsafeArg(arg) + if unsafeArgName != nil { + // arg has been copied already, use keepalive copy + argname = unsafeArgName + unsafeArgs[i] = unsafeArgName + } else { + argname = mkArgCopy(arg) + } + newNames = append(newNames, argname) + } + + // Deal with cases where the function expression (what we're + // calling) is not a simple function symbol. + var fnExpr *ir.Name + var methSelectorExpr *ir.SelectorExpr + if callX != nil { + switch { + case callX.Op() == ir.ODOTMETH || callX.Op() == ir.ODOTINTER: + // Handle defer of a method call, e.g. "defer v.MyMethod(x, y)" + n := callX.(*ir.SelectorExpr) + n.X = mkArgCopy(n.X) + methSelectorExpr = n + if callX.Op() == ir.ODOTINTER { + // Currently for "defer i.M()" if i is nil it panics at the + // point of defer statement, not when deferred function is called. + // (I think there is an issue discussing what is the intended + // behavior but I cannot find it.) + // We need to do the nil check outside of the wrapper. + tab := typecheck.Expr(ir.NewUnaryExpr(base.Pos, ir.OITAB, n.X)) + c := ir.NewUnaryExpr(n.Pos(), ir.OCHECKNIL, tab) + c.SetTypecheck(1) + o.append(c) + } + case !(callX.Op() == ir.ONAME && callX.(*ir.Name).Class == ir.PFUNC): + // Deal with "defer returnsafunc()(x, y)" (for + // example) by copying the callee expression. + fnExpr = mkArgCopy(callX) + if callX.Op() == ir.OCLOSURE { + // For "defer func(...)", in addition to copying the + // closure into a temp, mark it as no longer directly + // called. + callX.(*ir.ClosureExpr).Func.SetClosureCalled(false) + } + } + } + + // Create a new no-argument function that we'll hand off to defer. + var noFuncArgs []*ir.Field + noargst := ir.NewFuncType(base.Pos, nil, noFuncArgs, nil) + wrapGoDefer_prgen++ + outerfn := ir.CurFunc + wrapname := fmt.Sprintf("%v·dwrap·%d", outerfn, wrapGoDefer_prgen) + sym := types.LocalPkg.Lookup(wrapname) + fn := typecheck.DeclFunc(sym, noargst) + fn.SetIsHiddenClosure(true) + fn.SetWrapper(true) + + // helper for capturing reference to a var declared in an outer scope. + capName := func(pos src.XPos, fn *ir.Func, n *ir.Name) *ir.Name { + t := n.Type() + cv := ir.CaptureName(pos, fn, n) + cv.SetType(t) + return typecheck.Expr(cv).(*ir.Name) + } + + // Call args (x1, y1) need to be captured as part of the newly + // created closure. + newCallArgs := []ir.Node{} + for i := range newNames { + var arg ir.Node + arg = capName(callArgs[i].Pos(), fn, newNames[i]) + if unsafeArgs[i] != nil { + arg = ir.NewConvExpr(arg.Pos(), origArgs[i].Op(), origArgs[i].Type(), arg) + } + newCallArgs = append(newCallArgs, arg) + } + // Also capture the function or method expression (if needed) into + // the closure. + if fnExpr != nil { + callX = capName(callX.Pos(), fn, fnExpr) + } + if methSelectorExpr != nil { + methSelectorExpr.X = capName(callX.Pos(), fn, methSelectorExpr.X.(*ir.Name)) + } + ir.FinishCaptureNames(n.Pos(), outerfn, fn) + + // This flags a builtin as opposed to a regular call. + irregular := (call.Op() != ir.OCALLFUNC && + call.Op() != ir.OCALLMETH && + call.Op() != ir.OCALLINTER) + + // Construct new function body: f(x1, y1) + op := ir.OCALL + if irregular { + op = call.Op() + } + newcall := mkNewCall(call.Pos(), op, callX, newCallArgs) + + // Type-check the result. + if !irregular { + typecheck.Call(newcall.(*ir.CallExpr)) + } else { + typecheck.Stmt(newcall) + } + + // Finalize body, register function on the main decls list. + fn.Body = []ir.Node{newcall} + typecheck.FinishFuncBody() + typecheck.Func(fn) + typecheck.Target.Decls = append(typecheck.Target.Decls, fn) + + // Create closure expr + clo := ir.NewClosureExpr(n.Pos(), fn) + fn.OClosure = clo + clo.SetType(fn.Type()) + + // Set escape properties for closure. + if n.Op() == ir.OGO { + // For "go", assume that the closure is going to escape + // (with an exception for the runtime, which doesn't + // permit heap-allocated closures). + if base.Ctxt.Pkgpath != "runtime" { + clo.SetEsc(ir.EscHeap) + } + } else { + // For defer, just use whatever result escape analysis + // has determined for the defer. + if n.Esc() == ir.EscNever { + clo.SetTransient(true) + clo.SetEsc(ir.EscNone) + } + } + + // Create new top level call to closure over argless function. + topcall := ir.NewCallExpr(n.Pos(), ir.OCALL, clo, []ir.Node{}) + typecheck.Call(topcall) + + // Tag the call to insure that directClosureCall doesn't undo our work. + topcall.PreserveClosure = true + + fn.SetClosureCalled(false) + + // Finally, point the defer statement at the newly generated call. + n.Call = topcall +} + +// isFuncPCIntrinsic returns whether n is a direct call of internal/abi.FuncPCABIxxx functions. +func isFuncPCIntrinsic(n *ir.CallExpr) bool { + if n.Op() != ir.OCALLFUNC || n.X.Op() != ir.ONAME { + return false + } + fn := n.X.(*ir.Name).Sym() + return (fn.Name == "FuncPCABI0" || fn.Name == "FuncPCABIInternal") && + (fn.Pkg.Path == "internal/abi" || fn.Pkg == types.LocalPkg && base.Ctxt.Pkgpath == "internal/abi") +} + +// isIfaceOfFunc returns whether n is an interface conversion from a direct reference of a func. +func isIfaceOfFunc(n ir.Node) bool { + return n.Op() == ir.OCONVIFACE && n.(*ir.ConvExpr).X.Op() == ir.ONAME && n.(*ir.ConvExpr).X.(*ir.Name).Class == ir.PFUNC +} diff --git a/src/cmd/compile/internal/walk/race.go b/src/cmd/compile/internal/walk/race.go new file mode 100644 index 0000000000000000000000000000000000000000..859e5c57f06ff8ebb31d93fe8d84a5507abe7448 --- /dev/null +++ b/src/cmd/compile/internal/walk/race.go @@ -0,0 +1,34 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +func instrument(fn *ir.Func) { + if fn.Pragma&ir.Norace != 0 || (fn.Linksym() != nil && fn.Linksym().ABIWrapper()) { + return + } + + if !base.Flag.Race || !base.Compiling(base.NoRacePkgs) { + fn.SetInstrumentBody(true) + } + + if base.Flag.Race { + lno := base.Pos + base.Pos = src.NoXPos + var init ir.Nodes + fn.Enter.Prepend(mkcallstmt("racefuncenter", mkcall("getcallerpc", types.Types[types.TUINTPTR], &init))) + if len(init) != 0 { + base.Fatalf("race walk: unexpected init for getcallerpc") + } + fn.Exit.Append(mkcallstmt("racefuncexit")) + base.Pos = lno + } +} diff --git a/src/cmd/compile/internal/walk/range.go b/src/cmd/compile/internal/walk/range.go new file mode 100644 index 0000000000000000000000000000000000000000..b1169fdae8a1aba973a7e3023d56b882634e721b --- /dev/null +++ b/src/cmd/compile/internal/walk/range.go @@ -0,0 +1,475 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "unicode/utf8" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/sys" +) + +func cheapComputableIndex(width int64) bool { + switch ssagen.Arch.LinkArch.Family { + // MIPS does not have R+R addressing + // Arm64 may lack ability to generate this code in our assembler, + // but the architecture supports it. + case sys.PPC64, sys.S390X: + return width == 1 + case sys.AMD64, sys.I386, sys.ARM64, sys.ARM: + switch width { + case 1, 2, 4, 8: + return true + } + } + return false +} + +// walkRange transforms various forms of ORANGE into +// simpler forms. The result must be assigned back to n. +// Node n may also be modified in place, and may also be +// the returned node. +func walkRange(nrange *ir.RangeStmt) ir.Node { + if isMapClear(nrange) { + m := nrange.X + lno := ir.SetPos(m) + n := mapClear(m) + base.Pos = lno + return n + } + + nfor := ir.NewForStmt(nrange.Pos(), nil, nil, nil, nil) + nfor.SetInit(nrange.Init()) + nfor.Label = nrange.Label + + // variable name conventions: + // ohv1, hv1, hv2: hidden (old) val 1, 2 + // ha, hit: hidden aggregate, iterator + // hn, hp: hidden len, pointer + // hb: hidden bool + // a, v1, v2: not hidden aggregate, val 1, 2 + + a := nrange.X + t := typecheck.RangeExprType(a.Type()) + lno := ir.SetPos(a) + + v1, v2 := nrange.Key, nrange.Value + + if ir.IsBlank(v2) { + v2 = nil + } + + if ir.IsBlank(v1) && v2 == nil { + v1 = nil + } + + if v1 == nil && v2 != nil { + base.Fatalf("walkRange: v2 != nil while v1 == nil") + } + + var ifGuard *ir.IfStmt + + var body []ir.Node + var init []ir.Node + switch t.Kind() { + default: + base.Fatalf("walkRange") + + case types.TARRAY, types.TSLICE: + if nn := arrayClear(nrange, v1, v2, a); nn != nil { + base.Pos = lno + return nn + } + + // order.stmt arranged for a copy of the array/slice variable if needed. + ha := a + + hv1 := typecheck.Temp(types.Types[types.TINT]) + hn := typecheck.Temp(types.Types[types.TINT]) + + init = append(init, ir.NewAssignStmt(base.Pos, hv1, nil)) + init = append(init, ir.NewAssignStmt(base.Pos, hn, ir.NewUnaryExpr(base.Pos, ir.OLEN, ha))) + + nfor.Cond = ir.NewBinaryExpr(base.Pos, ir.OLT, hv1, hn) + nfor.Post = ir.NewAssignStmt(base.Pos, hv1, ir.NewBinaryExpr(base.Pos, ir.OADD, hv1, ir.NewInt(1))) + + // for range ha { body } + if v1 == nil { + break + } + + // for v1 := range ha { body } + if v2 == nil { + body = []ir.Node{ir.NewAssignStmt(base.Pos, v1, hv1)} + break + } + + // for v1, v2 := range ha { body } + if cheapComputableIndex(t.Elem().Width) { + // v1, v2 = hv1, ha[hv1] + tmp := ir.NewIndexExpr(base.Pos, ha, hv1) + tmp.SetBounded(true) + // Use OAS2 to correctly handle assignments + // of the form "v1, a[v1] := range". + a := ir.NewAssignListStmt(base.Pos, ir.OAS2, []ir.Node{v1, v2}, []ir.Node{hv1, tmp}) + body = []ir.Node{a} + break + } + + // TODO(austin): OFORUNTIL is a strange beast, but is + // necessary for expressing the control flow we need + // while also making "break" and "continue" work. It + // would be nice to just lower ORANGE during SSA, but + // racewalk needs to see many of the operations + // involved in ORANGE's implementation. If racewalk + // moves into SSA, consider moving ORANGE into SSA and + // eliminating OFORUNTIL. + + // TODO(austin): OFORUNTIL inhibits bounds-check + // elimination on the index variable (see #20711). + // Enhance the prove pass to understand this. + ifGuard = ir.NewIfStmt(base.Pos, nil, nil, nil) + ifGuard.Cond = ir.NewBinaryExpr(base.Pos, ir.OLT, hv1, hn) + nfor.SetOp(ir.OFORUNTIL) + + hp := typecheck.Temp(types.NewPtr(t.Elem())) + tmp := ir.NewIndexExpr(base.Pos, ha, ir.NewInt(0)) + tmp.SetBounded(true) + init = append(init, ir.NewAssignStmt(base.Pos, hp, typecheck.NodAddr(tmp))) + + // Use OAS2 to correctly handle assignments + // of the form "v1, a[v1] := range". + a := ir.NewAssignListStmt(base.Pos, ir.OAS2, []ir.Node{v1, v2}, []ir.Node{hv1, ir.NewStarExpr(base.Pos, hp)}) + body = append(body, a) + + // Advance pointer as part of the late increment. + // + // This runs *after* the condition check, so we know + // advancing the pointer is safe and won't go past the + // end of the allocation. + as := ir.NewAssignStmt(base.Pos, hp, addptr(hp, t.Elem().Width)) + nfor.Late = []ir.Node{typecheck.Stmt(as)} + + case types.TMAP: + // order.stmt allocated the iterator for us. + // we only use a once, so no copy needed. + ha := a + + hit := nrange.Prealloc + th := hit.Type() + // depends on layout of iterator struct. + // See cmd/compile/internal/reflectdata/reflect.go:MapIterType + keysym := th.Field(0).Sym + elemsym := th.Field(1).Sym // ditto + + fn := typecheck.LookupRuntime("mapiterinit") + + fn = typecheck.SubstArgTypes(fn, t.Key(), t.Elem(), th) + init = append(init, mkcallstmt1(fn, reflectdata.TypePtr(t), ha, typecheck.NodAddr(hit))) + nfor.Cond = ir.NewBinaryExpr(base.Pos, ir.ONE, ir.NewSelectorExpr(base.Pos, ir.ODOT, hit, keysym), typecheck.NodNil()) + + fn = typecheck.LookupRuntime("mapiternext") + fn = typecheck.SubstArgTypes(fn, th) + nfor.Post = mkcallstmt1(fn, typecheck.NodAddr(hit)) + + key := ir.NewStarExpr(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, hit, keysym)) + if v1 == nil { + body = nil + } else if v2 == nil { + body = []ir.Node{ir.NewAssignStmt(base.Pos, v1, key)} + } else { + elem := ir.NewStarExpr(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, hit, elemsym)) + a := ir.NewAssignListStmt(base.Pos, ir.OAS2, []ir.Node{v1, v2}, []ir.Node{key, elem}) + body = []ir.Node{a} + } + + case types.TCHAN: + // order.stmt arranged for a copy of the channel variable. + ha := a + + hv1 := typecheck.Temp(t.Elem()) + hv1.SetTypecheck(1) + if t.Elem().HasPointers() { + init = append(init, ir.NewAssignStmt(base.Pos, hv1, nil)) + } + hb := typecheck.Temp(types.Types[types.TBOOL]) + + nfor.Cond = ir.NewBinaryExpr(base.Pos, ir.ONE, hb, ir.NewBool(false)) + lhs := []ir.Node{hv1, hb} + rhs := []ir.Node{ir.NewUnaryExpr(base.Pos, ir.ORECV, ha)} + a := ir.NewAssignListStmt(base.Pos, ir.OAS2RECV, lhs, rhs) + a.SetTypecheck(1) + nfor.Cond = ir.InitExpr([]ir.Node{a}, nfor.Cond) + if v1 == nil { + body = nil + } else { + body = []ir.Node{ir.NewAssignStmt(base.Pos, v1, hv1)} + } + // Zero hv1. This prevents hv1 from being the sole, inaccessible + // reference to an otherwise GC-able value during the next channel receive. + // See issue 15281. + body = append(body, ir.NewAssignStmt(base.Pos, hv1, nil)) + + case types.TSTRING: + // Transform string range statements like "for v1, v2 = range a" into + // + // ha := a + // for hv1 := 0; hv1 < len(ha); { + // hv1t := hv1 + // hv2 := rune(ha[hv1]) + // if hv2 < utf8.RuneSelf { + // hv1++ + // } else { + // hv2, hv1 = decoderune(ha, hv1) + // } + // v1, v2 = hv1t, hv2 + // // original body + // } + + // order.stmt arranged for a copy of the string variable. + ha := a + + hv1 := typecheck.Temp(types.Types[types.TINT]) + hv1t := typecheck.Temp(types.Types[types.TINT]) + hv2 := typecheck.Temp(types.RuneType) + + // hv1 := 0 + init = append(init, ir.NewAssignStmt(base.Pos, hv1, nil)) + + // hv1 < len(ha) + nfor.Cond = ir.NewBinaryExpr(base.Pos, ir.OLT, hv1, ir.NewUnaryExpr(base.Pos, ir.OLEN, ha)) + + if v1 != nil { + // hv1t = hv1 + body = append(body, ir.NewAssignStmt(base.Pos, hv1t, hv1)) + } + + // hv2 := rune(ha[hv1]) + nind := ir.NewIndexExpr(base.Pos, ha, hv1) + nind.SetBounded(true) + body = append(body, ir.NewAssignStmt(base.Pos, hv2, typecheck.Conv(nind, types.RuneType))) + + // if hv2 < utf8.RuneSelf + nif := ir.NewIfStmt(base.Pos, nil, nil, nil) + nif.Cond = ir.NewBinaryExpr(base.Pos, ir.OLT, hv2, ir.NewInt(utf8.RuneSelf)) + + // hv1++ + nif.Body = []ir.Node{ir.NewAssignStmt(base.Pos, hv1, ir.NewBinaryExpr(base.Pos, ir.OADD, hv1, ir.NewInt(1)))} + + // } else { + // hv2, hv1 = decoderune(ha, hv1) + fn := typecheck.LookupRuntime("decoderune") + call := mkcall1(fn, fn.Type().Results(), &nif.Else, ha, hv1) + a := ir.NewAssignListStmt(base.Pos, ir.OAS2, []ir.Node{hv2, hv1}, []ir.Node{call}) + nif.Else.Append(a) + + body = append(body, nif) + + if v1 != nil { + if v2 != nil { + // v1, v2 = hv1t, hv2 + a := ir.NewAssignListStmt(base.Pos, ir.OAS2, []ir.Node{v1, v2}, []ir.Node{hv1t, hv2}) + body = append(body, a) + } else { + // v1 = hv1t + body = append(body, ir.NewAssignStmt(base.Pos, v1, hv1t)) + } + } + } + + typecheck.Stmts(init) + + if ifGuard != nil { + ifGuard.PtrInit().Append(init...) + ifGuard = typecheck.Stmt(ifGuard).(*ir.IfStmt) + } else { + nfor.PtrInit().Append(init...) + } + + typecheck.Stmts(nfor.Cond.Init()) + + nfor.Cond = typecheck.Expr(nfor.Cond) + nfor.Cond = typecheck.DefaultLit(nfor.Cond, nil) + nfor.Post = typecheck.Stmt(nfor.Post) + typecheck.Stmts(body) + nfor.Body.Append(body...) + nfor.Body.Append(nrange.Body...) + + var n ir.Node = nfor + if ifGuard != nil { + ifGuard.Body = []ir.Node{n} + n = ifGuard + } + + n = walkStmt(n) + + base.Pos = lno + return n +} + +// isMapClear checks if n is of the form: +// +// for k := range m { +// delete(m, k) +// } +// +// where == for keys of map m is reflexive. +func isMapClear(n *ir.RangeStmt) bool { + if base.Flag.N != 0 || base.Flag.Cfg.Instrumenting { + return false + } + + t := n.X.Type() + if n.Op() != ir.ORANGE || t.Kind() != types.TMAP || n.Key == nil || n.Value != nil { + return false + } + + k := n.Key + // Require k to be a new variable name. + if !ir.DeclaredBy(k, n) { + return false + } + + if len(n.Body) != 1 { + return false + } + + stmt := n.Body[0] // only stmt in body + if stmt == nil || stmt.Op() != ir.ODELETE { + return false + } + + m := n.X + if delete := stmt.(*ir.CallExpr); !ir.SameSafeExpr(delete.Args[0], m) || !ir.SameSafeExpr(delete.Args[1], k) { + return false + } + + // Keys where equality is not reflexive can not be deleted from maps. + if !types.IsReflexive(t.Key()) { + return false + } + + return true +} + +// mapClear constructs a call to runtime.mapclear for the map m. +func mapClear(m ir.Node) ir.Node { + t := m.Type() + + // instantiate mapclear(typ *type, hmap map[any]any) + fn := typecheck.LookupRuntime("mapclear") + fn = typecheck.SubstArgTypes(fn, t.Key(), t.Elem()) + n := mkcallstmt1(fn, reflectdata.TypePtr(t), m) + return walkStmt(typecheck.Stmt(n)) +} + +// Lower n into runtime·memclr if possible, for +// fast zeroing of slices and arrays (issue 5373). +// Look for instances of +// +// for i := range a { +// a[i] = zero +// } +// +// in which the evaluation of a is side-effect-free. +// +// Parameters are as in walkRange: "for v1, v2 = range a". +func arrayClear(loop *ir.RangeStmt, v1, v2, a ir.Node) ir.Node { + if base.Flag.N != 0 || base.Flag.Cfg.Instrumenting { + return nil + } + + if v1 == nil || v2 != nil { + return nil + } + + if len(loop.Body) != 1 || loop.Body[0] == nil { + return nil + } + + stmt1 := loop.Body[0] // only stmt in body + if stmt1.Op() != ir.OAS { + return nil + } + stmt := stmt1.(*ir.AssignStmt) + if stmt.X.Op() != ir.OINDEX { + return nil + } + lhs := stmt.X.(*ir.IndexExpr) + + if !ir.SameSafeExpr(lhs.X, a) || !ir.SameSafeExpr(lhs.Index, v1) { + return nil + } + + elemsize := typecheck.RangeExprType(loop.X.Type()).Elem().Width + if elemsize <= 0 || !ir.IsZero(stmt.Y) { + return nil + } + + // Convert to + // if len(a) != 0 { + // hp = &a[0] + // hn = len(a)*sizeof(elem(a)) + // memclr{NoHeap,Has}Pointers(hp, hn) + // i = len(a) - 1 + // } + n := ir.NewIfStmt(base.Pos, nil, nil, nil) + n.Cond = ir.NewBinaryExpr(base.Pos, ir.ONE, ir.NewUnaryExpr(base.Pos, ir.OLEN, a), ir.NewInt(0)) + + // hp = &a[0] + hp := typecheck.Temp(types.Types[types.TUNSAFEPTR]) + + ix := ir.NewIndexExpr(base.Pos, a, ir.NewInt(0)) + ix.SetBounded(true) + addr := typecheck.ConvNop(typecheck.NodAddr(ix), types.Types[types.TUNSAFEPTR]) + n.Body.Append(ir.NewAssignStmt(base.Pos, hp, addr)) + + // hn = len(a) * sizeof(elem(a)) + hn := typecheck.Temp(types.Types[types.TUINTPTR]) + mul := typecheck.Conv(ir.NewBinaryExpr(base.Pos, ir.OMUL, ir.NewUnaryExpr(base.Pos, ir.OLEN, a), ir.NewInt(elemsize)), types.Types[types.TUINTPTR]) + n.Body.Append(ir.NewAssignStmt(base.Pos, hn, mul)) + + var fn ir.Node + if a.Type().Elem().HasPointers() { + // memclrHasPointers(hp, hn) + ir.CurFunc.SetWBPos(stmt.Pos()) + fn = mkcallstmt("memclrHasPointers", hp, hn) + } else { + // memclrNoHeapPointers(hp, hn) + fn = mkcallstmt("memclrNoHeapPointers", hp, hn) + } + + n.Body.Append(fn) + + // i = len(a) - 1 + v1 = ir.NewAssignStmt(base.Pos, v1, ir.NewBinaryExpr(base.Pos, ir.OSUB, ir.NewUnaryExpr(base.Pos, ir.OLEN, a), ir.NewInt(1))) + + n.Body.Append(v1) + + n.Cond = typecheck.Expr(n.Cond) + n.Cond = typecheck.DefaultLit(n.Cond, nil) + typecheck.Stmts(n.Body) + return walkStmt(n) +} + +// addptr returns (*T)(uintptr(p) + n). +func addptr(p ir.Node, n int64) ir.Node { + t := p.Type() + + p = ir.NewConvExpr(base.Pos, ir.OCONVNOP, nil, p) + p.SetType(types.Types[types.TUINTPTR]) + + p = ir.NewBinaryExpr(base.Pos, ir.OADD, p, ir.NewInt(n)) + + p = ir.NewConvExpr(base.Pos, ir.OCONVNOP, nil, p) + p.SetType(t) + + return p +} diff --git a/src/cmd/compile/internal/walk/select.go b/src/cmd/compile/internal/walk/select.go new file mode 100644 index 0000000000000000000000000000000000000000..d2b67ddf55a7abc8d5ccba5dc9d991aa7cd7fd2e --- /dev/null +++ b/src/cmd/compile/internal/walk/select.go @@ -0,0 +1,291 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" +) + +func walkSelect(sel *ir.SelectStmt) { + lno := ir.SetPos(sel) + if sel.Walked() { + base.Fatalf("double walkSelect") + } + sel.SetWalked(true) + + init := ir.TakeInit(sel) + + init = append(init, walkSelectCases(sel.Cases)...) + sel.Cases = nil + + sel.Compiled = init + walkStmtList(sel.Compiled) + + base.Pos = lno +} + +func walkSelectCases(cases []*ir.CommClause) []ir.Node { + ncas := len(cases) + sellineno := base.Pos + + // optimization: zero-case select + if ncas == 0 { + return []ir.Node{mkcallstmt("block")} + } + + // optimization: one-case select: single op. + if ncas == 1 { + cas := cases[0] + ir.SetPos(cas) + l := cas.Init() + if cas.Comm != nil { // not default: + n := cas.Comm + l = append(l, ir.TakeInit(n)...) + switch n.Op() { + default: + base.Fatalf("select %v", n.Op()) + + case ir.OSEND: + // already ok + + case ir.OSELRECV2: + r := n.(*ir.AssignListStmt) + if ir.IsBlank(r.Lhs[0]) && ir.IsBlank(r.Lhs[1]) { + n = r.Rhs[0] + break + } + r.SetOp(ir.OAS2RECV) + } + + l = append(l, n) + } + + l = append(l, cas.Body...) + l = append(l, ir.NewBranchStmt(base.Pos, ir.OBREAK, nil)) + return l + } + + // convert case value arguments to addresses. + // this rewrite is used by both the general code and the next optimization. + var dflt *ir.CommClause + for _, cas := range cases { + ir.SetPos(cas) + n := cas.Comm + if n == nil { + dflt = cas + continue + } + switch n.Op() { + case ir.OSEND: + n := n.(*ir.SendStmt) + n.Value = typecheck.NodAddr(n.Value) + n.Value = typecheck.Expr(n.Value) + + case ir.OSELRECV2: + n := n.(*ir.AssignListStmt) + if !ir.IsBlank(n.Lhs[0]) { + n.Lhs[0] = typecheck.NodAddr(n.Lhs[0]) + n.Lhs[0] = typecheck.Expr(n.Lhs[0]) + } + } + } + + // optimization: two-case select but one is default: single non-blocking op. + if ncas == 2 && dflt != nil { + cas := cases[0] + if cas == dflt { + cas = cases[1] + } + + n := cas.Comm + ir.SetPos(n) + r := ir.NewIfStmt(base.Pos, nil, nil, nil) + *r.PtrInit() = cas.Init() + var cond ir.Node + switch n.Op() { + default: + base.Fatalf("select %v", n.Op()) + + case ir.OSEND: + // if selectnbsend(c, v) { body } else { default body } + n := n.(*ir.SendStmt) + ch := n.Chan + cond = mkcall1(chanfn("selectnbsend", 2, ch.Type()), types.Types[types.TBOOL], r.PtrInit(), ch, n.Value) + + case ir.OSELRECV2: + n := n.(*ir.AssignListStmt) + recv := n.Rhs[0].(*ir.UnaryExpr) + ch := recv.X + elem := n.Lhs[0] + if ir.IsBlank(elem) { + elem = typecheck.NodNil() + } + cond = typecheck.Temp(types.Types[types.TBOOL]) + fn := chanfn("selectnbrecv", 2, ch.Type()) + call := mkcall1(fn, fn.Type().Results(), r.PtrInit(), elem, ch) + as := ir.NewAssignListStmt(r.Pos(), ir.OAS2, []ir.Node{cond, n.Lhs[1]}, []ir.Node{call}) + r.PtrInit().Append(typecheck.Stmt(as)) + } + + r.Cond = typecheck.Expr(cond) + r.Body = cas.Body + r.Else = append(dflt.Init(), dflt.Body...) + return []ir.Node{r, ir.NewBranchStmt(base.Pos, ir.OBREAK, nil)} + } + + if dflt != nil { + ncas-- + } + casorder := make([]*ir.CommClause, ncas) + nsends, nrecvs := 0, 0 + + var init []ir.Node + + // generate sel-struct + base.Pos = sellineno + selv := typecheck.Temp(types.NewArray(scasetype(), int64(ncas))) + init = append(init, typecheck.Stmt(ir.NewAssignStmt(base.Pos, selv, nil))) + + // No initialization for order; runtime.selectgo is responsible for that. + order := typecheck.Temp(types.NewArray(types.Types[types.TUINT16], 2*int64(ncas))) + + var pc0, pcs ir.Node + if base.Flag.Race { + pcs = typecheck.Temp(types.NewArray(types.Types[types.TUINTPTR], int64(ncas))) + pc0 = typecheck.Expr(typecheck.NodAddr(ir.NewIndexExpr(base.Pos, pcs, ir.NewInt(0)))) + } else { + pc0 = typecheck.NodNil() + } + + // register cases + for _, cas := range cases { + ir.SetPos(cas) + + init = append(init, ir.TakeInit(cas)...) + + n := cas.Comm + if n == nil { // default: + continue + } + + var i int + var c, elem ir.Node + switch n.Op() { + default: + base.Fatalf("select %v", n.Op()) + case ir.OSEND: + n := n.(*ir.SendStmt) + i = nsends + nsends++ + c = n.Chan + elem = n.Value + case ir.OSELRECV2: + n := n.(*ir.AssignListStmt) + nrecvs++ + i = ncas - nrecvs + recv := n.Rhs[0].(*ir.UnaryExpr) + c = recv.X + elem = n.Lhs[0] + } + + casorder[i] = cas + + setField := func(f string, val ir.Node) { + r := ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, ir.NewIndexExpr(base.Pos, selv, ir.NewInt(int64(i))), typecheck.Lookup(f)), val) + init = append(init, typecheck.Stmt(r)) + } + + c = typecheck.ConvNop(c, types.Types[types.TUNSAFEPTR]) + setField("c", c) + if !ir.IsBlank(elem) { + elem = typecheck.ConvNop(elem, types.Types[types.TUNSAFEPTR]) + setField("elem", elem) + } + + // TODO(mdempsky): There should be a cleaner way to + // handle this. + if base.Flag.Race { + r := mkcallstmt("selectsetpc", typecheck.NodAddr(ir.NewIndexExpr(base.Pos, pcs, ir.NewInt(int64(i))))) + init = append(init, r) + } + } + if nsends+nrecvs != ncas { + base.Fatalf("walkSelectCases: miscount: %v + %v != %v", nsends, nrecvs, ncas) + } + + // run the select + base.Pos = sellineno + chosen := typecheck.Temp(types.Types[types.TINT]) + recvOK := typecheck.Temp(types.Types[types.TBOOL]) + r := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil) + r.Lhs = []ir.Node{chosen, recvOK} + fn := typecheck.LookupRuntime("selectgo") + var fnInit ir.Nodes + r.Rhs = []ir.Node{mkcall1(fn, fn.Type().Results(), &fnInit, bytePtrToIndex(selv, 0), bytePtrToIndex(order, 0), pc0, ir.NewInt(int64(nsends)), ir.NewInt(int64(nrecvs)), ir.NewBool(dflt == nil))} + init = append(init, fnInit...) + init = append(init, typecheck.Stmt(r)) + + // selv and order are no longer alive after selectgo. + init = append(init, ir.NewUnaryExpr(base.Pos, ir.OVARKILL, selv)) + init = append(init, ir.NewUnaryExpr(base.Pos, ir.OVARKILL, order)) + if base.Flag.Race { + init = append(init, ir.NewUnaryExpr(base.Pos, ir.OVARKILL, pcs)) + } + + // dispatch cases + dispatch := func(cond ir.Node, cas *ir.CommClause) { + cond = typecheck.Expr(cond) + cond = typecheck.DefaultLit(cond, nil) + + r := ir.NewIfStmt(base.Pos, cond, nil, nil) + + if n := cas.Comm; n != nil && n.Op() == ir.OSELRECV2 { + n := n.(*ir.AssignListStmt) + if !ir.IsBlank(n.Lhs[1]) { + x := ir.NewAssignStmt(base.Pos, n.Lhs[1], recvOK) + r.Body.Append(typecheck.Stmt(x)) + } + } + + r.Body.Append(cas.Body.Take()...) + r.Body.Append(ir.NewBranchStmt(base.Pos, ir.OBREAK, nil)) + init = append(init, r) + } + + if dflt != nil { + ir.SetPos(dflt) + dispatch(ir.NewBinaryExpr(base.Pos, ir.OLT, chosen, ir.NewInt(0)), dflt) + } + for i, cas := range casorder { + ir.SetPos(cas) + dispatch(ir.NewBinaryExpr(base.Pos, ir.OEQ, chosen, ir.NewInt(int64(i))), cas) + } + + return init +} + +// bytePtrToIndex returns a Node representing "(*byte)(&n[i])". +func bytePtrToIndex(n ir.Node, i int64) ir.Node { + s := typecheck.NodAddr(ir.NewIndexExpr(base.Pos, n, ir.NewInt(i))) + t := types.NewPtr(types.Types[types.TUINT8]) + return typecheck.ConvNop(s, t) +} + +var scase *types.Type + +// Keep in sync with src/runtime/select.go. +func scasetype() *types.Type { + if scase == nil { + scase = types.NewStruct(types.NoPkg, []*types.Field{ + types.NewField(base.Pos, typecheck.Lookup("c"), types.Types[types.TUNSAFEPTR]), + types.NewField(base.Pos, typecheck.Lookup("elem"), types.Types[types.TUNSAFEPTR]), + }) + scase.SetNoalg(true) + } + return scase +} diff --git a/src/cmd/compile/internal/walk/stmt.go b/src/cmd/compile/internal/walk/stmt.go new file mode 100644 index 0000000000000000000000000000000000000000..0bf76680c4626546f00fee0890a4418a157c77b5 --- /dev/null +++ b/src/cmd/compile/internal/walk/stmt.go @@ -0,0 +1,337 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" +) + +// The result of walkStmt MUST be assigned back to n, e.g. +// n.Left = walkStmt(n.Left) +func walkStmt(n ir.Node) ir.Node { + if n == nil { + return n + } + + ir.SetPos(n) + + walkStmtList(n.Init()) + + switch n.Op() { + default: + if n.Op() == ir.ONAME { + n := n.(*ir.Name) + base.Errorf("%v is not a top level statement", n.Sym()) + } else { + base.Errorf("%v is not a top level statement", n.Op()) + } + ir.Dump("nottop", n) + return n + + case ir.OAS, + ir.OASOP, + ir.OAS2, + ir.OAS2DOTTYPE, + ir.OAS2RECV, + ir.OAS2FUNC, + ir.OAS2MAPR, + ir.OCLOSE, + ir.OCOPY, + ir.OCALLMETH, + ir.OCALLINTER, + ir.OCALL, + ir.OCALLFUNC, + ir.ODELETE, + ir.OSEND, + ir.OPRINT, + ir.OPRINTN, + ir.OPANIC, + ir.ORECOVER, + ir.OGETG: + if n.Typecheck() == 0 { + base.Fatalf("missing typecheck: %+v", n) + } + init := ir.TakeInit(n) + n = walkExpr(n, &init) + if n.Op() == ir.ONAME { + // copy rewrote to a statement list and a temp for the length. + // Throw away the temp to avoid plain values as statements. + n = ir.NewBlockStmt(n.Pos(), init) + init = nil + } + if len(init) > 0 { + switch n.Op() { + case ir.OAS, ir.OAS2, ir.OBLOCK: + n.(ir.InitNode).PtrInit().Prepend(init...) + + default: + init.Append(n) + n = ir.NewBlockStmt(n.Pos(), init) + } + } + return n + + // special case for a receive where we throw away + // the value received. + case ir.ORECV: + n := n.(*ir.UnaryExpr) + return walkRecv(n) + + case ir.OBREAK, + ir.OCONTINUE, + ir.OFALL, + ir.OGOTO, + ir.OLABEL, + ir.ODCL, + ir.ODCLCONST, + ir.ODCLTYPE, + ir.OCHECKNIL, + ir.OVARDEF, + ir.OVARKILL, + ir.OVARLIVE: + return n + + case ir.OBLOCK: + n := n.(*ir.BlockStmt) + walkStmtList(n.List) + return n + + case ir.OCASE: + base.Errorf("case statement out of place") + panic("unreachable") + + case ir.ODEFER: + n := n.(*ir.GoDeferStmt) + ir.CurFunc.SetHasDefer(true) + ir.CurFunc.NumDefers++ + if ir.CurFunc.NumDefers > maxOpenDefers { + // Don't allow open-coded defers if there are more than + // 8 defers in the function, since we use a single + // byte to record active defers. + ir.CurFunc.SetOpenCodedDeferDisallowed(true) + } + if n.Esc() != ir.EscNever { + // If n.Esc is not EscNever, then this defer occurs in a loop, + // so open-coded defers cannot be used in this function. + ir.CurFunc.SetOpenCodedDeferDisallowed(true) + } + fallthrough + case ir.OGO: + n := n.(*ir.GoDeferStmt) + return walkGoDefer(n) + + case ir.OFOR, ir.OFORUNTIL: + n := n.(*ir.ForStmt) + return walkFor(n) + + case ir.OIF: + n := n.(*ir.IfStmt) + return walkIf(n) + + case ir.ORETURN: + n := n.(*ir.ReturnStmt) + return walkReturn(n) + + case ir.OTAILCALL: + n := n.(*ir.TailCallStmt) + return n + + case ir.OINLMARK: + n := n.(*ir.InlineMarkStmt) + return n + + case ir.OSELECT: + n := n.(*ir.SelectStmt) + walkSelect(n) + return n + + case ir.OSWITCH: + n := n.(*ir.SwitchStmt) + walkSwitch(n) + return n + + case ir.ORANGE: + n := n.(*ir.RangeStmt) + return walkRange(n) + } + + // No return! Each case must return (or panic), + // to avoid confusion about what gets returned + // in the presence of type assertions. +} + +func walkStmtList(s []ir.Node) { + for i := range s { + s[i] = walkStmt(s[i]) + } +} + +// walkFor walks an OFOR or OFORUNTIL node. +func walkFor(n *ir.ForStmt) ir.Node { + if n.Cond != nil { + init := ir.TakeInit(n.Cond) + walkStmtList(init) + n.Cond = walkExpr(n.Cond, &init) + n.Cond = ir.InitExpr(init, n.Cond) + } + + n.Post = walkStmt(n.Post) + if n.Op() == ir.OFORUNTIL { + walkStmtList(n.Late) + } + walkStmtList(n.Body) + return n +} + +// walkGoDefer walks an OGO or ODEFER node. +func walkGoDefer(n *ir.GoDeferStmt) ir.Node { + var init ir.Nodes + switch call := n.Call; call.Op() { + case ir.OPRINT, ir.OPRINTN: + call := call.(*ir.CallExpr) + n.Call = wrapCall(call, &init) + + case ir.ODELETE: + call := call.(*ir.CallExpr) + n.Call = wrapCall(call, &init) + + case ir.OCOPY: + call := call.(*ir.BinaryExpr) + n.Call = walkCopy(call, &init, true) + + case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER: + call := call.(*ir.CallExpr) + if len(call.KeepAlive) > 0 { + n.Call = wrapCall(call, &init) + } else { + n.Call = walkExpr(call, &init) + } + + default: + n.Call = walkExpr(call, &init) + } + if len(init) > 0 { + init.Append(n) + return ir.NewBlockStmt(n.Pos(), init) + } + return n +} + +// walkIf walks an OIF node. +func walkIf(n *ir.IfStmt) ir.Node { + n.Cond = walkExpr(n.Cond, n.PtrInit()) + walkStmtList(n.Body) + walkStmtList(n.Else) + return n +} + +// Rewrite +// go builtin(x, y, z) +// into +// go func(a1, a2, a3) { +// builtin(a1, a2, a3) +// }(x, y, z) +// for print, println, and delete. +// +// Rewrite +// go f(x, y, uintptr(unsafe.Pointer(z))) +// into +// go func(a1, a2, a3) { +// f(a1, a2, uintptr(a3)) +// }(x, y, unsafe.Pointer(z)) +// for function contains unsafe-uintptr arguments. + +var wrapCall_prgen int + +// The result of wrapCall MUST be assigned back to n, e.g. +// n.Left = wrapCall(n.Left, init) +func wrapCall(n *ir.CallExpr, init *ir.Nodes) ir.Node { + if len(n.Init()) != 0 { + walkStmtList(n.Init()) + init.Append(ir.TakeInit(n)...) + } + + isBuiltinCall := n.Op() != ir.OCALLFUNC && n.Op() != ir.OCALLMETH && n.Op() != ir.OCALLINTER + + // Turn f(a, b, []T{c, d, e}...) back into f(a, b, c, d, e). + if !isBuiltinCall && n.IsDDD { + undoVariadic(n) + } + + wrapArgs := n.Args + // If there's a receiver argument, it needs to be passed through the wrapper too. + if n.Op() == ir.OCALLMETH || n.Op() == ir.OCALLINTER { + recv := n.X.(*ir.SelectorExpr).X + wrapArgs = append([]ir.Node{recv}, wrapArgs...) + } + + // origArgs keeps track of what argument is uintptr-unsafe/unsafe-uintptr conversion. + origArgs := make([]ir.Node, len(wrapArgs)) + var funcArgs []*ir.Field + for i, arg := range wrapArgs { + s := typecheck.LookupNum("a", i) + if !isBuiltinCall && arg.Op() == ir.OCONVNOP && arg.Type().IsUintptr() && arg.(*ir.ConvExpr).X.Type().IsUnsafePtr() { + origArgs[i] = arg + arg = arg.(*ir.ConvExpr).X + wrapArgs[i] = arg + } + funcArgs = append(funcArgs, ir.NewField(base.Pos, s, nil, arg.Type())) + } + t := ir.NewFuncType(base.Pos, nil, funcArgs, nil) + + wrapCall_prgen++ + sym := typecheck.LookupNum("wrap·", wrapCall_prgen) + fn := typecheck.DeclFunc(sym, t) + + args := ir.ParamNames(t.Type()) + for i, origArg := range origArgs { + if origArg == nil { + continue + } + args[i] = ir.NewConvExpr(base.Pos, origArg.Op(), origArg.Type(), args[i]) + } + if n.Op() == ir.OCALLMETH || n.Op() == ir.OCALLINTER { + // Move wrapped receiver argument back to its appropriate place. + recv := typecheck.Expr(args[0]) + n.X.(*ir.SelectorExpr).X = recv + args = args[1:] + } + call := ir.NewCallExpr(base.Pos, n.Op(), n.X, args) + if !isBuiltinCall { + call.SetOp(ir.OCALL) + call.IsDDD = n.IsDDD + } + fn.Body = []ir.Node{call} + + typecheck.FinishFuncBody() + + typecheck.Func(fn) + typecheck.Stmts(fn.Body) + typecheck.Target.Decls = append(typecheck.Target.Decls, fn) + + call = ir.NewCallExpr(base.Pos, ir.OCALL, fn.Nname, wrapArgs) + return walkExpr(typecheck.Stmt(call), init) +} + +// undoVariadic turns a call to a variadic function of the form +// +// f(a, b, []T{c, d, e}...) +// +// back into +// +// f(a, b, c, d, e) +// +func undoVariadic(call *ir.CallExpr) { + if call.IsDDD { + last := len(call.Args) - 1 + if va := call.Args[last]; va.Op() == ir.OSLICELIT { + va := va.(*ir.CompLitExpr) + call.Args = append(call.Args[:last], va.List...) + call.IsDDD = false + } + } +} diff --git a/src/cmd/compile/internal/walk/switch.go b/src/cmd/compile/internal/walk/switch.go new file mode 100644 index 0000000000000000000000000000000000000000..162de018f637ef5a329d8a31cab1f64cc7906b54 --- /dev/null +++ b/src/cmd/compile/internal/walk/switch.go @@ -0,0 +1,568 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "go/constant" + "go/token" + "sort" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +// walkSwitch walks a switch statement. +func walkSwitch(sw *ir.SwitchStmt) { + // Guard against double walk, see #25776. + if sw.Walked() { + return // Was fatal, but eliminating every possible source of double-walking is hard + } + sw.SetWalked(true) + + if sw.Tag != nil && sw.Tag.Op() == ir.OTYPESW { + walkSwitchType(sw) + } else { + walkSwitchExpr(sw) + } +} + +// walkSwitchExpr generates an AST implementing sw. sw is an +// expression switch. +func walkSwitchExpr(sw *ir.SwitchStmt) { + lno := ir.SetPos(sw) + + cond := sw.Tag + sw.Tag = nil + + // convert switch {...} to switch true {...} + if cond == nil { + cond = ir.NewBool(true) + cond = typecheck.Expr(cond) + cond = typecheck.DefaultLit(cond, nil) + } + + // Given "switch string(byteslice)", + // with all cases being side-effect free, + // use a zero-cost alias of the byte slice. + // Do this before calling walkExpr on cond, + // because walkExpr will lower the string + // conversion into a runtime call. + // See issue 24937 for more discussion. + if cond.Op() == ir.OBYTES2STR && allCaseExprsAreSideEffectFree(sw) { + cond := cond.(*ir.ConvExpr) + cond.SetOp(ir.OBYTES2STRTMP) + } + + cond = walkExpr(cond, sw.PtrInit()) + if cond.Op() != ir.OLITERAL && cond.Op() != ir.ONIL { + cond = copyExpr(cond, cond.Type(), &sw.Compiled) + } + + base.Pos = lno + + s := exprSwitch{ + exprname: cond, + } + + var defaultGoto ir.Node + var body ir.Nodes + for _, ncase := range sw.Cases { + label := typecheck.AutoLabel(".s") + jmp := ir.NewBranchStmt(ncase.Pos(), ir.OGOTO, label) + + // Process case dispatch. + if len(ncase.List) == 0 { + if defaultGoto != nil { + base.Fatalf("duplicate default case not detected during typechecking") + } + defaultGoto = jmp + } + + for _, n1 := range ncase.List { + s.Add(ncase.Pos(), n1, jmp) + } + + // Process body. + body.Append(ir.NewLabelStmt(ncase.Pos(), label)) + body.Append(ncase.Body...) + if fall, pos := endsInFallthrough(ncase.Body); !fall { + br := ir.NewBranchStmt(base.Pos, ir.OBREAK, nil) + br.SetPos(pos) + body.Append(br) + } + } + sw.Cases = nil + + if defaultGoto == nil { + br := ir.NewBranchStmt(base.Pos, ir.OBREAK, nil) + br.SetPos(br.Pos().WithNotStmt()) + defaultGoto = br + } + + s.Emit(&sw.Compiled) + sw.Compiled.Append(defaultGoto) + sw.Compiled.Append(body.Take()...) + walkStmtList(sw.Compiled) +} + +// An exprSwitch walks an expression switch. +type exprSwitch struct { + exprname ir.Node // value being switched on + + done ir.Nodes + clauses []exprClause +} + +type exprClause struct { + pos src.XPos + lo, hi ir.Node + jmp ir.Node +} + +func (s *exprSwitch) Add(pos src.XPos, expr, jmp ir.Node) { + c := exprClause{pos: pos, lo: expr, hi: expr, jmp: jmp} + if types.IsOrdered[s.exprname.Type().Kind()] && expr.Op() == ir.OLITERAL { + s.clauses = append(s.clauses, c) + return + } + + s.flush() + s.clauses = append(s.clauses, c) + s.flush() +} + +func (s *exprSwitch) Emit(out *ir.Nodes) { + s.flush() + out.Append(s.done.Take()...) +} + +func (s *exprSwitch) flush() { + cc := s.clauses + s.clauses = nil + if len(cc) == 0 { + return + } + + // Caution: If len(cc) == 1, then cc[0] might not an OLITERAL. + // The code below is structured to implicitly handle this case + // (e.g., sort.Slice doesn't need to invoke the less function + // when there's only a single slice element). + + if s.exprname.Type().IsString() && len(cc) >= 2 { + // Sort strings by length and then by value. It is + // much cheaper to compare lengths than values, and + // all we need here is consistency. We respect this + // sorting below. + sort.Slice(cc, func(i, j int) bool { + si := ir.StringVal(cc[i].lo) + sj := ir.StringVal(cc[j].lo) + if len(si) != len(sj) { + return len(si) < len(sj) + } + return si < sj + }) + + // runLen returns the string length associated with a + // particular run of exprClauses. + runLen := func(run []exprClause) int64 { return int64(len(ir.StringVal(run[0].lo))) } + + // Collapse runs of consecutive strings with the same length. + var runs [][]exprClause + start := 0 + for i := 1; i < len(cc); i++ { + if runLen(cc[start:]) != runLen(cc[i:]) { + runs = append(runs, cc[start:i]) + start = i + } + } + runs = append(runs, cc[start:]) + + // Perform two-level binary search. + binarySearch(len(runs), &s.done, + func(i int) ir.Node { + return ir.NewBinaryExpr(base.Pos, ir.OLE, ir.NewUnaryExpr(base.Pos, ir.OLEN, s.exprname), ir.NewInt(runLen(runs[i-1]))) + }, + func(i int, nif *ir.IfStmt) { + run := runs[i] + nif.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, ir.NewUnaryExpr(base.Pos, ir.OLEN, s.exprname), ir.NewInt(runLen(run))) + s.search(run, &nif.Body) + }, + ) + return + } + + sort.Slice(cc, func(i, j int) bool { + return constant.Compare(cc[i].lo.Val(), token.LSS, cc[j].lo.Val()) + }) + + // Merge consecutive integer cases. + if s.exprname.Type().IsInteger() { + consecutive := func(last, next constant.Value) bool { + delta := constant.BinaryOp(next, token.SUB, last) + return constant.Compare(delta, token.EQL, constant.MakeInt64(1)) + } + + merged := cc[:1] + for _, c := range cc[1:] { + last := &merged[len(merged)-1] + if last.jmp == c.jmp && consecutive(last.hi.Val(), c.lo.Val()) { + last.hi = c.lo + } else { + merged = append(merged, c) + } + } + cc = merged + } + + s.search(cc, &s.done) +} + +func (s *exprSwitch) search(cc []exprClause, out *ir.Nodes) { + binarySearch(len(cc), out, + func(i int) ir.Node { + return ir.NewBinaryExpr(base.Pos, ir.OLE, s.exprname, cc[i-1].hi) + }, + func(i int, nif *ir.IfStmt) { + c := &cc[i] + nif.Cond = c.test(s.exprname) + nif.Body = []ir.Node{c.jmp} + }, + ) +} + +func (c *exprClause) test(exprname ir.Node) ir.Node { + // Integer range. + if c.hi != c.lo { + low := ir.NewBinaryExpr(c.pos, ir.OGE, exprname, c.lo) + high := ir.NewBinaryExpr(c.pos, ir.OLE, exprname, c.hi) + return ir.NewLogicalExpr(c.pos, ir.OANDAND, low, high) + } + + // Optimize "switch true { ...}" and "switch false { ... }". + if ir.IsConst(exprname, constant.Bool) && !c.lo.Type().IsInterface() { + if ir.BoolVal(exprname) { + return c.lo + } else { + return ir.NewUnaryExpr(c.pos, ir.ONOT, c.lo) + } + } + + return ir.NewBinaryExpr(c.pos, ir.OEQ, exprname, c.lo) +} + +func allCaseExprsAreSideEffectFree(sw *ir.SwitchStmt) bool { + // In theory, we could be more aggressive, allowing any + // side-effect-free expressions in cases, but it's a bit + // tricky because some of that information is unavailable due + // to the introduction of temporaries during order. + // Restricting to constants is simple and probably powerful + // enough. + + for _, ncase := range sw.Cases { + for _, v := range ncase.List { + if v.Op() != ir.OLITERAL { + return false + } + } + } + return true +} + +// endsInFallthrough reports whether stmts ends with a "fallthrough" statement. +func endsInFallthrough(stmts []ir.Node) (bool, src.XPos) { + // Search backwards for the index of the fallthrough + // statement. Do not assume it'll be in the last + // position, since in some cases (e.g. when the statement + // list contains autotmp_ variables), one or more OVARKILL + // nodes will be at the end of the list. + + i := len(stmts) - 1 + for i >= 0 && stmts[i].Op() == ir.OVARKILL { + i-- + } + if i < 0 { + return false, src.NoXPos + } + return stmts[i].Op() == ir.OFALL, stmts[i].Pos() +} + +// walkSwitchType generates an AST that implements sw, where sw is a +// type switch. +func walkSwitchType(sw *ir.SwitchStmt) { + var s typeSwitch + s.facename = sw.Tag.(*ir.TypeSwitchGuard).X + sw.Tag = nil + + s.facename = walkExpr(s.facename, sw.PtrInit()) + s.facename = copyExpr(s.facename, s.facename.Type(), &sw.Compiled) + s.okname = typecheck.Temp(types.Types[types.TBOOL]) + + // Get interface descriptor word. + // For empty interfaces this will be the type. + // For non-empty interfaces this will be the itab. + itab := ir.NewUnaryExpr(base.Pos, ir.OITAB, s.facename) + + // For empty interfaces, do: + // if e._type == nil { + // do nil case if it exists, otherwise default + // } + // h := e._type.hash + // Use a similar strategy for non-empty interfaces. + ifNil := ir.NewIfStmt(base.Pos, nil, nil, nil) + ifNil.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, itab, typecheck.NodNil()) + base.Pos = base.Pos.WithNotStmt() // disable statement marks after the first check. + ifNil.Cond = typecheck.Expr(ifNil.Cond) + ifNil.Cond = typecheck.DefaultLit(ifNil.Cond, nil) + // ifNil.Nbody assigned at end. + sw.Compiled.Append(ifNil) + + // Load hash from type or itab. + dotHash := typeHashFieldOf(base.Pos, itab) + s.hashname = copyExpr(dotHash, dotHash.Type(), &sw.Compiled) + + br := ir.NewBranchStmt(base.Pos, ir.OBREAK, nil) + var defaultGoto, nilGoto ir.Node + var body ir.Nodes + for _, ncase := range sw.Cases { + caseVar := ncase.Var + + // For single-type cases with an interface type, + // we initialize the case variable as part of the type assertion. + // In other cases, we initialize it in the body. + var singleType *types.Type + if len(ncase.List) == 1 && ncase.List[0].Op() == ir.OTYPE { + singleType = ncase.List[0].Type() + } + caseVarInitialized := false + + label := typecheck.AutoLabel(".s") + jmp := ir.NewBranchStmt(ncase.Pos(), ir.OGOTO, label) + + if len(ncase.List) == 0 { // default: + if defaultGoto != nil { + base.Fatalf("duplicate default case not detected during typechecking") + } + defaultGoto = jmp + } + + for _, n1 := range ncase.List { + if ir.IsNil(n1) { // case nil: + if nilGoto != nil { + base.Fatalf("duplicate nil case not detected during typechecking") + } + nilGoto = jmp + continue + } + + if singleType != nil && singleType.IsInterface() { + s.Add(ncase.Pos(), n1.Type(), caseVar, jmp) + caseVarInitialized = true + } else { + s.Add(ncase.Pos(), n1.Type(), nil, jmp) + } + } + + body.Append(ir.NewLabelStmt(ncase.Pos(), label)) + if caseVar != nil && !caseVarInitialized { + val := s.facename + if singleType != nil { + // We have a single concrete type. Extract the data. + if singleType.IsInterface() { + base.Fatalf("singleType interface should have been handled in Add") + } + val = ifaceData(ncase.Pos(), s.facename, singleType) + } + l := []ir.Node{ + ir.NewDecl(ncase.Pos(), ir.ODCL, caseVar), + ir.NewAssignStmt(ncase.Pos(), caseVar, val), + } + typecheck.Stmts(l) + body.Append(l...) + } + body.Append(ncase.Body...) + body.Append(br) + } + sw.Cases = nil + + if defaultGoto == nil { + defaultGoto = br + } + if nilGoto == nil { + nilGoto = defaultGoto + } + ifNil.Body = []ir.Node{nilGoto} + + s.Emit(&sw.Compiled) + sw.Compiled.Append(defaultGoto) + sw.Compiled.Append(body.Take()...) + + walkStmtList(sw.Compiled) +} + +// typeHashFieldOf returns an expression to select the type hash field +// from an interface's descriptor word (whether a *runtime._type or +// *runtime.itab pointer). +func typeHashFieldOf(pos src.XPos, itab *ir.UnaryExpr) *ir.SelectorExpr { + if itab.Op() != ir.OITAB { + base.Fatalf("expected OITAB, got %v", itab.Op()) + } + var hashField *types.Field + if itab.X.Type().IsEmptyInterface() { + // runtime._type's hash field + if rtypeHashField == nil { + rtypeHashField = runtimeField("hash", int64(2*types.PtrSize), types.Types[types.TUINT32]) + } + hashField = rtypeHashField + } else { + // runtime.itab's hash field + if itabHashField == nil { + itabHashField = runtimeField("hash", int64(2*types.PtrSize), types.Types[types.TUINT32]) + } + hashField = itabHashField + } + return boundedDotPtr(pos, itab, hashField) +} + +var rtypeHashField, itabHashField *types.Field + +// A typeSwitch walks a type switch. +type typeSwitch struct { + // Temporary variables (i.e., ONAMEs) used by type switch dispatch logic: + facename ir.Node // value being type-switched on + hashname ir.Node // type hash of the value being type-switched on + okname ir.Node // boolean used for comma-ok type assertions + + done ir.Nodes + clauses []typeClause +} + +type typeClause struct { + hash uint32 + body ir.Nodes +} + +func (s *typeSwitch) Add(pos src.XPos, typ *types.Type, caseVar *ir.Name, jmp ir.Node) { + var body ir.Nodes + if caseVar != nil { + l := []ir.Node{ + ir.NewDecl(pos, ir.ODCL, caseVar), + ir.NewAssignStmt(pos, caseVar, nil), + } + typecheck.Stmts(l) + body.Append(l...) + } else { + caseVar = ir.BlankNode.(*ir.Name) + } + + // cv, ok = iface.(type) + as := ir.NewAssignListStmt(pos, ir.OAS2, nil, nil) + as.Lhs = []ir.Node{caseVar, s.okname} // cv, ok = + dot := ir.NewTypeAssertExpr(pos, s.facename, nil) + dot.SetType(typ) // iface.(type) + as.Rhs = []ir.Node{dot} + appendWalkStmt(&body, as) + + // if ok { goto label } + nif := ir.NewIfStmt(pos, nil, nil, nil) + nif.Cond = s.okname + nif.Body = []ir.Node{jmp} + body.Append(nif) + + if !typ.IsInterface() { + s.clauses = append(s.clauses, typeClause{ + hash: types.TypeHash(typ), + body: body, + }) + return + } + + s.flush() + s.done.Append(body.Take()...) +} + +func (s *typeSwitch) Emit(out *ir.Nodes) { + s.flush() + out.Append(s.done.Take()...) +} + +func (s *typeSwitch) flush() { + cc := s.clauses + s.clauses = nil + if len(cc) == 0 { + return + } + + sort.Slice(cc, func(i, j int) bool { return cc[i].hash < cc[j].hash }) + + // Combine adjacent cases with the same hash. + merged := cc[:1] + for _, c := range cc[1:] { + last := &merged[len(merged)-1] + if last.hash == c.hash { + last.body.Append(c.body.Take()...) + } else { + merged = append(merged, c) + } + } + cc = merged + + binarySearch(len(cc), &s.done, + func(i int) ir.Node { + return ir.NewBinaryExpr(base.Pos, ir.OLE, s.hashname, ir.NewInt(int64(cc[i-1].hash))) + }, + func(i int, nif *ir.IfStmt) { + // TODO(mdempsky): Omit hash equality check if + // there's only one type. + c := cc[i] + nif.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, s.hashname, ir.NewInt(int64(c.hash))) + nif.Body.Append(c.body.Take()...) + }, + ) +} + +// binarySearch constructs a binary search tree for handling n cases, +// and appends it to out. It's used for efficiently implementing +// switch statements. +// +// less(i) should return a boolean expression. If it evaluates true, +// then cases before i will be tested; otherwise, cases i and later. +// +// leaf(i, nif) should setup nif (an OIF node) to test case i. In +// particular, it should set nif.Left and nif.Nbody. +func binarySearch(n int, out *ir.Nodes, less func(i int) ir.Node, leaf func(i int, nif *ir.IfStmt)) { + const binarySearchMin = 4 // minimum number of cases for binary search + + var do func(lo, hi int, out *ir.Nodes) + do = func(lo, hi int, out *ir.Nodes) { + n := hi - lo + if n < binarySearchMin { + for i := lo; i < hi; i++ { + nif := ir.NewIfStmt(base.Pos, nil, nil, nil) + leaf(i, nif) + base.Pos = base.Pos.WithNotStmt() + nif.Cond = typecheck.Expr(nif.Cond) + nif.Cond = typecheck.DefaultLit(nif.Cond, nil) + out.Append(nif) + out = &nif.Else + } + return + } + + half := lo + n/2 + nif := ir.NewIfStmt(base.Pos, nil, nil, nil) + nif.Cond = less(half) + base.Pos = base.Pos.WithNotStmt() + nif.Cond = typecheck.Expr(nif.Cond) + nif.Cond = typecheck.DefaultLit(nif.Cond, nil) + do(lo, half, &nif.Body) + do(half, hi, &nif.Else) + out.Append(nif) + } + + do(0, n, out) +} diff --git a/src/cmd/compile/internal/walk/temp.go b/src/cmd/compile/internal/walk/temp.go new file mode 100644 index 0000000000000000000000000000000000000000..9879a6c69d7c604027f232049f3ae7d8e02c6ed4 --- /dev/null +++ b/src/cmd/compile/internal/walk/temp.go @@ -0,0 +1,40 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" +) + +// initStackTemp appends statements to init to initialize the given +// temporary variable to val, and then returns the expression &tmp. +func initStackTemp(init *ir.Nodes, tmp *ir.Name, val ir.Node) *ir.AddrExpr { + if val != nil && !types.Identical(tmp.Type(), val.Type()) { + base.Fatalf("bad initial value for %L: %L", tmp, val) + } + appendWalkStmt(init, ir.NewAssignStmt(base.Pos, tmp, val)) + return typecheck.Expr(typecheck.NodAddr(tmp)).(*ir.AddrExpr) +} + +// stackTempAddr returns the expression &tmp, where tmp is a newly +// allocated temporary variable of the given type. Statements to +// zero-initialize tmp are appended to init. +func stackTempAddr(init *ir.Nodes, typ *types.Type) *ir.AddrExpr { + return initStackTemp(init, typecheck.Temp(typ), nil) +} + +// stackBufAddr returns thte expression &tmp, where tmp is a newly +// allocated temporary variable of type [len]elem. This variable is +// initialized, and elem must not contain pointers. +func stackBufAddr(len int64, elem *types.Type) *ir.AddrExpr { + if elem.HasPointers() { + base.FatalfAt(base.Pos, "%v has pointers", elem) + } + tmp := typecheck.Temp(types.NewArray(elem, len)) + return typecheck.Expr(typecheck.NodAddr(tmp)).(*ir.AddrExpr) +} diff --git a/src/cmd/compile/internal/walk/walk.go b/src/cmd/compile/internal/walk/walk.go new file mode 100644 index 0000000000000000000000000000000000000000..26da6e314574431ef88187128097d4c644f00c1d --- /dev/null +++ b/src/cmd/compile/internal/walk/walk.go @@ -0,0 +1,403 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package walk + +import ( + "errors" + "fmt" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/src" +) + +// The constant is known to runtime. +const tmpstringbufsize = 32 +const zeroValSize = 1024 // must match value of runtime/map.go:maxZero + +func Walk(fn *ir.Func) { + ir.CurFunc = fn + errorsBefore := base.Errors() + order(fn) + if base.Errors() > errorsBefore { + return + } + + if base.Flag.W != 0 { + s := fmt.Sprintf("\nbefore walk %v", ir.CurFunc.Sym()) + ir.DumpList(s, ir.CurFunc.Body) + } + + lno := base.Pos + + base.Pos = lno + if base.Errors() > errorsBefore { + return + } + walkStmtList(ir.CurFunc.Body) + if base.Flag.W != 0 { + s := fmt.Sprintf("after walk %v", ir.CurFunc.Sym()) + ir.DumpList(s, ir.CurFunc.Body) + } + + if base.Flag.Cfg.Instrumenting { + instrument(fn) + } + + // Eagerly compute sizes of all variables for SSA. + for _, n := range fn.Dcl { + types.CalcSize(n.Type()) + } +} + +// walkRecv walks an ORECV node. +func walkRecv(n *ir.UnaryExpr) ir.Node { + if n.Typecheck() == 0 { + base.Fatalf("missing typecheck: %+v", n) + } + init := ir.TakeInit(n) + + n.X = walkExpr(n.X, &init) + call := walkExpr(mkcall1(chanfn("chanrecv1", 2, n.X.Type()), nil, &init, n.X, typecheck.NodNil()), &init) + return ir.InitExpr(init, call) +} + +func convas(n *ir.AssignStmt, init *ir.Nodes) *ir.AssignStmt { + if n.Op() != ir.OAS { + base.Fatalf("convas: not OAS %v", n.Op()) + } + n.SetTypecheck(1) + + if n.X == nil || n.Y == nil { + return n + } + + lt := n.X.Type() + rt := n.Y.Type() + if lt == nil || rt == nil { + return n + } + + if ir.IsBlank(n.X) { + n.Y = typecheck.DefaultLit(n.Y, nil) + return n + } + + if !types.Identical(lt, rt) { + n.Y = typecheck.AssignConv(n.Y, lt, "assignment") + n.Y = walkExpr(n.Y, init) + } + types.CalcSize(n.Y.Type()) + + return n +} + +var stop = errors.New("stop") + +func vmkcall(fn ir.Node, t *types.Type, init *ir.Nodes, va []ir.Node) *ir.CallExpr { + if init == nil { + base.Fatalf("mkcall with nil init: %v", fn) + } + if fn.Type() == nil || fn.Type().Kind() != types.TFUNC { + base.Fatalf("mkcall %v %v", fn, fn.Type()) + } + + n := fn.Type().NumParams() + if n != len(va) { + base.Fatalf("vmkcall %v needs %v args got %v", fn, n, len(va)) + } + + call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, va) + typecheck.Call(call) + call.SetType(t) + return walkExpr(call, init).(*ir.CallExpr) +} + +func mkcall(name string, t *types.Type, init *ir.Nodes, args ...ir.Node) *ir.CallExpr { + return vmkcall(typecheck.LookupRuntime(name), t, init, args) +} + +func mkcallstmt(name string, args ...ir.Node) ir.Node { + return mkcallstmt1(typecheck.LookupRuntime(name), args...) +} + +func mkcall1(fn ir.Node, t *types.Type, init *ir.Nodes, args ...ir.Node) *ir.CallExpr { + return vmkcall(fn, t, init, args) +} + +func mkcallstmt1(fn ir.Node, args ...ir.Node) ir.Node { + var init ir.Nodes + n := vmkcall(fn, nil, &init, args) + if len(init) == 0 { + return n + } + init.Append(n) + return ir.NewBlockStmt(n.Pos(), init) +} + +func chanfn(name string, n int, t *types.Type) ir.Node { + if !t.IsChan() { + base.Fatalf("chanfn %v", t) + } + fn := typecheck.LookupRuntime(name) + switch n { + default: + base.Fatalf("chanfn %d", n) + case 1: + fn = typecheck.SubstArgTypes(fn, t.Elem()) + case 2: + fn = typecheck.SubstArgTypes(fn, t.Elem(), t.Elem()) + } + return fn +} + +func mapfn(name string, t *types.Type, isfat bool) ir.Node { + if !t.IsMap() { + base.Fatalf("mapfn %v", t) + } + fn := typecheck.LookupRuntime(name) + if mapfast(t) == mapslow || isfat { + fn = typecheck.SubstArgTypes(fn, t.Key(), t.Elem(), t.Key(), t.Elem()) + } else { + fn = typecheck.SubstArgTypes(fn, t.Key(), t.Elem(), t.Elem()) + } + return fn +} + +func mapfndel(name string, t *types.Type) ir.Node { + if !t.IsMap() { + base.Fatalf("mapfn %v", t) + } + fn := typecheck.LookupRuntime(name) + if mapfast(t) == mapslow { + fn = typecheck.SubstArgTypes(fn, t.Key(), t.Elem(), t.Key()) + } else { + fn = typecheck.SubstArgTypes(fn, t.Key(), t.Elem()) + } + return fn +} + +const ( + mapslow = iota + mapfast32 + mapfast32ptr + mapfast64 + mapfast64ptr + mapfaststr + nmapfast +) + +type mapnames [nmapfast]string + +func mkmapnames(base string, ptr string) mapnames { + return mapnames{base, base + "_fast32", base + "_fast32" + ptr, base + "_fast64", base + "_fast64" + ptr, base + "_faststr"} +} + +var mapaccess1 = mkmapnames("mapaccess1", "") +var mapaccess2 = mkmapnames("mapaccess2", "") +var mapassign = mkmapnames("mapassign", "ptr") +var mapdelete = mkmapnames("mapdelete", "") + +func mapfast(t *types.Type) int { + // Check runtime/map.go:maxElemSize before changing. + if t.Elem().Width > 128 { + return mapslow + } + switch reflectdata.AlgType(t.Key()) { + case types.AMEM32: + if !t.Key().HasPointers() { + return mapfast32 + } + if types.PtrSize == 4 { + return mapfast32ptr + } + base.Fatalf("small pointer %v", t.Key()) + case types.AMEM64: + if !t.Key().HasPointers() { + return mapfast64 + } + if types.PtrSize == 8 { + return mapfast64ptr + } + // Two-word object, at least one of which is a pointer. + // Use the slow path. + case types.ASTRING: + return mapfaststr + } + return mapslow +} + +func walkAppendArgs(n *ir.CallExpr, init *ir.Nodes) { + walkExprListSafe(n.Args, init) + + // walkExprListSafe will leave OINDEX (s[n]) alone if both s + // and n are name or literal, but those may index the slice we're + // modifying here. Fix explicitly. + ls := n.Args + for i1, n1 := range ls { + ls[i1] = cheapExpr(n1, init) + } +} + +// appendWalkStmt typechecks and walks stmt and then appends it to init. +func appendWalkStmt(init *ir.Nodes, stmt ir.Node) { + op := stmt.Op() + n := typecheck.Stmt(stmt) + if op == ir.OAS || op == ir.OAS2 { + // If the assignment has side effects, walkExpr will append them + // directly to init for us, while walkStmt will wrap it in an OBLOCK. + // We need to append them directly. + // TODO(rsc): Clean this up. + n = walkExpr(n, init) + } else { + n = walkStmt(n) + } + init.Append(n) +} + +// The max number of defers in a function using open-coded defers. We enforce this +// limit because the deferBits bitmask is currently a single byte (to minimize code size) +const maxOpenDefers = 8 + +// backingArrayPtrLen extracts the pointer and length from a slice or string. +// This constructs two nodes referring to n, so n must be a cheapExpr. +func backingArrayPtrLen(n ir.Node) (ptr, length ir.Node) { + var init ir.Nodes + c := cheapExpr(n, &init) + if c != n || len(init) != 0 { + base.Fatalf("backingArrayPtrLen not cheap: %v", n) + } + ptr = ir.NewUnaryExpr(base.Pos, ir.OSPTR, n) + if n.Type().IsString() { + ptr.SetType(types.Types[types.TUINT8].PtrTo()) + } else { + ptr.SetType(n.Type().Elem().PtrTo()) + } + length = ir.NewUnaryExpr(base.Pos, ir.OLEN, n) + length.SetType(types.Types[types.TINT]) + return ptr, length +} + +// mayCall reports whether evaluating expression n may require +// function calls, which could clobber function call arguments/results +// currently on the stack. +func mayCall(n ir.Node) bool { + // When instrumenting, any expression might require function calls. + if base.Flag.Cfg.Instrumenting { + return true + } + + isSoftFloat := func(typ *types.Type) bool { + return types.IsFloat[typ.Kind()] || types.IsComplex[typ.Kind()] + } + + return ir.Any(n, func(n ir.Node) bool { + // walk should have already moved any Init blocks off of + // expressions. + if len(n.Init()) != 0 { + base.FatalfAt(n.Pos(), "mayCall %+v", n) + } + + switch n.Op() { + default: + base.FatalfAt(n.Pos(), "mayCall %+v", n) + + case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER, + ir.OUNSAFEADD, ir.OUNSAFESLICE: + return true + + case ir.OINDEX, ir.OSLICE, ir.OSLICEARR, ir.OSLICE3, ir.OSLICE3ARR, ir.OSLICESTR, + ir.ODEREF, ir.ODOTPTR, ir.ODOTTYPE, ir.ODIV, ir.OMOD, ir.OSLICE2ARRPTR: + // These ops might panic, make sure they are done + // before we start marshaling args for a call. See issue 16760. + return true + + case ir.OANDAND, ir.OOROR: + n := n.(*ir.LogicalExpr) + // The RHS expression may have init statements that + // should only execute conditionally, and so cannot be + // pulled out to the top-level init list. We could try + // to be more precise here. + return len(n.Y.Init()) != 0 + + // When using soft-float, these ops might be rewritten to function calls + // so we ensure they are evaluated first. + case ir.OADD, ir.OSUB, ir.OMUL, ir.ONEG: + return ssagen.Arch.SoftFloat && isSoftFloat(n.Type()) + case ir.OLT, ir.OEQ, ir.ONE, ir.OLE, ir.OGE, ir.OGT: + n := n.(*ir.BinaryExpr) + return ssagen.Arch.SoftFloat && isSoftFloat(n.X.Type()) + case ir.OCONV: + n := n.(*ir.ConvExpr) + return ssagen.Arch.SoftFloat && (isSoftFloat(n.Type()) || isSoftFloat(n.X.Type())) + + case ir.OLITERAL, ir.ONIL, ir.ONAME, ir.OLINKSYMOFFSET, ir.OMETHEXPR, + ir.OAND, ir.OANDNOT, ir.OLSH, ir.OOR, ir.ORSH, ir.OXOR, ir.OCOMPLEX, ir.OEFACE, + ir.OADDR, ir.OBITNOT, ir.ONOT, ir.OPLUS, + ir.OCAP, ir.OIMAG, ir.OLEN, ir.OREAL, + ir.OCONVNOP, ir.ODOT, + ir.OCFUNC, ir.OIDATA, ir.OITAB, ir.OSPTR, + ir.OBYTES2STRTMP, ir.OGETG, ir.OSLICEHEADER: + // ok: operations that don't require function calls. + // Expand as needed. + } + + return false + }) +} + +// itabType loads the _type field from a runtime.itab struct. +func itabType(itab ir.Node) ir.Node { + if itabTypeField == nil { + // runtime.itab's _type field + itabTypeField = runtimeField("_type", int64(types.PtrSize), types.NewPtr(types.Types[types.TUINT8])) + } + return boundedDotPtr(base.Pos, itab, itabTypeField) +} + +var itabTypeField *types.Field + +// boundedDotPtr returns a selector expression representing ptr.field +// and omits nil-pointer checks for ptr. +func boundedDotPtr(pos src.XPos, ptr ir.Node, field *types.Field) *ir.SelectorExpr { + sel := ir.NewSelectorExpr(pos, ir.ODOTPTR, ptr, field.Sym) + sel.Selection = field + sel.SetType(field.Type) + sel.SetTypecheck(1) + sel.SetBounded(true) // guaranteed not to fault + return sel +} + +func runtimeField(name string, offset int64, typ *types.Type) *types.Field { + f := types.NewField(src.NoXPos, ir.Pkgs.Runtime.Lookup(name), typ) + f.Offset = offset + return f +} + +// ifaceData loads the data field from an interface. +// The concrete type must be known to have type t. +// It follows the pointer if !IsDirectIface(t). +func ifaceData(pos src.XPos, n ir.Node, t *types.Type) ir.Node { + if t.IsInterface() { + base.Fatalf("ifaceData interface: %v", t) + } + ptr := ir.NewUnaryExpr(pos, ir.OIDATA, n) + if types.IsDirectIface(t) { + ptr.SetType(t) + ptr.SetTypecheck(1) + return ptr + } + ptr.SetType(types.NewPtr(t)) + ptr.SetTypecheck(1) + ind := ir.NewStarExpr(pos, ptr) + ind.SetType(t) + ind.SetTypecheck(1) + ind.SetBounded(true) + return ind +} diff --git a/src/cmd/compile/internal/wasm/ssa.go b/src/cmd/compile/internal/wasm/ssa.go index 9c9f6edc5f4912162af268f8f11318fbbdacbb01..31b09016eb98296a3838bc39125dbb3f4f73a101 100644 --- a/src/cmd/compile/internal/wasm/ssa.go +++ b/src/cmd/compile/internal/wasm/ssa.go @@ -5,16 +5,19 @@ package wasm import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/logopt" + "cmd/compile/internal/objw" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/wasm" - "cmd/internal/objabi" + "internal/buildcfg" ) -func Init(arch *gc.Arch) { +func Init(arch *ssagen.ArchInfo) { arch.LinkArch = &wasm.Linkwasm arch.REGSP = wasm.REG_SP arch.MAXWIDTH = 1 << 50 @@ -28,31 +31,31 @@ func Init(arch *gc.Arch) { arch.SSAGenBlock = ssaGenBlock } -func zeroRange(pp *gc.Progs, p *obj.Prog, off, cnt int64, state *uint32) *obj.Prog { +func zeroRange(pp *objw.Progs, p *obj.Prog, off, cnt int64, state *uint32) *obj.Prog { if cnt == 0 { return p } if cnt%8 != 0 { - gc.Fatalf("zerorange count not a multiple of widthptr %d", cnt) + base.Fatalf("zerorange count not a multiple of widthptr %d", cnt) } for i := int64(0); i < cnt; i += 8 { - p = pp.Appendpp(p, wasm.AGet, obj.TYPE_REG, wasm.REG_SP, 0, 0, 0, 0) - p = pp.Appendpp(p, wasm.AI64Const, obj.TYPE_CONST, 0, 0, 0, 0, 0) - p = pp.Appendpp(p, wasm.AI64Store, 0, 0, 0, obj.TYPE_CONST, 0, off+i) + p = pp.Append(p, wasm.AGet, obj.TYPE_REG, wasm.REG_SP, 0, 0, 0, 0) + p = pp.Append(p, wasm.AI64Const, obj.TYPE_CONST, 0, 0, 0, 0, 0) + p = pp.Append(p, wasm.AI64Store, 0, 0, 0, obj.TYPE_CONST, 0, off+i) } return p } -func ginsnop(pp *gc.Progs) *obj.Prog { +func ginsnop(pp *objw.Progs) *obj.Prog { return pp.Prog(wasm.ANop) } -func ssaMarkMoves(s *gc.SSAGenState, b *ssa.Block) { +func ssaMarkMoves(s *ssagen.State, b *ssa.Block) { } -func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { switch b.Kind { case ssa.BlockPlain: if next != b.Succs[0].Block() { @@ -118,11 +121,11 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { } } -func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { +func ssaGenValue(s *ssagen.State, v *ssa.Value) { switch v.Op { case ssa.OpWasmLoweredStaticCall, ssa.OpWasmLoweredClosureCall, ssa.OpWasmLoweredInterCall: s.PrepareCall(v) - if call, ok := v.Aux.(*ssa.AuxCall); ok && call.Fn == gc.Deferreturn { + if call, ok := v.Aux.(*ssa.AuxCall); ok && call.Fn == ir.Syms.Deferreturn { // add a resume point before call to deferreturn so it can be called again via jmpdefer s.Prog(wasm.ARESUMEPOINT) } @@ -147,26 +150,26 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { getValue32(s, v.Args[1]) i32Const(s, int32(v.AuxInt)) p := s.Prog(wasm.ACall) - p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmMove} + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmMove} case ssa.OpWasmLoweredZero: getValue32(s, v.Args[0]) i32Const(s, int32(v.AuxInt)) p := s.Prog(wasm.ACall) - p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmZero} + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmZero} case ssa.OpWasmLoweredNilCheck: getValue64(s, v.Args[0]) s.Prog(wasm.AI64Eqz) s.Prog(wasm.AIf) p := s.Prog(wasm.ACALLNORESUME) - p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.SigPanic} + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.SigPanic} s.Prog(wasm.AEnd) if logopt.Enabled() { logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name) } - if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers - gc.Warnl(v.Pos, "generated nil check") + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") } case ssa.OpWasmLoweredWB: @@ -185,7 +188,10 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { getReg(s, wasm.REG_SP) getValue64(s, v.Args[0]) p := s.Prog(storeOp(v.Type)) - gc.AddrAuto(&p.To, v) + ssagen.AddrAuto(&p.To, v) + + case ssa.OpClobber, ssa.OpClobberReg: + // TODO: implement for clobberdead experiment. Nop is ok for now. default: if v.Type.IsMemory() { @@ -205,7 +211,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { } } -func ssaGenValueOnStack(s *gc.SSAGenState, v *ssa.Value, extend bool) { +func ssaGenValueOnStack(s *ssagen.State, v *ssa.Value, extend bool) { switch v.Op { case ssa.OpWasmLoweredGetClosurePtr: getReg(s, wasm.REG_CTXT) @@ -240,10 +246,10 @@ func ssaGenValueOnStack(s *gc.SSAGenState, v *ssa.Value, extend bool) { p.From.Type = obj.TYPE_ADDR switch v.Aux.(type) { case *obj.LSym: - gc.AddAux(&p.From, v) - case *gc.Node: + ssagen.AddAux(&p.From, v) + case *ir.Name: p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) default: panic("wasm: bad LoweredAddr") } @@ -312,33 +318,33 @@ func ssaGenValueOnStack(s *gc.SSAGenState, v *ssa.Value, extend bool) { if v.Type.Size() == 8 { // Division of int64 needs helper function wasmDiv to handle the MinInt64 / -1 case. p := s.Prog(wasm.ACall) - p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmDiv} + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmDiv} break } s.Prog(wasm.AI64DivS) case ssa.OpWasmI64TruncSatF32S, ssa.OpWasmI64TruncSatF64S: getValue64(s, v.Args[0]) - if objabi.GOWASM.SatConv { + if buildcfg.GOWASM.SatConv { s.Prog(v.Op.Asm()) } else { if v.Op == ssa.OpWasmI64TruncSatF32S { s.Prog(wasm.AF64PromoteF32) } p := s.Prog(wasm.ACall) - p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmTruncS} + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmTruncS} } case ssa.OpWasmI64TruncSatF32U, ssa.OpWasmI64TruncSatF64U: getValue64(s, v.Args[0]) - if objabi.GOWASM.SatConv { + if buildcfg.GOWASM.SatConv { s.Prog(v.Op.Asm()) } else { if v.Op == ssa.OpWasmI64TruncSatF32U { s.Prog(wasm.AF64PromoteF32) } p := s.Prog(wasm.ACall) - p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmTruncU} + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmTruncU} } case ssa.OpWasmF32DemoteF64: @@ -360,7 +366,7 @@ func ssaGenValueOnStack(s *gc.SSAGenState, v *ssa.Value, extend bool) { case ssa.OpLoadReg: p := s.Prog(loadOp(v.Type)) - gc.AddrAuto(&p.From, v.Args[0]) + ssagen.AddrAuto(&p.From, v.Args[0]) case ssa.OpCopy: getValue64(s, v.Args[0]) @@ -382,7 +388,7 @@ func isCmp(v *ssa.Value) bool { } } -func getValue32(s *gc.SSAGenState, v *ssa.Value) { +func getValue32(s *ssagen.State, v *ssa.Value) { if v.OnWasmStack { s.OnWasmStackSkipped-- ssaGenValueOnStack(s, v, false) @@ -399,7 +405,7 @@ func getValue32(s *gc.SSAGenState, v *ssa.Value) { } } -func getValue64(s *gc.SSAGenState, v *ssa.Value) { +func getValue64(s *ssagen.State, v *ssa.Value) { if v.OnWasmStack { s.OnWasmStackSkipped-- ssaGenValueOnStack(s, v, true) @@ -413,32 +419,32 @@ func getValue64(s *gc.SSAGenState, v *ssa.Value) { } } -func i32Const(s *gc.SSAGenState, val int32) { +func i32Const(s *ssagen.State, val int32) { p := s.Prog(wasm.AI32Const) p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: int64(val)} } -func i64Const(s *gc.SSAGenState, val int64) { +func i64Const(s *ssagen.State, val int64) { p := s.Prog(wasm.AI64Const) p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: val} } -func f32Const(s *gc.SSAGenState, val float64) { +func f32Const(s *ssagen.State, val float64) { p := s.Prog(wasm.AF32Const) p.From = obj.Addr{Type: obj.TYPE_FCONST, Val: val} } -func f64Const(s *gc.SSAGenState, val float64) { +func f64Const(s *ssagen.State, val float64) { p := s.Prog(wasm.AF64Const) p.From = obj.Addr{Type: obj.TYPE_FCONST, Val: val} } -func getReg(s *gc.SSAGenState, reg int16) { +func getReg(s *ssagen.State, reg int16) { p := s.Prog(wasm.AGet) p.From = obj.Addr{Type: obj.TYPE_REG, Reg: reg} } -func setReg(s *gc.SSAGenState, reg int16) { +func setReg(s *ssagen.State, reg int16) { p := s.Prog(wasm.ASet) p.To = obj.Addr{Type: obj.TYPE_REG, Reg: reg} } diff --git a/src/cmd/compile/internal/x86/galign.go b/src/cmd/compile/internal/x86/galign.go index e137daa3fc590c3c749723d50dff357982a66cc5..00a20e429f1d6112cbf985e0b3c8706b187b0356 100644 --- a/src/cmd/compile/internal/x86/galign.go +++ b/src/cmd/compile/internal/x86/galign.go @@ -5,29 +5,30 @@ package x86 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ssagen" "cmd/internal/obj/x86" - "cmd/internal/objabi" "fmt" + "internal/buildcfg" "os" ) -func Init(arch *gc.Arch) { +func Init(arch *ssagen.ArchInfo) { arch.LinkArch = &x86.Link386 arch.REGSP = x86.REGSP arch.SSAGenValue = ssaGenValue arch.SSAGenBlock = ssaGenBlock arch.MAXWIDTH = (1 << 32) - 1 - switch v := objabi.GO386; v { + switch v := buildcfg.GO386; v { case "sse2": case "softfloat": arch.SoftFloat = true case "387": fmt.Fprintf(os.Stderr, "unsupported setting GO386=387. Consider using GO386=softfloat instead.\n") - gc.Exit(1) + base.Exit(1) default: fmt.Fprintf(os.Stderr, "unsupported setting GO386=%s\n", v) - gc.Exit(1) + base.Exit(1) } diff --git a/src/cmd/compile/internal/x86/ggen.go b/src/cmd/compile/internal/x86/ggen.go index a33ddc81e3dc00b5809405720db80409c1f7966b..3ca479763e63a21fb4ca852d1ac211cd41a34c67 100644 --- a/src/cmd/compile/internal/x86/ggen.go +++ b/src/cmd/compile/internal/x86/ggen.go @@ -5,39 +5,41 @@ package x86 import ( - "cmd/compile/internal/gc" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/x86" ) -func zerorange(pp *gc.Progs, p *obj.Prog, off, cnt int64, ax *uint32) *obj.Prog { +func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, ax *uint32) *obj.Prog { if cnt == 0 { return p } if *ax == 0 { - p = pp.Appendpp(p, x86.AMOVL, obj.TYPE_CONST, 0, 0, obj.TYPE_REG, x86.REG_AX, 0) + p = pp.Append(p, x86.AMOVL, obj.TYPE_CONST, 0, 0, obj.TYPE_REG, x86.REG_AX, 0) *ax = 1 } - if cnt <= int64(4*gc.Widthreg) { - for i := int64(0); i < cnt; i += int64(gc.Widthreg) { - p = pp.Appendpp(p, x86.AMOVL, obj.TYPE_REG, x86.REG_AX, 0, obj.TYPE_MEM, x86.REG_SP, off+i) + if cnt <= int64(4*types.RegSize) { + for i := int64(0); i < cnt; i += int64(types.RegSize) { + p = pp.Append(p, x86.AMOVL, obj.TYPE_REG, x86.REG_AX, 0, obj.TYPE_MEM, x86.REG_SP, off+i) } - } else if cnt <= int64(128*gc.Widthreg) { - p = pp.Appendpp(p, x86.ALEAL, obj.TYPE_MEM, x86.REG_SP, off, obj.TYPE_REG, x86.REG_DI, 0) - p = pp.Appendpp(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_ADDR, 0, 1*(128-cnt/int64(gc.Widthreg))) - p.To.Sym = gc.Duffzero + } else if cnt <= int64(128*types.RegSize) { + p = pp.Append(p, x86.ALEAL, obj.TYPE_MEM, x86.REG_SP, off, obj.TYPE_REG, x86.REG_DI, 0) + p = pp.Append(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_ADDR, 0, 1*(128-cnt/int64(types.RegSize))) + p.To.Sym = ir.Syms.Duffzero } else { - p = pp.Appendpp(p, x86.AMOVL, obj.TYPE_CONST, 0, cnt/int64(gc.Widthreg), obj.TYPE_REG, x86.REG_CX, 0) - p = pp.Appendpp(p, x86.ALEAL, obj.TYPE_MEM, x86.REG_SP, off, obj.TYPE_REG, x86.REG_DI, 0) - p = pp.Appendpp(p, x86.AREP, obj.TYPE_NONE, 0, 0, obj.TYPE_NONE, 0, 0) - p = pp.Appendpp(p, x86.ASTOSL, obj.TYPE_NONE, 0, 0, obj.TYPE_NONE, 0, 0) + p = pp.Append(p, x86.AMOVL, obj.TYPE_CONST, 0, cnt/int64(types.RegSize), obj.TYPE_REG, x86.REG_CX, 0) + p = pp.Append(p, x86.ALEAL, obj.TYPE_MEM, x86.REG_SP, off, obj.TYPE_REG, x86.REG_DI, 0) + p = pp.Append(p, x86.AREP, obj.TYPE_NONE, 0, 0, obj.TYPE_NONE, 0, 0) + p = pp.Append(p, x86.ASTOSL, obj.TYPE_NONE, 0, 0, obj.TYPE_NONE, 0, 0) } return p } -func ginsnop(pp *gc.Progs) *obj.Prog { +func ginsnop(pp *objw.Progs) *obj.Prog { // See comment in ../amd64/ggen.go. p := pp.Prog(x86.AXCHGL) p.From.Type = obj.TYPE_REG diff --git a/src/cmd/compile/internal/x86/ssa.go b/src/cmd/compile/internal/x86/ssa.go index fbf76d0c5efb979e37dab6666008708b26a36fcb..a06fdbcb71770ebce782dcbba69fdc673b236ac7 100644 --- a/src/cmd/compile/internal/x86/ssa.go +++ b/src/cmd/compile/internal/x86/ssa.go @@ -8,16 +8,18 @@ import ( "fmt" "math" - "cmd/compile/internal/gc" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/logopt" "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/x86" ) // markMoves marks any MOVXconst ops that need to avoid clobbering flags. -func ssaMarkMoves(s *gc.SSAGenState, b *ssa.Block) { +func ssaMarkMoves(s *ssagen.State, b *ssa.Block) { flive := b.FlagsLiveAtEnd for _, c := range b.ControlValues() { flive = c.Type.IsFlags() || flive @@ -107,7 +109,7 @@ func moveByType(t *types.Type) obj.As { // dest := dest(To) op src(From) // and also returns the created obj.Prog so it // may be further adjusted (offset, scale, etc). -func opregreg(s *gc.SSAGenState, op obj.As, dest, src int16) *obj.Prog { +func opregreg(s *ssagen.State, op obj.As, dest, src int16) *obj.Prog { p := s.Prog(op) p.From.Type = obj.TYPE_REG p.To.Type = obj.TYPE_REG @@ -116,7 +118,7 @@ func opregreg(s *gc.SSAGenState, op obj.As, dest, src int16) *obj.Prog { return p } -func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { +func ssaGenValue(s *ssagen.State, v *ssa.Value) { switch v.Op { case ssa.Op386ADDL: r := v.Reg() @@ -159,31 +161,19 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.Op386PXOR, ssa.Op386ADCL, ssa.Op386SBBL: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } - opregreg(s, v.Op.Asm(), r, v.Args[1].Reg()) + opregreg(s, v.Op.Asm(), v.Reg(), v.Args[1].Reg()) case ssa.Op386ADDLcarry, ssa.Op386SUBLcarry: // output 0 is carry/borrow, output 1 is the low 32 bits. - r := v.Reg0() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output[0] not in same register %s", v.LongString()) - } - opregreg(s, v.Op.Asm(), r, v.Args[1].Reg()) + opregreg(s, v.Op.Asm(), v.Reg0(), v.Args[1].Reg()) case ssa.Op386ADDLconstcarry, ssa.Op386SUBLconstcarry: // output 0 is carry/borrow, output 1 is the low 32 bits. - r := v.Reg0() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output[0] not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = v.AuxInt p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg0() case ssa.Op386DIVL, ssa.Op386DIVW, ssa.Op386DIVLU, ssa.Op386DIVWU, @@ -304,20 +294,16 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // compute (x+y)/2 unsigned. // Do a 32-bit add, the overflow goes into the carry. // Shift right once and pull the carry back into the 31st bit. - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(x86.AADDL) p.From.Type = obj.TYPE_REG p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() p.From.Reg = v.Args[1].Reg() p = s.Prog(x86.ARCRL) p.From.Type = obj.TYPE_CONST p.From.Offset = 1 p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.Op386ADDLconst: r := v.Reg() @@ -356,7 +342,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Offset = v.AuxInt p.To.Type = obj.TYPE_REG p.To.Reg = r - p.SetFrom3(obj.Addr{Type: obj.TYPE_REG, Reg: v.Args[0].Reg()}) + p.SetFrom3Reg(v.Args[0].Reg()) case ssa.Op386SUBLconst, ssa.Op386ADCLconst, @@ -368,15 +354,11 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ssa.Op386SHRLconst, ssa.Op386SHRWconst, ssa.Op386SHRBconst, ssa.Op386SARLconst, ssa.Op386SARWconst, ssa.Op386SARBconst, ssa.Op386ROLLconst, ssa.Op386ROLWconst, ssa.Op386ROLBconst: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = v.AuxInt p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.Op386SBBLcarrymask: r := v.Reg() p := s.Prog(v.Op.Asm()) @@ -404,14 +386,14 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Type = obj.TYPE_MEM p.From.Reg = r p.From.Index = i - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.Op386LEAL: p := s.Prog(x86.ALEAL) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.Op386CMPL, ssa.Op386CMPW, ssa.Op386CMPB, @@ -437,7 +419,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Args[1].Reg() case ssa.Op386CMPLconstload, ssa.Op386CMPWconstload, ssa.Op386CMPBconstload: @@ -445,9 +427,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux2(&p.From, v, sc.Off()) + ssagen.AddAux2(&p.From, v, sc.Off64()) p.To.Type = obj.TYPE_CONST - p.To.Offset = sc.Val() + p.To.Offset = sc.Val64() case ssa.Op386MOVLconst: x := v.Reg() @@ -480,9 +462,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Name = obj.NAME_EXTERN f := math.Float64frombits(uint64(v.AuxInt)) if v.Op == ssa.Op386MOVSDconst1 { - p.From.Sym = gc.Ctxt.Float64Sym(f) + p.From.Sym = base.Ctxt.Float64Sym(f) } else { - p.From.Sym = gc.Ctxt.Float32Sym(float32(f)) + p.From.Sym = base.Ctxt.Float32Sym(float32(f)) } p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -497,7 +479,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[0].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.Op386MOVBloadidx1, ssa.Op386MOVWloadidx1, ssa.Op386MOVLloadidx1, ssa.Op386MOVSSloadidx1, ssa.Op386MOVSDloadidx1, @@ -521,7 +503,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { } p.From.Reg = r p.From.Index = i - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.Op386ADDLloadidx4, ssa.Op386SUBLloadidx4, ssa.Op386MULLloadidx4, @@ -531,12 +513,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.From.Index = v.Args[2].Reg() p.From.Scale = 4 - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } case ssa.Op386ADDLload, ssa.Op386SUBLload, ssa.Op386MULLload, ssa.Op386ANDLload, ssa.Op386ORLload, ssa.Op386XORLload, ssa.Op386ADDSDload, ssa.Op386ADDSSload, ssa.Op386SUBSDload, ssa.Op386SUBSSload, @@ -544,12 +523,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM p.From.Reg = v.Args[1].Reg() - gc.AddAux(&p.From, v) + ssagen.AddAux(&p.From, v) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() - if v.Reg() != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } case ssa.Op386MOVSSstore, ssa.Op386MOVSDstore, ssa.Op386MOVLstore, ssa.Op386MOVWstore, ssa.Op386MOVBstore, ssa.Op386ADDLmodify, ssa.Op386SUBLmodify, ssa.Op386ANDLmodify, ssa.Op386ORLmodify, ssa.Op386XORLmodify: p := s.Prog(v.Op.Asm()) @@ -557,7 +533,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = v.Args[1].Reg() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.Op386ADDLconstmodify: sc := v.AuxValAndOff() val := sc.Val() @@ -568,23 +544,23 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { } else { p = s.Prog(x86.ADECL) } - off := sc.Off() + off := sc.Off64() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux2(&p.To, v, off) + ssagen.AddAux2(&p.To, v, off) break } fallthrough case ssa.Op386ANDLconstmodify, ssa.Op386ORLconstmodify, ssa.Op386XORLconstmodify: sc := v.AuxValAndOff() - off := sc.Off() - val := sc.Val() + off := sc.Off64() + val := sc.Val64() p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST p.From.Offset = val p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux2(&p.To, v, off) + ssagen.AddAux2(&p.To, v, off) case ssa.Op386MOVBstoreidx1, ssa.Op386MOVWstoreidx1, ssa.Op386MOVLstoreidx1, ssa.Op386MOVSSstoreidx1, ssa.Op386MOVSDstoreidx1, ssa.Op386MOVSDstoreidx8, ssa.Op386MOVSSstoreidx4, ssa.Op386MOVLstoreidx4, ssa.Op386MOVWstoreidx2, ssa.Op386ADDLmodifyidx4, ssa.Op386SUBLmodifyidx4, ssa.Op386ANDLmodifyidx4, ssa.Op386ORLmodifyidx4, ssa.Op386XORLmodifyidx4: @@ -610,15 +586,15 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { } p.To.Reg = r p.To.Index = i - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) case ssa.Op386MOVLstoreconst, ssa.Op386MOVWstoreconst, ssa.Op386MOVBstoreconst: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST sc := v.AuxValAndOff() - p.From.Offset = sc.Val() + p.From.Offset = sc.Val64() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux2(&p.To, v, sc.Off()) + ssagen.AddAux2(&p.To, v, sc.Off64()) case ssa.Op386ADDLconstmodifyidx4: sc := v.AuxValAndOff() val := sc.Val() @@ -629,12 +605,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { } else { p = s.Prog(x86.ADECL) } - off := sc.Off() + off := sc.Off64() p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() p.To.Scale = 4 p.To.Index = v.Args[1].Reg() - gc.AddAux2(&p.To, v, off) + ssagen.AddAux2(&p.To, v, off) break } fallthrough @@ -643,7 +619,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST sc := v.AuxValAndOff() - p.From.Offset = sc.Val() + p.From.Offset = sc.Val64() r := v.Args[0].Reg() i := v.Args[1].Reg() switch v.Op { @@ -661,7 +637,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_MEM p.To.Reg = r p.To.Index = i - gc.AddAux2(&p.To, v, sc.Off()) + ssagen.AddAux2(&p.To, v, sc.Off64()) case ssa.Op386MOVWLSX, ssa.Op386MOVBLSX, ssa.Op386MOVWLZX, ssa.Op386MOVBLZX, ssa.Op386CVTSL2SS, ssa.Op386CVTSL2SD, ssa.Op386CVTTSS2SL, ssa.Op386CVTTSD2SL, @@ -670,12 +646,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.Op386DUFFZERO: p := s.Prog(obj.ADUFFZERO) p.To.Type = obj.TYPE_ADDR - p.To.Sym = gc.Duffzero + p.To.Sym = ir.Syms.Duffzero p.To.Offset = v.AuxInt case ssa.Op386DUFFCOPY: p := s.Prog(obj.ADUFFCOPY) p.To.Type = obj.TYPE_ADDR - p.To.Sym = gc.Duffcopy + p.To.Sym = ir.Syms.Duffcopy p.To.Offset = v.AuxInt case ssa.OpCopy: // TODO: use MOVLreg for reg->reg copies instead of OpCopy? @@ -693,7 +669,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { return } p := s.Prog(loadByType(v.Type)) - gc.AddrAuto(&p.From, v.Args[0]) + ssagen.AddrAuto(&p.From, v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -705,15 +681,15 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(storeByType(v.Type)) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() - gc.AddrAuto(&p.To, v) + ssagen.AddrAuto(&p.To, v) case ssa.Op386LoweredGetClosurePtr: // Closure pointer is DX. - gc.CheckLoweredGetClosurePtr(v) + ssagen.CheckLoweredGetClosurePtr(v) case ssa.Op386LoweredGetG: r := v.Reg() // See the comments in cmd/internal/obj/x86/obj6.go // near CanUse1InsnTLS for a detailed explanation of these instructions. - if x86.CanUse1InsnTLS(gc.Ctxt) { + if x86.CanUse1InsnTLS(base.Ctxt) { // MOVL (TLS), r p := s.Prog(x86.AMOVL) p.From.Type = obj.TYPE_MEM @@ -749,7 +725,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // caller's SP is the address of the first arg p := s.Prog(x86.AMOVL) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -gc.Ctxt.FixedFrameSize() // 0 on 386, just to be consistent with other architectures + p.From.Offset = -base.Ctxt.FixedFrameSize() // 0 on 386, just to be consistent with other architectures p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -764,14 +740,14 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.BoundsCheckFunc[v.AuxInt] + p.To.Sym = ssagen.BoundsCheckFunc[v.AuxInt] s.UseArgs(8) // space used in callee args area by assembly stubs case ssa.Op386LoweredPanicExtendA, ssa.Op386LoweredPanicExtendB, ssa.Op386LoweredPanicExtendC: p := s.Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN - p.To.Sym = gc.ExtendCheckFunc[v.AuxInt] + p.To.Sym = ssagen.ExtendCheckFunc[v.AuxInt] s.UseArgs(12) // space used in callee args area by assembly stubs case ssa.Op386CALLstatic, ssa.Op386CALLclosure, ssa.Op386CALLinter: @@ -779,16 +755,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.Op386NEGL, ssa.Op386BSWAPL, ssa.Op386NOTL: - r := v.Reg() - if r != v.Args[0].Reg() { - v.Fatalf("input[0] and output not in same register %s", v.LongString()) - } p := s.Prog(v.Op.Asm()) p.To.Type = obj.TYPE_REG - p.To.Reg = r + p.To.Reg = v.Reg() case ssa.Op386BSFL, ssa.Op386BSFW, ssa.Op386BSRL, ssa.Op386BSRW, - ssa.Op386SQRTSD: + ssa.Op386SQRTSS, ssa.Op386SQRTSD: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[0].Reg() @@ -846,12 +818,12 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Reg = x86.REG_AX p.To.Type = obj.TYPE_MEM p.To.Reg = v.Args[0].Reg() - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) if logopt.Enabled() { logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name) } - if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers - gc.Warnl(v.Pos, "generated nil check") + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") } case ssa.OpClobber: p := s.Prog(x86.AMOVL) @@ -859,7 +831,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.From.Offset = 0xdeaddead p.To.Type = obj.TYPE_MEM p.To.Reg = x86.REG_SP - gc.AddAux(&p.To, v) + ssagen.AddAux(&p.To, v) + case ssa.OpClobberReg: + // TODO: implement for clobberdead experiment. Nop is ok for now. default: v.Fatalf("genValue not implemented: %s", v.LongString()) } @@ -884,22 +858,22 @@ var blockJump = [...]struct { ssa.Block386NAN: {x86.AJPS, x86.AJPC}, } -var eqfJumps = [2][2]gc.IndexJump{ +var eqfJumps = [2][2]ssagen.IndexJump{ {{Jump: x86.AJNE, Index: 1}, {Jump: x86.AJPS, Index: 1}}, // next == b.Succs[0] {{Jump: x86.AJNE, Index: 1}, {Jump: x86.AJPC, Index: 0}}, // next == b.Succs[1] } -var nefJumps = [2][2]gc.IndexJump{ +var nefJumps = [2][2]ssagen.IndexJump{ {{Jump: x86.AJNE, Index: 0}, {Jump: x86.AJPC, Index: 1}}, // next == b.Succs[0] {{Jump: x86.AJNE, Index: 0}, {Jump: x86.AJPS, Index: 0}}, // next == b.Succs[1] } -func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { switch b.Kind { case ssa.BlockPlain: if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockDefer: // defer returns in rax: @@ -912,11 +886,11 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.To.Reg = x86.REG_AX p = s.Prog(x86.AJNE) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[1].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()}) if b.Succs[0].Block() != next { p := s.Prog(obj.AJMP) p.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()}) + s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()}) } case ssa.BlockExit: case ssa.BlockRet: diff --git a/src/cmd/compile/main.go b/src/cmd/compile/main.go index 3aa64a5ce251b6dd672864f91613f35f04011915..3af1e1fafdfef2dbea6686e3e33398276ffb69f8 100644 --- a/src/cmd/compile/main.go +++ b/src/cmd/compile/main.go @@ -8,21 +8,23 @@ import ( "cmd/compile/internal/amd64" "cmd/compile/internal/arm" "cmd/compile/internal/arm64" + "cmd/compile/internal/base" "cmd/compile/internal/gc" "cmd/compile/internal/mips" "cmd/compile/internal/mips64" "cmd/compile/internal/ppc64" "cmd/compile/internal/riscv64" "cmd/compile/internal/s390x" + "cmd/compile/internal/ssagen" "cmd/compile/internal/wasm" "cmd/compile/internal/x86" - "cmd/internal/objabi" "fmt" + "internal/buildcfg" "log" "os" ) -var archInits = map[string]func(*gc.Arch){ +var archInits = map[string]func(*ssagen.ArchInfo){ "386": x86.Init, "amd64": amd64.Init, "arm": arm.Init, @@ -43,12 +45,13 @@ func main() { log.SetFlags(0) log.SetPrefix("compile: ") - archInit, ok := archInits[objabi.GOARCH] + buildcfg.Check() + archInit, ok := archInits[buildcfg.GOARCH] if !ok { - fmt.Fprintf(os.Stderr, "compile: unknown architecture %q\n", objabi.GOARCH) + fmt.Fprintf(os.Stderr, "compile: unknown architecture %q\n", buildcfg.GOARCH) os.Exit(2) } gc.Main(archInit) - gc.Exit(0) + base.Exit(0) } diff --git a/src/cmd/cover/func.go b/src/cmd/cover/func.go index ce7c771ac961971af670b00c1c79ea1c8b22153c..76a16b3fc4a11ef75c9ba4aabde154189ddf151d 100644 --- a/src/cmd/cover/func.go +++ b/src/cmd/cover/func.go @@ -23,6 +23,8 @@ import ( "runtime" "strings" "text/tabwriter" + + "golang.org/x/tools/cover" ) // funcOutput takes two file names as arguments, a coverage profile to read as input and an output @@ -38,7 +40,7 @@ import ( // total: (statements) 91.9% func funcOutput(profile, outputFile string) error { - profiles, err := ParseProfiles(profile) + profiles, err := cover.ParseProfiles(profile) if err != nil { return err } @@ -144,7 +146,7 @@ func (v *FuncVisitor) Visit(node ast.Node) ast.Visitor { } // coverage returns the fraction of the statements in the function that were covered, as a numerator and denominator. -func (f *FuncExtent) coverage(profile *Profile) (num, den int64) { +func (f *FuncExtent) coverage(profile *cover.Profile) (num, den int64) { // We could avoid making this n^2 overall by doing a single scan and annotating the functions, // but the sizes of the data structures is never very large and the scan is almost instantaneous. var covered, total int64 @@ -175,7 +177,7 @@ type Pkg struct { } } -func findPkgs(profiles []*Profile) (map[string]*Pkg, error) { +func findPkgs(profiles []*cover.Profile) (map[string]*Pkg, error) { // Run go list to find the location of every package we care about. pkgs := make(map[string]*Pkg) var list []string diff --git a/src/cmd/cover/html.go b/src/cmd/cover/html.go index b2865c427c8161239355a6ad88b4ec1c0cb1af41..3c1d17e7b956af110d816bc58a7a7878a0e411ec 100644 --- a/src/cmd/cover/html.go +++ b/src/cmd/cover/html.go @@ -15,13 +15,15 @@ import ( "os" "path/filepath" "strings" + + "golang.org/x/tools/cover" ) // htmlOutput reads the profile data from profile and generates an HTML // coverage report, writing it to outfile. If outfile is empty, // it writes the report to a temporary file and opens it in a web browser. func htmlOutput(profile, outfile string) error { - profiles, err := ParseProfiles(profile) + profiles, err := cover.ParseProfiles(profile) if err != nil { return err } @@ -92,7 +94,7 @@ func htmlOutput(profile, outfile string) error { // percentCovered returns, as a percentage, the fraction of the statements in // the profile covered by the test run. // In effect, it reports the coverage of a given source file. -func percentCovered(p *Profile) float64 { +func percentCovered(p *cover.Profile) float64 { var total, covered int64 for _, b := range p.Blocks { total += int64(b.NumStmt) @@ -108,7 +110,7 @@ func percentCovered(p *Profile) float64 { // htmlGen generates an HTML coverage report with the provided filename, // source code, and tokens, and writes it to the given Writer. -func htmlGen(w io.Writer, src []byte, boundaries []Boundary) error { +func htmlGen(w io.Writer, src []byte, boundaries []cover.Boundary) error { dst := bufio.NewWriter(w) for i := range src { for len(boundaries) > 0 && boundaries[0].Offset == i { diff --git a/src/cmd/cover/testdata/toolexec.go b/src/cmd/cover/testdata/toolexec.go index 386de79038a355c2de01fead816a1410479490f8..458adaeaaa53696e09b04686c0515b419aaa1fff 100644 --- a/src/cmd/cover/testdata/toolexec.go +++ b/src/cmd/cover/testdata/toolexec.go @@ -15,8 +15,8 @@ package main import ( - "os" exec "internal/execabs" + "os" "strings" ) diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go index c8c3212d1661f8eb835c20493bba15c35d84fdd4..bec17696f3040acd6f78750f3306c1d3bbeba102 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -14,6 +14,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "sort" "strings" "sync" @@ -39,6 +40,7 @@ var ( goextlinkenabled string gogcflags string // For running built compiler goldflags string + goexperiment string workdir string tooldir string oldgoos string @@ -111,9 +113,6 @@ func xinit() { fatalf("$GOROOT must be set") } goroot = filepath.Clean(b) - if modRoot := findModuleRoot(goroot); modRoot != "" { - fatalf("found go.mod file in %s: $GOROOT must not be inside a module", modRoot) - } b = os.Getenv("GOROOT_FINAL") if b == "" { @@ -197,6 +196,9 @@ func xinit() { goextlinkenabled = b } + goexperiment = os.Getenv("GOEXPERIMENT") + // TODO(mdempsky): Validate known experiments? + gogcflags = os.Getenv("BOOT_GO_GCFLAGS") goldflags = os.Getenv("BOOT_GO_LDFLAGS") @@ -241,6 +243,9 @@ func xinit() { os.Setenv("LANGUAGE", "en_US.UTF8") workdir = xworkdir() + if err := ioutil.WriteFile(pathf("%s/go.mod", workdir), []byte("module bootstrap"), 0666); err != nil { + fatalf("cannot write stub go.mod: %s", err) + } xatexit(rmworkdir) tooldir = pathf("%s/pkg/tool/%s_%s", goroot, gohostos, gohostarch) @@ -401,8 +406,22 @@ func findgoversion() string { } if !precise { - // Tag does not point at HEAD; add hash and date to version. - tag += chomp(run(goroot, CheckExit, "git", "log", "-n", "1", "--format=format: +%h %cd", "HEAD")) + // Tag does not point at HEAD; add 1.x base version, hash, and date to version. + // + // Note that we lightly parse internal/goversion/goversion.go to + // obtain the base version. We can't just import the package, + // because cmd/dist is built with a bootstrap GOROOT which could + // be an entirely different version of Go, like 1.4. We assume + // that the file contains "const Version = ". + + goversionSource := readfile(pathf("%s/src/internal/goversion/goversion.go", goroot)) + m := regexp.MustCompile(`(?m)^const Version = (\d+)`).FindStringSubmatch(goversionSource) + if m == nil { + fatalf("internal/goversion/goversion.go does not contain 'const Version = ...'") + } + tag += fmt.Sprintf(" go1.%s-", m[1]) + + tag += chomp(run(goroot, CheckExit, "git", "log", "-n", "1", "--format=format:%h %cd", "HEAD")) } // Cache version. @@ -834,18 +853,6 @@ func runInstall(pkg string, ch chan struct{}) { goasmh := pathf("%s/go_asm.h", workdir) if IsRuntimePackagePath(pkg) { asmArgs = append(asmArgs, "-compiling-runtime") - if os.Getenv("GOEXPERIMENT") == "regabi" { - // In order to make it easier to port runtime assembly - // to the register ABI, we introduce a macro - // indicating the experiment is enabled. - // - // Note: a similar change also appears in - // cmd/go/internal/work/gc.go. - // - // TODO(austin): Remove this once we commit to the - // register ABI (#40724). - asmArgs = append(asmArgs, "-D=GOEXPERIMENT_REGABI=1") - } } // Collect symabis from assembly code. @@ -1256,14 +1263,19 @@ func cmdbootstrap() { timelog("start", "dist bootstrap") defer timelog("end", "dist bootstrap") - var noBanner bool + var noBanner, noClean bool var debug bool flag.BoolVar(&rebuildall, "a", rebuildall, "rebuild all") flag.BoolVar(&debug, "d", debug, "enable debugging of bootstrap process") flag.BoolVar(&noBanner, "no-banner", noBanner, "do not print banner") + flag.BoolVar(&noClean, "no-clean", noClean, "print deprecation warning") xflagparse(0) + if noClean { + xprintf("warning: --no-clean is deprecated and has no effect; use 'go install std cmd' instead\n") + } + // Set GOPATH to an internal directory. We shouldn't actually // need to store files here, since the toolchain won't // depend on modules outside of vendor directories, but if @@ -1271,6 +1283,20 @@ func cmdbootstrap() { // go tool may complain. os.Setenv("GOPATH", pathf("%s/pkg/obj/gopath", goroot)) + // Disable GOEXPERIMENT when building toolchain1 and + // go_bootstrap. We don't need any experiments for the + // bootstrap toolchain, and this lets us avoid duplicating the + // GOEXPERIMENT-related build logic from cmd/go here. If the + // bootstrap toolchain is < Go 1.17, it will ignore this + // anyway since GOEXPERIMENT is baked in; otherwise it will + // pick it up from the environment we set here. Once we're + // using toolchain1 with dist as the build system, we need to + // override this to keep the experiments assumed by the + // toolchain and by dist consistent. Once go_bootstrap takes + // over the build process, we'll set this back to the original + // GOEXPERIMENT. + os.Setenv("GOEXPERIMENT", "none") + if debug { // cmd/buildid is used in debug mode. toolchain = append(toolchain, "cmd/buildid") @@ -1348,6 +1374,8 @@ func cmdbootstrap() { } xprintf("Building Go toolchain2 using go_bootstrap and Go toolchain1.\n") os.Setenv("CC", compilerEnvLookup(defaultcc, goos, goarch)) + // Now that cmd/go is in charge of the build process, enable GOEXPERIMENT. + os.Setenv("GOEXPERIMENT", goexperiment) goInstall(goBootstrap, append([]string{"-i"}, toolchain...)...) if debug { run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full") @@ -1500,11 +1528,11 @@ func goCmd(goBinary string, cmd string, args ...string) { goCmd = append(goCmd, "-p=1") } - run(goroot, ShowOutput|CheckExit, append(goCmd, args...)...) + run(workdir, ShowOutput|CheckExit, append(goCmd, args...)...) } func checkNotStale(goBinary string, targets ...string) { - out := run(goroot, CheckExit, + out := run(workdir, CheckExit, append([]string{ goBinary, "list", "-gcflags=all=" + gogcflags, "-ldflags=all=" + goldflags, @@ -1514,11 +1542,11 @@ func checkNotStale(goBinary string, targets ...string) { os.Setenv("GODEBUG", "gocachehash=1") for _, target := range []string{"runtime/internal/sys", "cmd/dist", "cmd/link"} { if strings.Contains(out, "STALE "+target) { - run(goroot, ShowOutput|CheckExit, goBinary, "list", "-f={{.ImportPath}} {{.Stale}}", target) + run(workdir, ShowOutput|CheckExit, goBinary, "list", "-f={{.ImportPath}} {{.Stale}}", target) break } } - fatalf("unexpected stale targets reported by %s list -gcflags=\"%s\" -ldflags=\"%s\" for %v:\n%s", goBinary, gogcflags, goldflags, targets, out) + fatalf("unexpected stale targets reported by %s list -gcflags=\"%s\" -ldflags=\"%s\" for %v (consider rerunning with GOMAXPROCS=1 GODEBUG=gocachehash=1):\n%s", goBinary, gogcflags, goldflags, targets, out) } } @@ -1567,7 +1595,7 @@ var cgoEnabled = map[string]bool{ "openbsd/amd64": true, "openbsd/arm": true, "openbsd/arm64": true, - "openbsd/mips64": false, + "openbsd/mips64": true, "plan9/386": false, "plan9/amd64": false, "plan9/arm": false, @@ -1575,6 +1603,7 @@ var cgoEnabled = map[string]bool{ "windows/386": true, "windows/amd64": true, "windows/arm": false, + "windows/arm64": true, } // List of platforms which are supported but not complete yet. These get @@ -1583,6 +1612,18 @@ var incomplete = map[string]bool{ "linux/sparc64": true, } +// List of platforms which are first class ports. See golang.org/issue/38874. +var firstClass = map[string]bool{ + "darwin/amd64": true, + "darwin/arm64": true, + "linux/386": true, + "linux/amd64": true, + "linux/arm": true, + "linux/arm64": true, + "windows/386": true, + "windows/amd64": true, +} + func needCC() bool { switch os.Getenv("CGO_ENABLED") { case "1": @@ -1609,20 +1650,6 @@ func checkCC() { } } -func findModuleRoot(dir string) (root string) { - for { - if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() { - return dir - } - d := filepath.Dir(dir) - if d == dir { - break - } - dir = d - } - return "" -} - func defaulttarg() string { // xgetwd might return a path with symlinks fully resolved, and if // there happens to be symlinks in goroot, then the hasprefix test @@ -1733,6 +1760,7 @@ func cmdlist() { GOOS string GOARCH string CgoSupported bool + FirstClass bool } var results []jsonResult for _, p := range plats { @@ -1740,7 +1768,8 @@ func cmdlist() { results = append(results, jsonResult{ GOOS: fields[0], GOARCH: fields[1], - CgoSupported: cgoEnabled[p]}) + CgoSupported: cgoEnabled[p], + FirstClass: firstClass[p]}) } out, err := json.MarshalIndent(results, "", "\t") if err != nil { @@ -1754,8 +1783,9 @@ func cmdlist() { // IsRuntimePackagePath examines 'pkgpath' and returns TRUE if it // belongs to the collection of "runtime-related" packages, including // "runtime" itself, "reflect", "syscall", and the -// "runtime/internal/*" packages. See also the function of the same -// name in cmd/internal/objabi/path.go. +// "runtime/internal/*" packages. +// +// Keep in sync with cmd/internal/objabi/path.go:IsRuntimePackagePath. func IsRuntimePackagePath(pkgpath string) bool { rval := false switch pkgpath { @@ -1765,6 +1795,8 @@ func IsRuntimePackagePath(pkgpath string) bool { rval = true case "syscall": rval = true + case "internal/bytealg": + rval = true default: rval = strings.HasPrefix(pkgpath, "runtime/internal") } diff --git a/src/cmd/dist/buildruntime.go b/src/cmd/dist/buildruntime.go index 27449515976b36449ecd7a92c3341d3a98fbb832..54e935ad3bec1d35c5bf37732ed1bb659965792c 100644 --- a/src/cmd/dist/buildruntime.go +++ b/src/cmd/dist/buildruntime.go @@ -19,8 +19,6 @@ import ( // // package sys // -// const TheVersion = -// const Goexperiment = // const StackGuardMultiplier = // func mkzversion(dir, file string) { @@ -29,29 +27,20 @@ func mkzversion(dir, file string) { fmt.Fprintln(&buf) fmt.Fprintf(&buf, "package sys\n") fmt.Fprintln(&buf) - fmt.Fprintf(&buf, "const TheVersion = `%s`\n", findgoversion()) - fmt.Fprintf(&buf, "const Goexperiment = `%s`\n", os.Getenv("GOEXPERIMENT")) fmt.Fprintf(&buf, "const StackGuardMultiplierDefault = %d\n", stackGuardMultiplierDefault()) writefile(buf.String(), file, writeSkipSame) } -// mkzbootstrap writes cmd/internal/objabi/zbootstrap.go: +// mkbuildcfg writes internal/buildcfg/zbootstrap.go: // -// package objabi +// package buildcfg // // const defaultGOROOT = // const defaultGO386 = -// const defaultGOARM = -// const defaultGOMIPS = -// const defaultGOMIPS64 = -// const defaultGOPPC64 = +// ... // const defaultGOOS = runtime.GOOS // const defaultGOARCH = runtime.GOARCH -// const defaultGO_EXTLINK_ENABLED = -// const version = -// const stackGuardMultiplierDefault = -// const goexperiment = // // The use of runtime.GOOS and runtime.GOARCH makes sure that // a cross-compiled compiler expects to compile for its own target @@ -62,11 +51,11 @@ func mkzversion(dir, file string) { // the resulting compiler will default to generating linux/ppc64 object files. // This is more useful than having it default to generating objects for the // original target (in this example, a Mac). -func mkzbootstrap(file string) { +func mkbuildcfg(file string) { var buf bytes.Buffer fmt.Fprintf(&buf, "// Code generated by go tool dist; DO NOT EDIT.\n") fmt.Fprintln(&buf) - fmt.Fprintf(&buf, "package objabi\n") + fmt.Fprintf(&buf, "package buildcfg\n") fmt.Fprintln(&buf) fmt.Fprintf(&buf, "import \"runtime\"\n") fmt.Fprintln(&buf) @@ -75,13 +64,29 @@ func mkzbootstrap(file string) { fmt.Fprintf(&buf, "const defaultGOMIPS = `%s`\n", gomips) fmt.Fprintf(&buf, "const defaultGOMIPS64 = `%s`\n", gomips64) fmt.Fprintf(&buf, "const defaultGOPPC64 = `%s`\n", goppc64) - fmt.Fprintf(&buf, "const defaultGOOS = runtime.GOOS\n") - fmt.Fprintf(&buf, "const defaultGOARCH = runtime.GOARCH\n") + fmt.Fprintf(&buf, "const defaultGOEXPERIMENT = `%s`\n", goexperiment) fmt.Fprintf(&buf, "const defaultGO_EXTLINK_ENABLED = `%s`\n", goextlinkenabled) fmt.Fprintf(&buf, "const defaultGO_LDSO = `%s`\n", defaultldso) fmt.Fprintf(&buf, "const version = `%s`\n", findgoversion()) + fmt.Fprintf(&buf, "const defaultGOOS = runtime.GOOS\n") + fmt.Fprintf(&buf, "const defaultGOARCH = runtime.GOARCH\n") + + writefile(buf.String(), file, writeSkipSame) +} + +// mkobjabi writes cmd/internal/objabi/zbootstrap.go: +// +// package objabi +// +// const stackGuardMultiplierDefault = +// +func mkobjabi(file string) { + var buf bytes.Buffer + fmt.Fprintf(&buf, "// Code generated by go tool dist; DO NOT EDIT.\n") + fmt.Fprintln(&buf) + fmt.Fprintf(&buf, "package objabi\n") + fmt.Fprintln(&buf) fmt.Fprintf(&buf, "const stackGuardMultiplierDefault = %d\n", stackGuardMultiplierDefault()) - fmt.Fprintf(&buf, "const goexperiment = `%s`\n", os.Getenv("GOEXPERIMENT")) writefile(buf.String(), file, writeSkipSame) } diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go index cf85f2ac8ea5ffe408d8d36de8672c554ed40cc1..26b33e389fe9283aabd5c0c8f44fba88fa44ea8c 100644 --- a/src/cmd/dist/buildtool.go +++ b/src/cmd/dist/buildtool.go @@ -23,80 +23,43 @@ import ( // compiled with a Go 1.4 toolchain to produce the bootstrapTargets. // All directories in this list are relative to and must be below $GOROOT/src. // -// The list has have two kinds of entries: names beginning with cmd/ with +// The list has two kinds of entries: names beginning with cmd/ with // no other slashes, which are commands, and other paths, which are packages // supporting the commands. Packages in the standard library can be listed // if a newer copy needs to be substituted for the Go 1.4 copy when used -// by the command packages. +// by the command packages. Paths ending with /... automatically +// include all packages within subdirectories as well. // These will be imported during bootstrap as bootstrap/name, like bootstrap/math/big. var bootstrapDirs = []string{ "cmd/asm", - "cmd/asm/internal/arch", - "cmd/asm/internal/asm", - "cmd/asm/internal/flags", - "cmd/asm/internal/lex", + "cmd/asm/internal/...", "cmd/cgo", "cmd/compile", - "cmd/compile/internal/amd64", - "cmd/compile/internal/arm", - "cmd/compile/internal/arm64", - "cmd/compile/internal/gc", - "cmd/compile/internal/logopt", - "cmd/compile/internal/mips", - "cmd/compile/internal/mips64", - "cmd/compile/internal/ppc64", - "cmd/compile/internal/riscv64", - "cmd/compile/internal/s390x", - "cmd/compile/internal/ssa", - "cmd/compile/internal/syntax", - "cmd/compile/internal/types", - "cmd/compile/internal/x86", - "cmd/compile/internal/wasm", + "cmd/compile/internal/...", + "cmd/internal/archive", "cmd/internal/bio", "cmd/internal/codesign", - "cmd/internal/gcprog", "cmd/internal/dwarf", "cmd/internal/edit", + "cmd/internal/gcprog", "cmd/internal/goobj", + "cmd/internal/obj/...", "cmd/internal/objabi", - "cmd/internal/obj", - "cmd/internal/obj/arm", - "cmd/internal/obj/arm64", - "cmd/internal/obj/mips", - "cmd/internal/obj/ppc64", - "cmd/internal/obj/riscv", - "cmd/internal/obj/s390x", - "cmd/internal/obj/x86", - "cmd/internal/obj/wasm", "cmd/internal/pkgpath", "cmd/internal/src", "cmd/internal/sys", "cmd/link", - "cmd/link/internal/amd64", - "cmd/link/internal/arm", - "cmd/link/internal/arm64", - "cmd/link/internal/benchmark", - "cmd/link/internal/ld", - "cmd/link/internal/loadelf", - "cmd/link/internal/loader", - "cmd/link/internal/loadmacho", - "cmd/link/internal/loadpe", - "cmd/link/internal/loadxcoff", - "cmd/link/internal/mips", - "cmd/link/internal/mips64", - "cmd/link/internal/ppc64", - "cmd/link/internal/riscv64", - "cmd/link/internal/s390x", - "cmd/link/internal/sym", - "cmd/link/internal/x86", + "cmd/link/internal/...", "compress/flate", "compress/zlib", - "cmd/link/internal/wasm", "container/heap", "debug/dwarf", "debug/elf", "debug/macho", "debug/pe", + "go/constant", + "internal/buildcfg", + "internal/goexperiment", "internal/goversion", "internal/race", "internal/unsafeheader", @@ -104,6 +67,7 @@ var bootstrapDirs = []string{ "math/big", "math/bits", "sort", + "strconv", } // File prefixes that are ignored by go/build anyway, and cause @@ -111,6 +75,7 @@ var bootstrapDirs = []string{ var ignorePrefixes = []string{ ".", "_", + "#", } // File suffixes that use build tags introduced since Go 1.4. @@ -124,6 +89,7 @@ var ignoreSuffixes = []string{ "_wasm.s", "_wasm.go", "_test.s", + "_test.go", } func bootstrapBuildTools() { @@ -133,7 +99,8 @@ func bootstrapBuildTools() { } xprintf("Building Go toolchain1 using %s.\n", goroot_bootstrap) - mkzbootstrap(pathf("%s/src/cmd/internal/objabi/zbootstrap.go", goroot)) + mkbuildcfg(pathf("%s/src/internal/buildcfg/zbootstrap.go", goroot)) + mkobjabi(pathf("%s/src/cmd/internal/objabi/zbootstrap.go", goroot)) // Use $GOROOT/pkg/bootstrap as the bootstrap workspace root. // We use a subdirectory of $GOROOT/pkg because that's the @@ -149,31 +116,47 @@ func bootstrapBuildTools() { // Copy source code into $GOROOT/pkg/bootstrap and rewrite import paths. writefile("module bootstrap\n", pathf("%s/%s", base, "go.mod"), 0) for _, dir := range bootstrapDirs { - src := pathf("%s/src/%s", goroot, dir) - dst := pathf("%s/%s", base, dir) - xmkdirall(dst) - if dir == "cmd/cgo" { - // Write to src because we need the file both for bootstrap - // and for later in the main build. - mkzdefaultcc("", pathf("%s/zdefaultcc.go", src)) - } - Dir: - for _, name := range xreaddirfiles(src) { + recurse := strings.HasSuffix(dir, "/...") + dir = strings.TrimSuffix(dir, "/...") + filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + fatalf("walking bootstrap dirs failed: %v: %v", path, err) + } + + name := filepath.Base(path) + src := pathf("%s/src/%s", goroot, path) + dst := pathf("%s/%s", base, path) + + if info.IsDir() { + if !recurse && path != dir || name == "testdata" { + return filepath.SkipDir + } + + xmkdirall(dst) + if path == "cmd/cgo" { + // Write to src because we need the file both for bootstrap + // and for later in the main build. + mkzdefaultcc("", pathf("%s/zdefaultcc.go", src)) + mkzdefaultcc("", pathf("%s/zdefaultcc.go", dst)) + } + return nil + } + for _, pre := range ignorePrefixes { if strings.HasPrefix(name, pre) { - continue Dir + return nil } } for _, suf := range ignoreSuffixes { if strings.HasSuffix(name, suf) { - continue Dir + return nil } } - srcFile := pathf("%s/%s", src, name) - dstFile := pathf("%s/%s", dst, name) - text := bootstrapRewriteFile(srcFile) - writefile(text, dstFile, 0) - } + + text := bootstrapRewriteFile(src) + writefile(text, dst, 0) + return nil + }) } // Set up environment for invoking Go 1.4 go command. diff --git a/src/cmd/dist/sys_default.go b/src/cmd/dist/sys_default.go index 821dc273d60353b23ec32e8a6b4a67f7dd9d975a..e87c84ce3eea4ce07ba6ab387df7a7c3871d2f2c 100644 --- a/src/cmd/dist/sys_default.go +++ b/src/cmd/dist/sys_default.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !windows // +build !windows package main diff --git a/src/cmd/dist/sys_windows.go b/src/cmd/dist/sys_windows.go index 2f6a1b0dceb6fe29b5f5280173ec7f53037652d7..265f729d0fc345611f27e25037a955f9bc1fca17 100644 --- a/src/cmd/dist/sys_windows.go +++ b/src/cmd/dist/sys_windows.go @@ -29,10 +29,13 @@ type systeminfo struct { wProcessorRevision uint16 } +// See https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info const ( PROCESSOR_ARCHITECTURE_AMD64 = 9 PROCESSOR_ARCHITECTURE_INTEL = 0 PROCESSOR_ARCHITECTURE_ARM = 5 + PROCESSOR_ARCHITECTURE_ARM64 = 12 + PROCESSOR_ARCHITECTURE_IA64 = 6 ) var sysinfo systeminfo @@ -46,6 +49,8 @@ func sysinit() { gohostarch = "386" case PROCESSOR_ARCHITECTURE_ARM: gohostarch = "arm" + case PROCESSOR_ARCHITECTURE_ARM64: + gohostarch = "arm64" default: fatalf("unknown processor architecture") } diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 4f081c9f888987ca37e32ac1af5d46ef28533bf3..f40fa926dfd398b2c8adf239122358ef49e358cc 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -42,6 +42,7 @@ func cmdtest() { if noRebuild { t.rebuild = false } + t.run() } @@ -117,6 +118,21 @@ func (t *tester) run() { } } + // Set GOTRACEBACK to system if the user didn't set a level explicitly. + // Since we're running tests for Go, we want as much detail as possible + // if something goes wrong. + // + // Set it before running any commands just in case something goes wrong. + if ok := isEnvSet("GOTRACEBACK"); !ok { + if err := os.Setenv("GOTRACEBACK", "system"); err != nil { + if t.keepGoing { + log.Printf("Failed to set GOTRACEBACK: %v", err) + } else { + fatalf("Failed to set GOTRACEBACK: %v", err) + } + } + } + if t.rebuild { t.out("Building packages and commands.") // Force rebuild the whole toolchain. @@ -309,14 +325,24 @@ var ( benchMatches []string ) -func (t *tester) registerStdTest(pkg string) { - testName := "go_test:" + pkg +func (t *tester) registerStdTest(pkg string, useG3 bool) { + heading := "Testing packages." + testPrefix := "go_test:" + gcflags := gogcflags + if useG3 { + heading = "Testing packages with -G=3." + testPrefix = "go_test_g3:" + gcflags += " -G=3" + } + + testName := testPrefix + pkg if t.runRx == nil || t.runRx.MatchString(testName) == t.runRxWant { stdMatches = append(stdMatches, pkg) } + t.tests = append(t.tests, distTest{ name: testName, - heading: "Testing packages.", + heading: heading, fn: func(dt *distTest) error { if ranGoTest { return nil @@ -343,7 +369,7 @@ func (t *tester) registerStdTest(pkg string) { "-short=" + short(), t.tags(), t.timeout(timeoutSec), - "-gcflags=all=" + gogcflags, + "-gcflags=all=" + gcflags, } if t.race { args = append(args, "-race") @@ -408,7 +434,10 @@ func (t *tester) registerTests() { if len(t.runNames) > 0 { for _, name := range t.runNames { if strings.HasPrefix(name, "go_test:") { - t.registerStdTest(strings.TrimPrefix(name, "go_test:")) + t.registerStdTest(strings.TrimPrefix(name, "go_test:"), false) + } + if strings.HasPrefix(name, "go_test_g3:") { + t.registerStdTest(strings.TrimPrefix(name, "go_test_g3:"), true) } if strings.HasPrefix(name, "go_test_bench:") { t.registerRaceBenchTest(strings.TrimPrefix(name, "go_test_bench:")) @@ -431,8 +460,15 @@ func (t *tester) registerTests() { fatalf("Error running go list std cmd: %v:\n%s", err, cmd.Stderr) } pkgs := strings.Fields(string(all)) + if false { + // Disable -G=3 option for standard tests for now, since + // they are flaky on the builder. + for _, pkg := range pkgs { + t.registerStdTest(pkg, true /* -G=3 flag */) + } + } for _, pkg := range pkgs { - t.registerStdTest(pkg) + t.registerStdTest(pkg, false) } if t.race { for _, pkg := range pkgs { @@ -455,6 +491,19 @@ func (t *tester) registerTests() { }) } + // Test go/... cmd/gofmt with type parameters enabled. + if !t.compileOnly { + t.tests = append(t.tests, distTest{ + name: "tyepparams", + heading: "go/... and cmd/gofmt tests with tag typeparams", + fn: func(dt *distTest) error { + t.addCmd(dt, "src", t.goTest(), t.timeout(300), "-tags=typeparams", "go/...") + t.addCmd(dt, "src", t.goTest(), t.timeout(300), "-tags=typeparams", "cmd/gofmt") + return nil + }, + }) + } + if t.iOS() && !t.compileOnly { t.tests = append(t.tests, distTest{ name: "x509omitbundledroots", @@ -673,14 +722,29 @@ func (t *tester) registerTests() { }, }) if t.hasCxx() { - t.tests = append(t.tests, distTest{ - name: "swig_callback", - heading: "../misc/swig/callback", - fn: func(dt *distTest) error { - t.addCmd(dt, "misc/swig/callback", t.goTest()) - return nil + t.tests = append(t.tests, + distTest{ + name: "swig_callback", + heading: "../misc/swig/callback", + fn: func(dt *distTest) error { + t.addCmd(dt, "misc/swig/callback", t.goTest()) + return nil + }, }, - }) + distTest{ + name: "swig_callback_lto", + heading: "../misc/swig/callback", + fn: func(dt *distTest) error { + cmd := t.addCmd(dt, "misc/swig/callback", t.goTest()) + cmd.Env = append(os.Environ(), + "CGO_CFLAGS=-flto -Wno-lto-type-mismatch -Wno-unknown-warning-option", + "CGO_CXXFLAGS=-flto -Wno-lto-type-mismatch -Wno-unknown-warning-option", + "CGO_LDFLAGS=-flto -Wno-lto-type-mismatch -Wno-unknown-warning-option", + ) + return nil + }, + }, + ) } } } @@ -716,8 +780,10 @@ func (t *tester) registerTests() { if gohostos == "linux" && goarch == "amd64" { t.registerTest("testasan", "../misc/cgo/testasan", "go", "run", ".") } - if mSanSupported(goos, goarch) { - t.registerHostTest("testsanitizers/msan", "../misc/cgo/testsanitizers", "misc/cgo/testsanitizers", ".") + if goos == "linux" && goarch != "ppc64le" { + // because syscall.SysProcAttr struct used in misc/cgo/testsanitizers is only built on linux. + // Some inconsistent failures happen on ppc64le so disable for now. + t.registerHostTest("testsanitizers", "../misc/cgo/testsanitizers", "misc/cgo/testsanitizers", ".") } if t.hasBash() && goos != "android" && !t.iOS() && gohostos != "windows" { t.registerHostTest("cgo_errors", "../misc/cgo/errors", "misc/cgo/errors", ".") @@ -942,6 +1008,9 @@ func (t *tester) internalLink() bool { if goos == "ios" { return false } + if goos == "windows" && goarch == "arm64" { + return false + } // Internally linking cgo is incomplete on some architectures. // https://golang.org/issue/10373 // https://golang.org/issue/14449 @@ -988,7 +1057,7 @@ func (t *tester) supportedBuildmode(mode string) bool { "darwin-amd64", "darwin-arm64", "freebsd-amd64", "android-arm", "android-arm64", "android-386", - "windows-amd64", "windows-386": + "windows-amd64", "windows-386", "windows-arm64": return true } return false @@ -1074,8 +1143,7 @@ func (t *tester) cgoTest(dt *distTest) error { cmd := t.addCmd(dt, "misc/cgo/test", t.goTest()) cmd.Env = append(os.Environ(), "GOFLAGS=-ldflags=-linkmode=auto") - // Skip internal linking cases on arm64 to support GCC-9.4 and above, - // only for linux, conservatively. + // Skip internal linking cases on linux/arm64 to support GCC-9.4 and above. // See issue #39466. skipInternalLink := goarch == "arm64" && goos == "linux" @@ -1613,24 +1681,13 @@ func raceDetectorSupported(goos, goarch string) bool { return goarch == "amd64" || goarch == "ppc64le" || goarch == "arm64" case "darwin": return goarch == "amd64" || goarch == "arm64" - case "freebsd", "netbsd", "windows": + case "freebsd", "netbsd", "openbsd", "windows": return goarch == "amd64" default: return false } } -// mSanSupported is a copy of the function cmd/internal/sys.MSanSupported, -// which can't be used here because cmd/dist has to be buildable by Go 1.4. -func mSanSupported(goos, goarch string) bool { - switch goos { - case "linux": - return goarch == "amd64" || goarch == "arm64" - default: - return false - } -} - // isUnsupportedVMASize reports whether the failure is caused by an unsupported // VMA for the race detector (for example, running the race detector on an // arm64 machine configured with 39-bit VMA) @@ -1638,3 +1695,15 @@ func isUnsupportedVMASize(w *work) bool { unsupportedVMA := []byte("unsupported VMA range") return w.dt.name == "race" && bytes.Contains(w.out, unsupportedVMA) } + +// isEnvSet reports whether the environment variable evar is +// set in the environment. +func isEnvSet(evar string) bool { + evarEq := evar + "=" + for _, e := range os.Environ() { + if strings.HasPrefix(e, evarEq) { + return true + } + } + return false +} diff --git a/src/cmd/dist/test_linux.go b/src/cmd/dist/test_linux.go index b6d0aedbbf70117a9235b82fd6e1a8d4c1dea272..43d28dc661985cbc3d6550b122a1558e7512dbbe 100644 --- a/src/cmd/dist/test_linux.go +++ b/src/cmd/dist/test_linux.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build linux // +build linux package main diff --git a/src/cmd/dist/util.go b/src/cmd/dist/util.go index 0a419e465fe87a7f3b088611aa56c8d7c50e5d5d..df60145d1e21e8f2a9a4fc421e56c2f20d305c6b 100644 --- a/src/cmd/dist/util.go +++ b/src/cmd/dist/util.go @@ -249,6 +249,7 @@ func writefile(text, file string, flag int) { if flag&writeExec != 0 { mode = 0777 } + xremove(file) // in case of symlink tricks by misc/reboot test err := ioutil.WriteFile(file, new, mode) if err != nil { fatalf("%v", err) @@ -389,6 +390,10 @@ func xgetgoarm() string { // sense to auto-detect the setting. return "7" } + if goos == "windows" { + // windows/arm only works with ARMv7 executables. + return "7" + } if gohostarch != "arm" || goos != gohostos { // Conservative default for cross-compilation. return "5" diff --git a/src/cmd/dist/util_gc.go b/src/cmd/dist/util_gc.go index 17a0e6fbb56227d34226682c5154d43a554238b7..875784d3830bccea2d23a7fed4134e538c2d1077 100644 --- a/src/cmd/dist/util_gc.go +++ b/src/cmd/dist/util_gc.go @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !gccgo +//go:build gc +// +build gc package main diff --git a/src/cmd/dist/util_gccgo.go b/src/cmd/dist/util_gccgo.go index dc897236fbbe19a1f956675c1636e358bb7d3c80..3255b8036526688e1495ec2b6017d3ab2057b8c9 100644 --- a/src/cmd/dist/util_gccgo.go +++ b/src/cmd/dist/util_gccgo.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build gccgo // +build gccgo package main diff --git a/src/cmd/dist/vfp_arm.s b/src/cmd/dist/vfp_arm.s index d571f8b82a2d39e6e9d1495bfb03af3c0aaa072f..525ee9b3661f529cab4e62ef676e2fddfa4d4458 100644 --- a/src/cmd/dist/vfp_arm.s +++ b/src/cmd/dist/vfp_arm.s @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build gc,arm +//go:build gc +// +build gc #include "textflag.h" diff --git a/src/cmd/dist/vfp_default.s b/src/cmd/dist/vfp_default.s index 84829beeff35742ab79ff67ed8714d9b37b830e5..0c1e16b0aa052380bcfc6f541c49c84c4cf74467 100644 --- a/src/cmd/dist/vfp_default.s +++ b/src/cmd/dist/vfp_default.s @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !arm,gc +//go:build gc && !arm +// +build gc,!arm #include "textflag.h" diff --git a/src/cmd/doc/doc_test.go b/src/cmd/doc/doc_test.go index 39530e3c2d67dec54e8a274fc25e89ddcb8ad2a8..af7793133ef68bd0d064ee75a1f4e6f80e9396c5 100644 --- a/src/cmd/doc/doc_test.go +++ b/src/cmd/doc/doc_test.go @@ -579,7 +579,7 @@ var tests = []test{ []string{ `Comment about exported interface`, // Include comment. `type ExportedInterface interface`, // Interface definition. - `Comment before exported method.*\n.*ExportedMethod\(\)` + + `Comment before exported method.\n.*//\n.*// // Code block showing how to use ExportedMethod\n.*// func DoSomething\(\) error {\n.*// ExportedMethod\(\)\n.*// return nil\n.*// }\n.*//.*\n.*ExportedMethod\(\)` + `.*Comment on line with exported method`, `io.Reader.*Comment on line with embedded Reader`, `error.*Comment on line with embedded error`, @@ -599,8 +599,7 @@ var tests = []test{ []string{ `Comment about exported interface`, // Include comment. `type ExportedInterface interface`, // Interface definition. - `Comment before exported method.*\n.*ExportedMethod\(\)` + - `.*Comment on line with exported method`, + `Comment before exported method.\n.*//\n.*// // Code block showing how to use ExportedMethod\n.*// func DoSomething\(\) error {\n.*// ExportedMethod\(\)\n.*// return nil\n.*// }\n.*//.*\n.*ExportedMethod\(\)` + `.*Comment on line with exported method`, `unexportedMethod\(\).*Comment on line with unexported method`, `io.Reader.*Comment on line with embedded Reader`, `error.*Comment on line with embedded error`, @@ -615,7 +614,7 @@ var tests = []test{ "interface method", []string{p, `ExportedInterface.ExportedMethod`}, []string{ - `Comment before exported method.*\n.*ExportedMethod\(\)` + + `Comment before exported method.\n.*//\n.*// // Code block showing how to use ExportedMethod\n.*// func DoSomething\(\) error {\n.*// ExportedMethod\(\)\n.*// return nil\n.*// }\n.*//.*\n.*ExportedMethod\(\)` + `.*Comment on line with exported method`, }, []string{ diff --git a/src/cmd/doc/pkg.go b/src/cmd/doc/pkg.go index c2e06ebc8b071108d052d6e1b7cad049b038a8e8..587f0bdc1468f2fd23a2f15d8a097a89137f4fef 100644 --- a/src/cmd/doc/pkg.go +++ b/src/cmd/doc/pkg.go @@ -950,6 +950,9 @@ func (pkg *Package) printMethodDoc(symbol, method string) bool { // Not an interface type. continue } + + // Collect and print only the methods that match. + var methods []*ast.Field for _, iMethod := range inter.Methods.List { // This is an interface, so there can be only one name. // TODO: Anonymous methods (embedding) @@ -958,22 +961,21 @@ func (pkg *Package) printMethodDoc(symbol, method string) bool { } name := iMethod.Names[0].Name if match(method, name) { - if iMethod.Doc != nil { - for _, comment := range iMethod.Doc.List { - doc.ToText(&pkg.buf, comment.Text, "", indent, indentedWidth) - } - } - s := pkg.oneLineNode(iMethod.Type) - // Hack: s starts "func" but there is no name present. - // We could instead build a FuncDecl but it's not worthwhile. - lineComment := "" - if iMethod.Comment != nil { - lineComment = fmt.Sprintf(" %s", iMethod.Comment.List[0].Text) - } - pkg.Printf("func %s%s%s\n", name, s[4:], lineComment) + methods = append(methods, iMethod) found = true } } + if found { + pkg.Printf("type %s ", spec.Name) + inter.Methods.List, methods = methods, inter.Methods.List + err := format.Node(&pkg.buf, pkg.fs, inter) + if err != nil { + log.Fatal(err) + } + pkg.newlines(1) + // Restore the original methods. + inter.Methods.List = methods + } } return found } diff --git a/src/cmd/doc/testdata/pkg.go b/src/cmd/doc/testdata/pkg.go index d695bdf1c5f6bbf10419f6ad2925d3251877fee5..5ece8325651ebad4d2e39af1e67e1b8272992624 100644 --- a/src/cmd/doc/testdata/pkg.go +++ b/src/cmd/doc/testdata/pkg.go @@ -111,6 +111,13 @@ const unexportedTypedConstant ExportedType = 1 // In a separate section to test // Comment about exported interface. type ExportedInterface interface { // Comment before exported method. + // + // // Code block showing how to use ExportedMethod + // func DoSomething() error { + // ExportedMethod() + // return nil + // } + // ExportedMethod() // Comment on line with exported method. unexportedMethod() // Comment on line with unexported method. io.Reader // Comment on line with embedded Reader. diff --git a/src/cmd/go.mod b/src/cmd/go.mod index 70b1b0690b38a251fe7ce8a10a97e2b6846d1d86..cd03968eedcf618d0ecef8da89ce790646dba399 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -1,12 +1,15 @@ module cmd -go 1.16 +go 1.17 require ( - github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2 - golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff - golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 - golang.org/x/mod v0.4.2-0.20210325185522-dbbbf8a3c6ea - golang.org/x/sys v0.0.0-20201204225414-ed752295db88 // indirect - golang.org/x/tools v0.0.0-20210107193943-4ed967dd8eff + github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a + github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 // indirect + golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e + golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e // indirect + golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a + golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect + golang.org/x/term v0.0.0-20210503060354-a79de5458b56 + golang.org/x/tools v0.1.2-0.20210519160823-49064d2332f9 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect ) diff --git a/src/cmd/go.sum b/src/cmd/go.sum index edda18b526b147c7858515cc033391644d9ccdbc..d728acaec9925e702dac5a61edeaebac48d878c6 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -1,38 +1,43 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2 h1:HyOHhUtuB/Ruw/L5s5pG2D0kckkN2/IzBs9OClGHnHI= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a h1:jmAp/2PZAScNd62lTD3Mcb0Ey9FvIIJtLohPhtxZJ+Q= +github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff h1:XmKBi9R6duxOB3lfc72wyrwiOY7X2Jl1wuI+RFOyMDE= -golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e h1:pv3V0NlNSh5Q6AX/StwGLBjcLS7UN4m4Gq+V+uSecqM= +golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 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/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2-0.20210325185522-dbbbf8a3c6ea h1:zAn46O7Vmm6KdLXx+635hPZSArrt/wNctv4Ab70Jw3k= -golang.org/x/mod v0.4.2-0.20210325185522-dbbbf8a3c6ea/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI= +golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a h1:e8qnjKz4EE6OjRki9wTadWSIogINvq10sMcuBRORxMY= +golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 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/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88 h1:KmZPnMocC93w341XZp26yTJg8Za7lhb2KhkYmixoeso= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q= +golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20210107193943-4ed967dd8eff h1:6EkB024TP1fu6cmQqeCNw685zYDVt5g8N1BXh755SQM= -golang.org/x/tools v0.0.0-20210107193943-4ed967dd8eff/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.2-0.20210519160823-49064d2332f9 h1:2XlR/j4I4xz5GQZI7zBjqTfezYyRIE2jD5IMousB2rg= +golang.org/x/tools v0.1.2-0.20210519160823-49064d2332f9/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 84f89c9d2d2be49f3911aa00121d3ba2e3869f31..7f88d3216cf080a9573026997231cc608a801589 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -111,7 +111,7 @@ // -p n // the number of programs, such as build commands or // test binaries, that can be run in parallel. -// The default is the number of CPUs available. +// The default is GOMAXPROCS, normally the number of CPUs available. // -race // enable data race detection. // Supported only on linux/amd64, freebsd/amd64, darwin/amd64, windows/amd64, @@ -174,8 +174,8 @@ // a build will run as if the disk file path exists with the contents // given by the backing file paths, or as if the disk file path does not // exist if its backing file path is empty. Support for the -overlay flag -// has some limitations:importantly, cgo files included from outside the -// include path must be in the same directory as the Go package they are +// has some limitations: importantly, cgo files included from outside the +// include path must be in the same directory as the Go package they are // included from, and overlays will not appear when binaries and tests are // run through go run and go test respectively. // -pkgdir dir @@ -198,6 +198,8 @@ // a program to use to invoke toolchain programs like vet and asm. // For example, instead of running asm, the go command will run // 'cmd args /path/to/asm '. +// The TOOLEXEC_IMPORTPATH environment variable will be set, +// matching 'go list -f {{.ImportPath}}' for the package being built. // // The -asmflags, -gccgoflags, -gcflags, and -ldflags flags accept a // space-separated list of arguments to pass to an underlying tool @@ -291,7 +293,7 @@ // // Usage: // -// go doc [-u] [-c] [package|[package.]symbol[.methodOrField]] +// go doc [doc flags] [package|[package.]symbol[.methodOrField]] // // Doc prints the documentation comments associated with the item identified by its // arguments (a package, const, func, type, var, method, or struct field) @@ -596,7 +598,7 @@ // // Usage: // -// go get [-d] [-t] [-u] [-v] [-insecure] [build flags] [packages] +// go get [-d] [-t] [-u] [-v] [build flags] [packages] // // Get resolves its command-line arguments to packages at specific module versions, // updates go.mod to require those versions, downloads source code into the @@ -641,14 +643,6 @@ // When the -t and -u flags are used together, get will update // test dependencies as well. // -// The -insecure flag permits fetching from repositories and resolving -// custom domains using insecure schemes such as HTTP, and also bypassess -// module sum validation using the checksum database. Use with caution. -// This flag is deprecated and will be removed in a future version of go. -// To permit the use of insecure schemes, use the GOINSECURE environment -// variable instead. To bypass module sum validation, use GOPRIVATE or -// GONOSUMDB. See 'go help environment' for details. -// // The -d flag instructs get not to build or install packages. get will only // update go.mod and download source code needed to build packages. // @@ -849,6 +843,7 @@ // UseAllFiles bool // use files regardless of +build lines, file names // Compiler string // compiler to assume when computing target paths // BuildTags []string // build constraints to match in +build lines +// ToolTags []string // toolchain-specific build constraints // ReleaseTags []string // releases the current release is compatible with // InstallSuffix string // suffix to use in the name of the install dir // } @@ -1083,7 +1078,7 @@ // // Usage: // -// go mod edit [editing flags] [go.mod] +// go mod edit [editing flags] [-fmt|-print|-json] [go.mod] // // Edit provides a command-line interface for editing go.mod, // for use primarily by tools or scripts. It reads only go.mod; @@ -1142,12 +1137,12 @@ // writing it back to go.mod. The JSON output corresponds to these Go types: // // type Module struct { -// Path string +// Path string // Version string // } // // type GoMod struct { -// Module Module +// Module ModPath // Go string // Require []Require // Exclude []Module @@ -1155,6 +1150,11 @@ // Retract []Retract // } // +// type ModPath struct { +// Path string +// Deprecated string +// } +// // type Require struct { // Path string // Version string @@ -1186,13 +1186,17 @@ // // Usage: // -// go mod graph +// go mod graph [-go=version] // // Graph prints the module requirement graph (with replacements applied) // in text form. Each line in the output has two space-separated fields: a module // and one of its requirements. Each module is identified as a string of the form // path@version, except for the main module, which has no @version suffix. // +// The -go flag causes graph to report the module graph as loaded by the +// given Go version, instead of the version indicated by the 'go' directive +// in the go.mod file. +// // See https://golang.org/ref/mod#go-mod-graph for more about 'go mod graph'. // // @@ -1200,7 +1204,7 @@ // // Usage: // -// go mod init [module] +// go mod init [module-path] // // Init initializes and writes a new go.mod file in the current directory, in // effect creating a new module rooted at the current directory. The go.mod file @@ -1221,7 +1225,7 @@ // // Usage: // -// go mod tidy [-e] [-v] +// go mod tidy [-e] [-v] [-go=version] [-compat=version] // // Tidy makes sure go.mod matches the source code in the module. // It adds any missing modules necessary to build the current module's @@ -1235,6 +1239,20 @@ // The -e flag causes tidy to attempt to proceed despite errors // encountered while loading packages. // +// The -go flag causes tidy to update the 'go' directive in the go.mod +// file to the given version, which may change which module dependencies +// are retained as explicit requirements in the go.mod file. +// (Go versions 1.17 and higher retain more requirements in order to +// support lazy module loading.) +// +// The -compat flag preserves any additional checksums needed for the +// 'go' command from the indicated major Go release to successfully load +// the module graph, and causes tidy to error out if that version of the +// 'go' command would load any imported package from a different module +// version. By default, tidy acts as if the -compat flag were set to the +// version prior to the one indicated by the 'go' directive in the go.mod +// file. +// // See https://golang.org/ref/mod#go-mod-tidy for more about 'go mod tidy'. // // @@ -1318,10 +1336,21 @@ // go run [build flags] [-exec xprog] package [arguments...] // // Run compiles and runs the named main Go package. -// Typically the package is specified as a list of .go source files from a single directory, -// but it may also be an import path, file system path, or pattern +// Typically the package is specified as a list of .go source files from a single +// directory, but it may also be an import path, file system path, or pattern // matching a single known package, as in 'go run .' or 'go run my/cmd'. // +// If the package argument has a version suffix (like @latest or @v1.0.0), +// "go run" builds the program in module-aware mode, ignoring the go.mod file in +// the current directory or any parent directory, if there is one. This is useful +// for running programs without affecting the dependencies of the main module. +// +// If the package argument doesn't have a version suffix, "go run" may run in +// module-aware mode or GOPATH mode, depending on the GO111MODULE environment +// variable and the presence of a go.mod file. See 'go help modules' for details. +// If module-aware mode is enabled, "go run" runs in the context of the main +// module. +// // By default, 'go run' runs the compiled binary directly: 'a.out arguments...'. // If the -exec flag is given, 'go run' invokes the binary using xprog: // 'xprog a.out arguments...'. @@ -1416,8 +1445,8 @@ // // The rule for a match in the cache is that the run involves the same // test binary and the flags on the command line come entirely from a -// restricted set of 'cacheable' test flags, defined as -cpu, -list, -// -parallel, -run, -short, and -v. If a run of go test has any test +// restricted set of 'cacheable' test flags, defined as -benchtime, -cpu, +// -list, -parallel, -run, -short, and -v. If a run of go test has any test // or non-test flags outside this set, the result is not cached. To // disable test caching, use any test flag or argument other than the // cacheable flags. The idiomatic way to disable test caching explicitly @@ -1543,7 +1572,7 @@ // // A build constraint, also known as a build tag, is a line comment that begins // -// // +build +// //go:build // // that lists the conditions under which a file should be included in the package. // Constraints may appear in any kind of source file (not just Go), but @@ -1551,30 +1580,20 @@ // only by blank lines and other line comments. These rules mean that in Go // files a build constraint must appear before the package clause. // -// To distinguish build constraints from package documentation, a series of -// build constraints must be followed by a blank line. -// -// A build constraint is evaluated as the OR of space-separated options. -// Each option evaluates as the AND of its comma-separated terms. -// Each term consists of letters, digits, underscores, and dots. -// A term may be negated with a preceding !. -// For example, the build constraint: -// -// // +build linux,386 darwin,!cgo -// -// corresponds to the boolean formula: +// To distinguish build constraints from package documentation, +// a build constraint should be followed by a blank line. // -// (linux AND 386) OR (darwin AND (NOT cgo)) +// A build constraint is evaluated as an expression containing options +// combined by ||, &&, and ! operators and parentheses. Operators have +// the same meaning as in Go. // -// A file may have multiple build constraints. The overall constraint is the AND -// of the individual constraints. That is, the build constraints: +// For example, the following build constraint constrains a file to +// build when the "linux" and "386" constraints are satisfied, or when +// "darwin" is satisfied and "cgo" is not: // -// // +build linux darwin -// // +build amd64 +// //go:build (linux && 386) || (darwin && !cgo) // -// corresponds to the boolean formula: -// -// (linux OR darwin) AND amd64 +// It is an error for a file to have more than one //go:build line. // // During a particular build, the following words are satisfied: // @@ -1612,24 +1631,28 @@ // // To keep a file from being considered for the build: // -// // +build ignore +// //go:build ignore // // (any other unsatisfied word will work as well, but "ignore" is conventional.) // // To build a file only when using cgo, and only on Linux and OS X: // -// // +build linux,cgo darwin,cgo +// //go:build cgo && (linux || darwin) // // Such a file is usually paired with another file implementing the // default functionality for other systems, which in this case would // carry the constraint: // -// // +build !linux,!darwin !cgo +// //go:build !(cgo && (linux || darwin)) // // Naming a file dns_windows.go will cause it to be included only when // building the package for Windows; similarly, math_386.s will be included // only when building the package for 32-bit x86. // +// Go versions 1.16 and earlier used a different syntax for build constraints, +// with a "// +build" prefix. The gofmt command will add an equivalent //go:build +// constraint when encountering the older syntax. +// // // Build modes // @@ -1787,9 +1810,8 @@ // Comma-separated list of glob patterns (in the syntax of Go's path.Match) // of module path prefixes that should always be fetched in an insecure // manner. Only applies to dependencies that are being fetched directly. -// Unlike the -insecure flag on 'go get', GOINSECURE does not disable -// checksum database validation. GOPRIVATE or GONOSUMDB may be used -// to achieve that. +// GOINSECURE does not disable checksum database validation. GOPRIVATE or +// GONOSUMDB may be used to achieve that. // GOOS // The operating system for which to compile code. // Examples are linux, darwin, windows, netbsd. @@ -1869,6 +1891,9 @@ // GOMIPS64 // For GOARCH=mips64{,le}, whether to use floating point instructions. // Valid values are hardfloat (default), softfloat. +// GOPPC64 +// For GOARCH=ppc64{,le}, the target ISA (Instruction Set Architecture). +// Valid values are power8 (default), power9. // GOWASM // For GOARCH=wasm, comma-separated list of experimental WebAssembly features to use. // Valid values are satconv, signext. @@ -1878,6 +1903,12 @@ // GCCGOTOOLDIR // If set, where to find gccgo tools, such as cgo. // The default is based on how gccgo was configured. +// GOEXPERIMENT +// Comma-separated list of toolchain experiments to enable or disable. +// The list of available experiments may change arbitrarily over time. +// See src/internal/goexperiment/flags.go for currently valid values. +// Warning: This variable is provided for the development and testing +// of the Go toolchain itself. Use beyond that purpose is unsupported. // GOROOT_FINAL // The root of the installed Go tree, when it is // installed in a location other than where it is built. @@ -1961,7 +1992,7 @@ // The go.mod file format is described in detail at // https://golang.org/ref/mod#go-mod-file. // -// To create a new go.mod file, use 'go help init'. For details see +// To create a new go.mod file, use 'go mod init'. For details see // 'go help mod init' or https://golang.org/ref/mod#go-mod-init. // // To add missing module requirements or remove unneeded requirements, @@ -2139,7 +2170,7 @@ // This help text, accessible as 'go help gopath-get' even in module-aware mode, // describes 'go get' as it operates in legacy GOPATH mode. // -// Usage: go get [-d] [-f] [-t] [-u] [-v] [-fix] [-insecure] [build flags] [packages] +// Usage: go get [-d] [-f] [-t] [-u] [-v] [-fix] [build flags] [packages] // // Get downloads the packages named by the import paths, along with their // dependencies. It then installs the named packages, like 'go install'. @@ -2155,13 +2186,6 @@ // The -fix flag instructs get to run the fix tool on the downloaded packages // before resolving dependencies or building the code. // -// The -insecure flag permits fetching from repositories and resolving -// custom domains using insecure schemes such as HTTP. Use with caution. -// This flag is deprecated and will be removed in a future version of go. -// The GOINSECURE environment variable should be used instead, since it -// provides control over which packages may be retrieved using an insecure -// scheme. See 'go help environment' for details. -// // The -t flag instructs get to also download the packages required to build // the tests for the specified packages. // @@ -2346,7 +2370,7 @@ // will result in the following requests: // // https://example.org/pkg/foo?go-get=1 (preferred) -// http://example.org/pkg/foo?go-get=1 (fallback, only with -insecure) +// http://example.org/pkg/foo?go-get=1 (fallback, only with use of correctly set GOINSECURE) // // If that page contains the meta tag // @@ -2664,6 +2688,13 @@ // the Go tree can run a sanity check but not spend time running // exhaustive tests. // +// -shuffle off,on,N +// Randomize the execution order of tests and benchmarks. +// It is off by default. If -shuffle is set to on, then it will seed +// the randomizer using the system clock. If -shuffle is set to an +// integer N, then N will be used as the seed value. In both cases, +// the seed will be reported for reproducibility. +// // -timeout d // If a test binary runs longer than duration d, panic. // If d is 0, the timeout is disabled. diff --git a/src/cmd/go/go11.go b/src/cmd/go/go11.go index 7e383f4b5b0e73578b623f4d07c93ef6747ebef7..a1f2727825ede1f1ead32d3e240311206a186ed5 100644 --- a/src/cmd/go/go11.go +++ b/src/cmd/go/go11.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build go1.1 // +build go1.1 package main diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index 3ce32388d05dd8785ed94e074b4dc1ef47a2c128..6ce276537babd88765937e1f7ec0f3c98cc5ac68 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -72,7 +72,6 @@ func tooSlow(t *testing.T) { // (temp) directory. var testGOROOT string -var testCC string var testGOCACHE string var testGo string @@ -179,13 +178,6 @@ func TestMain(m *testing.M) { os.Exit(2) } - out, err = exec.Command(gotool, "env", "CC").CombinedOutput() - if err != nil { - fmt.Fprintf(os.Stderr, "could not find testing CC: %v\n%s", err, out) - os.Exit(2) - } - testCC = strings.TrimSpace(string(out)) - cmd := exec.Command(testGo, "env", "CGO_ENABLED") cmd.Stderr = new(strings.Builder) if out, err := cmd.Output(); err != nil { @@ -811,8 +803,10 @@ func TestNewReleaseRebuildsStalePackagesInGOPATH(t *testing.T) { // so that we can change files. for _, copydir := range []string{ "src/runtime", + "src/internal/abi", "src/internal/bytealg", "src/internal/cpu", + "src/internal/goexperiment", "src/math/bits", "src/unsafe", filepath.Join("pkg", runtime.GOOS+"_"+runtime.GOARCH), @@ -2183,7 +2177,7 @@ func testBuildmodePIE(t *testing.T, useCgo, setBuildmodeToPIE bool) { // See https://sourceware.org/bugzilla/show_bug.cgi?id=19011 section := f.Section(".edata") if section == nil { - t.Fatalf(".edata section is not present") + t.Skip(".edata section is not present") } // TODO: deduplicate this struct from cmd/link/internal/ld/pe.go type IMAGE_EXPORT_DIRECTORY struct { @@ -2830,3 +2824,59 @@ func TestCoverpkgTestOnly(t *testing.T) { tg.grepStderrNot("no packages being tested depend on matches", "bad match message") tg.grepStdout("coverage: 100", "no coverage") } + +// Regression test for golang.org/issue/34499: version command should not crash +// when executed in a deleted directory on Linux. +func TestExecInDeletedDir(t *testing.T) { + switch runtime.GOOS { + case "windows", "plan9", + "aix", // Fails with "device busy". + "solaris", "illumos": // Fails with "invalid argument". + t.Skipf("%v does not support removing the current working directory", runtime.GOOS) + } + tg := testgo(t) + defer tg.cleanup() + + wd, err := os.Getwd() + tg.check(err) + tg.makeTempdir() + tg.check(os.Chdir(tg.tempdir)) + defer func() { tg.check(os.Chdir(wd)) }() + + tg.check(os.Remove(tg.tempdir)) + + // `go version` should not fail + tg.run("version") +} + +// A missing C compiler should not force the net package to be stale. +// Issue 47215. +func TestMissingCC(t *testing.T) { + if !canCgo { + t.Skip("test is only meaningful on systems with cgo") + } + cc := os.Getenv("CC") + if cc == "" { + cc = "gcc" + } + if filepath.IsAbs(cc) { + t.Skipf(`"CC" (%s) is an absolute path`, cc) + } + _, err := exec.LookPath(cc) + if err != nil { + t.Skipf(`"CC" (%s) not on PATH`, cc) + } + + tg := testgo(t) + defer tg.cleanup() + netStale, _ := tg.isStale("net") + if netStale { + t.Skip(`skipping test because "net" package is currently stale`) + } + + tg.setenv("PATH", "") // No C compiler on PATH. + netStale, _ = tg.isStale("net") + if netStale { + t.Error(`clearing "PATH" causes "net" to be stale`) + } +} diff --git a/src/cmd/go/go_unix_test.go b/src/cmd/go/go_unix_test.go index f6e10ca59c743f8a2f26bcdbea731196c0259d86..7d5ff9bbb748391dac7cc2f2be0c7fc032977bac 100644 --- a/src/cmd/go/go_unix_test.go +++ b/src/cmd/go/go_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris // +build darwin dragonfly freebsd linux netbsd openbsd solaris package main_test diff --git a/src/cmd/go/internal/base/path.go b/src/cmd/go/internal/base/path.go index 7a51181c9736e9cdf8549a7192f9bdcf67f419ce..4d8715ef5fe002a349d83e956eb4493e9c698fe0 100644 --- a/src/cmd/go/internal/base/path.go +++ b/src/cmd/go/internal/base/path.go @@ -8,21 +8,27 @@ import ( "os" "path/filepath" "strings" + "sync" ) -func getwd() string { - wd, err := os.Getwd() - if err != nil { - Fatalf("cannot determine current directory: %v", err) - } - return wd -} +var cwd string +var cwdOnce sync.Once -var Cwd = getwd() +// Cwd returns the current working directory at the time of the first call. +func Cwd() string { + cwdOnce.Do(func() { + var err error + cwd, err = os.Getwd() + if err != nil { + Fatalf("cannot determine current directory: %v", err) + } + }) + return cwd +} // ShortPath returns an absolute or relative name for path, whatever is shorter. func ShortPath(path string) string { - if rel, err := filepath.Rel(Cwd, path); err == nil && len(rel) < len(path) { + if rel, err := filepath.Rel(Cwd(), path); err == nil && len(rel) < len(path) { return rel } return path @@ -32,10 +38,8 @@ func ShortPath(path string) string { // made relative to the current directory if they would be shorter. func RelPaths(paths []string) []string { var out []string - // TODO(rsc): Can this use Cwd from above? - pwd, _ := os.Getwd() for _, p := range paths { - rel, err := filepath.Rel(pwd, p) + rel, err := filepath.Rel(Cwd(), p) if err == nil && len(rel) < len(p) { p = rel } diff --git a/src/cmd/go/internal/base/signal_notunix.go b/src/cmd/go/internal/base/signal_notunix.go index 9e869b03ea8aafa10c288abfc2771b7c9616d2cd..5cc0b0f1011e5a1a0c2ff3a0eb256960d90deb7c 100644 --- a/src/cmd/go/internal/base/signal_notunix.go +++ b/src/cmd/go/internal/base/signal_notunix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build plan9 || windows // +build plan9 windows package base diff --git a/src/cmd/go/internal/base/signal_unix.go b/src/cmd/go/internal/base/signal_unix.go index 342775a118286f9ecf790a02d3f87226bd14f17b..cdc2658372e429930170b5a25b6a8ee7948e0c9f 100644 --- a/src/cmd/go/internal/base/signal_unix.go +++ b/src/cmd/go/internal/base/signal_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || js || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd js linux netbsd openbsd solaris package base diff --git a/src/cmd/go/internal/bug/bug.go b/src/cmd/go/internal/bug/bug.go index 4aa08b4ff6ea7ab988daa86f3243b12948f0b64f..307527c695cbededab0ae1dfa4447e2fc3161c4b 100644 --- a/src/cmd/go/internal/bug/bug.go +++ b/src/cmd/go/internal/bug/bug.go @@ -20,6 +20,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" + "cmd/go/internal/envcmd" "cmd/go/internal/web" ) @@ -81,7 +82,7 @@ func printGoVersion(w io.Writer) { fmt.Fprintf(w, "### What version of Go are you using (`go version`)?\n\n") fmt.Fprintf(w, "
    \n")
     	fmt.Fprintf(w, "$ go version\n")
    -	printCmdOut(w, "", "go", "version")
    +	fmt.Fprintf(w, "go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
     	fmt.Fprintf(w, "
    \n") fmt.Fprintf(w, "\n") } @@ -90,13 +91,20 @@ func printEnvDetails(w io.Writer) { fmt.Fprintf(w, "### What operating system and processor architecture are you using (`go env`)?\n\n") fmt.Fprintf(w, "
    go env Output
    \n")
     	fmt.Fprintf(w, "$ go env\n")
    -	printCmdOut(w, "", "go", "env")
    +	printGoEnv(w)
     	printGoDetails(w)
     	printOSDetails(w)
     	printCDetails(w)
     	fmt.Fprintf(w, "
    \n\n") } +func printGoEnv(w io.Writer) { + env := envcmd.MkEnv() + env = append(env, envcmd.ExtraEnvVars()...) + env = append(env, envcmd.ExtraEnvVarsCostly()...) + envcmd.PrintEnv(w, env) +} + func printGoDetails(w io.Writer) { printCmdOut(w, "GOROOT/bin/go version: ", filepath.Join(runtime.GOROOT(), "bin/go"), "version") printCmdOut(w, "GOROOT/bin/go tool compile -V: ", filepath.Join(runtime.GOROOT(), "bin/go"), "tool", "compile", "-V") diff --git a/src/cmd/go/internal/cache/cache.go b/src/cmd/go/internal/cache/cache.go index 41f921641d499744a2a88224355333c4f76d2693..d592d7049786ce9d3fd5d15a67f2884327f149a0 100644 --- a/src/cmd/go/internal/cache/cache.go +++ b/src/cmd/go/internal/cache/cache.go @@ -19,7 +19,7 @@ import ( "strings" "time" - "cmd/go/internal/renameio" + "cmd/go/internal/lockedfile" ) // An ActionID is a cache action key, the hash of a complete description of a @@ -294,10 +294,17 @@ func (c *Cache) Trim() { // We maintain in dir/trim.txt the time of the last completed cache trim. // If the cache has been trimmed recently enough, do nothing. // This is the common case. - data, _ := renameio.ReadFile(filepath.Join(c.dir, "trim.txt")) - t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64) - if err == nil && now.Sub(time.Unix(t, 0)) < trimInterval { - return + // If the trim file is corrupt, detected if the file can't be parsed, or the + // trim time is too far in the future, attempt the trim anyway. It's possible that + // the cache was full when the corruption happened. Attempting a trim on + // an empty cache is cheap, so there wouldn't be a big performance hit in that case. + if data, err := lockedfile.Read(filepath.Join(c.dir, "trim.txt")); err == nil { + if t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64); err == nil { + lastTrim := time.Unix(t, 0) + if d := now.Sub(lastTrim); d < trimInterval && d > -mtimeInterval { + return + } + } } // Trim each of the 256 subdirectories. @@ -311,7 +318,11 @@ func (c *Cache) Trim() { // Ignore errors from here: if we don't write the complete timestamp, the // cache will appear older than it is, and we'll trim it again next time. - renameio.WriteFile(filepath.Join(c.dir, "trim.txt"), []byte(fmt.Sprintf("%d", now.Unix())), 0666) + var b bytes.Buffer + fmt.Fprintf(&b, "%d", now.Unix()) + if err := lockedfile.Write(filepath.Join(c.dir, "trim.txt"), &b, 0666); err != nil { + return + } } // trimSubdir trims a single cache subdirectory. diff --git a/src/cmd/go/internal/cache/hash.go b/src/cmd/go/internal/cache/hash.go index e4bb2a34bb4a1a371193864fb8b24139b52bd16c..4f79c3150024492db1572271c08ffb1a3eeca082 100644 --- a/src/cmd/go/internal/cache/hash.go +++ b/src/cmd/go/internal/cache/hash.go @@ -12,6 +12,7 @@ import ( "io" "os" "runtime" + "strings" "sync" ) @@ -36,7 +37,22 @@ type Hash struct { // of other versions. This salt will result in additional ActionID files // in the cache, but not additional copies of the large output files, // which are still addressed by unsalted SHA256. -var hashSalt = []byte(runtime.Version()) +// +// We strip any GOEXPERIMENTs the go tool was built with from this +// version string on the assumption that they shouldn't affect go tool +// execution. This allows bootstrapping to converge faster: dist builds +// go_bootstrap without any experiments, so by stripping experiments +// go_bootstrap and the final go binary will use the same salt. +var hashSalt = []byte(stripExperiment(runtime.Version())) + +// stripExperiment strips any GOEXPERIMENT configuration from the Go +// version string. +func stripExperiment(version string) string { + if i := strings.Index(version, " X:"); i >= 0 { + return version[:i] + } + return version +} // Subkey returns an action ID corresponding to mixing a parent // action ID with a string description of the subkey. diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go index c48904eacceab90949bc12248ad82b91803a16ca..57a3c1ff6fbdc1f9f29a10a4580e8d40e4f8c908 100644 --- a/src/cmd/go/internal/cfg/cfg.go +++ b/src/cmd/go/internal/cfg/cfg.go @@ -10,6 +10,7 @@ import ( "bytes" "fmt" "go/build" + "internal/buildcfg" "internal/cfg" "io" "os" @@ -19,8 +20,6 @@ import ( "sync" "cmd/go/internal/fsys" - - "cmd/internal/objabi" ) // These are general "build flags" used by build and other commands. @@ -28,18 +27,18 @@ var ( BuildA bool // -a flag BuildBuildmode string // -buildmode flag BuildContext = defaultContext() - BuildMod string // -mod flag - BuildModExplicit bool // whether -mod was set explicitly - BuildModReason string // reason -mod was set, if set by default - BuildI bool // -i flag - BuildLinkshared bool // -linkshared flag - BuildMSan bool // -msan flag - BuildN bool // -n flag - BuildO string // -o flag - BuildP = runtime.NumCPU() // -p flag - BuildPkgdir string // -pkgdir flag - BuildRace bool // -race flag - BuildToolexec []string // -toolexec flag + BuildMod string // -mod flag + BuildModExplicit bool // whether -mod was set explicitly + BuildModReason string // reason -mod was set, if set by default + BuildI bool // -i flag + BuildLinkshared bool // -linkshared flag + BuildMSan bool // -msan flag + BuildN bool // -n flag + BuildO string // -o flag + BuildP = runtime.GOMAXPROCS(0) // -p flag + BuildPkgdir string // -pkgdir flag + BuildRace bool // -race flag + BuildToolexec []string // -toolexec flag BuildToolchainName string BuildToolchainCompiler func() string BuildToolchainLinker func() string @@ -51,8 +50,6 @@ var ( ModCacheRW bool // -modcacherw flag ModFile string // -modfile flag - Insecure bool // -insecure flag - CmdName string // "build", "install", "list", "mod tidy", etc. DebugActiongraph string // -debug-actiongraph flag (undocumented, unstable) @@ -80,6 +77,14 @@ func defaultContext() build.Context { ctxt.GOOS = envOr("GOOS", ctxt.GOOS) ctxt.GOARCH = envOr("GOARCH", ctxt.GOARCH) + // The experiments flags are based on GOARCH, so they may + // need to change. TODO: This should be cleaned up. + buildcfg.UpdateExperiments(ctxt.GOOS, ctxt.GOARCH, envOr("GOEXPERIMENT", buildcfg.DefaultGOEXPERIMENT)) + ctxt.ToolTags = nil + for _, exp := range buildcfg.EnabledExperiments() { + ctxt.ToolTags = append(ctxt.ToolTags, "goexperiment."+exp) + } + // The go/build rule for whether cgo is enabled is: // 1. If $CGO_ENABLED is set, respect it. // 2. Otherwise, if this is a cross-compile, disable cgo. @@ -254,12 +259,12 @@ var ( GOMODCACHE = envOr("GOMODCACHE", gopathDir("pkg/mod")) // Used in envcmd.MkEnv and build ID computations. - GOARM = envOr("GOARM", fmt.Sprint(objabi.GOARM)) - GO386 = envOr("GO386", objabi.GO386) - GOMIPS = envOr("GOMIPS", objabi.GOMIPS) - GOMIPS64 = envOr("GOMIPS64", objabi.GOMIPS64) - GOPPC64 = envOr("GOPPC64", fmt.Sprintf("%s%d", "power", objabi.GOPPC64)) - GOWASM = envOr("GOWASM", fmt.Sprint(objabi.GOWASM)) + GOARM = envOr("GOARM", fmt.Sprint(buildcfg.GOARM)) + GO386 = envOr("GO386", buildcfg.GO386) + GOMIPS = envOr("GOMIPS", buildcfg.GOMIPS) + GOMIPS64 = envOr("GOMIPS64", buildcfg.GOMIPS64) + GOPPC64 = envOr("GOPPC64", fmt.Sprintf("%s%d", "power", buildcfg.GOPPC64)) + GOWASM = envOr("GOWASM", fmt.Sprint(buildcfg.GOWASM)) GOPROXY = envOr("GOPROXY", "https://proxy.golang.org,direct") GOSUMDB = envOr("GOSUMDB", "sum.golang.org") diff --git a/src/cmd/go/internal/clean/clean.go b/src/cmd/go/internal/clean/clean.go index b1d40feb273b89d6604747ff8e2de6949572221a..fd4cb205591105d0ad527e25e811cd62dfadf5a8 100644 --- a/src/cmd/go/internal/clean/clean.go +++ b/src/cmd/go/internal/clean/clean.go @@ -117,7 +117,7 @@ func runClean(ctx context.Context, cmd *base.Command, args []string) { } if cleanPkg { - for _, pkg := range load.PackagesAndErrors(ctx, args) { + for _, pkg := range load.PackagesAndErrors(ctx, load.PackageOpts{}, args) { clean(pkg) } } diff --git a/src/cmd/go/internal/doc/doc.go b/src/cmd/go/internal/doc/doc.go index 67f76e22563b6e84d21b7622b55675d03af60db2..8580a5dc4d2482a7e2f88640abe9a76a245adb18 100644 --- a/src/cmd/go/internal/doc/doc.go +++ b/src/cmd/go/internal/doc/doc.go @@ -13,7 +13,7 @@ import ( var CmdDoc = &base.Command{ Run: runDoc, - UsageLine: "go doc [-u] [-c] [package|[package.]symbol[.methodOrField]]", + UsageLine: "go doc [doc flags] [package|[package.]symbol[.methodOrField]]", CustomFlags: true, Short: "show documentation for package or symbol", Long: ` diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index 6937187522bd237fbefac0c13727754af40fcb86..1553d263914541f05a639e03b8041969ba244301 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -10,6 +10,8 @@ import ( "encoding/json" "fmt" "go/build" + "internal/buildcfg" + "io" "os" "path/filepath" "runtime" @@ -71,6 +73,7 @@ func MkEnv() []cfg.EnvVar { {Name: "GOCACHE", Value: cache.DefaultDir()}, {Name: "GOENV", Value: envFile}, {Name: "GOEXE", Value: cfg.ExeSuffix}, + {Name: "GOEXPERIMENT", Value: buildcfg.GOEXPERIMENT()}, {Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")}, {Name: "GOHOSTARCH", Value: runtime.GOARCH}, {Name: "GOHOSTOS", Value: runtime.GOOS}, @@ -196,23 +199,31 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) { if *envU && *envW { base.Fatalf("go env: cannot use -u with -w") } + + // Handle 'go env -w' and 'go env -u' before calling buildcfg.Check, + // so they can be used to recover from an invalid configuration. + if *envW { + runEnvW(args) + return + } + + if *envU { + runEnvU(args) + return + } + + buildcfg.Check() + env := cfg.CmdEnv env = append(env, ExtraEnvVars()...) - if err := fsys.Init(base.Cwd); err != nil { + if err := fsys.Init(base.Cwd()); err != nil { base.Fatalf("go: %v", err) } // Do we need to call ExtraEnvVarsCostly, which is a bit expensive? needCostly := false - if *envU || *envW { - // We're overwriting or removing default settings, - // so it doesn't really matter what the existing settings are. - // - // Moreover, we haven't validated the new settings yet, so it is - // important that we NOT perform any actions based on them, - // such as initializing the builder to compute other variables. - } else if len(args) == 0 { + if len(args) == 0 { // We're listing all environment variables ("go env"), // including the expensive ones. needCostly = true @@ -237,95 +248,6 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) { env = append(env, ExtraEnvVarsCostly()...) } - if *envW { - // Process and sanity-check command line. - if len(args) == 0 { - base.Fatalf("go env -w: no KEY=VALUE arguments given") - } - osEnv := make(map[string]string) - for _, e := range cfg.OrigEnv { - if i := strings.Index(e, "="); i >= 0 { - osEnv[e[:i]] = e[i+1:] - } - } - add := make(map[string]string) - for _, arg := range args { - i := strings.Index(arg, "=") - if i < 0 { - base.Fatalf("go env -w: arguments must be KEY=VALUE: invalid argument: %s", arg) - } - key, val := arg[:i], arg[i+1:] - if err := checkEnvWrite(key, val); err != nil { - base.Fatalf("go env -w: %v", err) - } - if _, ok := add[key]; ok { - base.Fatalf("go env -w: multiple values for key: %s", key) - } - add[key] = val - if osVal := osEnv[key]; osVal != "" && osVal != val { - fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key) - } - } - - goos, okGOOS := add["GOOS"] - goarch, okGOARCH := add["GOARCH"] - if okGOOS || okGOARCH { - if !okGOOS { - goos = cfg.Goos - } - if !okGOARCH { - goarch = cfg.Goarch - } - if err := work.CheckGOOSARCHPair(goos, goarch); err != nil { - base.Fatalf("go env -w: %v", err) - } - } - - gotmp, okGOTMP := add["GOTMPDIR"] - if okGOTMP { - if !filepath.IsAbs(gotmp) && gotmp != "" { - base.Fatalf("go env -w: GOTMPDIR must be an absolute path") - } - } - - updateEnvFile(add, nil) - return - } - - if *envU { - // Process and sanity-check command line. - if len(args) == 0 { - base.Fatalf("go env -u: no arguments given") - } - del := make(map[string]bool) - for _, arg := range args { - if err := checkEnvWrite(arg, ""); err != nil { - base.Fatalf("go env -u: %v", err) - } - del[arg] = true - } - if del["GOOS"] || del["GOARCH"] { - goos, goarch := cfg.Goos, cfg.Goarch - if del["GOOS"] { - goos = getOrigEnv("GOOS") - if goos == "" { - goos = build.Default.GOOS - } - } - if del["GOARCH"] { - goarch = getOrigEnv("GOARCH") - if goarch == "" { - goarch = build.Default.GOARCH - } - } - if err := work.CheckGOOSARCHPair(goos, goarch); err != nil { - base.Fatalf("go env -u: %v", err) - } - } - updateEnvFile(nil, del) - return - } - if len(args) > 0 { if *envJson { var es []cfg.EnvVar @@ -347,27 +269,135 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) { return } + PrintEnv(os.Stdout, env) +} + +func runEnvW(args []string) { + // Process and sanity-check command line. + if len(args) == 0 { + base.Fatalf("go env -w: no KEY=VALUE arguments given") + } + osEnv := make(map[string]string) + for _, e := range cfg.OrigEnv { + if i := strings.Index(e, "="); i >= 0 { + osEnv[e[:i]] = e[i+1:] + } + } + add := make(map[string]string) + for _, arg := range args { + i := strings.Index(arg, "=") + if i < 0 { + base.Fatalf("go env -w: arguments must be KEY=VALUE: invalid argument: %s", arg) + } + key, val := arg[:i], arg[i+1:] + if err := checkEnvWrite(key, val); err != nil { + base.Fatalf("go env -w: %v", err) + } + if _, ok := add[key]; ok { + base.Fatalf("go env -w: multiple values for key: %s", key) + } + add[key] = val + if osVal := osEnv[key]; osVal != "" && osVal != val { + fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key) + } + } + + if err := checkBuildConfig(add, nil); err != nil { + base.Fatalf("go env -w: %v", err) + } + + gotmp, okGOTMP := add["GOTMPDIR"] + if okGOTMP { + if !filepath.IsAbs(gotmp) && gotmp != "" { + base.Fatalf("go env -w: GOTMPDIR must be an absolute path") + } + } + + updateEnvFile(add, nil) +} + +func runEnvU(args []string) { + // Process and sanity-check command line. + if len(args) == 0 { + base.Fatalf("go env -u: no arguments given") + } + del := make(map[string]bool) + for _, arg := range args { + if err := checkEnvWrite(arg, ""); err != nil { + base.Fatalf("go env -u: %v", err) + } + del[arg] = true + } + + if err := checkBuildConfig(nil, del); err != nil { + base.Fatalf("go env -u: %v", err) + } + + updateEnvFile(nil, del) +} + +// checkBuildConfig checks whether the build configuration is valid +// after the specified configuration environment changes are applied. +func checkBuildConfig(add map[string]string, del map[string]bool) error { + // get returns the value for key after applying add and del and + // reports whether it changed. cur should be the current value + // (i.e., before applying changes) and def should be the default + // value (i.e., when no environment variables are provided at all). + get := func(key, cur, def string) (string, bool) { + if val, ok := add[key]; ok { + return val, true + } + if del[key] { + val := getOrigEnv(key) + if val == "" { + val = def + } + return val, true + } + return cur, false + } + + goos, okGOOS := get("GOOS", cfg.Goos, build.Default.GOOS) + goarch, okGOARCH := get("GOARCH", cfg.Goarch, build.Default.GOARCH) + if okGOOS || okGOARCH { + if err := work.CheckGOOSARCHPair(goos, goarch); err != nil { + return err + } + } + + goexperiment, okGOEXPERIMENT := get("GOEXPERIMENT", buildcfg.GOEXPERIMENT(), "") + if okGOEXPERIMENT { + if _, _, err := buildcfg.ParseGOEXPERIMENT(goos, goarch, goexperiment); err != nil { + return err + } + } + + return nil +} + +// PrintEnv prints the environment variables to w. +func PrintEnv(w io.Writer, env []cfg.EnvVar) { for _, e := range env { if e.Name != "TERM" { switch runtime.GOOS { default: - fmt.Printf("%s=\"%s\"\n", e.Name, e.Value) + fmt.Fprintf(w, "%s=\"%s\"\n", e.Name, e.Value) case "plan9": if strings.IndexByte(e.Value, '\x00') < 0 { - fmt.Printf("%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''")) + fmt.Fprintf(w, "%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''")) } else { v := strings.Split(e.Value, "\x00") - fmt.Printf("%s=(", e.Name) + fmt.Fprintf(w, "%s=(", e.Name) for x, s := range v { if x > 0 { - fmt.Printf(" ") + fmt.Fprintf(w, " ") } - fmt.Printf("%s", s) + fmt.Fprintf(w, "%s", s) } - fmt.Printf(")\n") + fmt.Fprintf(w, ")\n") } case "windows": - fmt.Printf("set %s=%s\n", e.Name, e.Value) + fmt.Fprintf(w, "set %s=%s\n", e.Name, e.Value) } } } @@ -428,7 +458,7 @@ func checkEnvWrite(key, val string) error { return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val) } // Make sure CC and CXX are absolute paths - case "CC", "CXX": + case "CC", "CXX", "GOMODCACHE": if !filepath.IsAbs(val) && val != "" && val != filepath.Base(val) { return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, val) } diff --git a/src/cmd/go/internal/fix/fix.go b/src/cmd/go/internal/fix/fix.go index c7588c66d3ebcc56bab6f83ad91c0f85e9c15d29..988d45e71ccfe2ccf571e409962bdd3ade4e3430 100644 --- a/src/cmd/go/internal/fix/fix.go +++ b/src/cmd/go/internal/fix/fix.go @@ -33,7 +33,7 @@ See also: go fmt, go vet. } func runFix(ctx context.Context, cmd *base.Command, args []string) { - pkgs := load.PackagesAndErrors(ctx, args) + pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{}, args) w := 0 for _, pkg := range pkgs { if pkg.Error != nil { diff --git a/src/cmd/go/internal/fmtcmd/fmt.go b/src/cmd/go/internal/fmtcmd/fmt.go index 6b98f0ccd31865c937def4ac99b22597ca2b52e3..8a040087539e8f9195c61a88d89883a379a963b2 100644 --- a/src/cmd/go/internal/fmtcmd/fmt.go +++ b/src/cmd/go/internal/fmtcmd/fmt.go @@ -65,7 +65,7 @@ func runFmt(ctx context.Context, cmd *base.Command, args []string) { } }() } - for _, pkg := range load.PackagesAndErrors(ctx, args) { + for _, pkg := range load.PackagesAndErrors(ctx, load.PackageOpts{}, args) { if modload.Enabled() && pkg.Module != nil && !pkg.Module.Main { if !printed { fmt.Fprintf(os.Stderr, "go: not formatting packages in dependency modules\n") diff --git a/src/cmd/go/internal/fsys/fsys.go b/src/cmd/go/internal/fsys/fsys.go index 7b06c3c7f3a0118614d39d25a1f91af65d263966..0b806027e6469a3cac3b19e2e5e162b7ead8e92d 100644 --- a/src/cmd/go/internal/fsys/fsys.go +++ b/src/cmd/go/internal/fsys/fsys.go @@ -44,7 +44,7 @@ func (n *node) isDeleted() bool { // TODO(matloob): encapsulate these in an io/fs-like interface var overlay map[string]*node // path -> file or directory node -var cwd string // copy of base.Cwd to avoid dependency +var cwd string // copy of base.Cwd() to avoid dependency // Canonicalize a path for looking it up in the overlay. // Important: filepath.Join(cwd, path) doesn't always produce @@ -100,7 +100,7 @@ func Init(wd string) error { } func initFromJSON(overlayJSON OverlayJSON) error { - // Canonicalize the paths in in the overlay map. + // Canonicalize the paths in the overlay map. // Use reverseCanonicalized to check for collisions: // no two 'from' paths should canonicalize to the same path. overlay = make(map[string]*node) diff --git a/src/cmd/go/internal/generate/generate.go b/src/cmd/go/internal/generate/generate.go index a48311d51b09b7a2a356e1e567abfcaa1af60b7b..80ea32b4284011712eb82b2bd671e4f5f9a35b2e 100644 --- a/src/cmd/go/internal/generate/generate.go +++ b/src/cmd/go/internal/generate/generate.go @@ -161,8 +161,6 @@ func init() { } func runGenerate(ctx context.Context, cmd *base.Command, args []string) { - load.IgnoreImports = true - if generateRunFlag != "" { var err error generateRunRE, err = regexp.Compile(generateRunFlag) @@ -175,7 +173,8 @@ func runGenerate(ctx context.Context, cmd *base.Command, args []string) { // Even if the arguments are .go files, this loop suffices. printed := false - for _, pkg := range load.PackagesAndErrors(ctx, args) { + pkgOpts := load.PackageOpts{IgnoreImports: true} + for _, pkg := range load.PackagesAndErrors(ctx, pkgOpts, args) { if modload.Enabled() && pkg.Module != nil && !pkg.Module.Main { if !printed { fmt.Fprintf(os.Stderr, "go: not generating in packages in dependency modules\n") @@ -334,6 +333,7 @@ func (g *Generator) setEnv() { "GOPACKAGE=" + g.pkg, "DOLLAR=" + "$", } + g.env = base.AppendPWD(g.env, g.dir) } // split breaks the line into words, evaluating quoted diff --git a/src/cmd/go/internal/get/get.go b/src/cmd/go/internal/get/get.go index 329a2f5eda48175bac1ef07742e7511adba1eb07..3c16dc3040facba13a7a59416268ae7944250599 100644 --- a/src/cmd/go/internal/get/get.go +++ b/src/cmd/go/internal/get/get.go @@ -26,7 +26,7 @@ import ( ) var CmdGet = &base.Command{ - UsageLine: "go get [-d] [-f] [-t] [-u] [-v] [-fix] [-insecure] [build flags] [packages]", + UsageLine: "go get [-d] [-f] [-t] [-u] [-v] [-fix] [build flags] [packages]", Short: "download and install packages and dependencies", Long: ` Get downloads the packages named by the import paths, along with their @@ -43,13 +43,6 @@ of the original. The -fix flag instructs get to run the fix tool on the downloaded packages before resolving dependencies or building the code. -The -insecure flag permits fetching from repositories and resolving -custom domains using insecure schemes such as HTTP. Use with caution. -This flag is deprecated and will be removed in a future version of go. -The GOINSECURE environment variable should be used instead, since it -provides control over which packages may be retrieved using an insecure -scheme. See 'go help environment' for details. - The -t flag instructs get to also download the packages required to build the tests for the specified packages. @@ -105,17 +98,17 @@ Usage: ` + CmdGet.UsageLine + ` } var ( - getD = CmdGet.Flag.Bool("d", false, "") - getF = CmdGet.Flag.Bool("f", false, "") - getT = CmdGet.Flag.Bool("t", false, "") - getU = CmdGet.Flag.Bool("u", false, "") - getFix = CmdGet.Flag.Bool("fix", false, "") + getD = CmdGet.Flag.Bool("d", false, "") + getF = CmdGet.Flag.Bool("f", false, "") + getT = CmdGet.Flag.Bool("t", false, "") + getU = CmdGet.Flag.Bool("u", false, "") + getFix = CmdGet.Flag.Bool("fix", false, "") + getInsecure = CmdGet.Flag.Bool("insecure", false, "") ) func init() { work.AddBuildFlags(CmdGet, work.OmitModFlag|work.OmitModCommonFlags) CmdGet.Run = runGet // break init loop - CmdGet.Flag.BoolVar(&cfg.Insecure, "insecure", cfg.Insecure, "") } func runGet(ctx context.Context, cmd *base.Command, args []string) { @@ -129,11 +122,11 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { if *getF && !*getU { base.Fatalf("go get: cannot use -f flag without -u") } - if cfg.Insecure { - fmt.Fprintf(os.Stderr, "go get: -insecure flag is deprecated; see 'go help get' for details\n") + if *getInsecure { + base.Fatalf("go get: -insecure flag is no longer supported; use GOINSECURE instead") } - // Disable any prompting for passwords by Git. + // Disable any prompting for passwords by Git itself. // Only has an effect for 2.3.0 or later, but avoiding // the prompt in earlier versions is just too hard. // If user has explicitly set GIT_TERMINAL_PROMPT=1, keep @@ -143,7 +136,10 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { os.Setenv("GIT_TERMINAL_PROMPT", "0") } - // Disable any ssh connection pooling by Git. + // Also disable prompting for passwords by the 'ssh' subprocess spawned by + // Git, because apparently GIT_TERMINAL_PROMPT isn't sufficient to do that. + // Adding '-o BatchMode=yes' should do the trick. + // // If a Git subprocess forks a child into the background to cache a new connection, // that child keeps stdout/stderr open. After the Git subprocess exits, // os /exec expects to be able to read from the stdout/stderr pipe @@ -157,7 +153,14 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // assume they know what they are doing and don't step on it. // But default to turning off ControlMaster. if os.Getenv("GIT_SSH") == "" && os.Getenv("GIT_SSH_COMMAND") == "" { - os.Setenv("GIT_SSH_COMMAND", "ssh -o ControlMaster=no") + os.Setenv("GIT_SSH_COMMAND", "ssh -o ControlMaster=no -o BatchMode=yes") + } + + // And one more source of Git prompts: the Git Credential Manager Core for Windows. + // + // See https://github.com/microsoft/Git-Credential-Manager-Core/blob/master/docs/environment.md#gcm_interactive. + if os.Getenv("GCM_INTERACTIVE") == "" { + os.Setenv("GCM_INTERACTIVE", "never") } // Phase 1. Download/update. @@ -180,7 +183,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // everything. load.ClearPackageCache() - pkgs := load.PackagesAndErrors(ctx, args) + pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{}, args) load.CheckPackageErrors(pkgs) // Phase 3. Install. @@ -255,9 +258,9 @@ func download(arg string, parent *load.Package, stk *load.ImportStack, mode int) load1 := func(path string, mode int) *load.Package { if parent == nil { mode := 0 // don't do module or vendor resolution - return load.LoadImport(context.TODO(), path, base.Cwd, nil, stk, nil, mode) + return load.LoadImport(context.TODO(), load.PackageOpts{}, path, base.Cwd(), nil, stk, nil, mode) } - return load.LoadImport(context.TODO(), path, parent.Dir, parent, stk, nil, mode|load.ResolveModule) + return load.LoadImport(context.TODO(), load.PackageOpts{}, path, parent.Dir, parent, stk, nil, mode|load.ResolveModule) } p := load1(arg, mode) @@ -435,7 +438,7 @@ func downloadPackage(p *load.Package) error { return fmt.Errorf("%s: invalid import path: %v", p.ImportPath, err) } security := web.SecureOnly - if cfg.Insecure || module.MatchPrefixPatterns(cfg.GOINSECURE, importPrefix) { + if module.MatchPrefixPatterns(cfg.GOINSECURE, importPrefix) { security = web.Insecure } diff --git a/src/cmd/go/internal/help/helpdoc.go b/src/cmd/go/internal/help/helpdoc.go index 57cee4ff96c1acc42c01d69483bc630d86e8b750..490ff1fb7cf05bd5a7e107c1ea40bb44174cc0db 100644 --- a/src/cmd/go/internal/help/helpdoc.go +++ b/src/cmd/go/internal/help/helpdoc.go @@ -251,7 +251,7 @@ For example, will result in the following requests: https://example.org/pkg/foo?go-get=1 (preferred) - http://example.org/pkg/foo?go-get=1 (fallback, only with -insecure) + http://example.org/pkg/foo?go-get=1 (fallback, only with use of correctly set GOINSECURE) If that page contains the meta tag @@ -517,9 +517,8 @@ General-purpose environment variables: Comma-separated list of glob patterns (in the syntax of Go's path.Match) of module path prefixes that should always be fetched in an insecure manner. Only applies to dependencies that are being fetched directly. - Unlike the -insecure flag on 'go get', GOINSECURE does not disable - checksum database validation. GOPRIVATE or GONOSUMDB may be used - to achieve that. + GOINSECURE does not disable checksum database validation. GOPRIVATE or + GONOSUMDB may be used to achieve that. GOOS The operating system for which to compile code. Examples are linux, darwin, windows, netbsd. @@ -599,6 +598,9 @@ Architecture-specific environment variables: GOMIPS64 For GOARCH=mips64{,le}, whether to use floating point instructions. Valid values are hardfloat (default), softfloat. + GOPPC64 + For GOARCH=ppc64{,le}, the target ISA (Instruction Set Architecture). + Valid values are power8 (default), power9. GOWASM For GOARCH=wasm, comma-separated list of experimental WebAssembly features to use. Valid values are satconv, signext. @@ -608,6 +610,12 @@ Special-purpose environment variables: GCCGOTOOLDIR If set, where to find gccgo tools, such as cgo. The default is based on how gccgo was configured. + GOEXPERIMENT + Comma-separated list of toolchain experiments to enable or disable. + The list of available experiments may change arbitrarily over time. + See src/internal/goexperiment/flags.go for currently valid values. + Warning: This variable is provided for the development and testing + of the Go toolchain itself. Use beyond that purpose is unsupported. GOROOT_FINAL The root of the installed Go tree, when it is installed in a location other than where it is built. @@ -785,7 +793,7 @@ var HelpBuildConstraint = &base.Command{ Long: ` A build constraint, also known as a build tag, is a line comment that begins - // +build + //go:build that lists the conditions under which a file should be included in the package. Constraints may appear in any kind of source file (not just Go), but @@ -793,30 +801,20 @@ they must appear near the top of the file, preceded only by blank lines and other line comments. These rules mean that in Go files a build constraint must appear before the package clause. -To distinguish build constraints from package documentation, a series of -build constraints must be followed by a blank line. +To distinguish build constraints from package documentation, +a build constraint should be followed by a blank line. -A build constraint is evaluated as the OR of space-separated options. -Each option evaluates as the AND of its comma-separated terms. -Each term consists of letters, digits, underscores, and dots. -A term may be negated with a preceding !. -For example, the build constraint: +A build constraint is evaluated as an expression containing options +combined by ||, &&, and ! operators and parentheses. Operators have +the same meaning as in Go. - // +build linux,386 darwin,!cgo +For example, the following build constraint constrains a file to +build when the "linux" and "386" constraints are satisfied, or when +"darwin" is satisfied and "cgo" is not: -corresponds to the boolean formula: + //go:build (linux && 386) || (darwin && !cgo) - (linux AND 386) OR (darwin AND (NOT cgo)) - -A file may have multiple build constraints. The overall constraint is the AND -of the individual constraints. That is, the build constraints: - - // +build linux darwin - // +build amd64 - -corresponds to the boolean formula: - - (linux OR darwin) AND amd64 +It is an error for a file to have more than one //go:build line. During a particular build, the following words are satisfied: @@ -854,22 +852,26 @@ in addition to ios tags and files. To keep a file from being considered for the build: - // +build ignore + //go:build ignore (any other unsatisfied word will work as well, but "ignore" is conventional.) To build a file only when using cgo, and only on Linux and OS X: - // +build linux,cgo darwin,cgo + //go:build cgo && (linux || darwin) Such a file is usually paired with another file implementing the default functionality for other systems, which in this case would carry the constraint: - // +build !linux,!darwin !cgo + //go:build !(cgo && (linux || darwin)) Naming a file dns_windows.go will cause it to be included only when building the package for Windows; similarly, math_386.s will be included only when building the package for 32-bit x86. + +Go versions 1.16 and earlier used a different syntax for build constraints, +with a "// +build" prefix. The gofmt command will add an equivalent //go:build +constraint when encountering the older syntax. `, } diff --git a/src/cmd/go/internal/imports/read.go b/src/cmd/go/internal/imports/read.go index 5e270781d77bfeaca88908daf97d95d287ce7308..70d5190450502d042c2a2d0ed3d17105d50e6dbc 100644 --- a/src/cmd/go/internal/imports/read.go +++ b/src/cmd/go/internal/imports/read.go @@ -8,6 +8,7 @@ package imports import ( "bufio" + "bytes" "errors" "io" "unicode/utf8" @@ -22,6 +23,19 @@ type importReader struct { nerr int } +var bom = []byte{0xef, 0xbb, 0xbf} + +func newImportReader(b *bufio.Reader) *importReader { + // Remove leading UTF-8 BOM. + // Per https://golang.org/ref/spec#Source_code_representation: + // a compiler may ignore a UTF-8-encoded byte order mark (U+FEFF) + // if it is the first Unicode code point in the source text. + if leadingBytes, err := b.Peek(3); err == nil && bytes.Equal(leadingBytes, bom) { + b.Discard(3) + } + return &importReader{b: b} +} + func isIdent(c byte) bool { return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf } @@ -201,7 +215,7 @@ func (r *importReader) readImport(imports *[]string) { // ReadComments is like io.ReadAll, except that it only reads the leading // block of comments in the file. func ReadComments(f io.Reader) ([]byte, error) { - r := &importReader{b: bufio.NewReader(f)} + r := newImportReader(bufio.NewReader(f)) r.peekByte(true) if r.err == nil && !r.eof { // Didn't reach EOF, so must have found a non-space byte. Remove it. @@ -213,7 +227,7 @@ func ReadComments(f io.Reader) ([]byte, error) { // ReadImports is like io.ReadAll, except that it expects a Go file as input // and stops reading the input once the imports have completed. func ReadImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, error) { - r := &importReader{b: bufio.NewReader(f)} + r := newImportReader(bufio.NewReader(f)) r.readKeyword("package") r.readIdent() diff --git a/src/cmd/go/internal/imports/read_test.go b/src/cmd/go/internal/imports/read_test.go index 6ea356f1ff05acbd8aee3e9de9b5564cb1537dd3..6a1a6524a116d50c8036baa0b0ff3d60e08ccf1a 100644 --- a/src/cmd/go/internal/imports/read_test.go +++ b/src/cmd/go/internal/imports/read_test.go @@ -66,6 +66,10 @@ var readImportsTests = []readTest{ `, "", }, + { + "\ufeff𝔻" + `package p; import "x";ℙvar x = 1`, + "", + }, } var readCommentsTests = []readTest{ @@ -81,6 +85,10 @@ var readCommentsTests = []readTest{ `ℙpackage p; import . "x"`, "", }, + { + "\ufeff𝔻" + `ℙpackage p; import . "x"`, + "", + }, { `// foo @@ -90,6 +98,19 @@ var readCommentsTests = []readTest{ /*/ zot */ + // asdf + ℙHello, world`, + "", + }, + { + "\ufeff𝔻" + `// foo + + /* bar */ + + /* quux */ // baz + + /*/ zot */ + // asdf ℙHello, world`, "", @@ -107,6 +128,11 @@ func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, erro in = tt.in[:j] + tt.in[j+len("ℙ"):] testOut = tt.in[:j] } + d := strings.Index(tt.in, "𝔻") + if d >= 0 { + in = in[:d] + in[d+len("𝔻"):] + testOut = testOut[d+len("𝔻"):] + } r := strings.NewReader(in) buf, err := read(r) if err != nil { diff --git a/src/cmd/go/internal/list/context.go b/src/cmd/go/internal/list/context.go index 68d691ebe2eeccd0e7f16ca8528204d0296854e9..2dc63766b70654576156bebd007d6b35c0def832 100644 --- a/src/cmd/go/internal/list/context.go +++ b/src/cmd/go/internal/list/context.go @@ -17,6 +17,7 @@ type Context struct { UseAllFiles bool `json:",omitempty"` // use files regardless of +build lines, file names Compiler string `json:",omitempty"` // compiler to assume when computing target paths BuildTags []string `json:",omitempty"` // build constraints to match in +build lines + ToolTags []string `json:",omitempty"` // toolchain-specific build constraints ReleaseTags []string `json:",omitempty"` // releases the current release is compatible with InstallSuffix string `json:",omitempty"` // suffix to use in the name of the install dir } @@ -31,6 +32,7 @@ func newContext(c *build.Context) *Context { UseAllFiles: c.UseAllFiles, Compiler: c.Compiler, BuildTags: c.BuildTags, + ToolTags: c.ToolTags, ReleaseTags: c.ReleaseTags, InstallSuffix: c.InstallSuffix, } diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go index b4d82d9f8ccc97cb4e9400b15774958d4b4ce921..7cb9ec6d9492428aa0c9afe111fde89bf35fa536 100644 --- a/src/cmd/go/internal/list/list.go +++ b/src/cmd/go/internal/list/list.go @@ -148,6 +148,7 @@ The template function "context" returns the build context, defined as: UseAllFiles bool // use files regardless of +build lines, file names Compiler string // compiler to assume when computing target paths BuildTags []string // build constraints to match in +build lines + ToolTags []string // toolchain-specific build constraints ReleaseTags []string // releases the current release is compatible with InstallSuffix string // suffix to use in the name of the install dir } @@ -335,7 +336,10 @@ var ( var nl = []byte{'\n'} func runList(ctx context.Context, cmd *base.Command, args []string) { - load.ModResolveTests = *listTest + if *listFmt != "" && *listJson == true { + base.Fatalf("go list -f cannot be used with -json") + } + work.BuildInit() out := newTrackingWriter(os.Stdout) defer out.w.Flush() @@ -344,7 +348,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { if *listM { *listFmt = "{{.String}}" if *listVersions { - *listFmt = `{{.Path}}{{range .Versions}} {{.}}{{end}}` + *listFmt = `{{.Path}}{{range .Versions}} {{.}}{{end}}{{if .Deprecated}} (deprecated){{end}}` } } else { *listFmt = "{{.ImportPath}}" @@ -423,7 +427,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { base.Fatalf("go list -m: not using modules") } - modload.LoadModFile(ctx) // Parses go.mod and sets cfg.BuildMod. + modload.LoadModFile(ctx) // Sets cfg.BuildMod as a side-effect. if cfg.BuildMod == "vendor" { const actionDisabledFormat = "go list -m: can't %s using the vendor directory\n\t(Use -mod=mod or -mod=readonly to bypass.)" @@ -447,13 +451,29 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { } } - mods := modload.ListModules(ctx, args, *listU, *listVersions, *listRetracted) + var mode modload.ListMode + if *listU { + mode |= modload.ListU | modload.ListRetracted | modload.ListDeprecated + } + if *listRetracted { + mode |= modload.ListRetracted + } + if *listVersions { + mode |= modload.ListVersions + if *listRetracted { + mode |= modload.ListRetractedVersions + } + } + mods, err := modload.ListModules(ctx, args, mode) if !*listE { for _, m := range mods { if m.Error != nil { base.Errorf("go list -m: %v", m.Error.Err) } } + if err != nil { + base.Errorf("go list -m: %v", err) + } base.ExitIfErrors() } for _, m := range mods { @@ -478,8 +498,11 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { base.Fatalf("go list -test cannot be used with -find") } - load.IgnoreImports = *listFind - pkgs := load.PackagesAndErrors(ctx, args) + pkgOpts := load.PackageOpts{ + IgnoreImports: *listFind, + ModResolveTests: *listTest, + } + pkgs := load.PackagesAndErrors(ctx, pkgOpts, args) if !*listE { w := 0 for _, pkg := range pkgs { @@ -516,9 +539,9 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { var pmain, ptest, pxtest *load.Package var err error if *listE { - pmain, ptest, pxtest = load.TestPackagesAndErrors(ctx, p, nil) + pmain, ptest, pxtest = load.TestPackagesAndErrors(ctx, pkgOpts, p, nil) } else { - pmain, ptest, pxtest, err = load.TestPackagesFor(ctx, p, nil) + pmain, ptest, pxtest, err = load.TestPackagesFor(ctx, pkgOpts, p, nil) if err != nil { base.Errorf("can't load test package: %s", err) } @@ -605,7 +628,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { old := make(map[string]string) for _, p := range all { if p.ForTest != "" { - new := p.ImportPath + " [" + p.ForTest + ".test]" + new := p.Desc() old[new] = p.ImportPath p.ImportPath = new } @@ -679,9 +702,14 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { } if len(args) > 0 { - listU := false - listVersions := false - rmods := modload.ListModules(ctx, args, listU, listVersions, *listRetracted) + var mode modload.ListMode + if *listRetracted { + mode |= modload.ListRetracted + } + rmods, err := modload.ListModules(ctx, args, mode) + if err != nil && !*listE { + base.Errorf("go list -retracted: %v", err) + } for i, arg := range args { rmod := rmods[i] for _, mod := range argToMods[arg] { @@ -696,8 +724,18 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { // Record non-identity import mappings in p.ImportMap. for _, p := range pkgs { - for i, srcPath := range p.Internal.RawImports { - path := p.Imports[i] + nRaw := len(p.Internal.RawImports) + for i, path := range p.Imports { + var srcPath string + if i < nRaw { + srcPath = p.Internal.RawImports[i] + } else { + // This path is not within the raw imports, so it must be an import + // found only within CompiledGoFiles. Those paths are found in + // CompiledImports. + srcPath = p.Internal.CompiledImports[i-nRaw] + } + if path != srcPath { if p.ImportMap == nil { p.ImportMap = make(map[string]string) diff --git a/src/cmd/go/internal/load/flag.go b/src/cmd/go/internal/load/flag.go index 7534e65f54c75309ee5fc3bcbe4fa3222481f9d0..440cb86134489a7e023ce516629fe9bccc00fe0c 100644 --- a/src/cmd/go/internal/load/flag.go +++ b/src/cmd/go/internal/load/flag.go @@ -34,7 +34,7 @@ type ppfValue struct { // Set is called each time the flag is encountered on the command line. func (f *PerPackageFlag) Set(v string) error { - return f.set(v, base.Cwd) + return f.set(v, base.Cwd()) } // set is the implementation of Set, taking a cwd (current working directory) for easier testing. diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 8b12faf4cd2e51d8665a838c68275dce87cba018..a83cc9a812b674fe863b19c16273f2f6626042f8 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -14,6 +14,7 @@ import ( "go/build" "go/scanner" "go/token" + "internal/goroot" "io/fs" "os" "path" @@ -29,6 +30,8 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/fsys" + "cmd/go/internal/imports" + "cmd/go/internal/modfetch" "cmd/go/internal/modinfo" "cmd/go/internal/modload" "cmd/go/internal/par" @@ -37,11 +40,10 @@ import ( "cmd/go/internal/trace" "cmd/internal/sys" + "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) -var IgnoreImports bool // control whether we ignore imports in packages - // A Package describes a single package found in a directory. type Package struct { PackagePublic // visible in 'go list' @@ -85,6 +87,7 @@ type PackagePublic struct { CgoFiles []string `json:",omitempty"` // .go source files that import "C" CompiledGoFiles []string `json:",omitempty"` // .go output from running cgo on CgoFiles IgnoredGoFiles []string `json:",omitempty"` // .go source files ignored due to build constraints + InvalidGoFiles []string `json:",omitempty"` // .go source files with detected problems (parse error, wrong package name, and so on) IgnoredOtherFiles []string `json:",omitempty"` // non-.go source files ignored due to build constraints CFiles []string `json:",omitempty"` // .c source files CXXFiles []string `json:",omitempty"` // .cc, .cpp and .cxx source files @@ -142,6 +145,7 @@ func (p *Package) AllFiles() []string { p.CgoFiles, // no p.CompiledGoFiles, because they are from GoFiles or generated by us p.IgnoredGoFiles, + // no p.InvalidGoFiles, because they are from GoFiles p.IgnoredOtherFiles, p.CFiles, p.CXXFiles, @@ -190,8 +194,8 @@ type PackageInternal struct { // Unexported fields are not part of the public API. Build *build.Package Imports []*Package // this package's direct imports - CompiledImports []string // additional Imports necessary when using CompiledGoFiles (all from standard library) - RawImports []string // this package's original imports as they appear in the text of the program + CompiledImports []string // additional Imports necessary when using CompiledGoFiles (all from standard library); 1:1 with the end of PackagePublic.Imports + RawImports []string // this package's original imports as they appear in the text of the program; 1:1 with the end of PackagePublic.Imports ForceLibrary bool // this package is a library (even if named "main") CmdlineFiles bool // package built from files listed on command line CmdlinePkg bool // package listed on command line @@ -206,6 +210,7 @@ type PackageInternal struct { BuildInfo string // add this info to package main TestmainGo *[]byte // content for _testmain.go Embed map[string][]string // //go:embed comment mapping + OrigImportPath string // original import path before adding '_test' suffix Asmflags []string // -asmflags for this package Gcflags []string // -gcflags for this package @@ -343,7 +348,7 @@ type CoverVar struct { Var string // name of count struct } -func (p *Package) copyBuild(pp *build.Package) { +func (p *Package) copyBuild(opts PackageOpts, pp *build.Package) { p.Internal.Build = pp if pp.PkgTargetRoot != "" && cfg.BuildPkgdir != "" { @@ -368,6 +373,7 @@ func (p *Package) copyBuild(pp *build.Package) { p.GoFiles = pp.GoFiles p.CgoFiles = pp.CgoFiles p.IgnoredGoFiles = pp.IgnoredGoFiles + p.InvalidGoFiles = pp.InvalidGoFiles p.IgnoredOtherFiles = pp.IgnoredOtherFiles p.CFiles = pp.CFiles p.CXXFiles = pp.CXXFiles @@ -392,7 +398,7 @@ func (p *Package) copyBuild(pp *build.Package) { p.TestImports = pp.TestImports p.XTestGoFiles = pp.XTestGoFiles p.XTestImports = pp.XTestImports - if IgnoreImports { + if opts.IgnoreImports { p.Imports = nil p.Internal.RawImports = nil p.TestImports = nil @@ -401,6 +407,7 @@ func (p *Package) copyBuild(pp *build.Package) { p.EmbedPatterns = pp.EmbedPatterns p.TestEmbedPatterns = pp.TestEmbedPatterns p.XTestEmbedPatterns = pp.XTestEmbedPatterns + p.Internal.OrigImportPath = pp.ImportPath } // A PackageError describes an error loading information about a package. @@ -476,8 +483,10 @@ type ImportPathError interface { var ( _ ImportPathError = (*importError)(nil) + _ ImportPathError = (*mainPackageError)(nil) _ ImportPathError = (*modload.ImportMissingError)(nil) _ ImportPathError = (*modload.ImportMissingSumError)(nil) + _ ImportPathError = (*modload.DirectImportFromImplicitDependencyError)(nil) ) type importError struct { @@ -597,7 +606,7 @@ func ReloadPackageNoFlags(arg string, stk *ImportStack) *Package { }) packageDataCache.Delete(p.ImportPath) } - return LoadImport(context.TODO(), arg, base.Cwd, nil, stk, nil, 0) + return LoadImport(context.TODO(), PackageOpts{}, arg, base.Cwd(), nil, stk, nil, 0) } // dirToImportPath returns the pseudo-import path we use for a package @@ -649,11 +658,11 @@ const ( // LoadImport does not set tool flags and should only be used by // this package, as part of a bigger load operation, and by GOPATH-based "go get". // TODO(rsc): When GOPATH-based "go get" is removed, unexport this function. -func LoadImport(ctx context.Context, path, srcDir string, parent *Package, stk *ImportStack, importPos []token.Position, mode int) *Package { - return loadImport(ctx, nil, path, srcDir, parent, stk, importPos, mode) +func LoadImport(ctx context.Context, opts PackageOpts, path, srcDir string, parent *Package, stk *ImportStack, importPos []token.Position, mode int) *Package { + return loadImport(ctx, opts, nil, path, srcDir, parent, stk, importPos, mode) } -func loadImport(ctx context.Context, pre *preload, path, srcDir string, parent *Package, stk *ImportStack, importPos []token.Position, mode int) *Package { +func loadImport(ctx context.Context, opts PackageOpts, pre *preload, path, srcDir string, parent *Package, stk *ImportStack, importPos []token.Position, mode int) *Package { if path == "" { panic("LoadImport called with empty package path") } @@ -665,25 +674,30 @@ func loadImport(ctx context.Context, pre *preload, path, srcDir string, parent * parentRoot = parent.Root parentIsStd = parent.Standard } - bp, loaded, err := loadPackageData(path, parentPath, srcDir, parentRoot, parentIsStd, mode) - if loaded && pre != nil && !IgnoreImports { - pre.preloadImports(bp.Imports, bp) + bp, loaded, err := loadPackageData(ctx, path, parentPath, srcDir, parentRoot, parentIsStd, mode) + if loaded && pre != nil && !opts.IgnoreImports { + pre.preloadImports(ctx, opts, bp.Imports, bp) } if bp == nil { - if importErr, ok := err.(ImportPathError); !ok || importErr.ImportPath() != path { - // Only add path to the error's import stack if it's not already present on the error. - stk.Push(path) - defer stk.Pop() - } - return &Package{ + p := &Package{ PackagePublic: PackagePublic{ ImportPath: path, - Error: &PackageError{ - ImportStack: stk.Copy(), - Err: err, - }, + Incomplete: true, }, } + if importErr, ok := err.(ImportPathError); !ok || importErr.ImportPath() != path { + // Only add path to the error's import stack if it's not already present + // in the error. + // + // TODO(bcmills): setLoadPackageDataError itself has a similar Push / Pop + // sequence that empirically doesn't trigger for these errors, guarded by + // a somewhat complex condition. Figure out how to generalize that + // condition and eliminate the explicit calls here. + stk.Push(path) + defer stk.Pop() + } + p.setLoadPackageDataError(err, path, stk, nil) + return p } importPath := bp.ImportPath @@ -701,7 +715,7 @@ func loadImport(ctx context.Context, pre *preload, path, srcDir string, parent * // Load package. // loadPackageData may return bp != nil even if an error occurs, // in order to return partial information. - p.load(ctx, path, stk, importPos, bp, err) + p.load(ctx, opts, path, stk, importPos, bp, err) if !cfg.ModulesEnabled && path != cleanImport(path) { p.Error = &PackageError{ @@ -714,7 +728,7 @@ func loadImport(ctx context.Context, pre *preload, path, srcDir string, parent * } // Checked on every import because the rules depend on the code doing the importing. - if perr := disallowInternal(srcDir, parent, parentPath, p, stk); perr != p { + if perr := disallowInternal(ctx, srcDir, parent, parentPath, p, stk); perr != p { perr.Error.setPos(importPos) return perr } @@ -763,7 +777,7 @@ func loadImport(ctx context.Context, pre *preload, path, srcDir string, parent * // // loadPackageData returns a boolean, loaded, which is true if this is the // first time the package was loaded. Callers may preload imports in this case. -func loadPackageData(path, parentPath, parentDir, parentRoot string, parentIsStd bool, mode int) (bp *build.Package, loaded bool, err error) { +func loadPackageData(ctx context.Context, path, parentPath, parentDir, parentRoot string, parentIsStd bool, mode int) (bp *build.Package, loaded bool, err error) { if path == "" { panic("loadPackageData called with empty package path") } @@ -835,11 +849,34 @@ func loadPackageData(path, parentPath, parentDir, parentRoot string, parentIsStd buildMode = build.ImportComment } data.p, data.err = cfg.BuildContext.ImportDir(r.dir, buildMode) - if data.p.Root == "" && cfg.ModulesEnabled { - if info := modload.PackageModuleInfo(path); info != nil { + if cfg.ModulesEnabled { + // Override data.p.Root, since ImportDir sets it to $GOPATH, if + // the module is inside $GOPATH/src. + if info := modload.PackageModuleInfo(ctx, path); info != nil { data.p.Root = info.Dir } } + if r.err != nil { + if data.err != nil { + // ImportDir gave us one error, and the module loader gave us another. + // We arbitrarily choose to keep the error from ImportDir because + // that's what our tests already expect, and it seems to provide a bit + // more detail in most cases. + } else if errors.Is(r.err, imports.ErrNoGo) { + // ImportDir said there were files in the package, but the module + // loader said there weren't. Which one is right? + // Without this special-case hack, the TestScript/test_vet case fails + // on the vetfail/p1 package (added in CL 83955). + // Apparently, imports.ShouldBuild biases toward rejecting files + // with invalid build constraints, whereas ImportDir biases toward + // accepting them. + // + // TODO(#41410: Figure out how this actually ought to work and fix + // this mess. + } else { + data.err = r.err + } + } } else if r.err != nil { data.p = new(build.Package) data.err = r.err @@ -950,7 +987,7 @@ func newPreload() *preload { // preloadMatches loads data for package paths matched by patterns. // When preloadMatches returns, some packages may not be loaded yet, but // loadPackageData and loadImport are always safe to call. -func (pre *preload) preloadMatches(matches []*search.Match) { +func (pre *preload) preloadMatches(ctx context.Context, opts PackageOpts, matches []*search.Match) { for _, m := range matches { for _, pkg := range m.Pkgs { select { @@ -959,10 +996,10 @@ func (pre *preload) preloadMatches(matches []*search.Match) { case pre.sema <- struct{}{}: go func(pkg string) { mode := 0 // don't use vendoring or module import resolution - bp, loaded, err := loadPackageData(pkg, "", base.Cwd, "", false, mode) + bp, loaded, err := loadPackageData(ctx, pkg, "", base.Cwd(), "", false, mode) <-pre.sema - if bp != nil && loaded && err == nil && !IgnoreImports { - pre.preloadImports(bp.Imports, bp) + if bp != nil && loaded && err == nil && !opts.IgnoreImports { + pre.preloadImports(ctx, opts, bp.Imports, bp) } }(pkg) } @@ -973,7 +1010,7 @@ func (pre *preload) preloadMatches(matches []*search.Match) { // preloadImports queues a list of imports for preloading. // When preloadImports returns, some packages may not be loaded yet, // but loadPackageData and loadImport are always safe to call. -func (pre *preload) preloadImports(imports []string, parent *build.Package) { +func (pre *preload) preloadImports(ctx context.Context, opts PackageOpts, imports []string, parent *build.Package) { parentIsStd := parent.Goroot && parent.ImportPath != "" && search.IsStandardImportPath(parent.ImportPath) for _, path := range imports { if path == "C" || path == "unsafe" { @@ -984,10 +1021,10 @@ func (pre *preload) preloadImports(imports []string, parent *build.Package) { return case pre.sema <- struct{}{}: go func(path string) { - bp, loaded, err := loadPackageData(path, parent.ImportPath, parent.Dir, parent.Root, parentIsStd, ResolveImport) + bp, loaded, err := loadPackageData(ctx, path, parent.ImportPath, parent.Dir, parent.Root, parentIsStd, ResolveImport) <-pre.sema - if bp != nil && loaded && err == nil && !IgnoreImports { - pre.preloadImports(bp.Imports, bp) + if bp != nil && loaded && err == nil && !opts.IgnoreImports { + pre.preloadImports(ctx, opts, bp.Imports, bp) } }(path) } @@ -1323,6 +1360,11 @@ func reusePackage(p *Package, stk *ImportStack) *Package { Err: errors.New("import cycle not allowed"), IsImportCycle: true, } + } else if !p.Error.IsImportCycle { + // If the error is already set, but it does not indicate that + // we are in an import cycle, set IsImportCycle so that we don't + // end up stuck in a loop down the road. + p.Error.IsImportCycle = true } p.Incomplete = true } @@ -1338,7 +1380,7 @@ func reusePackage(p *Package, stk *ImportStack) *Package { // is allowed to import p. // If the import is allowed, disallowInternal returns the original package p. // If not, it returns a new package containing just an appropriate error. -func disallowInternal(srcDir string, importer *Package, importerPath string, p *Package, stk *ImportStack) *Package { +func disallowInternal(ctx context.Context, srcDir string, importer *Package, importerPath string, p *Package, stk *ImportStack) *Package { // golang.org/s/go14internal: // An import of a path containing the element “internal” // is disallowed if the importing code is outside the tree @@ -1410,7 +1452,7 @@ func disallowInternal(srcDir string, importer *Package, importerPath string, p * // directory containing them. // If the directory is outside the main module, this will resolve to ".", // which is not a prefix of any valid module. - importerPath = modload.DirImportPath(importer.Dir) + importerPath = modload.DirImportPath(ctx, importer.Dir) } parentOfInternal := p.ImportPath[:i] if str.HasPathPrefix(importerPath, parentOfInternal) { @@ -1632,8 +1674,8 @@ func (p *Package) DefaultExecName() string { // load populates p using information from bp, err, which should // be the result of calling build.Context.Import. // stk contains the import stack, not including path itself. -func (p *Package) load(ctx context.Context, path string, stk *ImportStack, importPos []token.Position, bp *build.Package, err error) { - p.copyBuild(bp) +func (p *Package) load(ctx context.Context, opts PackageOpts, path string, stk *ImportStack, importPos []token.Position, bp *build.Package, err error) { + p.copyBuild(opts, bp) // The localPrefix is the path we interpret ./ imports relative to. // Synthesized main packages sometimes override this. @@ -1757,35 +1799,37 @@ func (p *Package) load(ctx context.Context, path string, stk *ImportStack, impor } } - // Cgo translation adds imports of "unsafe", "runtime/cgo" and "syscall", - // except for certain packages, to avoid circular dependencies. - if p.UsesCgo() { - addImport("unsafe", true) - } - if p.UsesCgo() && (!p.Standard || !cgoExclude[p.ImportPath]) && cfg.BuildContext.Compiler != "gccgo" { - addImport("runtime/cgo", true) - } - if p.UsesCgo() && (!p.Standard || !cgoSyscallExclude[p.ImportPath]) { - addImport("syscall", true) - } - - // SWIG adds imports of some standard packages. - if p.UsesSwig() { - addImport("unsafe", true) - if cfg.BuildContext.Compiler != "gccgo" { + if !opts.IgnoreImports { + // Cgo translation adds imports of "unsafe", "runtime/cgo" and "syscall", + // except for certain packages, to avoid circular dependencies. + if p.UsesCgo() { + addImport("unsafe", true) + } + if p.UsesCgo() && (!p.Standard || !cgoExclude[p.ImportPath]) && cfg.BuildContext.Compiler != "gccgo" { addImport("runtime/cgo", true) } - addImport("syscall", true) - addImport("sync", true) + if p.UsesCgo() && (!p.Standard || !cgoSyscallExclude[p.ImportPath]) { + addImport("syscall", true) + } - // TODO: The .swig and .swigcxx files can use - // %go_import directives to import other packages. - } + // SWIG adds imports of some standard packages. + if p.UsesSwig() { + addImport("unsafe", true) + if cfg.BuildContext.Compiler != "gccgo" { + addImport("runtime/cgo", true) + } + addImport("syscall", true) + addImport("sync", true) - // The linker loads implicit dependencies. - if p.Name == "main" && !p.Internal.ForceLibrary { - for _, dep := range LinkerDeps(p) { - addImport(dep, false) + // TODO: The .swig and .swigcxx files can use + // %go_import directives to import other packages. + } + + // The linker loads implicit dependencies. + if p.Name == "main" && !p.Internal.ForceLibrary { + for _, dep := range LinkerDeps(p) { + addImport(dep, false) + } } } @@ -1809,6 +1853,14 @@ func (p *Package) load(ctx context.Context, path string, stk *ImportStack, impor stk.Push(path) defer stk.Pop() + pkgPath := p.ImportPath + if p.Internal.CmdlineFiles { + pkgPath = "command-line-arguments" + } + if cfg.ModulesEnabled { + p.Module = modload.PackageModuleInfo(ctx, pkgPath) + } + p.EmbedFiles, p.Internal.Embed, err = resolveEmbed(p.Dir, p.EmbedPatterns) if err != nil { p.Incomplete = true @@ -1852,7 +1904,7 @@ func (p *Package) load(ctx context.Context, path string, stk *ImportStack, impor if path == "C" { continue } - p1 := LoadImport(ctx, path, p.Dir, p, stk, p.Internal.Build.ImportPos[path], ResolveImport) + p1 := LoadImport(ctx, opts, path, p.Dir, p, stk, p.Internal.Build.ImportPos[path], ResolveImport) path = p1.ImportPath importPaths[i] = path @@ -1868,6 +1920,10 @@ func (p *Package) load(ctx context.Context, path string, stk *ImportStack, impor p.Internal.Imports = imports p.collectDeps() + if cfg.ModulesEnabled && p.Error == nil && p.Name == "main" && len(p.DepsErrors) == 0 { + p.Internal.BuildInfo = modload.PackageBuildInfo(pkgPath, p.Deps) + } + // unsafe is a fake package. if p.Standard && (p.ImportPath == "unsafe" || cfg.BuildContext.Compiler == "gccgo") { p.Target = "" @@ -1907,17 +1963,6 @@ func (p *Package) load(ctx context.Context, path string, stk *ImportStack, impor setError(fmt.Errorf("Fortran source files not allowed when not using cgo or SWIG: %s", strings.Join(p.FFiles, " "))) return } - - if cfg.ModulesEnabled && p.Error == nil { - mainPath := p.ImportPath - if p.Internal.CmdlineFiles { - mainPath = "command-line-arguments" - } - p.Module = modload.PackageModuleInfo(mainPath) - if p.Name == "main" && len(p.DepsErrors) == 0 { - p.Internal.BuildInfo = modload.PackageBuildInfo(mainPath, p.Deps) - } - } } // An EmbedError indicates a problem with a go:embed directive. @@ -2299,7 +2344,7 @@ func PackageList(roots []*Package) []*Package { // TestPackageList returns the list of packages in the dag rooted at roots // as visited in a depth-first post-order traversal, including the test // imports of the roots. This ignores errors in test packages. -func TestPackageList(ctx context.Context, roots []*Package) []*Package { +func TestPackageList(ctx context.Context, opts PackageOpts, roots []*Package) []*Package { seen := map[*Package]bool{} all := []*Package{} var walk func(*Package) @@ -2315,7 +2360,7 @@ func TestPackageList(ctx context.Context, roots []*Package) []*Package { } walkTest := func(root *Package, path string) { var stk ImportStack - p1 := LoadImport(ctx, path, root.Dir, root, &stk, root.Internal.Build.TestImportPos[path], ResolveImport) + p1 := LoadImport(ctx, opts, path, root.Dir, root, &stk, root.Internal.Build.TestImportPos[path], ResolveImport) if p1.Error == nil { walk(p1) } @@ -2338,22 +2383,35 @@ func TestPackageList(ctx context.Context, roots []*Package) []*Package { // TODO(jayconrod): delete this function and set flags automatically // in LoadImport instead. func LoadImportWithFlags(path, srcDir string, parent *Package, stk *ImportStack, importPos []token.Position, mode int) *Package { - p := LoadImport(context.TODO(), path, srcDir, parent, stk, importPos, mode) + p := LoadImport(context.TODO(), PackageOpts{}, path, srcDir, parent, stk, importPos, mode) setToolFlags(p) return p } -// ModResolveTests indicates whether calls to the module loader should also -// resolve test dependencies of the requested packages. -// -// If ModResolveTests is true, then the module loader needs to resolve test -// dependencies at the same time as packages; otherwise, the test dependencies -// of those packages could be missing, and resolving those missing dependencies -// could change the selected versions of modules that provide other packages. -// -// TODO(#40775): Change this from a global variable to an explicit function -// argument where needed. -var ModResolveTests bool +// PackageOpts control the behavior of PackagesAndErrors and other package +// loading functions. +type PackageOpts struct { + // IgnoreImports controls whether we ignore explicit and implicit imports + // when loading packages. Implicit imports are added when supporting Cgo + // or SWIG and when linking main packages. + IgnoreImports bool + + // ModResolveTests indicates whether calls to the module loader should also + // resolve test dependencies of the requested packages. + // + // If ModResolveTests is true, then the module loader needs to resolve test + // dependencies at the same time as packages; otherwise, the test dependencies + // of those packages could be missing, and resolving those missing dependencies + // could change the selected versions of modules that provide other packages. + ModResolveTests bool + + // MainOnly is true if the caller only wants to load main packages. + // For a literal argument matching a non-main package, a stub may be returned + // with an error. For a non-literal argument (with "..."), non-main packages + // are not be matched, and their dependencies may not be loaded. A warning + // may be printed for non-literal arguments that match no main packages. + MainOnly bool +} // PackagesAndErrors returns the packages named by the command line arguments // 'patterns'. If a named package cannot be loaded, PackagesAndErrors returns @@ -2363,7 +2421,7 @@ var ModResolveTests bool // // To obtain a flat list of packages, use PackageList. // To report errors loading packages, use ReportPackageErrors. -func PackagesAndErrors(ctx context.Context, patterns []string) []*Package { +func PackagesAndErrors(ctx context.Context, opts PackageOpts, patterns []string) []*Package { ctx, span := trace.StartSpan(ctx, "load.PackagesAndErrors") defer span.Done() @@ -2375,19 +2433,19 @@ func PackagesAndErrors(ctx context.Context, patterns []string) []*Package { // We need to test whether the path is an actual Go file and not a // package path or pattern ending in '.go' (see golang.org/issue/34653). if fi, err := fsys.Stat(p); err == nil && !fi.IsDir() { - return []*Package{GoFilesPackage(ctx, patterns)} + return []*Package{GoFilesPackage(ctx, opts, patterns)} } } } var matches []*search.Match if modload.Init(); cfg.ModulesEnabled { - loadOpts := modload.PackageOpts{ + modOpts := modload.PackageOpts{ ResolveMissingImports: true, - LoadTests: ModResolveTests, - SilenceErrors: true, + LoadTests: opts.ModResolveTests, + SilencePackageErrors: true, } - matches, _ = modload.LoadPackages(ctx, loadOpts, patterns...) + matches, _ = modload.LoadPackages(ctx, modOpts, patterns...) } else { matches = search.ImportPaths(patterns) } @@ -2400,14 +2458,14 @@ func PackagesAndErrors(ctx context.Context, patterns []string) []*Package { pre := newPreload() defer pre.flush() - pre.preloadMatches(matches) + pre.preloadMatches(ctx, opts, matches) for _, m := range matches { for _, pkg := range m.Pkgs { if pkg == "" { panic(fmt.Sprintf("ImportPaths returned empty package for pattern %s", m.Pattern())) } - p := loadImport(ctx, pre, pkg, base.Cwd, nil, &stk, nil, 0) + p := loadImport(ctx, opts, pre, pkg, base.Cwd(), nil, &stk, nil, 0) p.Match = append(p.Match, m.Pattern()) p.Internal.CmdlinePkg = true if m.IsLiteral() { @@ -2443,6 +2501,10 @@ func PackagesAndErrors(ctx context.Context, patterns []string) []*Package { } } + if opts.MainOnly { + pkgs = mainPackagesOnly(pkgs, matches) + } + // Now that CmdlinePkg is set correctly, // compute the effective flags for all loaded packages // (not just the ones matching the patterns but also @@ -2491,6 +2553,80 @@ func CheckPackageErrors(pkgs []*Package) { base.ExitIfErrors() } +// mainPackagesOnly filters out non-main packages matched only by arguments +// containing "..." and returns the remaining main packages. +// +// Packages with missing, invalid, or ambiguous names may be treated as +// possibly-main packages. +// +// mainPackagesOnly sets a non-main package's Error field and returns it if it +// is named by a literal argument. +// +// mainPackagesOnly prints warnings for non-literal arguments that only match +// non-main packages. +func mainPackagesOnly(pkgs []*Package, matches []*search.Match) []*Package { + treatAsMain := map[string]bool{} + for _, m := range matches { + if m.IsLiteral() { + for _, path := range m.Pkgs { + treatAsMain[path] = true + } + } + } + + var mains []*Package + for _, pkg := range pkgs { + if pkg.Name == "main" { + treatAsMain[pkg.ImportPath] = true + mains = append(mains, pkg) + continue + } + + if len(pkg.InvalidGoFiles) > 0 { // TODO(#45999): && pkg.Name == "", but currently go/build sets pkg.Name arbitrarily if it is ambiguous. + // The package has (or may have) conflicting names, and we can't easily + // tell whether one of them is "main". So assume that it could be, and + // report an error for the package. + treatAsMain[pkg.ImportPath] = true + } + if treatAsMain[pkg.ImportPath] { + if pkg.Error == nil { + pkg.Error = &PackageError{Err: &mainPackageError{importPath: pkg.ImportPath}} + } + mains = append(mains, pkg) + } + } + + for _, m := range matches { + if m.IsLiteral() || len(m.Pkgs) == 0 { + continue + } + foundMain := false + for _, path := range m.Pkgs { + if treatAsMain[path] { + foundMain = true + break + } + } + if !foundMain { + fmt.Fprintf(os.Stderr, "go: warning: %q matched only non-main packages\n", m.Pattern()) + } + } + + return mains +} + +type mainPackageError struct { + importPath string +} + +func (e *mainPackageError) Error() string { + return fmt.Sprintf("package %s is not a main package", e.importPath) +} + +func (e *mainPackageError) ImportPath() string { + return e.importPath +} + func setToolFlags(pkgs ...*Package) { for _, p := range PackageList(pkgs) { p.Internal.Asmflags = BuildAsmflags.For(p) @@ -2503,7 +2639,7 @@ func setToolFlags(pkgs ...*Package) { // GoFilesPackage creates a package for building a collection of Go files // (typically named on the command line). The target is named p.a for // package p or named after the first Go file for package main. -func GoFilesPackage(ctx context.Context, gofiles []string) *Package { +func GoFilesPackage(ctx context.Context, opts PackageOpts, gofiles []string) *Package { modload.Init() for _, f := range gofiles { @@ -2556,7 +2692,7 @@ func GoFilesPackage(ctx context.Context, gofiles []string) *Package { var err error if dir == "" { - dir = base.Cwd + dir = base.Cwd() } dir, err = filepath.Abs(dir) if err != nil { @@ -2567,7 +2703,7 @@ func GoFilesPackage(ctx context.Context, gofiles []string) *Package { pkg := new(Package) pkg.Internal.Local = true pkg.Internal.CmdlineFiles = true - pkg.load(ctx, "command-line-arguments", &stk, nil, bp, err) + pkg.load(ctx, opts, "command-line-arguments", &stk, nil, bp, err) pkg.Internal.LocalPrefix = dirToImportPath(dir) pkg.ImportPath = "command-line-arguments" pkg.Target = "" @@ -2583,7 +2719,138 @@ func GoFilesPackage(ctx context.Context, gofiles []string) *Package { } } + if opts.MainOnly && pkg.Name != "main" && pkg.Error == nil { + pkg.Error = &PackageError{Err: &mainPackageError{importPath: pkg.ImportPath}} + } setToolFlags(pkg) return pkg } + +// PackagesAndErrorsOutsideModule is like PackagesAndErrors but runs in +// module-aware mode and ignores the go.mod file in the current directory or any +// parent directory, if there is one. This is used in the implementation of 'go +// install pkg@version' and other commands that support similar forms. +// +// modload.ForceUseModules must be true, and modload.RootMode must be NoRoot +// before calling this function. +// +// PackagesAndErrorsOutsideModule imposes several constraints to avoid +// ambiguity. All arguments must have the same version suffix (not just a suffix +// that resolves to the same version). They must refer to packages in the same +// module, which must not be std or cmd. That module is not considered the main +// module, but its go.mod file (if it has one) must not contain directives that +// would cause it to be interpreted differently if it were the main module +// (replace, exclude). +func PackagesAndErrorsOutsideModule(ctx context.Context, opts PackageOpts, args []string) ([]*Package, error) { + if !modload.ForceUseModules { + panic("modload.ForceUseModules must be true") + } + if modload.RootMode != modload.NoRoot { + panic("modload.RootMode must be NoRoot") + } + + // Check that the arguments satisfy syntactic constraints. + var version string + for _, arg := range args { + if i := strings.Index(arg, "@"); i >= 0 { + version = arg[i+1:] + if version == "" { + return nil, fmt.Errorf("%s: version must not be empty", arg) + } + break + } + } + patterns := make([]string, len(args)) + for i, arg := range args { + if !strings.HasSuffix(arg, "@"+version) { + return nil, fmt.Errorf("%s: all arguments must have the same version (@%s)", arg, version) + } + p := arg[:len(arg)-len(version)-1] + switch { + case build.IsLocalImport(p): + return nil, fmt.Errorf("%s: argument must be a package path, not a relative path", arg) + case filepath.IsAbs(p): + return nil, fmt.Errorf("%s: argument must be a package path, not an absolute path", arg) + case search.IsMetaPackage(p): + return nil, fmt.Errorf("%s: argument must be a package path, not a meta-package", arg) + case path.Clean(p) != p: + return nil, fmt.Errorf("%s: argument must be a clean package path", arg) + case !strings.Contains(p, "...") && search.IsStandardImportPath(p) && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, p): + return nil, fmt.Errorf("%s: argument must not be a package in the standard library", arg) + default: + patterns[i] = p + } + } + + // Query the module providing the first argument, load its go.mod file, and + // check that it doesn't contain directives that would cause it to be + // interpreted differently if it were the main module. + // + // If multiple modules match the first argument, accept the longest match + // (first result). It's possible this module won't provide packages named by + // later arguments, and other modules would. Let's not try to be too + // magical though. + allowed := modload.CheckAllowed + if modload.IsRevisionQuery(version) { + // Don't check for retractions if a specific revision is requested. + allowed = nil + } + noneSelected := func(path string) (version string) { return "none" } + qrs, err := modload.QueryPackages(ctx, patterns[0], version, noneSelected, allowed) + if err != nil { + return nil, fmt.Errorf("%s: %w", args[0], err) + } + rootMod := qrs[0].Mod + data, err := modfetch.GoMod(rootMod.Path, rootMod.Version) + if err != nil { + return nil, fmt.Errorf("%s: %w", args[0], err) + } + f, err := modfile.Parse("go.mod", data, nil) + if err != nil { + return nil, fmt.Errorf("%s (in %s): %w", args[0], rootMod, err) + } + directiveFmt := "%s (in %s):\n" + + "\tThe go.mod file for the module providing named packages contains one or\n" + + "\tmore %s directives. It must not contain directives that would cause\n" + + "\tit to be interpreted differently than if it were the main module." + if len(f.Replace) > 0 { + return nil, fmt.Errorf(directiveFmt, args[0], rootMod, "replace") + } + if len(f.Exclude) > 0 { + return nil, fmt.Errorf(directiveFmt, args[0], rootMod, "exclude") + } + + // Since we are in NoRoot mode, the build list initially contains only + // the dummy command-line-arguments module. Add a requirement on the + // module that provides the packages named on the command line. + if _, err := modload.EditBuildList(ctx, nil, []module.Version{rootMod}); err != nil { + return nil, fmt.Errorf("%s: %w", args[0], err) + } + + // Load packages for all arguments. + pkgs := PackagesAndErrors(ctx, opts, patterns) + + // Check that named packages are all provided by the same module. + for _, pkg := range pkgs { + var pkgErr error + if pkg.Module == nil { + // Packages in std, cmd, and their vendored dependencies + // don't have this field set. + pkgErr = fmt.Errorf("package %s not provided by module %s", pkg.ImportPath, rootMod) + } else if pkg.Module.Path != rootMod.Path || pkg.Module.Version != rootMod.Version { + pkgErr = fmt.Errorf("package %s provided by module %s@%s\n\tAll packages must be provided by the same module (%s).", pkg.ImportPath, pkg.Module.Path, pkg.Module.Version, rootMod) + } + if pkgErr != nil && pkg.Error == nil { + pkg.Error = &PackageError{Err: pkgErr} + } + } + + matchers := make([]func(string) bool, len(patterns)) + for i, p := range patterns { + if strings.Contains(p, "...") { + matchers[i] = search.MatchPattern(p) + } + } + return pkgs, nil +} diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go index eb8aef3ee28126d289e39a91f57ca285051e339e..c8282965669c56d330c68a2390ba51d8c39452b8 100644 --- a/src/cmd/go/internal/load/test.go +++ b/src/cmd/go/internal/load/test.go @@ -21,6 +21,7 @@ import ( "unicode" "unicode/utf8" + "cmd/go/internal/fsys" "cmd/go/internal/str" "cmd/go/internal/trace" ) @@ -45,8 +46,8 @@ type TestCover struct { // TestPackagesFor is like TestPackagesAndErrors but it returns // an error if the test packages or their dependencies have errors. // Only test packages without errors are returned. -func TestPackagesFor(ctx context.Context, p *Package, cover *TestCover) (pmain, ptest, pxtest *Package, err error) { - pmain, ptest, pxtest = TestPackagesAndErrors(ctx, p, cover) +func TestPackagesFor(ctx context.Context, opts PackageOpts, p *Package, cover *TestCover) (pmain, ptest, pxtest *Package, err error) { + pmain, ptest, pxtest = TestPackagesAndErrors(ctx, opts, p, cover) for _, p1 := range []*Package{ptest, pxtest, pmain} { if p1 == nil { // pxtest may be nil @@ -92,7 +93,7 @@ func TestPackagesFor(ctx context.Context, p *Package, cover *TestCover) (pmain, // // The caller is expected to have checked that len(p.TestGoFiles)+len(p.XTestGoFiles) > 0, // or else there's no point in any of this. -func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (pmain, ptest, pxtest *Package) { +func TestPackagesAndErrors(ctx context.Context, opts PackageOpts, p *Package, cover *TestCover) (pmain, ptest, pxtest *Package) { ctx, span := trace.StartSpan(ctx, "load.TestPackagesAndErrors") defer span.Done() @@ -100,7 +101,7 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p defer pre.flush() allImports := append([]string{}, p.TestImports...) allImports = append(allImports, p.XTestImports...) - pre.preloadImports(allImports, p.Internal.Build) + pre.preloadImports(ctx, opts, allImports, p.Internal.Build) var ptestErr, pxtestErr *PackageError var imports, ximports []*Package @@ -109,13 +110,13 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p stk.Push(p.ImportPath + " (test)") rawTestImports := str.StringList(p.TestImports) for i, path := range p.TestImports { - p1 := loadImport(ctx, pre, path, p.Dir, p, &stk, p.Internal.Build.TestImportPos[path], ResolveImport) + p1 := loadImport(ctx, opts, pre, path, p.Dir, p, &stk, p.Internal.Build.TestImportPos[path], ResolveImport) if str.Contains(p1.Deps, p.ImportPath) || p1.ImportPath == p.ImportPath { // Same error that loadPackage returns (via reusePackage) in pkg.go. // Can't change that code, because that code is only for loading the // non-test copy of a package. ptestErr = &PackageError{ - ImportStack: testImportStack(stk[0], p1, p.ImportPath), + ImportStack: importCycleStack(p1, p.ImportPath), Err: errors.New("import cycle not allowed in test"), IsImportCycle: true, } @@ -139,7 +140,7 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p pxtestNeedsPtest := false rawXTestImports := str.StringList(p.XTestImports) for i, path := range p.XTestImports { - p1 := loadImport(ctx, pre, path, p.Dir, p, &stk, p.Internal.Build.XTestImportPos[path], ResolveImport) + p1 := loadImport(ctx, opts, pre, path, p.Dir, p, &stk, p.Internal.Build.XTestImportPos[path], ResolveImport) if p1.ImportPath == p.ImportPath { pxtestNeedsPtest = true } else { @@ -203,6 +204,7 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p } ptest.Internal.Embed = testEmbed ptest.EmbedFiles = str.StringList(p.EmbedFiles, p.TestEmbedFiles) + ptest.Internal.OrigImportPath = p.Internal.OrigImportPath ptest.collectDeps() } else { ptest = p @@ -232,11 +234,12 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p Imports: ximports, RawImports: rawXTestImports, - Asmflags: p.Internal.Asmflags, - Gcflags: p.Internal.Gcflags, - Ldflags: p.Internal.Ldflags, - Gccgoflags: p.Internal.Gccgoflags, - Embed: xtestEmbed, + Asmflags: p.Internal.Asmflags, + Gcflags: p.Internal.Gcflags, + Ldflags: p.Internal.Ldflags, + Gccgoflags: p.Internal.Gccgoflags, + Embed: xtestEmbed, + OrigImportPath: p.Internal.OrigImportPath, }, } if pxtestNeedsPtest { @@ -257,12 +260,13 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p Module: p.Module, }, Internal: PackageInternal{ - Build: &build.Package{Name: "main"}, - BuildInfo: p.Internal.BuildInfo, - Asmflags: p.Internal.Asmflags, - Gcflags: p.Internal.Gcflags, - Ldflags: p.Internal.Ldflags, - Gccgoflags: p.Internal.Gccgoflags, + Build: &build.Package{Name: "main"}, + BuildInfo: p.Internal.BuildInfo, + Asmflags: p.Internal.Asmflags, + Gcflags: p.Internal.Gcflags, + Ldflags: p.Internal.Ldflags, + Gccgoflags: p.Internal.Gccgoflags, + OrigImportPath: p.Internal.OrigImportPath, }, } @@ -277,7 +281,7 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p if dep == ptest.ImportPath { pmain.Internal.Imports = append(pmain.Internal.Imports, ptest) } else { - p1 := loadImport(ctx, pre, dep, "", nil, &stk, nil, 0) + p1 := loadImport(ctx, opts, pre, dep, "", nil, &stk, nil, 0) pmain.Internal.Imports = append(pmain.Internal.Imports, p1) } } @@ -290,10 +294,12 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p seen[p1] = true } for _, p1 := range cover.Pkgs { - if !seen[p1] { - seen[p1] = true - pmain.Internal.Imports = append(pmain.Internal.Imports, p1) + if seen[p1] { + // Don't add duplicate imports. + continue } + seen[p1] = true + pmain.Internal.Imports = append(pmain.Internal.Imports, p1) } } @@ -369,22 +375,44 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p return pmain, ptest, pxtest } -func testImportStack(top string, p *Package, target string) []string { - stk := []string{top, p.ImportPath} -Search: - for p.ImportPath != target { - for _, p1 := range p.Internal.Imports { - if p1.ImportPath == target || str.Contains(p1.Deps, target) { - stk = append(stk, p1.ImportPath) - p = p1 - continue Search +// importCycleStack returns an import stack from p to the package whose import +// path is target. +func importCycleStack(p *Package, target string) []string { + // importerOf maps each import path to its importer nearest to p. + importerOf := map[string]string{p.ImportPath: ""} + + // q is a breadth-first queue of packages to search for target. + // Every package added to q has a corresponding entry in pathTo. + // + // We search breadth-first for two reasons: + // + // 1. We want to report the shortest cycle. + // + // 2. If p contains multiple cycles, the first cycle we encounter might not + // contain target. To ensure termination, we have to break all cycles + // other than the first. + q := []*Package{p} + + for len(q) > 0 { + p := q[0] + q = q[1:] + if path := p.ImportPath; path == target { + var stk []string + for path != "" { + stk = append(stk, path) + path = importerOf[path] + } + return stk + } + for _, dep := range p.Internal.Imports { + if _, ok := importerOf[dep.ImportPath]; !ok { + importerOf[dep.ImportPath] = p.ImportPath + q = append(q, dep) } } - // Can't happen, but in case it does... - stk = append(stk, "") - break } - return stk + + panic("lost path to cycle") } // recompileForTest copies and replaces certain packages in pmain's dependency @@ -576,7 +604,13 @@ type testFunc struct { var testFileSet = token.NewFileSet() func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error { - f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments) + // Pass in the overlaid source if we have an overlay for this file. + src, err := fsys.Open(filename) + if err != nil { + return err + } + defer src.Close() + f, err := parser.ParseFile(testFileSet, filename, src, parser.ParseComments) if err != nil { return err } diff --git a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go index 1fa4327a89d8a1f89e333bc2539b223ca083b5c0..a37b2ad6d184b0f89169e4e0e1d8ff5e396b8370 100644 --- a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go +++ b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || (solaris && !illumos) // +build aix solaris,!illumos // This code implements the filelock API using POSIX 'fcntl' locks, which attach diff --git a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_other.go b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_other.go index bc480343fc3be7dedba979cfb2fba1fe66ef8e04..70f5d7a688a070e5381c7da252fcd153cda80fb6 100644 --- a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_other.go +++ b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_other.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris && !windows // +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!plan9,!solaris,!windows package filelock diff --git a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_plan9.go b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_plan9.go index 0798ee469a46f40959bce21eea17ee36c5f6c360..908afb6c8cb7687d4d34545038b831e27434c2d8 100644 --- a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_plan9.go +++ b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_plan9.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build plan9 // +build plan9 package filelock diff --git a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_test.go b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_test.go index 2ac2052b8f57372dd674b5f8eb5fbdb15f198161..640d4406f4200cd1e44c426421955678feb66f91 100644 --- a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_test.go +++ b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js && !plan9 // +build !js,!plan9 package filelock_test diff --git a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_unix.go b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_unix.go index ed07bac608880a784348e7e3b06ef33ae395cefb..878a1e770d4d42a754950bc86f21221446c20262 100644 --- a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_unix.go +++ b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd // +build darwin dragonfly freebsd illumos linux netbsd openbsd package filelock diff --git a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_windows.go b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_windows.go index 19de27eb9b6021ad9761712218ca1402a9d88f74..dd27ce92bd8d617449952f0c792f7966d10d2b84 100644 --- a/src/cmd/go/internal/lockedfile/internal/filelock/filelock_windows.go +++ b/src/cmd/go/internal/lockedfile/internal/filelock/filelock_windows.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows // +build windows package filelock diff --git a/src/cmd/go/internal/lockedfile/lockedfile_filelock.go b/src/cmd/go/internal/lockedfile/lockedfile_filelock.go index efc66461ed2fd6a2f12ca0b0440cc3f6ddb81f73..e4923f68764dad89a06539bbffccbe112e645996 100644 --- a/src/cmd/go/internal/lockedfile/lockedfile_filelock.go +++ b/src/cmd/go/internal/lockedfile/lockedfile_filelock.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !plan9 // +build !plan9 package lockedfile @@ -10,7 +11,6 @@ import ( "io/fs" "os" - "cmd/go/internal/fsys" "cmd/go/internal/lockedfile/internal/filelock" ) @@ -20,7 +20,7 @@ func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) { // calls for Linux and Windows anyway, so it's simpler to use that approach // consistently. - f, err := fsys.OpenFile(name, flag&^os.O_TRUNC, perm) + f, err := os.OpenFile(name, flag&^os.O_TRUNC, perm) if err != nil { return nil, err } diff --git a/src/cmd/go/internal/lockedfile/lockedfile_plan9.go b/src/cmd/go/internal/lockedfile/lockedfile_plan9.go index 70d6eddf2d2da4b349e8de1ef114a986744aef91..979118b10ae83d13d01064ca3eb896f5b6a38d48 100644 --- a/src/cmd/go/internal/lockedfile/lockedfile_plan9.go +++ b/src/cmd/go/internal/lockedfile/lockedfile_plan9.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build plan9 // +build plan9 package lockedfile @@ -12,8 +13,6 @@ import ( "os" "strings" "time" - - "cmd/go/internal/fsys" ) // Opening an exclusive-use file returns an error. @@ -58,7 +57,7 @@ func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) { // If the file was unpacked or created by some other program, it might not // have the ModeExclusive bit set. Set it before we call OpenFile, so that we // can be confident that a successful OpenFile implies exclusive use. - if fi, err := fsys.Stat(name); err == nil { + if fi, err := os.Stat(name); err == nil { if fi.Mode()&fs.ModeExclusive == 0 { if err := os.Chmod(name, fi.Mode()|fs.ModeExclusive); err != nil { return nil, err @@ -71,7 +70,7 @@ func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) { nextSleep := 1 * time.Millisecond const maxSleep = 500 * time.Millisecond for { - f, err := fsys.OpenFile(name, flag, perm|fs.ModeExclusive) + f, err := os.OpenFile(name, flag, perm|fs.ModeExclusive) if err == nil { return f, nil } diff --git a/src/cmd/go/internal/lockedfile/lockedfile_test.go b/src/cmd/go/internal/lockedfile/lockedfile_test.go index 34327dd841e112cbb17f9f2789ccb858fbee433f..3acc6695a748003e6f0fc7107bc49ffcf5995162 100644 --- a/src/cmd/go/internal/lockedfile/lockedfile_test.go +++ b/src/cmd/go/internal/lockedfile/lockedfile_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // js does not support inter-process file locking. +//go:build !js // +build !js package lockedfile_test diff --git a/src/cmd/go/internal/lockedfile/transform_test.go b/src/cmd/go/internal/lockedfile/transform_test.go index 407d48ea4a35f5cad06b31b70b8b9c8e1aaae681..b753346e7da50bccbb6d904a3f59c30648093153 100644 --- a/src/cmd/go/internal/lockedfile/transform_test.go +++ b/src/cmd/go/internal/lockedfile/transform_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // js does not support inter-process file locking. +//go:build !js // +build !js package lockedfile_test diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go index e7d3d869cbcf0e0de5d951979f746565c18191ce..0e5af852376e76c5363d595d15c55f18d4acd8f6 100644 --- a/src/cmd/go/internal/modcmd/download.go +++ b/src/cmd/go/internal/modcmd/download.go @@ -86,9 +86,11 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) { if !modload.HasModRoot() && len(args) == 0 { base.Fatalf("go mod download: no modules specified (see 'go help mod download')") } - if len(args) == 0 { + haveExplicitArgs := len(args) > 0 + if !haveExplicitArgs { args = []string{"all"} - } else if modload.HasModRoot() { + } + if modload.HasModRoot() { modload.LoadModFile(ctx) // to fill Target targetAtUpgrade := modload.Target.Path + "@upgrade" targetAtPatch := modload.Target.Path + "@patch" @@ -132,12 +134,22 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) { } var mods []*moduleJSON - listU := false - listVersions := false - listRetractions := false type token struct{} sem := make(chan token, runtime.GOMAXPROCS(0)) - for _, info := range modload.ListModules(ctx, args, listU, listVersions, listRetractions) { + infos, infosErr := modload.ListModules(ctx, args, 0) + if !haveExplicitArgs { + // 'go mod download' is sometimes run without arguments to pre-populate the + // module cache. It may fetch modules that aren't needed to build packages + // in the main mdoule. This is usually not intended, so don't save sums for + // downloaded modules (golang.org/issue/45332). + // TODO(golang.org/issue/45551): For now, in ListModules, save sums needed + // to load the build list (same as 1.15 behavior). In the future, report an + // error if go.mod or go.sum need to be updated after loading the build + // list. + modload.DisallowWriteGoMod() + } + + for _, info := range infos { if info.Replace != nil { info = info.Replace } @@ -187,6 +199,20 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) { base.ExitIfErrors() } - // Update go.mod and especially go.sum if needed. - modload.WriteGoMod() + // If there were explicit arguments, update go.mod and especially go.sum. + // 'go mod download mod@version' is a useful way to add a sum without using + // 'go get mod@version', which may have other side effects. We print this in + // some error message hints. + // + // Don't save sums for 'go mod download' without arguments; see comment above. + if haveExplicitArgs { + modload.WriteGoMod(ctx) + } + + // If there was an error matching some of the requested packages, emit it now + // (after we've written the checksums for the modules that were downloaded + // successfully). + if infosErr != nil { + base.Errorf("go mod download: %v", infosErr) + } } diff --git a/src/cmd/go/internal/modcmd/edit.go b/src/cmd/go/internal/modcmd/edit.go index 1df104eb1dd54954a9670c5ee86a3372a2417067..bb3d5210926aef7993e93a8beb0fbd189859e399 100644 --- a/src/cmd/go/internal/modcmd/edit.go +++ b/src/cmd/go/internal/modcmd/edit.go @@ -25,7 +25,7 @@ import ( ) var cmdEdit = &base.Command{ - UsageLine: "go mod edit [editing flags] [go.mod]", + UsageLine: "go mod edit [editing flags] [-fmt|-print|-json] [go.mod]", Short: "edit go.mod from tools or scripts", Long: ` Edit provides a command-line interface for editing go.mod, @@ -85,12 +85,12 @@ The -json flag prints the final go.mod file in JSON format instead of writing it back to go.mod. The JSON output corresponds to these Go types: type Module struct { - Path string + Path string Version string } type GoMod struct { - Module Module + Module ModPath Go string Require []Require Exclude []Module @@ -98,6 +98,11 @@ writing it back to go.mod. The JSON output corresponds to these Go types: Retract []Retract } + type ModPath struct { + Path string + Deprecated string + } + type Require struct { Path string Version string @@ -191,7 +196,7 @@ func runEdit(ctx context.Context, cmd *base.Command, args []string) { if *editGo != "" { if !modfile.GoVersionRE.MatchString(*editGo) { - base.Fatalf(`go mod: invalid -go option; expecting something like "-go 1.12"`) + base.Fatalf(`go mod: invalid -go option; expecting something like "-go %s"`, modload.LatestGoVersion()) } } @@ -450,7 +455,7 @@ func flagDropRetract(arg string) { // fileJSON is the -json output data structure. type fileJSON struct { - Module module.Version + Module editModuleJSON Go string `json:",omitempty"` Require []requireJSON Exclude []module.Version @@ -458,6 +463,11 @@ type fileJSON struct { Retract []retractJSON } +type editModuleJSON struct { + Path string + Deprecated string `json:",omitempty"` +} + type requireJSON struct { Path string Version string `json:",omitempty"` @@ -479,7 +489,10 @@ type retractJSON struct { func editPrintJSON(modFile *modfile.File) { var f fileJSON if modFile.Module != nil { - f.Module = modFile.Module.Mod + f.Module = editModuleJSON{ + Path: modFile.Module.Mod.Path, + Deprecated: modFile.Module.Deprecated, + } } if modFile.Go != nil { f.Go = modFile.Go.Version diff --git a/src/cmd/go/internal/modcmd/graph.go b/src/cmd/go/internal/modcmd/graph.go index a88e9ef4557ad3459cfe9db87f8af3fc63b2f591..ac81f26dadea69dd3f58d7491caaa39aba7cfcad 100644 --- a/src/cmd/go/internal/modcmd/graph.go +++ b/src/cmd/go/internal/modcmd/graph.go @@ -10,7 +10,6 @@ import ( "bufio" "context" "os" - "sort" "cmd/go/internal/base" "cmd/go/internal/modload" @@ -19,7 +18,7 @@ import ( ) var cmdGraph = &base.Command{ - UsageLine: "go mod graph", + UsageLine: "go mod graph [-go=version]", Short: "print module requirement graph", Long: ` Graph prints the module requirement graph (with replacements applied) @@ -27,12 +26,21 @@ in text form. Each line in the output has two space-separated fields: a module and one of its requirements. Each module is identified as a string of the form path@version, except for the main module, which has no @version suffix. +The -go flag causes graph to report the module graph as loaded by the +given Go version, instead of the version indicated by the 'go' directive +in the go.mod file. + See https://golang.org/ref/mod#go-mod-graph for more about 'go mod graph'. `, Run: runGraph, } +var ( + graphGo goVersionFlag +) + func init() { + cmdGraph.Flag.Var(&graphGo, "go", "") base.AddModCommonFlags(&cmdGraph.Flag) } @@ -42,43 +50,26 @@ func runGraph(ctx context.Context, cmd *base.Command, args []string) { } modload.ForceUseModules = true modload.RootMode = modload.NeedRoot - modload.LoadAllModules(ctx) + mg := modload.LoadModGraph(ctx, graphGo.String()) - reqs := modload.MinReqs() - format := func(m module.Version) string { - if m.Version == "" { - return m.Path - } - return m.Path + "@" + m.Version - } + w := bufio.NewWriter(os.Stdout) + defer w.Flush() - var out []string - var deps int // index in out where deps start - seen := map[module.Version]bool{modload.Target: true} - queue := []module.Version{modload.Target} - for len(queue) > 0 { - var m module.Version - m, queue = queue[0], queue[1:] - list, _ := reqs.Required(m) - for _, r := range list { - if !seen[r] { - queue = append(queue, r) - seen[r] = true - } - out = append(out, format(m)+" "+format(r)+"\n") - } - if m == modload.Target { - deps = len(out) + format := func(m module.Version) { + w.WriteString(m.Path) + if m.Version != "" { + w.WriteString("@") + w.WriteString(m.Version) } } - sort.Slice(out[deps:], func(i, j int) bool { - return out[deps+i][0] < out[deps+j][0] + mg.WalkBreadthFirst(func(m module.Version) { + reqs, _ := mg.RequiredBy(m) + for _, r := range reqs { + format(m) + w.WriteByte(' ') + format(r) + w.WriteByte('\n') + } }) - - w := bufio.NewWriter(os.Stdout) - for _, line := range out { - w.WriteString(line) - } - w.Flush() } diff --git a/src/cmd/go/internal/modcmd/init.go b/src/cmd/go/internal/modcmd/init.go index 73cc282d81408a18dee2f405891fbc68813a7dc0..958c3066ac11082dee31fe8b0cc4f3f049124e5e 100644 --- a/src/cmd/go/internal/modcmd/init.go +++ b/src/cmd/go/internal/modcmd/init.go @@ -13,7 +13,7 @@ import ( ) var cmdInit = &base.Command{ - UsageLine: "go mod init [module]", + UsageLine: "go mod init [module-path]", Short: "initialize new module in current directory", Long: ` Init initializes and writes a new go.mod file in the current directory, in diff --git a/src/cmd/go/internal/modcmd/tidy.go b/src/cmd/go/internal/modcmd/tidy.go index 8bc9ed50bede7ffcc9069a4b2ff0a4d974a538b8..fe25507e94f4fe365ec17d48b09e91be1109d702 100644 --- a/src/cmd/go/internal/modcmd/tidy.go +++ b/src/cmd/go/internal/modcmd/tidy.go @@ -12,10 +12,14 @@ import ( "cmd/go/internal/imports" "cmd/go/internal/modload" "context" + "fmt" + + "golang.org/x/mod/modfile" + "golang.org/x/mod/semver" ) var cmdTidy = &base.Command{ - UsageLine: "go mod tidy [-e] [-v]", + UsageLine: "go mod tidy [-e] [-v] [-go=version] [-compat=version]", Short: "add missing and remove unused modules", Long: ` Tidy makes sure go.mod matches the source code in the module. @@ -30,19 +34,65 @@ to standard error. The -e flag causes tidy to attempt to proceed despite errors encountered while loading packages. +The -go flag causes tidy to update the 'go' directive in the go.mod +file to the given version, which may change which module dependencies +are retained as explicit requirements in the go.mod file. +(Go versions 1.17 and higher retain more requirements in order to +support lazy module loading.) + +The -compat flag preserves any additional checksums needed for the +'go' command from the indicated major Go release to successfully load +the module graph, and causes tidy to error out if that version of the +'go' command would load any imported package from a different module +version. By default, tidy acts as if the -compat flag were set to the +version prior to the one indicated by the 'go' directive in the go.mod +file. + See https://golang.org/ref/mod#go-mod-tidy for more about 'go mod tidy'. `, Run: runTidy, } -var tidyE bool // if true, report errors but proceed anyway. +var ( + tidyE bool // if true, report errors but proceed anyway. + tidyGo goVersionFlag // go version to write to the tidied go.mod file (toggles lazy loading) + tidyCompat goVersionFlag // go version for which the tidied go.mod and go.sum files should be “compatible” +) func init() { cmdTidy.Flag.BoolVar(&cfg.BuildV, "v", false, "") cmdTidy.Flag.BoolVar(&tidyE, "e", false, "") + cmdTidy.Flag.Var(&tidyGo, "go", "") + cmdTidy.Flag.Var(&tidyCompat, "compat", "") base.AddModCommonFlags(&cmdTidy.Flag) } +// A goVersionFlag is a flag.Value representing a supported Go version. +// +// (Note that the -go argument to 'go mod edit' is *not* a goVersionFlag. +// It intentionally allows newer-than-supported versions as arguments.) +type goVersionFlag struct { + v string +} + +func (f *goVersionFlag) String() string { return f.v } +func (f *goVersionFlag) Get() interface{} { return f.v } + +func (f *goVersionFlag) Set(s string) error { + if s != "" { + latest := modload.LatestGoVersion() + if !modfile.GoVersionRE.MatchString(s) { + return fmt.Errorf("expecting a Go version like %q", latest) + } + if semver.Compare("v"+s, "v"+latest) > 0 { + return fmt.Errorf("maximum supported Go version is %s", latest) + } + } + + f.v = s + return nil +} + func runTidy(ctx context.Context, cmd *base.Command, args []string) { if len(args) > 0 { base.Fatalf("go mod tidy: no arguments allowed") @@ -62,14 +112,14 @@ func runTidy(ctx context.Context, cmd *base.Command, args []string) { modload.RootMode = modload.NeedRoot modload.LoadPackages(ctx, modload.PackageOpts{ + GoVersion: tidyGo.String(), Tags: imports.AnyTags(), + Tidy: true, + TidyCompatibleVersion: tidyCompat.String(), + VendorModulesInGOROOTSrc: true, ResolveMissingImports: true, LoadTests: true, AllowErrors: tidyE, SilenceMissingStdImports: true, }, "all") - - modload.TidyBuildList() - modload.TrimGoSum() - modload.WriteGoMod() } diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go index ac1fb7720aad4c86e86cfab8a9193fcad4bde4ed..713d5f9f3faa6ac9fb58dffc561c96d2dd5b1b70 100644 --- a/src/cmd/go/internal/modcmd/vendor.go +++ b/src/cmd/go/internal/modcmd/vendor.go @@ -13,6 +13,7 @@ import ( "io" "io/fs" "os" + "path" "path/filepath" "sort" "strings" @@ -65,6 +66,7 @@ func runVendor(ctx context.Context, cmd *base.Command, args []string) { loadOpts := modload.PackageOpts{ Tags: imports.AnyTags(), + VendorModulesInGOROOTSrc: true, ResolveMissingImports: true, UseVendorAll: true, AllowErrors: vendorE, @@ -87,15 +89,23 @@ func runVendor(ctx context.Context, cmd *base.Command, args []string) { } includeAllReplacements := false + includeGoVersions := false isExplicit := map[module.Version]bool{} - if gv := modload.ModFile().Go; gv != nil && semver.Compare("v"+gv.Version, "v1.14") >= 0 { - // If the Go version is at least 1.14, annotate all explicit 'require' and - // 'replace' targets found in the go.mod file so that we can perform a - // stronger consistency check when -mod=vendor is set. - for _, r := range modload.ModFile().Require { - isExplicit[r.Mod] = true + if gv := modload.ModFile().Go; gv != nil { + if semver.Compare("v"+gv.Version, "v1.14") >= 0 { + // If the Go version is at least 1.14, annotate all explicit 'require' and + // 'replace' targets found in the go.mod file so that we can perform a + // stronger consistency check when -mod=vendor is set. + for _, r := range modload.ModFile().Require { + isExplicit[r.Mod] = true + } + includeAllReplacements = true + } + if semver.Compare("v"+gv.Version, "v1.17") >= 0 { + // If the Go version is at least 1.17, annotate all modules with their + // 'go' version directives. + includeGoVersions = true } - includeAllReplacements = true } var vendorMods []module.Version @@ -109,26 +119,35 @@ func runVendor(ctx context.Context, cmd *base.Command, args []string) { } module.Sort(vendorMods) - var buf bytes.Buffer + var ( + buf bytes.Buffer + w io.Writer = &buf + ) + if cfg.BuildV { + w = io.MultiWriter(&buf, os.Stderr) + } + for _, m := range vendorMods { line := moduleLine(m, modload.Replacement(m)) - buf.WriteString(line) - if cfg.BuildV { - os.Stderr.WriteString(line) + io.WriteString(w, line) + + goVersion := "" + if includeGoVersions { + goVersion = modload.ModuleInfo(ctx, m.Path).GoVersion } - if isExplicit[m] { - buf.WriteString("## explicit\n") - if cfg.BuildV { - os.Stderr.WriteString("## explicit\n") - } + switch { + case isExplicit[m] && goVersion != "": + fmt.Fprintf(w, "## explicit; go %s\n", goVersion) + case isExplicit[m]: + io.WriteString(w, "## explicit\n") + case goVersion != "": + fmt.Fprintf(w, "## go %s\n", goVersion) } + pkgs := modpkgs[m] sort.Strings(pkgs) for _, pkg := range pkgs { - fmt.Fprintf(&buf, "%s\n", pkg) - if cfg.BuildV { - fmt.Fprintf(os.Stderr, "%s\n", pkg) - } + fmt.Fprintf(w, "%s\n", pkg) vendorPkg(vdir, pkg) } } @@ -281,7 +300,7 @@ func copyMetadata(modPath, pkg, dst, src string, copiedFiles map[string]bool) { if modPath == pkg { break } - pkg = filepath.Dir(pkg) + pkg = path.Dir(pkg) dst = filepath.Dir(dst) src = filepath.Dir(src) } @@ -322,6 +341,15 @@ func matchPotentialSourceFile(dir string, info fs.DirEntry) bool { if strings.HasSuffix(info.Name(), "_test.go") { return false } + if info.Name() == "go.mod" || info.Name() == "go.sum" { + if gv := modload.ModFile().Go; gv != nil && semver.Compare("v"+gv.Version, "v1.17") >= 0 { + // As of Go 1.17, we strip go.mod and go.sum files from dependency modules. + // Otherwise, 'go' commands invoked within the vendor subtree may misidentify + // an arbitrary directory within the vendor tree as a module root. + // (See https://golang.org/issue/42970.) + return false + } + } if strings.HasSuffix(info.Name(), ".go") { f, err := fsys.Open(filepath.Join(dir, info.Name())) if err != nil { diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go index 832142913108976469a87a5ec4688b1ce9ae611a..5a6eca32cfb706bfcd50906937d9eec70e4fc4c4 100644 --- a/src/cmd/go/internal/modcmd/verify.go +++ b/src/cmd/go/internal/modcmd/verify.go @@ -54,7 +54,8 @@ func runVerify(ctx context.Context, cmd *base.Command, args []string) { sem := make(chan token, runtime.GOMAXPROCS(0)) // Use a slice of result channels, so that the output is deterministic. - mods := modload.LoadAllModules(ctx)[1:] + const defaultGoVersion = "" + mods := modload.LoadModGraph(ctx, defaultGoVersion).BuildList()[1:] errsChans := make([]<-chan []error, len(mods)) for i, mod := range mods { diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go index a5f3e8afcbe3a52072e7265eecb7e117f9dabc78..3b14b27c8c780d954a133cd46dc4bee2f875e442 100644 --- a/src/cmd/go/internal/modcmd/why.go +++ b/src/cmd/go/internal/modcmd/why.go @@ -68,22 +68,25 @@ func runWhy(ctx context.Context, cmd *base.Command, args []string) { modload.RootMode = modload.NeedRoot loadOpts := modload.PackageOpts{ - Tags: imports.AnyTags(), - LoadTests: !*whyVendor, - SilenceErrors: true, - UseVendorAll: *whyVendor, + Tags: imports.AnyTags(), + VendorModulesInGOROOTSrc: true, + LoadTests: !*whyVendor, + SilencePackageErrors: true, + UseVendorAll: *whyVendor, } if *whyM { - listU := false - listVersions := false - listRetractions := false for _, arg := range args { if strings.Contains(arg, "@") { base.Fatalf("go mod why: module query not allowed") } } - mods := modload.ListModules(ctx, args, listU, listVersions, listRetractions) + + mods, err := modload.ListModules(ctx, args, 0) + if err != nil { + base.Fatalf("go mod why: %v", err) + } + byModule := make(map[module.Version][]string) _, pkgs := modload.LoadPackages(ctx, loadOpts, "all") for _, path := range pkgs { diff --git a/src/cmd/go/internal/modconv/convert.go b/src/cmd/go/internal/modconv/convert.go index 5d4165c9443225163f6a50936c4d9ac2a3461dad..9c861f8e99e1537fd05d83f975ea5f85359fb797 100644 --- a/src/cmd/go/internal/modconv/convert.go +++ b/src/cmd/go/internal/modconv/convert.go @@ -12,7 +12,6 @@ import ( "strings" "cmd/go/internal/base" - "cmd/go/internal/modfetch" "golang.org/x/mod/modfile" "golang.org/x/mod/module" @@ -21,7 +20,7 @@ import ( // ConvertLegacyConfig converts legacy config to modfile. // The file argument is slash-delimited. -func ConvertLegacyConfig(f *modfile.File, file string, data []byte) error { +func ConvertLegacyConfig(f *modfile.File, file string, data []byte, queryPackage func(path, rev string) (module.Version, error)) error { i := strings.LastIndex(file, "/") j := -2 if i >= 0 { @@ -62,15 +61,13 @@ func ConvertLegacyConfig(f *modfile.File, file string, data []byte) error { sem <- token{} go func(i int, m module.Version) { defer func() { <-sem }() - repo, info, err := modfetch.ImportRepoRev(m.Path, m.Version) + version, err := queryPackage(m.Path, m.Version) if err != nil { fmt.Fprintf(os.Stderr, "go: converting %s: stat %s@%s: %v\n", base.ShortPath(file), m.Path, m.Version, err) return } - path := repo.ModulePath() - versions[i].Path = path - versions[i].Version = info.Version + versions[i] = version }(i, m) } // Fill semaphore channel to wait for all tasks to finish. diff --git a/src/cmd/go/internal/modconv/convert_test.go b/src/cmd/go/internal/modconv/convert_test.go deleted file mode 100644 index 66b9ff4f382f2678cf39a8869321b50c16c3307a..0000000000000000000000000000000000000000 --- a/src/cmd/go/internal/modconv/convert_test.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package modconv - -import ( - "bytes" - "context" - "fmt" - "internal/testenv" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - - "cmd/go/internal/cfg" - "cmd/go/internal/modfetch" - - "golang.org/x/mod/modfile" - "golang.org/x/mod/module" -) - -func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - cfg.GOPROXY = "direct" - - if _, err := exec.LookPath("git"); err != nil { - fmt.Fprintln(os.Stderr, "skipping because git binary not found") - fmt.Println("PASS") - return 0 - } - - dir, err := os.MkdirTemp("", "modconv-test-") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(dir) - cfg.GOMODCACHE = filepath.Join(dir, "pkg/mod") - - return m.Run() -} - -func TestConvertLegacyConfig(t *testing.T) { - testenv.MustHaveExternalNetwork(t) - - if testing.Verbose() { - old := cfg.BuildX - defer func() { - cfg.BuildX = old - }() - cfg.BuildX = true - } - - var tests = []struct { - path string - vers string - gomod string - }{ - /* - Different versions of git seem to find or not find - github.com/Masterminds/semver's a93e51b5a57e, - which is an unmerged pull request. - We'd rather not provide access to unmerged pull requests, - so the line is removed from the golden file here, - but some git commands still find it somehow. - - { - // Gopkg.lock parsing. - "github.com/golang/dep", "v0.4.0", - `module github.com/golang/dep - - require ( - github.com/Masterminds/vcs v1.11.1 - github.com/armon/go-radix v0.0.0-20160115234725-4239b77079c7 - github.com/boltdb/bolt v1.3.1 - github.com/go-yaml/yaml v0.0.0-20170407172122-cd8b52f8269e - github.com/golang/protobuf v0.0.0-20170901042739-5afd06f9d81a - github.com/jmank88/nuts v0.3.0 - github.com/nightlyone/lockfile v0.0.0-20170707060451-e83dc5e7bba0 - github.com/pelletier/go-toml v0.0.0-20171218135716-b8b5e7696574 - github.com/pkg/errors v0.8.0 - github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 - golang.org/x/net v0.0.0-20170828231752-66aacef3dd8a - golang.org/x/sync v0.0.0-20170517211232-f52d1811a629 - golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea - )`, - }, - */ - - // TODO: https://github.com/docker/distribution uses vendor.conf - - { - // Godeps.json parsing. - // TODO: Should v2.0.0 work here too? - "github.com/docker/distribution", "v0.0.0-20150410205453-85de3967aa93", - `module github.com/docker/distribution - - require ( - github.com/AdRoll/goamz v0.0.0-20150130162828-d3664b76d905 - github.com/MSOpenTech/azure-sdk-for-go v0.0.0-20150323223030-d90753bcad2e - github.com/Sirupsen/logrus v0.7.3 - github.com/bugsnag/bugsnag-go v1.0.3-0.20141110184014-b1d153021fcd - github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b - github.com/bugsnag/panicwrap v0.0.0-20141110184334-e5f9854865b9 - github.com/codegangsta/cli v1.4.2-0.20150131031259-6086d7927ec3 - github.com/docker/docker v1.4.2-0.20150204013315-165ea5c158cf - github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 - github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 - github.com/gorilla/context v0.0.0-20140604161150-14f550f51af5 - github.com/gorilla/handlers v0.0.0-20140825150757-0e84b7d810c1 - github.com/gorilla/mux v0.0.0-20140926153814-e444e69cbd2e - github.com/jlhawn/go-crypto v0.0.0-20150401213827-cd738dde20f0 - github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 - github.com/yvasiyarov/gorelic v0.0.7-0.20141212073537-a9bba5b9ab50 - github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f - golang.org/x/net v0.0.0-20150202051010-1dfe7915deaf - gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789 - gopkg.in/yaml.v2 v2.0.0-20150116202057-bef53efd0c76 - )`, - }, - - { - // golang.org/issue/24585 - confusion about v2.0.0 tag in legacy non-v2 module - "github.com/fishy/gcsbucket", "v0.0.0-20180217031846-618d60fe84e0", - `module github.com/fishy/gcsbucket - - require ( - cloud.google.com/go v0.18.0 - github.com/fishy/fsdb v0.0.0-20180217030800-5527ded01371 - github.com/golang/protobuf v1.0.0 - github.com/googleapis/gax-go v2.0.0+incompatible - golang.org/x/net v0.0.0-20180216171745-136a25c244d3 - golang.org/x/oauth2 v0.0.0-20180207181906-543e37812f10 - golang.org/x/text v0.3.1-0.20180208041248-4e4a3210bb54 - google.golang.org/api v0.0.0-20180217000815-c7a403bb5fe1 - google.golang.org/appengine v1.0.0 - google.golang.org/genproto v0.0.0-20180206005123-2b5a72b8730b - google.golang.org/grpc v1.10.0 - )`, - }, - } - - ctx := context.Background() - - for _, tt := range tests { - t.Run(strings.ReplaceAll(tt.path, "/", "_")+"_"+tt.vers, func(t *testing.T) { - f, err := modfile.Parse("golden", []byte(tt.gomod), nil) - if err != nil { - t.Fatal(err) - } - want, err := f.Format() - if err != nil { - t.Fatal(err) - } - - dir, err := modfetch.Download(ctx, module.Version{Path: tt.path, Version: tt.vers}) - if err != nil { - t.Fatal(err) - } - - for name := range Converters { - file := filepath.Join(dir, name) - data, err := os.ReadFile(file) - if err == nil { - f := new(modfile.File) - f.AddModuleStmt(tt.path) - if err := ConvertLegacyConfig(f, filepath.ToSlash(file), data); err != nil { - t.Fatal(err) - } - out, err := f.Format() - if err != nil { - t.Fatalf("format after conversion: %v", err) - } - if !bytes.Equal(out, want) { - t.Fatalf("final go.mod:\n%s\n\nwant:\n%s", out, want) - } - return - } - } - t.Fatalf("no converter found for %s@%s", tt.path, tt.vers) - }) - } -} diff --git a/src/cmd/go/internal/modfetch/bootstrap.go b/src/cmd/go/internal/modfetch/bootstrap.go index e4020b0b41e803f56dc2eed8d061d26d7a63f12b..ed694581a7c6af47ba12c789ece904fb15c1099c 100644 --- a/src/cmd/go/internal/modfetch/bootstrap.go +++ b/src/cmd/go/internal/modfetch/bootstrap.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cmd_go_bootstrap // +build cmd_go_bootstrap package modfetch diff --git a/src/cmd/go/internal/modfetch/cache.go b/src/cmd/go/internal/modfetch/cache.go index 07e046c8cba7328a949e80f9e521130a3dd2396d..b01b4674131e7af76436c5b5513b6dbc1c155cbe 100644 --- a/src/cmd/go/internal/modfetch/cache.go +++ b/src/cmd/go/internal/modfetch/cache.go @@ -11,8 +11,10 @@ import ( "fmt" "io" "io/fs" + "math/rand" "os" "path/filepath" + "strconv" "strings" "sync" @@ -21,17 +23,15 @@ import ( "cmd/go/internal/lockedfile" "cmd/go/internal/modfetch/codehost" "cmd/go/internal/par" - "cmd/go/internal/renameio" + "cmd/go/internal/robustio" "golang.org/x/mod/module" "golang.org/x/mod/semver" ) func cacheDir(path string) (string, error) { - if cfg.GOMODCACHE == "" { - // modload.Init exits if GOPATH[0] is empty, and cfg.GOMODCACHE - // is set to GOPATH[0]/pkg/mod if GOMODCACHE is empty, so this should never happen. - return "", fmt.Errorf("internal error: cfg.GOMODCACHE not set") + if err := checkCacheDir(); err != nil { + return "", err } enc, err := module.EscapePath(path) if err != nil { @@ -64,10 +64,8 @@ func CachePath(m module.Version, suffix string) (string, error) { // along with the directory if the directory does not exist or if the directory // is not completely populated. func DownloadDir(m module.Version) (string, error) { - if cfg.GOMODCACHE == "" { - // modload.Init exits if GOPATH[0] is empty, and cfg.GOMODCACHE - // is set to GOPATH[0]/pkg/mod if GOMODCACHE is empty, so this should never happen. - return "", fmt.Errorf("internal error: cfg.GOMODCACHE not set") + if err := checkCacheDir(); err != nil { + return "", err } enc, err := module.EscapePath(m.Path) if err != nil { @@ -108,7 +106,9 @@ func DownloadDir(m module.Version) (string, error) { // Check if a .ziphash file exists. It should be created before the // zip is extracted, but if it was deleted (by another program?), we need - // to re-calculate it. + // to re-calculate it. Note that checkMod will repopulate the ziphash + // file if it doesn't exist, but if the module is excluded by checks + // through GONOSUMDB or GOPRIVATE, that check and repopulation won't happen. ziphashPath, err := CachePath(m, "ziphash") if err != nil { return dir, err @@ -146,15 +146,13 @@ func lockVersion(mod module.Version) (unlock func(), err error) { return lockedfile.MutexAt(path).Lock() } -// SideLock locks a file within the module cache that that previously guarded +// SideLock locks a file within the module cache that previously guarded // edits to files outside the cache, such as go.sum and go.mod files in the // user's working directory. // If err is nil, the caller MUST eventually call the unlock function. func SideLock() (unlock func(), err error) { - if cfg.GOMODCACHE == "" { - // modload.Init exits if GOPATH[0] is empty, and cfg.GOMODCACHE - // is set to GOPATH[0]/pkg/mod if GOMODCACHE is empty, so this should never happen. - base.Fatalf("go: internal error: cfg.GOMODCACHE not set") + if err := checkCacheDir(); err != nil { + return nil, err } path := filepath.Join(cfg.GOMODCACHE, "cache", "lock") @@ -332,7 +330,7 @@ func InfoFile(path, version string) (string, error) { } // Stat should have populated the disk cache for us. - file, _, err := readDiskStat(path, version) + file, err := CachePath(module.Version{Path: path, Version: version}, "info") if err != nil { return "", err } @@ -349,6 +347,9 @@ func GoMod(path, rev string) ([]byte, error) { if _, info, err := readDiskStat(path, rev); err == nil { rev = info.Version } else { + if errors.Is(err, statCacheErr) { + return nil, err + } err := TryProxies(func(proxy string) error { info, err := Lookup(proxy, path).Stat(rev) if err == nil { @@ -384,7 +385,7 @@ func GoModFile(path, version string) (string, error) { return "", err } // GoMod should have populated the disk cache for us. - file, _, err := readDiskGoMod(path, version) + file, err := CachePath(module.Version{Path: path, Version: version}, "mod") if err != nil { return "", err } @@ -499,7 +500,7 @@ func readDiskStatByHash(path, rev string) (file string, info *RevInfo, err error for _, name := range names { if strings.HasSuffix(name, suffix) { v := strings.TrimSuffix(name, ".info") - if IsPseudoVersion(v) && semver.Compare(v, maxVersion) > 0 { + if module.IsPseudoVersion(v) && semver.Compare(v, maxVersion) > 0 { maxVersion = v file, info, err = readDiskStat(path, strings.TrimSuffix(name, ".info")) } @@ -547,7 +548,7 @@ func readDiskCache(path, rev, suffix string) (file string, data []byte, err erro if err != nil { return "", nil, errNotCached } - data, err = renameio.ReadFile(file) + data, err = robustio.ReadFile(file) if err != nil { return file, nil, errNotCached } @@ -584,7 +585,29 @@ func writeDiskCache(file string, data []byte) error { return err } - if err := renameio.WriteFile(file, data, 0666); err != nil { + // Write the file to a temporary location, and then rename it to its final + // path to reduce the likelihood of a corrupt file existing at that final path. + f, err := tempFile(filepath.Dir(file), filepath.Base(file), 0666) + if err != nil { + return err + } + defer func() { + // Only call os.Remove on f.Name() if we failed to rename it: otherwise, + // some other process may have created a new file with the same name after + // the rename completed. + if err != nil { + f.Close() + os.Remove(f.Name()) + } + }() + + if _, err := f.Write(data); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + if err := robustio.Rename(f.Name(), file); err != nil { return err } @@ -594,29 +617,49 @@ func writeDiskCache(file string, data []byte) error { return nil } +// tempFile creates a new temporary file with given permission bits. +func tempFile(dir, prefix string, perm fs.FileMode) (f *os.File, err error) { + for i := 0; i < 10000; i++ { + name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+".tmp") + f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm) + if os.IsExist(err) { + continue + } + break + } + return +} + // rewriteVersionList rewrites the version list in dir // after a new *.mod file has been written. -func rewriteVersionList(dir string) { +func rewriteVersionList(dir string) (err error) { if filepath.Base(dir) != "@v" { base.Fatalf("go: internal error: misuse of rewriteVersionList") } listFile := filepath.Join(dir, "list") - // We use a separate lockfile here instead of locking listFile itself because - // we want to use Rename to write the file atomically. The list may be read by - // a GOPROXY HTTP server, and if we crash midway through a rewrite (or if the - // HTTP server ignores our locking and serves the file midway through a - // rewrite) it's better to serve a stale list than a truncated one. - unlock, err := lockedfile.MutexAt(listFile + ".lock").Lock() + // Lock listfile when writing to it to try to avoid corruption to the file. + // Under rare circumstances, for instance, if the system loses power in the + // middle of a write it is possible for corrupt data to be written. This is + // not a problem for the go command itself, but may be an issue if the the + // cache is being served by a GOPROXY HTTP server. This will be corrected + // the next time a new version of the module is fetched and the file is rewritten. + // TODO(matloob): golang.org/issue/43313 covers adding a go mod verify + // command that removes module versions that fail checksums. It should also + // remove list files that are detected to be corrupt. + f, err := lockedfile.Edit(listFile) if err != nil { - base.Fatalf("go: can't lock version list lockfile: %v", err) + return err } - defer unlock() - + defer func() { + if cerr := f.Close(); cerr != nil && err == nil { + err = cerr + } + }() infos, err := os.ReadDir(dir) if err != nil { - return + return err } var list []string for _, info := range infos { @@ -634,19 +677,74 @@ func rewriteVersionList(dir string) { } } } - SortVersions(list) + semver.Sort(list) var buf bytes.Buffer for _, v := range list { buf.WriteString(v) buf.WriteString("\n") } - old, _ := renameio.ReadFile(listFile) - if bytes.Equal(buf.Bytes(), old) { - return + if fi, err := f.Stat(); err == nil && int(fi.Size()) == buf.Len() { + old := make([]byte, buf.Len()+1) + if n, err := f.ReadAt(old, 0); err == io.EOF && n == buf.Len() && bytes.Equal(buf.Bytes(), old) { + return nil // No edit needed. + } + } + // Remove existing contents, so that when we truncate to the actual size it will zero-fill, + // and we will be able to detect (some) incomplete writes as files containing trailing NUL bytes. + if err := f.Truncate(0); err != nil { + return err + } + // Reserve the final size and zero-fill. + if err := f.Truncate(int64(buf.Len())); err != nil { + return err + } + // Write the actual contents. If this fails partway through, + // the remainder of the file should remain as zeroes. + if _, err := f.Write(buf.Bytes()); err != nil { + f.Truncate(0) + return err } - if err := renameio.WriteFile(listFile, buf.Bytes(), 0666); err != nil { - base.Fatalf("go: failed to write version list: %v", err) + return nil +} + +var ( + statCacheOnce sync.Once + statCacheErr error +) + +// checkCacheDir checks if the directory specified by GOMODCACHE exists. An +// error is returned if it does not. +func checkCacheDir() error { + if cfg.GOMODCACHE == "" { + // modload.Init exits if GOPATH[0] is empty, and cfg.GOMODCACHE + // is set to GOPATH[0]/pkg/mod if GOMODCACHE is empty, so this should never happen. + return fmt.Errorf("internal error: cfg.GOMODCACHE not set") + } + if !filepath.IsAbs(cfg.GOMODCACHE) { + return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q.\n", cfg.GOMODCACHE) } + + // os.Stat is slow on Windows, so we only call it once to prevent unnecessary + // I/O every time this function is called. + statCacheOnce.Do(func() { + fi, err := os.Stat(cfg.GOMODCACHE) + if err != nil { + if !os.IsNotExist(err) { + statCacheErr = fmt.Errorf("could not create module cache: %w", err) + return + } + if err := os.MkdirAll(cfg.GOMODCACHE, 0777); err != nil { + statCacheErr = fmt.Errorf("could not create module cache: %w", err) + return + } + return + } + if !fi.IsDir() { + statCacheErr = fmt.Errorf("could not create module cache: %q is not a directory", cfg.GOMODCACHE) + return + } + }) + return statCacheErr } diff --git a/src/cmd/go/internal/modfetch/codehost/git.go b/src/cmd/go/internal/modfetch/codehost/git.go index 72005e27d5eb83e75d56053dae51e64eb8ea63c8..4d4964edf447ebb7e963acd2c0defb74bb5957f6 100644 --- a/src/cmd/go/internal/modfetch/codehost/git.go +++ b/src/cmd/go/internal/modfetch/codehost/git.go @@ -296,6 +296,9 @@ func (r *gitRepo) stat(rev string) (*RevInfo, error) { // Or maybe it's the prefix of a hash of a named ref. // Try to resolve to both a ref (git name) and full (40-hex-digit) commit hash. r.refsOnce.Do(r.loadRefs) + // loadRefs may return an error if git fails, for example segfaults, or + // could not load a private repo, but defer checking to the else block + // below, in case we already have the rev in question in the local cache. var ref, hash string if r.refs["refs/tags/"+rev] != "" { ref = "refs/tags/" + rev @@ -332,6 +335,9 @@ func (r *gitRepo) stat(rev string) (*RevInfo, error) { hash = rev } } else { + if r.refsErr != nil { + return nil, r.refsErr + } return nil, &UnknownRevisionError{Rev: rev} } diff --git a/src/cmd/go/internal/modfetch/codehost/git_test.go b/src/cmd/go/internal/modfetch/codehost/git_test.go index 89a73baad9d9c7f1329bca9da9a150b46429cafe..a684fa1a9bba9e4a253b19018840adf3b34c32ce 100644 --- a/src/cmd/go/internal/modfetch/codehost/git_test.go +++ b/src/cmd/go/internal/modfetch/codehost/git_test.go @@ -8,7 +8,6 @@ import ( "archive/zip" "bytes" "flag" - "fmt" "internal/testenv" "io" "io/fs" @@ -47,12 +46,6 @@ var altRepos = []string{ var localGitRepo string func testMain(m *testing.M) int { - if _, err := exec.LookPath("git"); err != nil { - fmt.Fprintln(os.Stderr, "skipping because git binary not found") - fmt.Println("PASS") - return 0 - } - dir, err := os.MkdirTemp("", "gitrepo-test-") if err != nil { log.Fatal(err) @@ -60,23 +53,25 @@ func testMain(m *testing.M) int { defer os.RemoveAll(dir) if testenv.HasExternalNetwork() && testenv.HasExec() { - // Clone gitrepo1 into a local directory. - // If we use a file:// URL to access the local directory, - // then git starts up all the usual protocol machinery, - // which will let us test remote git archive invocations. - localGitRepo = filepath.Join(dir, "gitrepo2") - if _, err := Run("", "git", "clone", "--mirror", gitrepo1, localGitRepo); err != nil { - log.Fatal(err) - } - if _, err := Run(localGitRepo, "git", "config", "daemon.uploadarch", "true"); err != nil { - log.Fatal(err) + if _, err := exec.LookPath("git"); err == nil { + // Clone gitrepo1 into a local directory. + // If we use a file:// URL to access the local directory, + // then git starts up all the usual protocol machinery, + // which will let us test remote git archive invocations. + localGitRepo = filepath.Join(dir, "gitrepo2") + if _, err := Run("", "git", "clone", "--mirror", gitrepo1, localGitRepo); err != nil { + log.Fatal(err) + } + if _, err := Run(localGitRepo, "git", "config", "daemon.uploadarch", "true"); err != nil { + log.Fatal(err) + } } } return m.Run() } -func testRepo(remote string) (Repo, error) { +func testRepo(t *testing.T, remote string) (Repo, error) { if remote == "localGitRepo" { // Convert absolute path to file URL. LocalGitRepo will not accept // Windows absolute paths because they look like a host:path remote. @@ -87,15 +82,17 @@ func testRepo(remote string) (Repo, error) { } else { url = "file:///" + filepath.ToSlash(localGitRepo) } + testenv.MustHaveExecPath(t, "git") return LocalGitRepo(url) } - kind := "git" + vcs := "git" for _, k := range []string{"hg"} { if strings.Contains(remote, "/"+k+"/") { - kind = k + vcs = k } } - return NewRepo(kind, remote) + testenv.MustHaveExecPath(t, vcs) + return NewRepo(vcs, remote) } var tagsTests = []struct { @@ -116,7 +113,7 @@ func TestTags(t *testing.T) { for _, tt := range tagsTests { f := func(t *testing.T) { - r, err := testRepo(tt.repo) + r, err := testRepo(t, tt.repo) if err != nil { t.Fatal(err) } @@ -168,7 +165,7 @@ func TestLatest(t *testing.T) { for _, tt := range latestTests { f := func(t *testing.T) { - r, err := testRepo(tt.repo) + r, err := testRepo(t, tt.repo) if err != nil { t.Fatal(err) } @@ -221,7 +218,7 @@ func TestReadFile(t *testing.T) { for _, tt := range readFileTests { f := func(t *testing.T) { - r, err := testRepo(tt.repo) + r, err := testRepo(t, tt.repo) if err != nil { t.Fatal(err) } @@ -412,7 +409,7 @@ func TestReadZip(t *testing.T) { for _, tt := range readZipTests { f := func(t *testing.T) { - r, err := testRepo(tt.repo) + r, err := testRepo(t, tt.repo) if err != nil { t.Fatal(err) } @@ -581,7 +578,7 @@ func TestStat(t *testing.T) { for _, tt := range statTests { f := func(t *testing.T) { - r, err := testRepo(tt.repo) + r, err := testRepo(t, tt.repo) if err != nil { t.Fatal(err) } diff --git a/src/cmd/go/internal/modfetch/codehost/shell.go b/src/cmd/go/internal/modfetch/codehost/shell.go index ce8b501d53c8a565ce538d7f9fcc54d8f70e3bf1..0e9f38196676b108a28c77e9c2d5b591ecb0ae02 100644 --- a/src/cmd/go/internal/modfetch/codehost/shell.go +++ b/src/cmd/go/internal/modfetch/codehost/shell.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore // Interactive debugging shell for codehost.Repo implementations. diff --git a/src/cmd/go/internal/modfetch/coderepo.go b/src/cmd/go/internal/modfetch/coderepo.go index 2dcbb99b18514ae3abd52e78a3406810fae83f3a..dfef9f73c27aefa1bd3073706280a62f5cd8173e 100644 --- a/src/cmd/go/internal/modfetch/coderepo.go +++ b/src/cmd/go/internal/modfetch/coderepo.go @@ -159,7 +159,7 @@ func (r *codeRepo) Versions(prefix string) ([]string, error) { if r.codeDir != "" { v = v[len(r.codeDir)+1:] } - if v == "" || v != module.CanonicalVersion(v) || IsPseudoVersion(v) { + if v == "" || v != module.CanonicalVersion(v) || module.IsPseudoVersion(v) { continue } @@ -172,8 +172,8 @@ func (r *codeRepo) Versions(prefix string) ([]string, error) { list = append(list, v) } - SortVersions(list) - SortVersions(incompatible) + semver.Sort(list) + semver.Sort(incompatible) return r.appendIncompatibleVersions(list, incompatible) } @@ -385,7 +385,7 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e if statVers != "" && statVers == module.CanonicalVersion(statVers) { info2.Version = statVers - if IsPseudoVersion(info2.Version) { + if module.IsPseudoVersion(info2.Version) { if err := r.validatePseudoVersion(info, info2.Version); err != nil { return nil, err } @@ -433,7 +433,7 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e } trimmed := tag[len(tagPrefix):] // Tags that look like pseudo-versions would be confusing. Ignore them. - if IsPseudoVersion(tag) { + if module.IsPseudoVersion(tag) { return "", false } @@ -531,7 +531,7 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e pseudoBase, _ = tagToVersion(tag) // empty if the tag is invalid } - info2.Version = PseudoVersion(r.pseudoMajor, pseudoBase, info.Time, info.Short) + info2.Version = module.PseudoVersion(r.pseudoMajor, pseudoBase, info.Time, info.Short) return checkGoMod() } @@ -560,7 +560,7 @@ func (r *codeRepo) validatePseudoVersion(info *codehost.RevInfo, version string) return err } - rev, err := PseudoVersionRev(version) + rev, err := module.PseudoVersionRev(version) if err != nil { return err } @@ -575,12 +575,12 @@ func (r *codeRepo) validatePseudoVersion(info *codehost.RevInfo, version string) } } - t, err := PseudoVersionTime(version) + t, err := module.PseudoVersionTime(version) if err != nil { return err } if !t.Equal(info.Time.Truncate(time.Second)) { - return fmt.Errorf("does not match version-control timestamp (expected %s)", info.Time.UTC().Format(pseudoVersionTimestampFormat)) + return fmt.Errorf("does not match version-control timestamp (expected %s)", info.Time.UTC().Format(module.PseudoVersionTimestampFormat)) } tagPrefix := "" @@ -604,7 +604,7 @@ func (r *codeRepo) validatePseudoVersion(info *codehost.RevInfo, version string) // not enforce that property when resolving existing pseudo-versions: we don't // know when the parent tags were added, and the highest-tagged parent may not // have existed when the pseudo-version was first resolved. - base, err := PseudoVersionBase(strings.TrimSuffix(version, "+incompatible")) + base, err := module.PseudoVersionBase(strings.TrimSuffix(version, "+incompatible")) if err != nil { return err } @@ -661,7 +661,7 @@ func (r *codeRepo) validatePseudoVersion(info *codehost.RevInfo, version string) if err != nil { return err } - rev, err := PseudoVersionRev(version) + rev, err := module.PseudoVersionRev(version) if err != nil { return fmt.Errorf("not a descendent of preceding tag (%s)", lastTag) } @@ -672,8 +672,8 @@ func (r *codeRepo) validatePseudoVersion(info *codehost.RevInfo, version string) func (r *codeRepo) revToRev(rev string) string { if semver.IsValid(rev) { - if IsPseudoVersion(rev) { - r, _ := PseudoVersionRev(rev) + if module.IsPseudoVersion(rev) { + r, _ := module.PseudoVersionRev(rev) return r } if semver.Build(rev) == "+incompatible" { @@ -843,7 +843,7 @@ func (r *codeRepo) GoMod(version string) (data []byte, err error) { return nil, fmt.Errorf("version %s is not canonical", version) } - if IsPseudoVersion(version) { + if module.IsPseudoVersion(version) { // findDir ignores the metadata encoded in a pseudo-version, // only using the revision at the end. // Invoke Stat to verify the metadata explicitly so we don't return @@ -864,22 +864,25 @@ func (r *codeRepo) GoMod(version string) (data []byte, err error) { data, err = r.code.ReadFile(rev, path.Join(dir, "go.mod"), codehost.MaxGoMod) if err != nil { if os.IsNotExist(err) { - return r.legacyGoMod(rev, dir), nil + return LegacyGoMod(r.modPath), nil } return nil, err } return data, nil } -func (r *codeRepo) legacyGoMod(rev, dir string) []byte { - // We used to try to build a go.mod reflecting pre-existing - // package management metadata files, but the conversion - // was inherently imperfect (because those files don't have - // exactly the same semantics as go.mod) and, when done - // for dependencies in the middle of a build, impossible to - // correct. So we stopped. - // Return a fake go.mod that simply declares the module path. - return []byte(fmt.Sprintf("module %s\n", modfile.AutoQuote(r.modPath))) +// LegacyGoMod generates a fake go.mod file for a module that doesn't have one. +// The go.mod file contains a module directive and nothing else: no go version, +// no requirements. +// +// We used to try to build a go.mod reflecting pre-existing +// package management metadata files, but the conversion +// was inherently imperfect (because those files don't have +// exactly the same semantics as go.mod) and, when done +// for dependencies in the middle of a build, impossible to +// correct. So we stopped. +func LegacyGoMod(modPath string) []byte { + return []byte(fmt.Sprintf("module %s\n", modfile.AutoQuote(modPath))) } func (r *codeRepo) modPrefix(rev string) string { @@ -942,7 +945,7 @@ func (r *codeRepo) Zip(dst io.Writer, version string) error { return fmt.Errorf("version %s is not canonical", version) } - if IsPseudoVersion(version) { + if module.IsPseudoVersion(version) { // findDir ignores the metadata encoded in a pseudo-version, // only using the revision at the end. // Invoke Stat to verify the metadata explicitly so we don't return diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index eb7d30e0ab9584a8fb47250b35cf8f2b716518a8..d3d30d970b3b979b14741ce2a2da38c8812a422c 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -8,6 +8,8 @@ import ( "archive/zip" "bytes" "context" + "crypto/sha256" + "encoding/base64" "errors" "fmt" "io" @@ -20,9 +22,9 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" + "cmd/go/internal/fsys" "cmd/go/internal/lockedfile" "cmd/go/internal/par" - "cmd/go/internal/renameio" "cmd/go/internal/robustio" "cmd/go/internal/trace" @@ -37,10 +39,8 @@ var downloadCache par.Cache // local download cache and returns the name of the directory // corresponding to the root of the module's file tree. func Download(ctx context.Context, mod module.Version) (dir string, err error) { - if cfg.GOMODCACHE == "" { - // modload.Init exits if GOPATH[0] is empty, and cfg.GOMODCACHE - // is set to GOPATH[0]/pkg/mod if GOMODCACHE is empty, so this should never happen. - base.Fatalf("go: internal error: cfg.GOMODCACHE not set") + if err := checkCacheDir(); err != nil { + base.Fatalf("go: %v", err) } // The par.Cache here avoids duplicate work. @@ -223,11 +223,10 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // Clean up any remaining tempfiles from previous runs. // This is only safe to do because the lock file ensures that their // writers are no longer active. - for _, base := range []string{zipfile, zipfile + "hash"} { - if old, err := filepath.Glob(renameio.Pattern(base)); err == nil { - for _, path := range old { - os.Remove(path) // best effort - } + tmpPattern := filepath.Base(zipfile) + "*.tmp" + if old, err := filepath.Glob(filepath.Join(filepath.Dir(zipfile), tmpPattern)); err == nil { + for _, path := range old { + os.Remove(path) // best effort } } @@ -242,7 +241,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // contents of the file (by hashing it) before we commit it. Because the file // is zip-compressed, we need an actual file — or at least an io.ReaderAt — to // validate it: we can't just tee the stream as we write it. - f, err := os.CreateTemp(filepath.Dir(zipfile), filepath.Base(renameio.Pattern(zipfile))) + f, err := os.CreateTemp(filepath.Dir(zipfile), tmpPattern) if err != nil { return err } @@ -298,12 +297,6 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e } } - // Sync the file before renaming it: otherwise, after a crash the reader may - // observe a 0-length file instead of the actual contents. - // See https://golang.org/issue/22397#issuecomment-380831736. - if err := f.Sync(); err != nil { - return err - } if err := f.Close(); err != nil { return err } @@ -334,7 +327,21 @@ func hashZip(mod module.Version, zipfile, ziphashfile string) error { if err := checkModSum(mod, hash); err != nil { return err } - return renameio.WriteFile(ziphashfile, []byte(hash), 0666) + hf, err := lockedfile.Create(ziphashfile) + if err != nil { + return err + } + if err := hf.Truncate(int64(len(hash))); err != nil { + return err + } + if _, err := hf.WriteAt([]byte(hash), 0); err != nil { + return err + } + if err := hf.Close(); err != nil { + return err + } + + return nil } // makeDirsReadOnly makes a best-effort attempt to remove write permissions for dir @@ -410,7 +417,18 @@ func initGoSum() (bool, error) { goSum.m = make(map[module.Version][]string) goSum.status = make(map[modSum]modSumStatus) - data, err := lockedfile.Read(GoSumFile) + var ( + data []byte + err error + ) + if actualSumFile, ok := fsys.OverlayPath(GoSumFile); ok { + // Don't lock go.sum if it's part of the overlay. + // On Plan 9, locking requires chmod, and we don't want to modify any file + // in the overlay. See #44700. + data, err = os.ReadFile(actualSumFile) + } else { + data, err = lockedfile.Read(GoSumFile) + } if err != nil && !os.IsNotExist(err) { return false, err } @@ -485,11 +503,24 @@ func checkMod(mod module.Version) { if err != nil { base.Fatalf("verifying %v", module.VersionError(mod, err)) } - data, err := renameio.ReadFile(ziphash) + data, err := lockedfile.Read(ziphash) if err != nil { base.Fatalf("verifying %v", module.VersionError(mod, err)) } - h := strings.TrimSpace(string(data)) + data = bytes.TrimSpace(data) + if !isValidSum(data) { + // Recreate ziphash file from zip file and use that to check the mod sum. + zip, err := CachePath(mod, "zip") + if err != nil { + base.Fatalf("verifying %v", module.VersionError(mod, err)) + } + err = hashZip(mod, zip, ziphash) + if err != nil { + base.Fatalf("verifying %v", module.VersionError(mod, err)) + } + return + } + h := string(data) if !strings.HasPrefix(h, "h1:") { base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h))) } @@ -634,11 +665,32 @@ func Sum(mod module.Version) string { if err != nil { return "" } - data, err := renameio.ReadFile(ziphash) + data, err := lockedfile.Read(ziphash) if err != nil { return "" } - return strings.TrimSpace(string(data)) + data = bytes.TrimSpace(data) + if !isValidSum(data) { + return "" + } + return string(data) +} + +// isValidSum returns true if data is the valid contents of a zip hash file. +// Certain critical files are written to disk by first truncating +// then writing the actual bytes, so that if the write fails +// the corrupt file should contain at least one of the null +// bytes written by the truncate operation. +func isValidSum(data []byte) bool { + if bytes.IndexByte(data, '\000') >= 0 { + return false + } + + if len(data) != len("h1:")+base64.StdEncoding.EncodedLen(sha256.Size) { + return false + } + + return true } // WriteGoSum writes the go.sum file if it needs to be updated. @@ -676,6 +728,9 @@ Outer: if cfg.BuildMod == "readonly" { base.Fatalf("go: updates to go.sum needed, disabled by -mod=readonly") } + if _, ok := fsys.OverlayPath(GoSumFile); ok { + base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay") + } // Make a best-effort attempt to acquire the side lock, only to exclude // previous versions of the 'go' command from making simultaneous edits. diff --git a/src/cmd/go/internal/modfetch/insecure.go b/src/cmd/go/internal/modfetch/insecure.go deleted file mode 100644 index 012d05f29db55cd36060296eea832c58d7b149fc..0000000000000000000000000000000000000000 --- a/src/cmd/go/internal/modfetch/insecure.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package modfetch - -import ( - "cmd/go/internal/cfg" - - "golang.org/x/mod/module" -) - -// allowInsecure reports whether we are allowed to fetch this path in an insecure manner. -func allowInsecure(path string) bool { - return cfg.Insecure || module.MatchPrefixPatterns(cfg.GOINSECURE, path) -} diff --git a/src/cmd/go/internal/modfetch/proxy.go b/src/cmd/go/internal/modfetch/proxy.go index 6c86d8d786d94a161f509a66fffcfbc9bb00189c..31d453c8074e6a52b4b9fe7009ef9ef79aba70e3 100644 --- a/src/cmd/go/internal/modfetch/proxy.go +++ b/src/cmd/go/internal/modfetch/proxy.go @@ -228,7 +228,7 @@ func (p *proxyRepo) versionError(version string, err error) error { Path: p.path, Err: &module.InvalidVersionError{ Version: version, - Pseudo: IsPseudoVersion(version), + Pseudo: module.IsPseudoVersion(version), Err: err, }, } @@ -276,11 +276,11 @@ func (p *proxyRepo) Versions(prefix string) ([]string, error) { var list []string for _, line := range strings.Split(string(data), "\n") { f := strings.Fields(line) - if len(f) >= 1 && semver.IsValid(f[0]) && strings.HasPrefix(f[0], prefix) && !IsPseudoVersion(f[0]) { + if len(f) >= 1 && semver.IsValid(f[0]) && strings.HasPrefix(f[0], prefix) && !module.IsPseudoVersion(f[0]) { list = append(list, f[0]) } } - SortVersions(list) + semver.Sort(list) return list, nil } @@ -307,8 +307,8 @@ func (p *proxyRepo) latest() (*RevInfo, error) { ) if len(f) >= 2 { ft, _ = time.Parse(time.RFC3339, f[1]) - } else if IsPseudoVersion(f[0]) { - ft, _ = PseudoVersionTime(f[0]) + } else if module.IsPseudoVersion(f[0]) { + ft, _ = module.PseudoVersionTime(f[0]) ftIsFromPseudo = true } else { // Repo.Latest promises that this method is only called where there are diff --git a/src/cmd/go/internal/modfetch/pseudo_test.go b/src/cmd/go/internal/modfetch/pseudo_test.go deleted file mode 100644 index 4483f8e962fa5212ea2ae65e1eac66e5117a15ed..0000000000000000000000000000000000000000 --- a/src/cmd/go/internal/modfetch/pseudo_test.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package modfetch - -import ( - "testing" - "time" -) - -var pseudoTests = []struct { - major string - older string - version string -}{ - {"", "", "v0.0.0-20060102150405-hash"}, - {"v0", "", "v0.0.0-20060102150405-hash"}, - {"v1", "", "v1.0.0-20060102150405-hash"}, - {"v2", "", "v2.0.0-20060102150405-hash"}, - {"unused", "v0.0.0", "v0.0.1-0.20060102150405-hash"}, - {"unused", "v1.2.3", "v1.2.4-0.20060102150405-hash"}, - {"unused", "v1.2.99999999999999999", "v1.2.100000000000000000-0.20060102150405-hash"}, - {"unused", "v1.2.3-pre", "v1.2.3-pre.0.20060102150405-hash"}, - {"unused", "v1.3.0-pre", "v1.3.0-pre.0.20060102150405-hash"}, - {"unused", "v0.0.0--", "v0.0.0--.0.20060102150405-hash"}, - {"unused", "v1.0.0+metadata", "v1.0.1-0.20060102150405-hash+metadata"}, - {"unused", "v2.0.0+incompatible", "v2.0.1-0.20060102150405-hash+incompatible"}, - {"unused", "v2.3.0-pre+incompatible", "v2.3.0-pre.0.20060102150405-hash+incompatible"}, -} - -var pseudoTime = time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC) - -func TestPseudoVersion(t *testing.T) { - for _, tt := range pseudoTests { - v := PseudoVersion(tt.major, tt.older, pseudoTime, "hash") - if v != tt.version { - t.Errorf("PseudoVersion(%q, %q, ...) = %v, want %v", tt.major, tt.older, v, tt.version) - } - } -} - -func TestIsPseudoVersion(t *testing.T) { - for _, tt := range pseudoTests { - if !IsPseudoVersion(tt.version) { - t.Errorf("IsPseudoVersion(%q) = false, want true", tt.version) - } - if IsPseudoVersion(tt.older) { - t.Errorf("IsPseudoVersion(%q) = true, want false", tt.older) - } - } -} - -func TestPseudoVersionTime(t *testing.T) { - for _, tt := range pseudoTests { - tm, err := PseudoVersionTime(tt.version) - if tm != pseudoTime || err != nil { - t.Errorf("PseudoVersionTime(%q) = %v, %v, want %v, nil", tt.version, tm.Format(time.RFC3339), err, pseudoTime.Format(time.RFC3339)) - } - tm, err = PseudoVersionTime(tt.older) - if tm != (time.Time{}) || err == nil { - t.Errorf("PseudoVersionTime(%q) = %v, %v, want %v, error", tt.older, tm.Format(time.RFC3339), err, time.Time{}.Format(time.RFC3339)) - } - } -} - -func TestInvalidPseudoVersionTime(t *testing.T) { - const v = "---" - if _, err := PseudoVersionTime(v); err == nil { - t.Error("expected error, got nil instead") - } -} - -func TestPseudoVersionRev(t *testing.T) { - for _, tt := range pseudoTests { - rev, err := PseudoVersionRev(tt.version) - if rev != "hash" || err != nil { - t.Errorf("PseudoVersionRev(%q) = %q, %v, want %q, nil", tt.older, rev, err, "hash") - } - rev, err = PseudoVersionRev(tt.older) - if rev != "" || err == nil { - t.Errorf("PseudoVersionRev(%q) = %q, %v, want %q, error", tt.older, rev, err, "") - } - } -} - -func TestPseudoVersionBase(t *testing.T) { - for _, tt := range pseudoTests { - base, err := PseudoVersionBase(tt.version) - if err != nil { - t.Errorf("PseudoVersionBase(%q): %v", tt.version, err) - } else if base != tt.older { - t.Errorf("PseudoVersionBase(%q) = %q; want %q", tt.version, base, tt.older) - } - } -} - -func TestInvalidPseudoVersionBase(t *testing.T) { - for _, in := range []string{ - "v0.0.0", - "v0.0.0-", // malformed: empty prerelease - "v0.0.0-0.20060102150405-hash", // Z+1 == 0 - "v0.1.0-0.20060102150405-hash", // Z+1 == 0 - "v1.0.0-0.20060102150405-hash", // Z+1 == 0 - "v0.0.0-20060102150405-hash+incompatible", // "+incompatible without base version - "v0.0.0-20060102150405-hash+metadata", // other metadata without base version - } { - base, err := PseudoVersionBase(in) - if err == nil || base != "" { - t.Errorf(`PseudoVersionBase(%q) = %q, %v; want "", error`, in, base, err) - } - } -} - -func TestIncDecimal(t *testing.T) { - cases := []struct { - in, want string - }{ - {"0", "1"}, - {"1", "2"}, - {"99", "100"}, - {"100", "101"}, - {"101", "102"}, - } - - for _, tc := range cases { - got := incDecimal(tc.in) - if got != tc.want { - t.Fatalf("incDecimal(%q) = %q; want %q", tc.in, tc.want, got) - } - } -} - -func TestDecDecimal(t *testing.T) { - cases := []struct { - in, want string - }{ - {"", ""}, - {"0", ""}, - {"00", ""}, - {"1", "0"}, - {"2", "1"}, - {"99", "98"}, - {"100", "99"}, - {"101", "100"}, - } - - for _, tc := range cases { - got := decDecimal(tc.in) - if got != tc.want { - t.Fatalf("decDecimal(%q) = %q; want %q", tc.in, tc.want, got) - } - } -} diff --git a/src/cmd/go/internal/modfetch/repo.go b/src/cmd/go/internal/modfetch/repo.go index af9e24cefdc8dd3e82463280511020a53fe5e3f8..0bffa55af6f2ed6c4b12ec8363a8bb4f7f434bc6 100644 --- a/src/cmd/go/internal/modfetch/repo.go +++ b/src/cmd/go/internal/modfetch/repo.go @@ -9,7 +9,6 @@ import ( "io" "io/fs" "os" - "sort" "strconv" "time" @@ -20,7 +19,6 @@ import ( web "cmd/go/internal/web" "golang.org/x/mod/module" - "golang.org/x/mod/semver" ) const traceRepo = false // trace all repo actions, for debugging @@ -35,7 +33,7 @@ type Repo interface { // Pseudo-versions are not included. // // Versions should be returned sorted in semver order - // (implementations can use SortVersions). + // (implementations can use semver.Sort). // // Versions returns a non-nil error only if there was a problem // fetching the list of versions: it may return an empty list @@ -171,15 +169,6 @@ type RevInfo struct { // and it can check that the path can be resolved to a target repository. // To avoid version control access except when absolutely necessary, // Lookup does not attempt to connect to the repository itself. -// -// The ImportRepoRev function is a variant of Import which is limited -// to code in a source code repository at a particular revision identifier -// (usually a commit hash or source code repository tag, not necessarily -// a module version). -// ImportRepoRev is used when converting legacy dependency requirements -// from older systems into go.mod files. Those older systems worked -// at either package or repository granularity, and most of the time they -// recorded commit hashes, not tagged versions. var lookupCache par.Cache @@ -194,7 +183,8 @@ type lookupCacheKey struct { // from its origin, and "noproxy" indicates that the patch should be fetched // directly only if GONOPROXY matches the given path. // -// For the distinguished proxy "off", Lookup always returns a non-nil error. +// For the distinguished proxy "off", Lookup always returns a Repo that returns +// a non-nil error for every method call. // // A successful return does not guarantee that the module // has any defined versions. @@ -267,7 +257,7 @@ var ( func lookupDirect(path string) (Repo, error) { security := web.SecureOnly - if allowInsecure(path) { + if module.MatchPrefixPatterns(cfg.GOINSECURE, path) { security = web.Insecure } rr, err := vcs.RepoRootForImportPath(path, vcs.PreferMod, security) @@ -299,63 +289,6 @@ func lookupCodeRepo(rr *vcs.RepoRoot) (codehost.Repo, error) { return code, nil } -// ImportRepoRev returns the module and version to use to access -// the given import path loaded from the source code repository that -// the original "go get" would have used, at the specific repository revision -// (typically a commit hash, but possibly also a source control tag). -func ImportRepoRev(path, rev string) (Repo, *RevInfo, error) { - if cfg.BuildMod == "vendor" || cfg.BuildMod == "readonly" { - return nil, nil, fmt.Errorf("repo version lookup disabled by -mod=%s", cfg.BuildMod) - } - - // Note: Because we are converting a code reference from a legacy - // version control system, we ignore meta tags about modules - // and use only direct source control entries (get.IgnoreMod). - security := web.SecureOnly - if allowInsecure(path) { - security = web.Insecure - } - rr, err := vcs.RepoRootForImportPath(path, vcs.IgnoreMod, security) - if err != nil { - return nil, nil, err - } - - code, err := lookupCodeRepo(rr) - if err != nil { - return nil, nil, err - } - - revInfo, err := code.Stat(rev) - if err != nil { - return nil, nil, err - } - - // TODO: Look in repo to find path, check for go.mod files. - // For now we're just assuming rr.Root is the module path, - // which is true in the absence of go.mod files. - - repo, err := newCodeRepo(code, rr.Root, rr.Root) - if err != nil { - return nil, nil, err - } - - info, err := repo.(*codeRepo).convert(revInfo, rev) - if err != nil { - return nil, nil, err - } - return repo, info, nil -} - -func SortVersions(list []string) { - sort.Slice(list, func(i, j int) bool { - cmp := semver.Compare(list[i], list[j]) - if cmp != 0 { - return cmp < 0 - } - return list[i] < list[j] - }) -} - // A loggingRepo is a wrapper around an underlying Repo // that prints a log message at the start and end of each call. // It can be inserted when debugging. diff --git a/src/cmd/go/internal/modfetch/sumdb.go b/src/cmd/go/internal/modfetch/sumdb.go index 4fbc54d15ce837ba73fe28f65a7c85a602de47da..f233cba6df1bb7e996735147834e9c40960158f4 100644 --- a/src/cmd/go/internal/modfetch/sumdb.go +++ b/src/cmd/go/internal/modfetch/sumdb.go @@ -4,6 +4,7 @@ // Go checksum database lookup +//go:build !cmd_go_bootstrap // +build !cmd_go_bootstrap package modfetch @@ -33,7 +34,7 @@ import ( // useSumDB reports whether to use the Go checksum database for the given module. func useSumDB(mod module.Version) bool { - return cfg.GOSUMDB != "off" && !cfg.Insecure && !module.MatchPrefixPatterns(cfg.GONOSUMDB, mod.Path) + return cfg.GOSUMDB != "off" && !module.MatchPrefixPatterns(cfg.GONOSUMDB, mod.Path) } // lookupSumDB returns the Go checksum database's go.sum lines for the given module, @@ -184,7 +185,7 @@ func (c *dbClient) initBase() { } }) if errors.Is(err, fs.ErrNotExist) { - // No proxies, or all proxies failed (with 404, 410, or were were allowed + // No proxies, or all proxies failed (with 404, 410, or were allowed // to fall back), or we reached an explicit "direct" or "off". c.base = c.direct } else if err != nil { diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go index 5a98408a325fb35f02f6a597727793af11032e9a..9672e5598e0d3a980570899a4770925effaee087 100644 --- a/src/cmd/go/internal/modget/get.go +++ b/src/cmd/go/internal/modget/get.go @@ -30,16 +30,15 @@ import ( "fmt" "os" "path/filepath" - "reflect" "runtime" "sort" "strings" "sync" "cmd/go/internal/base" - "cmd/go/internal/cfg" "cmd/go/internal/imports" "cmd/go/internal/load" + "cmd/go/internal/modfetch" "cmd/go/internal/modload" "cmd/go/internal/par" "cmd/go/internal/search" @@ -53,7 +52,7 @@ import ( var CmdGet = &base.Command{ // Note: -d -u are listed explicitly because they are the most common get flags. // Do not send CLs removing them because they're covered by [get flags]. - UsageLine: "go get [-d] [-t] [-u] [-v] [-insecure] [build flags] [packages]", + UsageLine: "go get [-d] [-t] [-u] [-v] [build flags] [packages]", Short: "add dependencies to current module and install them", Long: ` Get resolves its command-line arguments to packages at specific module versions, @@ -99,14 +98,6 @@ but changes the default to select patch releases. When the -t and -u flags are used together, get will update test dependencies as well. -The -insecure flag permits fetching from repositories and resolving -custom domains using insecure schemes such as HTTP, and also bypassess -module sum validation using the checksum database. Use with caution. -This flag is deprecated and will be removed in a future version of go. -To permit the use of insecure schemes, use the GOINSECURE environment -variable instead. To bypass module sum validation, use GOPRIVATE or -GONOSUMDB. See 'go help environment' for details. - The -d flag instructs get not to build or install packages. get will only update go.mod and download source code needed to build packages. @@ -227,13 +218,13 @@ variable for future go command invocations. } var ( - getD = CmdGet.Flag.Bool("d", false, "") - getF = CmdGet.Flag.Bool("f", false, "") - getFix = CmdGet.Flag.Bool("fix", false, "") - getM = CmdGet.Flag.Bool("m", false, "") - getT = CmdGet.Flag.Bool("t", false, "") - getU upgradeFlag - // -insecure is cfg.Insecure + getD = CmdGet.Flag.Bool("d", false, "") + getF = CmdGet.Flag.Bool("f", false, "") + getFix = CmdGet.Flag.Bool("fix", false, "") + getM = CmdGet.Flag.Bool("m", false, "") + getT = CmdGet.Flag.Bool("t", false, "") + getU upgradeFlag + getInsecure = CmdGet.Flag.Bool("insecure", false, "") // -v is cfg.BuildV ) @@ -264,7 +255,6 @@ func (v *upgradeFlag) String() string { return "" } func init() { work.AddBuildFlags(CmdGet, work.OmitModFlag) CmdGet.Run = runGet // break init loop - CmdGet.Flag.BoolVar(&cfg.Insecure, "insecure", cfg.Insecure, "") CmdGet.Flag.Var(&getU, "u", "") } @@ -284,10 +274,9 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { if *getM { base.Fatalf("go get: -m flag is no longer supported; consider -d to skip building packages") } - if cfg.Insecure { - fmt.Fprintf(os.Stderr, "go get: -insecure flag is deprecated; see 'go help get' for details\n") + if *getInsecure { + base.Fatalf("go get: -insecure flag is no longer supported; use GOINSECURE instead") } - load.ModResolveTests = *getT // Do not allow any updating of go.mod until we've applied // all the requested changes and checked that the result matches @@ -298,8 +287,6 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // 'go get' is expected to do this, unlike other commands. modload.AllowMissingModuleImports() - modload.LoadModFile(ctx) // Initializes modload.Target. - queries := parseArgs(ctx, args) r := newResolver(ctx, queries) @@ -310,7 +297,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { r.performWildcardQueries(ctx) r.performPatternAllQueries(ctx) - if changed := r.resolveCandidates(ctx, queries, nil); changed { + if changed := r.resolveQueries(ctx, queries); changed { // 'go get' arguments can be (and often are) package patterns rather than // (just) modules. A package can be provided by any module with a prefix // of its import path, and a wildcard can even match packages in modules @@ -347,12 +334,12 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // - ambiguous import errors. // TODO(#27899): Try to resolve ambiguous import errors automatically. upgrades := r.findAndUpgradeImports(ctx, queries) - if changed := r.resolveCandidates(ctx, nil, upgrades); changed { + if changed := r.applyUpgrades(ctx, upgrades); changed { continue } r.findMissingWildcards(ctx) - if changed := r.resolveCandidates(ctx, r.wildcardQueries, nil); changed { + if changed := r.resolveQueries(ctx, r.wildcardQueries); changed { continue } @@ -367,7 +354,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { pkgPatterns = append(pkgPatterns, q.pattern) } } - r.checkPackagesAndRetractions(ctx, pkgPatterns) + r.checkPackageProblems(ctx, pkgPatterns) // We've already downloaded modules (and identified direct and indirect // dependencies) by loading packages in findAndUpgradeImports. @@ -380,12 +367,51 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // directory. if !*getD && len(pkgPatterns) > 0 { work.BuildInit() - pkgs := load.PackagesAndErrors(ctx, pkgPatterns) + + pkgOpts := load.PackageOpts{ModResolveTests: *getT} + var pkgs []*load.Package + for _, pkg := range load.PackagesAndErrors(ctx, pkgOpts, pkgPatterns) { + if pkg.Error != nil { + var noGo *load.NoGoError + if errors.As(pkg.Error.Err, &noGo) { + if m := modload.PackageModule(pkg.ImportPath); m.Path == pkg.ImportPath { + // pkg is at the root of a module, and doesn't exist with the current + // build tags. Probably the user just wanted to change the version of + // that module — not also build the package — so suppress the error. + // (See https://golang.org/issue/33526.) + continue + } + } + } + pkgs = append(pkgs, pkg) + } load.CheckPackageErrors(pkgs) + + haveExternalExe := false + for _, pkg := range pkgs { + if pkg.Name == "main" && pkg.Module != nil && pkg.Module.Path != modload.Target.Path { + haveExternalExe = true + break + } + } + if haveExternalExe { + fmt.Fprint(os.Stderr, "go get: installing executables with 'go get' in module mode is deprecated.") + var altMsg string + if modload.HasModRoot() { + altMsg = ` + To adjust and download dependencies of the current module, use 'go get -d'. + To install using requirements of the current module, use 'go install'. + To install ignoring the current module, use 'go install' with a version, + like 'go install example.com/cmd@latest'. +` + } else { + altMsg = "\n\tUse 'go install pkg@version' instead.\n" + } + fmt.Fprint(os.Stderr, altMsg) + fmt.Fprintf(os.Stderr, "\tFor more information, see https://golang.org/doc/go-get-install-deprecation\n\tor run 'go help get' or 'go help install'.\n") + } + work.InstallPackages(ctx, pkgPatterns, pkgs) - // TODO(#40276): After Go 1.16, print a deprecation notice when building and - // installing main packages. 'go install pkg' or 'go install pkg@version' - // should be used instead. Give the specific argument to use if possible. } if !modload.HasModRoot() { @@ -396,7 +422,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { oldReqs := reqsFromGoMod(modload.ModFile()) modload.AllowWriteGoMod() - modload.WriteGoMod() + modload.WriteGoMod(ctx) modload.DisallowWriteGoMod() newReqs := reqsFromGoMod(modload.ModFile()) @@ -460,9 +486,8 @@ type resolver struct { // that resolved the module to that version (the “reason”). resolvedVersion map[string]versionReason - buildList []module.Version - buildListResolvedVersions int // len(resolvedVersion) when buildList was computed - buildListVersion map[string]string // index of buildList (module path → version) + buildList []module.Version + buildListVersion map[string]string // index of buildList (module path → version) initialVersion map[string]string // index of the initial build list at the start of 'go get' @@ -479,7 +504,12 @@ type versionReason struct { } func newResolver(ctx context.Context, queries []*query) *resolver { - buildList := modload.LoadAllModules(ctx) + // LoadModGraph also sets modload.Target, which is needed by various resolver + // methods. + const defaultGoVersion = "" + mg := modload.LoadModGraph(ctx, defaultGoVersion) + + buildList := mg.BuildList() initialVersion := make(map[string]string, len(buildList)) for _, m := range buildList { initialVersion[m.Path] = m.Version @@ -688,7 +718,7 @@ func (r *resolver) performLocalQueries(ctx context.Context) { // Absolute paths like C:\foo and relative paths like ../foo... are // restricted to matching packages in the main module. - pkgPattern := modload.DirImportPath(q.pattern) + pkgPattern := modload.DirImportPath(ctx, q.pattern) if pkgPattern == "." { return errSet(fmt.Errorf("%s%s is not within module rooted at %s", q.pattern, absDetail, modload.ModRoot())) } @@ -1121,9 +1151,11 @@ func (r *resolver) findAndUpgradeImports(ctx context.Context, queries []*query) // build list. func (r *resolver) loadPackages(ctx context.Context, patterns []string, findPackage func(ctx context.Context, path string, m module.Version) (versionOk bool)) { opts := modload.PackageOpts{ - Tags: imports.AnyTags(), - LoadTests: *getT, - SilenceErrors: true, // May be fixed by subsequent upgrades or downgrades. + Tags: imports.AnyTags(), + VendorModulesInGOROOTSrc: true, + LoadTests: *getT, + AssumeRootsImported: true, // After 'go get foo', imports of foo should build. + SilencePackageErrors: true, // May be fixed by subsequent upgrades or downgrades. } opts.AllowPackage = func(ctx context.Context, path string, m module.Version) error { @@ -1176,24 +1208,19 @@ func (r *resolver) loadPackages(ctx context.Context, patterns []string, findPack // to be updated before its dependencies can be loaded. var errVersionChange = errors.New("version change needed") -// resolveCandidates resolves candidates sets that are attached to the given +// resolveQueries resolves candidate sets that are attached to the given // queries and/or needed to provide the given missing-package dependencies. // -// resolveCandidates starts by resolving one module version from each +// resolveQueries starts by resolving one module version from each // unambiguous pathSet attached to the given queries. // // If no unambiguous query results in a change to the build list, -// resolveCandidates modifies the build list by adding one module version from -// each pathSet in missing, but does not mark those versions as resolved -// (so they can still be modified by other queries). -// -// If that still does not result in any changes to the build list, -// resolveCandidates revisits the ambiguous query candidates and resolves them +// resolveQueries revisits the ambiguous query candidates and resolves them // arbitrarily in order to guarantee forward progress. // // If all pathSets are resolved without any changes to the build list, -// resolveCandidates returns with changed=false. -func (r *resolver) resolveCandidates(ctx context.Context, queries []*query, upgrades []pathSet) (changed bool) { +// resolveQueries returns with changed=false. +func (r *resolver) resolveQueries(ctx context.Context, queries []*query) (changed bool) { defer base.ExitIfErrors() // Note: this is O(N²) with the number of pathSets in the worst case. @@ -1247,12 +1274,52 @@ func (r *resolver) resolveCandidates(ctx context.Context, queries []*query, upgr } } - if changed := r.updateBuildList(ctx, nil); changed { - // The build list has changed, so disregard any missing packages: they might - // now be determined by requirements in the build list, which we would - // prefer to use instead of arbitrary "latest" versions. - return true + if resolved > 0 { + if changed = r.updateBuildList(ctx, nil); changed { + // The build list has changed, so disregard any remaining ambiguous queries: + // they might now be determined by requirements in the build list, which we + // would prefer to use instead of arbitrary versions. + return true + } + } + + // The build list will be the same on the next iteration as it was on this + // iteration, so any ambiguous queries will remain so. In order to make + // progress, resolve them arbitrarily but deterministically. + // + // If that results in conflicting versions, the user can re-run 'go get' + // with additional explicit versions for the conflicting packages or + // modules. + resolvedArbitrarily := 0 + for _, q := range queries { + for _, cs := range q.candidates { + isPackage, m := r.chooseArbitrarily(cs) + if isPackage { + q.matchesPackages = true + } + r.resolve(q, m) + resolvedArbitrarily++ + } } + if resolvedArbitrarily > 0 { + changed = r.updateBuildList(ctx, nil) + } + return changed +} + +// applyUpgrades disambiguates candidate sets that are needed to upgrade (or +// provide) transitive dependencies imported by previously-resolved packages. +// +// applyUpgrades modifies the build list by adding one module version from each +// pathSet in upgrades, then downgrading (or further upgrading) those modules as +// needed to maintain any already-resolved versions of other modules. +// applyUpgrades does not mark the new versions as resolved, so they can still +// be further modified by other queries (such as wildcards). +// +// If all pathSets are resolved without any changes to the build list, +// applyUpgrades returns with changed=false. +func (r *resolver) applyUpgrades(ctx context.Context, upgrades []pathSet) (changed bool) { + defer base.ExitIfErrors() // Arbitrarily add a "latest" version that provides each missing package, but // do not mark the version as resolved: we still want to allow the explicit @@ -1276,27 +1343,9 @@ func (r *resolver) resolveCandidates(ctx context.Context, queries []*query, upgr tentative = append(tentative, m) } base.ExitIfErrors() - if changed := r.updateBuildList(ctx, tentative); changed { - return true - } - // The build list will be the same on the next iteration as it was on this - // iteration, so any ambiguous queries will remain so. In order to make - // progress, resolve them arbitrarily but deterministically. - // - // If that results in conflicting versions, the user can re-run 'go get' - // with additional explicit versions for the conflicting packages or - // modules. - for _, q := range queries { - for _, cs := range q.candidates { - isPackage, m := r.chooseArbitrarily(cs) - if isPackage { - q.matchesPackages = true - } - r.resolve(q, m) - } - } - return r.updateBuildList(ctx, nil) + changed = r.updateBuildList(ctx, tentative) + return changed } // disambiguate eliminates candidates from cs that conflict with other module @@ -1417,25 +1466,33 @@ func (r *resolver) chooseArbitrarily(cs pathSet) (isPackage bool, m module.Versi return false, cs.mod } -// checkPackagesAndRetractions reloads packages for the given patterns and -// reports missing and ambiguous package errors. It also reports loads and -// reports retractions for resolved modules and modules needed to build -// named packages. +// checkPackageProblems reloads packages for the given patterns and reports +// missing and ambiguous package errors. It also reports retractions and +// deprecations for resolved modules and modules needed to build named packages. +// It also adds a sum for each updated module in the build list if we had one +// before and didn't get one while loading packages. // // We skip missing-package errors earlier in the process, since we want to // resolve pathSets ourselves, but at that point, we don't have enough context // to log the package-import chains leading to each error. -func (r *resolver) checkPackagesAndRetractions(ctx context.Context, pkgPatterns []string) { +func (r *resolver) checkPackageProblems(ctx context.Context, pkgPatterns []string) { defer base.ExitIfErrors() - // Build a list of modules to load retractions for. Start with versions - // selected based on command line queries. - // - // This is a subset of the build list. If the main module has a lot of - // dependencies, loading retractions for the entire build list would be slow. - relevantMods := make(map[module.Version]struct{}) + // Gather information about modules we might want to load retractions and + // deprecations for. Loading this metadata requires at least one version + // lookup per module, and we don't want to load information that's neither + // relevant nor actionable. + type modFlags int + const ( + resolved modFlags = 1 << iota // version resolved by 'go get' + named // explicitly named on command line or provides a named package + hasPkg // needed to build named packages + direct // provides a direct dependency of the main module + ) + relevantMods := make(map[module.Version]modFlags) for path, reason := range r.resolvedVersion { - relevantMods[module.Version{Path: path, Version: reason.version}] = struct{}{} + m := module.Version{Path: path, Version: reason.version} + relevantMods[m] |= resolved } // Reload packages, reporting errors for missing and ambiguous imports. @@ -1443,9 +1500,11 @@ func (r *resolver) checkPackagesAndRetractions(ctx context.Context, pkgPatterns // LoadPackages will print errors (since it has more context) but will not // exit, since we need to load retractions later. pkgOpts := modload.PackageOpts{ - LoadTests: *getT, - ResolveMissingImports: false, - AllowErrors: true, + VendorModulesInGOROOTSrc: true, + LoadTests: *getT, + ResolveMissingImports: false, + AllowErrors: true, + SilenceNoGoErrors: true, } matches, pkgs := modload.LoadPackages(ctx, pkgOpts, pkgPatterns...) for _, m := range matches { @@ -1461,53 +1520,141 @@ func (r *resolver) checkPackagesAndRetractions(ctx context.Context, pkgPatterns // associated with either the package or its test — ErrNoGo must // indicate that none of those source files happen to apply in this // configuration. If we are actually building the package (no -d - // flag), the compiler will report the problem; otherwise, assume that - // the user is going to build or test it in some other configuration - // and suppress the error. + // flag), we will report the problem then; otherwise, assume that the + // user is going to build or test this package in some other + // configuration and suppress the error. continue } base.SetExitStatus(1) if ambiguousErr := (*modload.AmbiguousImportError)(nil); errors.As(err, &ambiguousErr) { for _, m := range ambiguousErr.Modules { - relevantMods[m] = struct{}{} + relevantMods[m] |= hasPkg } } } if m := modload.PackageModule(pkg); m.Path != "" { - relevantMods[m] = struct{}{} + relevantMods[m] |= hasPkg + } + } + for _, match := range matches { + for _, pkg := range match.Pkgs { + m := modload.PackageModule(pkg) + relevantMods[m] |= named } } } - // Load and report retractions. - type retraction struct { - m module.Version - err error - } - retractions := make([]retraction, 0, len(relevantMods)) + reqs := modload.LoadModFile(ctx) for m := range relevantMods { - retractions = append(retractions, retraction{m: m}) + if reqs.IsDirect(m.Path) { + relevantMods[m] |= direct + } } - sort.Slice(retractions, func(i, j int) bool { - return retractions[i].m.Path < retractions[j].m.Path - }) - for i := 0; i < len(retractions); i++ { + + // Load retractions for modules mentioned on the command line and modules + // needed to build named packages. We care about retractions of indirect + // dependencies, since we might be able to upgrade away from them. + type modMessage struct { + m module.Version + message string + } + retractions := make([]modMessage, 0, len(relevantMods)) + for m, flags := range relevantMods { + if flags&(resolved|named|hasPkg) != 0 { + retractions = append(retractions, modMessage{m: m}) + } + } + sort.Slice(retractions, func(i, j int) bool { return retractions[i].m.Path < retractions[j].m.Path }) + for i := range retractions { i := i r.work.Add(func() { err := modload.CheckRetractions(ctx, retractions[i].m) if retractErr := (*modload.ModuleRetractedError)(nil); errors.As(err, &retractErr) { - retractions[i].err = err + retractions[i].message = err.Error() + } + }) + } + + // Load deprecations for modules mentioned on the command line. Only load + // deprecations for indirect dependencies if they're also direct dependencies + // of the main module. Deprecations of purely indirect dependencies are + // not actionable. + deprecations := make([]modMessage, 0, len(relevantMods)) + for m, flags := range relevantMods { + if flags&(resolved|named) != 0 || flags&(hasPkg|direct) == hasPkg|direct { + deprecations = append(deprecations, modMessage{m: m}) + } + } + sort.Slice(deprecations, func(i, j int) bool { return deprecations[i].m.Path < deprecations[j].m.Path }) + for i := range deprecations { + i := i + r.work.Add(func() { + deprecation, err := modload.CheckDeprecation(ctx, deprecations[i].m) + if err != nil || deprecation == "" { + return + } + deprecations[i].message = modload.ShortMessage(deprecation, "") + }) + } + + // Load sums for updated modules that had sums before. When we update a + // module, we may update another module in the build list that provides a + // package in 'all' that wasn't loaded as part of this 'go get' command. + // If we don't add a sum for that module, builds may fail later. + // Note that an incidentally updated package could still import packages + // from unknown modules or from modules in the build list that we didn't + // need previously. We can't handle that case without loading 'all'. + sumErrs := make([]error, len(r.buildList)) + for i := range r.buildList { + i := i + m := r.buildList[i] + mActual := m + if mRepl := modload.Replacement(m); mRepl.Path != "" { + mActual = mRepl + } + old := module.Version{Path: m.Path, Version: r.initialVersion[m.Path]} + if old.Version == "" { + continue + } + oldActual := old + if oldRepl := modload.Replacement(old); oldRepl.Path != "" { + oldActual = oldRepl + } + if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(oldActual) { + continue + } + r.work.Add(func() { + if _, err := modfetch.DownloadZip(ctx, mActual); err != nil { + verb := "upgraded" + if semver.Compare(m.Version, old.Version) < 0 { + verb = "downgraded" + } + replaced := "" + if mActual != m { + replaced = fmt.Sprintf(" (replaced by %s)", mActual) + } + err = fmt.Errorf("%s %s %s => %s%s: error finding sum for %s: %v", verb, m.Path, old.Version, m.Version, replaced, mActual, err) + sumErrs[i] = err } }) } + <-r.work.Idle() + + // Report deprecations, then retractions, then errors fetching sums. + // Only errors fetching sums are hard errors. + for _, mm := range deprecations { + if mm.message != "" { + fmt.Fprintf(os.Stderr, "go: module %s is deprecated: %s\n", mm.m.Path, mm.message) + } + } var retractPath string - for _, r := range retractions { - if r.err != nil { - fmt.Fprintf(os.Stderr, "go: warning: %v\n", r.err) + for _, mm := range retractions { + if mm.message != "" { + fmt.Fprintf(os.Stderr, "go: warning: %v\n", mm.message) if retractPath == "" { - retractPath = r.m.Path + retractPath = mm.m.Path } else { retractPath = "" } @@ -1516,6 +1663,12 @@ func (r *resolver) checkPackagesAndRetractions(ctx context.Context, pkgPatterns if retractPath != "" { fmt.Fprintf(os.Stderr, "go: to switch to the latest unretracted version, run:\n\tgo get %s@latest\n", retractPath) } + for _, err := range sumErrs { + if err != nil { + base.Errorf("go: %v", err) + } + } + base.ExitIfErrors() } // reportChanges logs version changes to os.Stderr. @@ -1614,11 +1767,10 @@ func (r *resolver) resolve(q *query, m module.Version) { // // If the additional modules conflict with the resolved versions, they will be // downgraded to a non-conflicting version (possibly "none"). +// +// If the resulting build list is the same as the one resulting from the last +// call to updateBuildList, updateBuildList returns with changed=false. func (r *resolver) updateBuildList(ctx context.Context, additions []module.Version) (changed bool) { - if len(additions) == 0 && len(r.resolvedVersion) == r.buildListResolvedVersions { - return false - } - defer base.ExitIfErrors() resolved := make([]module.Version, 0, len(r.resolvedVersion)) @@ -1628,7 +1780,8 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi } } - if err := modload.EditBuildList(ctx, additions, resolved); err != nil { + changed, err := modload.EditBuildList(ctx, additions, resolved) + if err != nil { var constraint *modload.ConstraintError if !errors.As(err, &constraint) { base.Errorf("go get: %v", err) @@ -1647,13 +1800,12 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi } return false } - - buildList := modload.LoadAllModules(ctx) - r.buildListResolvedVersions = len(r.resolvedVersion) - if reflect.DeepEqual(r.buildList, buildList) { + if !changed { return false } - r.buildList = buildList + + const defaultGoVersion = "" + r.buildList = modload.LoadModGraph(ctx, defaultGoVersion).BuildList() r.buildListVersion = make(map[string]string, len(r.buildList)) for _, m := range r.buildList { r.buildListVersion[m.Path] = m.Version diff --git a/src/cmd/go/internal/modinfo/info.go b/src/cmd/go/internal/modinfo/info.go index 897be56397d144f4da6ab296dc636e19c8a82f07..19088352f058baa93bce316dd462b6b37dd9089d 100644 --- a/src/cmd/go/internal/modinfo/info.go +++ b/src/cmd/go/internal/modinfo/info.go @@ -10,19 +10,20 @@ import "time" // and the fields are documented in the help text in ../list/list.go type ModulePublic struct { - Path string `json:",omitempty"` // module path - Version string `json:",omitempty"` // module version - Versions []string `json:",omitempty"` // available module versions - Replace *ModulePublic `json:",omitempty"` // replaced by this module - Time *time.Time `json:",omitempty"` // time version was created - Update *ModulePublic `json:",omitempty"` // available update (with -u) - Main bool `json:",omitempty"` // is this the main module? - Indirect bool `json:",omitempty"` // module is only indirectly needed by main module - Dir string `json:",omitempty"` // directory holding local copy of files, if any - GoMod string `json:",omitempty"` // path to go.mod file describing module, if any - GoVersion string `json:",omitempty"` // go version used in module - Retracted []string `json:",omitempty"` // retraction information, if any (with -retracted or -u) - Error *ModuleError `json:",omitempty"` // error loading module + Path string `json:",omitempty"` // module path + Version string `json:",omitempty"` // module version + Versions []string `json:",omitempty"` // available module versions + Replace *ModulePublic `json:",omitempty"` // replaced by this module + Time *time.Time `json:",omitempty"` // time version was created + Update *ModulePublic `json:",omitempty"` // available update (with -u) + Main bool `json:",omitempty"` // is this the main module? + Indirect bool `json:",omitempty"` // module is only indirectly needed by main module + Dir string `json:",omitempty"` // directory holding local copy of files, if any + GoMod string `json:",omitempty"` // path to go.mod file describing module, if any + GoVersion string `json:",omitempty"` // go version used in module + Retracted []string `json:",omitempty"` // retraction information, if any (with -retracted or -u) + Deprecated string `json:",omitempty"` // deprecation message, if any (with -u) + Error *ModuleError `json:",omitempty"` // error loading module } type ModuleError struct { @@ -45,6 +46,9 @@ func (m *ModulePublic) String() string { s += " [" + versionString(m.Update) + "]" } } + if m.Deprecated != "" { + s += " (deprecated)" + } if m.Replace != nil { s += " => " + m.Replace.Path if m.Replace.Version != "" { @@ -53,6 +57,9 @@ func (m *ModulePublic) String() string { s += " [" + versionString(m.Replace.Update) + "]" } } + if m.Replace.Deprecated != "" { + s += " (deprecated)" + } } return s } diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go index 8ad5f834def7ef154ffdc57696effad8fb6a7753..76e1ad589f43c1c6c12fed6faa038186a19453d7 100644 --- a/src/cmd/go/internal/modload/build.go +++ b/src/cmd/go/internal/modload/build.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "internal/goroot" + "io/fs" "os" "path/filepath" "strings" @@ -50,17 +51,17 @@ func findStandardImportPath(path string) string { // a given package. If modules are not enabled or if the package is in the // standard library or if the package was not successfully loaded with // LoadPackages or ImportFromFiles, nil is returned. -func PackageModuleInfo(pkgpath string) *modinfo.ModulePublic { +func PackageModuleInfo(ctx context.Context, pkgpath string) *modinfo.ModulePublic { if isStandardImportPath(pkgpath) || !Enabled() { return nil } - m, ok := findModule(pkgpath) + m, ok := findModule(loaded, pkgpath) if !ok { return nil } - fromBuildList := true - listRetracted := false - return moduleInfo(context.TODO(), m, fromBuildList, listRetracted) + + rs := LoadModFile(ctx) + return moduleInfo(ctx, rs, m, 0) } func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic { @@ -68,26 +69,38 @@ func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic { return nil } - listRetracted := false if i := strings.Index(path, "@"); i >= 0 { m := module.Version{Path: path[:i], Version: path[i+1:]} - fromBuildList := false - return moduleInfo(ctx, m, fromBuildList, listRetracted) + return moduleInfo(ctx, nil, m, 0) } - for _, m := range buildList { - if m.Path == path { - fromBuildList := true - return moduleInfo(ctx, m, fromBuildList, listRetracted) + rs := LoadModFile(ctx) + + var ( + v string + ok bool + ) + if rs.depth == lazy { + v, ok = rs.rootSelected(path) + } + if !ok { + mg, err := rs.Graph(ctx) + if err != nil { + base.Fatalf("go: %v", err) } + v = mg.Selected(path) } - return &modinfo.ModulePublic{ - Path: path, - Error: &modinfo.ModuleError{ - Err: "module not in current build", - }, + if v == "none" { + return &modinfo.ModulePublic{ + Path: path, + Error: &modinfo.ModuleError{ + Err: "module not in current build", + }, + } } + + return moduleInfo(ctx, rs, module.Version{Path: path, Version: v}, 0) } // addUpdate fills in m.Update if an updated version is available. @@ -96,7 +109,26 @@ func addUpdate(ctx context.Context, m *modinfo.ModulePublic) { return } - if info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed); err == nil && semver.Compare(info.Version, m.Version) > 0 { + info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed) + var noVersionErr *NoMatchingVersionError + if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { + // Ignore "not found" and "no matching version" errors. + // This means the proxy has no matching version or no versions at all. + // + // We should report other errors though. An attacker that controls the + // network shouldn't be able to hide versions by interfering with + // the HTTPS connection. An attacker that controls the proxy may still + // hide versions, since the "list" and "latest" endpoints are not + // authenticated. + return + } else if err != nil { + if m.Error == nil { + m.Error = &modinfo.ModuleError{Err: err.Error()} + } + return + } + + if semver.Compare(info.Version, m.Version) > 0 { m.Update = &modinfo.ModulePublic{ Path: m.Path, Version: info.Version, @@ -113,7 +145,11 @@ func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted boo if listRetracted { allowed = CheckExclusions } - m.Versions, _ = versions(ctx, m.Path, allowed) + var err error + m.Versions, err = versions(ctx, m.Path, allowed) + if err != nil && m.Error == nil { + m.Error = &modinfo.ModuleError{Err: err.Error()} + } } // addRetraction fills in m.Retracted if the module was retracted by its author. @@ -124,31 +160,72 @@ func addRetraction(ctx context.Context, m *modinfo.ModulePublic) { } err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version}) - var rerr *ModuleRetractedError - if errors.As(err, &rerr) { - if len(rerr.Rationale) == 0 { + var noVersionErr *NoMatchingVersionError + var retractErr *ModuleRetractedError + if err == nil || errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { + // Ignore "not found" and "no matching version" errors. + // This means the proxy has no matching version or no versions at all. + // + // We should report other errors though. An attacker that controls the + // network shouldn't be able to hide versions by interfering with + // the HTTPS connection. An attacker that controls the proxy may still + // hide versions, since the "list" and "latest" endpoints are not + // authenticated. + return + } else if errors.As(err, &retractErr) { + if len(retractErr.Rationale) == 0 { m.Retracted = []string{"retracted by module author"} } else { - m.Retracted = rerr.Rationale + m.Retracted = retractErr.Rationale } - } else if err != nil && m.Error == nil { + } else if m.Error == nil { m.Error = &modinfo.ModuleError{Err: err.Error()} } } -func moduleInfo(ctx context.Context, m module.Version, fromBuildList, listRetracted bool) *modinfo.ModulePublic { +// addDeprecation fills in m.Deprecated if the module was deprecated by its +// author. m.Error is set if there's an error loading deprecation information. +func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) { + deprecation, err := CheckDeprecation(ctx, module.Version{Path: m.Path, Version: m.Version}) + var noVersionErr *NoMatchingVersionError + if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { + // Ignore "not found" and "no matching version" errors. + // This means the proxy has no matching version or no versions at all. + // + // We should report other errors though. An attacker that controls the + // network shouldn't be able to hide versions by interfering with + // the HTTPS connection. An attacker that controls the proxy may still + // hide versions, since the "list" and "latest" endpoints are not + // authenticated. + return + } + if err != nil { + if m.Error == nil { + m.Error = &modinfo.ModuleError{Err: err.Error()} + } + return + } + m.Deprecated = deprecation +} + +// moduleInfo returns information about module m, loaded from the requirements +// in rs (which may be nil to indicate that m was not loaded from a requirement +// graph). +func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic { if m == Target { info := &modinfo.ModulePublic{ Path: m.Path, Version: m.Version, Main: true, } + if v, ok := rawGoVersion.Load(Target); ok { + info.GoVersion = v.(string) + } else { + panic("internal error: GoVersion not set for main module") + } if HasModRoot() { info.Dir = ModRoot() info.GoMod = ModFilePath() - if modFile.Go != nil { - info.GoVersion = modFile.Go.Version - } } return info } @@ -156,7 +233,7 @@ func moduleInfo(ctx context.Context, m module.Version, fromBuildList, listRetrac info := &modinfo.ModulePublic{ Path: m.Path, Version: m.Version, - Indirect: fromBuildList && loaded != nil && !loaded.direct[m.Path], + Indirect: rs != nil && !rs.direct[m.Path], } if v, ok := rawGoVersion.Load(m); ok { info.GoVersion = v.(string) @@ -164,7 +241,10 @@ func moduleInfo(ctx context.Context, m module.Version, fromBuildList, listRetrac // completeFromModCache fills in the extra fields in m using the module cache. completeFromModCache := func(m *modinfo.ModulePublic) { - mod := module.Version{Path: m.Path, Version: m.Version} + checksumOk := func(suffix string) bool { + return rs == nil || m.Version == "" || cfg.BuildMod == "mod" || + modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix}) + } if m.Version != "" { if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil { @@ -173,31 +253,40 @@ func moduleInfo(ctx context.Context, m module.Version, fromBuildList, listRetrac m.Version = q.Version m.Time = &q.Time } + } + mod := module.Version{Path: m.Path, Version: m.Version} + + if m.GoVersion == "" && checksumOk("/go.mod") { + // Load the go.mod file to determine the Go version, since it hasn't + // already been populated from rawGoVersion. + if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" { + m.GoVersion = summary.goVersion + } + } - gomod, err := modfetch.CachePath(mod, "mod") - if err == nil { - if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() { - m.GoMod = gomod + if m.Version != "" { + if checksumOk("/go.mod") { + gomod, err := modfetch.CachePath(mod, "mod") + if err == nil { + if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() { + m.GoMod = gomod + } } } - dir, err := modfetch.DownloadDir(mod) - if err == nil { - m.Dir = dir + if checksumOk("") { + dir, err := modfetch.DownloadDir(mod) + if err == nil { + m.Dir = dir + } } - if listRetracted { + if mode&ListRetracted != 0 { addRetraction(ctx, m) } } - - if m.GoVersion == "" { - if summary, err := rawGoModSummary(mod); err == nil && summary.goVersionV != "" { - m.GoVersion = summary.goVersionV[1:] - } - } } - if !fromBuildList { + if rs == nil { // If this was an explicitly-versioned argument to 'go mod download' or // 'go list -m', report the actual requested version, not its replacement. completeFromModCache(info) // Will set m.Error in vendor mode. @@ -255,11 +344,11 @@ func PackageBuildInfo(path string, deps []string) string { return "" } - target := mustFindModule(path, path) + target := mustFindModule(loaded, path, path) mdeps := make(map[module.Version]bool) for _, dep := range deps { if !isStandardImportPath(dep) { - mdeps[mustFindModule(path, dep)] = true + mdeps[mustFindModule(loaded, path, dep)] = true } } var mods []module.Version @@ -298,8 +387,8 @@ func PackageBuildInfo(path string, deps []string) string { // // TODO(jayconrod): remove this. Callers should use findModule and return // errors instead of relying on base.Fatalf. -func mustFindModule(target, path string) module.Version { - pkg, ok := loaded.pkgCache.Get(path).(*loadPkg) +func mustFindModule(ld *loader, target, path string) module.Version { + pkg, ok := ld.pkgCache.Get(path).(*loadPkg) if ok { if pkg.err != nil { base.Fatalf("build %v: cannot load %v: %v", target, path, pkg.err) @@ -318,8 +407,8 @@ func mustFindModule(target, path string) module.Version { // findModule searches for the module that contains the package at path. // If the package was loaded, its containing module and true are returned. // Otherwise, module.Version{} and false are returend. -func findModule(path string) (module.Version, bool) { - if pkg, ok := loaded.pkgCache.Get(path).(*loadPkg); ok { +func findModule(ld *loader, path string) (module.Version, bool) { + if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok { return pkg.mod, pkg.mod != module.Version{} } if path == "command-line-arguments" { diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go index 45f220a6ee6946b510a6bb67cb06b21971d31576..bf6956731676d39e66e74642f6a43b40607d619a 100644 --- a/src/cmd/go/internal/modload/buildlist.go +++ b/src/cmd/go/internal/modload/buildlist.go @@ -7,185 +7,514 @@ package modload import ( "cmd/go/internal/base" "cmd/go/internal/cfg" - "cmd/go/internal/imports" "cmd/go/internal/mvs" + "cmd/go/internal/par" "context" "fmt" "os" + "reflect" + "runtime" + "runtime/debug" "strings" + "sync" + "sync/atomic" "golang.org/x/mod/module" + "golang.org/x/mod/semver" ) -// buildList is the list of modules to use for building packages. -// It is initialized by calling LoadPackages or ImportFromFiles, -// each of which uses loaded.load. -// -// Ideally, exactly ONE of those functions would be called, -// and exactly once. Most of the time, that's true. -// During "go get" it may not be. TODO(rsc): Figure out if -// that restriction can be established, or else document why not. -// -var buildList []module.Version - -// additionalExplicitRequirements is a list of modules paths for which -// WriteGoMod should record explicit requirements, even if they would be -// selected without those requirements. Each path must also appear in buildList. -var additionalExplicitRequirements []string - // capVersionSlice returns s with its cap reduced to its length. func capVersionSlice(s []module.Version) []module.Version { return s[:len(s):len(s)] } -// LoadAllModules loads and returns the list of modules matching the "all" -// module pattern, starting with the Target module and in a deterministic -// (stable) order, without loading any packages. +// A Requirements represents a logically-immutable set of root module requirements. +type Requirements struct { + // depth is the depth at which the requirement graph is computed. + // + // If eager, the graph includes all transitive requirements regardless of depth. + // + // If lazy, the graph includes only the root modules, the explicit + // requirements of those root modules, and the transitive requirements of only + // the *non-lazy* root modules. + depth modDepth + + // rootModules is the set of module versions explicitly required by the main + // module, sorted and capped to length. It may contain duplicates, and may + // contain multiple versions for a given module path. + rootModules []module.Version + maxRootVersion map[string]string + + // direct is the set of module paths for which we believe the module provides + // a package directly imported by a package or test in the main module. + // + // The "direct" map controls which modules are annotated with "// indirect" + // comments in the go.mod file, and may impact which modules are listed as + // explicit roots (vs. indirect-only dependencies). However, it should not + // have a semantic effect on the build list overall. + // + // The initial direct map is populated from the existing "// indirect" + // comments (or lack thereof) in the go.mod file. It is updated by the + // package loader: dependencies may be promoted to direct if new + // direct imports are observed, and may be demoted to indirect during + // 'go mod tidy' or 'go mod vendor'. + // + // The direct map is keyed by module paths, not module versions. When a + // module's selected version changes, we assume that it remains direct if the + // previous version was a direct dependency. That assumption might not hold in + // rare cases (such as if a dependency splits out a nested module, or merges a + // nested module back into a parent module). + direct map[string]bool + + graphOnce sync.Once // guards writes to (but not reads from) graph + graph atomic.Value // cachedGraph +} + +// A cachedGraph is a non-nil *ModuleGraph, together with any error discovered +// while loading that graph. +type cachedGraph struct { + mg *ModuleGraph + err error // If err is non-nil, mg may be incomplete (but must still be non-nil). +} + +// requirements is the requirement graph for the main module. // -// Modules are loaded automatically (and lazily) in LoadPackages: -// LoadAllModules need only be called if LoadPackages is not, -// typically in commands that care about modules but no particular package. +// It is always non-nil if the main module's go.mod file has been loaded. // -// The caller must not modify the returned list, but may append to it. -func LoadAllModules(ctx context.Context) []module.Version { - LoadModFile(ctx) - ReloadBuildList() - WriteGoMod() - return capVersionSlice(buildList) -} - -// Selected returns the selected version of the module with the given path, or -// the empty string if the given module has no selected version -// (either because it is not required or because it is the Target module). -func Selected(path string) (version string) { - if path == Target.Path { - return "" - } - for _, m := range buildList { - if m.Path == path { - return m.Version - } - } - return "" -} +// This variable should only be read from the loadModFile function, and should +// only be written in the loadModFile and commitRequirements functions. +// All other functions that need or produce a *Requirements should +// accept and/or return an explicit parameter. +var requirements *Requirements -// EditBuildList edits the global build list by first adding every module in add -// to the existing build list, then adjusting versions (and adding or removing -// requirements as needed) until every module in mustSelect is selected at the -// given version. +// newRequirements returns a new requirement set with the given root modules. +// The dependencies of the roots will be loaded lazily at the first call to the +// Graph method. // -// (Note that the newly-added modules might not be selected in the resulting -// build list: they could be lower than existing requirements or conflict with -// versions in mustSelect.) +// The rootModules slice must be sorted according to module.Sort. +// The caller must not modify the rootModules slice or direct map after passing +// them to newRequirements. // -// If the versions listed in mustSelect are mutually incompatible (due to one of -// the listed modules requiring a higher version of another), EditBuildList -// returns a *ConstraintError and leaves the build list in its previous state. -func EditBuildList(ctx context.Context, add, mustSelect []module.Version) error { - var upgraded = capVersionSlice(buildList) - if len(add) > 0 { - // First, upgrade the build list with any additions. - // In theory we could just append the additions to the build list and let - // mvs.Downgrade take care of resolving the upgrades too, but the - // diagnostics from Upgrade are currently much better in case of errors. - var err error - upgraded, err = mvs.Upgrade(Target, &mvsReqs{buildList: upgraded}, add...) - if err != nil { - return err +// If vendoring is in effect, the caller must invoke initVendor on the returned +// *Requirements before any other method. +func newRequirements(depth modDepth, rootModules []module.Version, direct map[string]bool) *Requirements { + for i, m := range rootModules { + if m == Target { + panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is Target", i)) + } + if m.Path == "" || m.Version == "" { + panic(fmt.Sprintf("bad requirement: rootModules[%v] = %v", i, m)) + } + if i > 0 { + prev := rootModules[i-1] + if prev.Path > m.Path || (prev.Path == m.Path && semver.Compare(prev.Version, m.Version) > 0) { + panic(fmt.Sprintf("newRequirements called with unsorted roots: %v", rootModules)) + } } } - downgraded, err := mvs.Downgrade(Target, &mvsReqs{buildList: append(upgraded, mustSelect...)}, mustSelect...) - if err != nil { - return err + rs := &Requirements{ + depth: depth, + rootModules: capVersionSlice(rootModules), + maxRootVersion: make(map[string]string, len(rootModules)), + direct: direct, } - final, err := mvs.Upgrade(Target, &mvsReqs{buildList: downgraded}, mustSelect...) - if err != nil { - return err + for _, m := range rootModules { + if v, ok := rs.maxRootVersion[m.Path]; ok && cmpVersion(v, m.Version) >= 0 { + continue + } + rs.maxRootVersion[m.Path] = m.Version } + return rs +} - selected := make(map[string]module.Version, len(final)) - for _, m := range final { - selected[m.Path] = m - } - inconsistent := false - for _, m := range mustSelect { - s, ok := selected[m.Path] - if !ok { - if m.Version != "none" { - panic(fmt.Sprintf("internal error: mvs.BuildList lost %v", m)) +// initVendor initializes rs.graph from the given list of vendored module +// dependencies, overriding the graph that would normally be loaded from module +// requirements. +func (rs *Requirements) initVendor(vendorList []module.Version) { + rs.graphOnce.Do(func() { + mg := &ModuleGraph{ + g: mvs.NewGraph(cmpVersion, []module.Version{Target}), + } + + if rs.depth == lazy { + // The roots of a lazy module should already include every module in the + // vendor list, because the vendored modules are the same as those + // maintained as roots by the lazy loading “import invariant”. + // + // Just to be sure, we'll double-check that here. + inconsistent := false + for _, m := range vendorList { + if v, ok := rs.rootSelected(m.Path); !ok || v != m.Version { + base.Errorf("go: vendored module %v should be required explicitly in go.mod", m) + inconsistent = true + } } - continue + if inconsistent { + base.Fatalf("go: %v", errGoModDirty) + } + + // Now we can treat the rest of the module graph as effectively “pruned + // out”, like a more aggressive version of lazy loading: in vendor mode, + // the root requirements *are* the complete module graph. + mg.g.Require(Target, rs.rootModules) + } else { + // The transitive requirements of the main module are not in general available + // from the vendor directory, and we don't actually know how we got from + // the roots to the final build list. + // + // Instead, we'll inject a fake "vendor/modules.txt" module that provides + // those transitive dependencies, and mark it as a dependency of the main + // module. That allows us to elide the actual structure of the module + // graph, but still distinguishes between direct and indirect + // dependencies. + vendorMod := module.Version{Path: "vendor/modules.txt", Version: ""} + mg.g.Require(Target, append(rs.rootModules, vendorMod)) + mg.g.Require(vendorMod, vendorList) } - if s.Version != m.Version { - inconsistent = true - break + + rs.graph.Store(cachedGraph{mg, nil}) + }) +} + +// rootSelected returns the version of the root dependency with the given module +// path, or the zero module.Version and ok=false if the module is not a root +// dependency. +func (rs *Requirements) rootSelected(path string) (version string, ok bool) { + if path == Target.Path { + return Target.Version, true + } + if v, ok := rs.maxRootVersion[path]; ok { + return v, true + } + return "", false +} + +// hasRedundantRoot returns true if the root list contains multiple requirements +// of the same module or a requirement on any version of the main module. +// Redundant requirements should be pruned, but they may influence version +// selection. +func (rs *Requirements) hasRedundantRoot() bool { + for i, m := range rs.rootModules { + if m.Path == Target.Path || (i > 0 && m.Path == rs.rootModules[i-1].Path) { + return true } } + return false +} + +// Graph returns the graph of module requirements loaded from the current +// root modules (as reported by RootModules). +// +// Graph always makes a best effort to load the requirement graph despite any +// errors, and always returns a non-nil *ModuleGraph. +// +// If the requirements of any relevant module fail to load, Graph also +// returns a non-nil error of type *mvs.BuildListError. +func (rs *Requirements) Graph(ctx context.Context) (*ModuleGraph, error) { + rs.graphOnce.Do(func() { + mg, mgErr := readModGraph(ctx, rs.depth, rs.rootModules) + rs.graph.Store(cachedGraph{mg, mgErr}) + }) + cached := rs.graph.Load().(cachedGraph) + return cached.mg, cached.err +} + +// IsDirect returns whether the given module provides a package directly +// imported by a package or test in the main module. +func (rs *Requirements) IsDirect(path string) bool { + return rs.direct[path] +} + +// A ModuleGraph represents the complete graph of module dependencies +// of a main module. +// +// If the main module is lazily loaded, the graph does not include +// transitive dependencies of non-root (implicit) dependencies. +type ModuleGraph struct { + g *mvs.Graph + loadCache par.Cache // module.Version → summaryError + + buildListOnce sync.Once + buildList []module.Version +} - if !inconsistent { - buildList = final - additionalExplicitRequirements = make([]string, 0, len(mustSelect)) - for _, m := range mustSelect { - if m.Version != "none" { - additionalExplicitRequirements = append(additionalExplicitRequirements, m.Path) +// A summaryError is either a non-nil modFileSummary or a non-nil error +// encountered while reading or parsing that summary. +type summaryError struct { + summary *modFileSummary + err error +} + +var readModGraphDebugOnce sync.Once + +// readModGraph reads and returns the module dependency graph starting at the +// given roots. +// +// Unlike LoadModGraph, readModGraph does not attempt to diagnose or update +// inconsistent roots. +func readModGraph(ctx context.Context, depth modDepth, roots []module.Version) (*ModuleGraph, error) { + if depth == lazy { + readModGraphDebugOnce.Do(func() { + for _, f := range strings.Split(os.Getenv("GODEBUG"), ",") { + switch f { + case "lazymod=log": + debug.PrintStack() + fmt.Fprintf(os.Stderr, "go: read full module graph.\n") + case "lazymod=strict": + debug.PrintStack() + base.Fatalf("go: read full module graph (forbidden by GODEBUG=lazymod=strict).") + } } - } - return nil + }) } - // We overshot one or more of the modules in mustSelected, which means that - // Downgrade removed something in mustSelect because it conflicted with - // something else in mustSelect. - // - // Walk the requirement graph to find the conflict. - // - // TODO(bcmills): Ideally, mvs.Downgrade (or a replacement for it) would do - // this directly. + var ( + mu sync.Mutex // guards mg.g and hasError during loading + hasError bool + mg = &ModuleGraph{ + g: mvs.NewGraph(cmpVersion, []module.Version{Target}), + } + ) + mg.g.Require(Target, roots) + + var ( + loadQueue = par.NewQueue(runtime.GOMAXPROCS(0)) + loadingEager sync.Map // module.Version → nil; the set of modules that have been or are being loaded via eager roots + ) - reqs := &mvsReqs{buildList: final} - reason := map[module.Version]module.Version{} - for _, m := range mustSelect { - reason[m] = m + // loadOne synchronously loads the explicit requirements for module m. + // It does not load the transitive requirements of m even if the go version in + // m's go.mod file indicates eager loading. + loadOne := func(m module.Version) (*modFileSummary, error) { + cached := mg.loadCache.Do(m, func() interface{} { + summary, err := goModSummary(m) + + mu.Lock() + if err == nil { + mg.g.Require(m, summary.require) + } else { + hasError = true + } + mu.Unlock() + + return summaryError{summary, err} + }).(summaryError) + + return cached.summary, cached.err } - queue := mustSelect[:len(mustSelect):len(mustSelect)] - for len(queue) > 0 { - var m module.Version - m, queue = queue[0], queue[1:] - required, err := reqs.Required(m) - if err != nil { - return err + + var enqueue func(m module.Version, depth modDepth) + enqueue = func(m module.Version, depth modDepth) { + if m.Version == "none" { + return } - for _, r := range required { - if _, ok := reason[r]; !ok { - reason[r] = reason[m] - queue = append(queue, r) + + if depth == eager { + if _, dup := loadingEager.LoadOrStore(m, nil); dup { + // m has already been enqueued for loading. Since eager loading may + // follow cycles in the the requirement graph, we need to return early + // to avoid making the load queue infinitely long. + return } } - } - var conflicts []Conflict - for _, m := range mustSelect { - s, ok := selected[m.Path] - if !ok { - if m.Version != "none" { - panic(fmt.Sprintf("internal error: mvs.BuildList lost %v", m)) + loadQueue.Add(func() { + summary, err := loadOne(m) + if err != nil { + return // findError will report the error later. } - continue + + // If the version in m's go.mod file implies eager loading, then we cannot + // assume that the explicit requirements of m (added by loadOne) are + // sufficient to build the packages it contains. We must load its full + // transitive dependency graph to be sure that we see all relevant + // dependencies. + if depth == eager || summary.depth == eager { + for _, r := range summary.require { + enqueue(r, eager) + } + } + }) + } + + for _, m := range roots { + enqueue(m, depth) + } + <-loadQueue.Idle() + + if hasError { + return mg, mg.findError() + } + return mg, nil +} + +// RequiredBy returns the dependencies required by module m in the graph, +// or ok=false if module m's dependencies are not relevant (such as if they +// are pruned out by lazy loading). +// +// The caller must not modify the returned slice, but may safely append to it +// and may rely on it not to be modified. +func (mg *ModuleGraph) RequiredBy(m module.Version) (reqs []module.Version, ok bool) { + return mg.g.RequiredBy(m) +} + +// Selected returns the selected version of the module with the given path. +// +// If no version is selected, Selected returns version "none". +func (mg *ModuleGraph) Selected(path string) (version string) { + return mg.g.Selected(path) +} + +// WalkBreadthFirst invokes f once, in breadth-first order, for each module +// version other than "none" that appears in the graph, regardless of whether +// that version is selected. +func (mg *ModuleGraph) WalkBreadthFirst(f func(m module.Version)) { + mg.g.WalkBreadthFirst(f) +} + +// BuildList returns the selected versions of all modules present in the graph, +// beginning with Target. +// +// The order of the remaining elements in the list is deterministic +// but arbitrary. +// +// The caller must not modify the returned list, but may safely append to it +// and may rely on it not to be modified. +func (mg *ModuleGraph) BuildList() []module.Version { + mg.buildListOnce.Do(func() { + mg.buildList = capVersionSlice(mg.g.BuildList()) + }) + return mg.buildList +} + +func (mg *ModuleGraph) findError() error { + errStack := mg.g.FindPath(func(m module.Version) bool { + cached := mg.loadCache.Get(m) + return cached != nil && cached.(summaryError).err != nil + }) + if len(errStack) > 0 { + err := mg.loadCache.Get(errStack[len(errStack)-1]).(summaryError).err + var noUpgrade func(from, to module.Version) bool + return mvs.NewBuildListError(err, errStack, noUpgrade) + } + + return nil +} + +func (mg *ModuleGraph) allRootsSelected() bool { + roots, _ := mg.g.RequiredBy(Target) + for _, m := range roots { + if mg.Selected(m.Path) != m.Version { + return false + } + } + return true +} + +// LoadModGraph loads and returns the graph of module dependencies of the main module, +// without loading any packages. +// +// If the goVersion string is non-empty, the returned graph is the graph +// as interpreted by the given Go version (instead of the version indicated +// in the go.mod file). +// +// Modules are loaded automatically (and lazily) in LoadPackages: +// LoadModGraph need only be called if LoadPackages is not, +// typically in commands that care about modules but no particular package. +func LoadModGraph(ctx context.Context, goVersion string) *ModuleGraph { + rs := LoadModFile(ctx) + + if goVersion != "" { + depth := modDepthFromGoVersion(goVersion) + if depth == eager && rs.depth != eager { + // Use newRequirements instead of convertDepth because convertDepth + // also updates roots; here, we want to report the unmodified roots + // even though they may seem inconsistent. + rs = newRequirements(eager, rs.rootModules, rs.direct) } - if s.Version != m.Version { - conflicts = append(conflicts, Conflict{ - Source: reason[s], - Dep: s, - Constraint: m, - }) + + mg, err := rs.Graph(ctx) + if err != nil { + base.Fatalf("go: %v", err) } + return mg } - return &ConstraintError{ - Conflicts: conflicts, + rs, mg, err := expandGraph(ctx, rs) + if err != nil { + base.Fatalf("go: %v", err) } + + commitRequirements(ctx, modFileGoVersion(), rs) + return mg +} + +// expandGraph loads the complete module graph from rs. +// +// If the complete graph reveals that some root of rs is not actually the +// selected version of its path, expandGraph computes a new set of roots that +// are consistent. (When lazy loading is implemented, this may result in +// upgrades to other modules due to requirements that were previously pruned +// out.) +// +// expandGraph returns the updated roots, along with the module graph loaded +// from those roots and any error encountered while loading that graph. +// expandGraph returns non-nil requirements and a non-nil graph regardless of +// errors. On error, the roots might not be updated to be consistent. +func expandGraph(ctx context.Context, rs *Requirements) (*Requirements, *ModuleGraph, error) { + mg, mgErr := rs.Graph(ctx) + if mgErr != nil { + // Without the graph, we can't update the roots: we don't know which + // versions of transitive dependencies would be selected. + return rs, mg, mgErr + } + + if !mg.allRootsSelected() { + // The roots of rs are not consistent with the rest of the graph. Update + // them. In an eager module this is a no-op for the build list as a whole — + // it just promotes what were previously transitive requirements to be + // roots — but in a lazy module it may pull in previously-irrelevant + // transitive dependencies. + + newRS, rsErr := updateRoots(ctx, rs.direct, rs, nil, nil, false) + if rsErr != nil { + // Failed to update roots, perhaps because of an error in a transitive + // dependency needed for the update. Return the original Requirements + // instead. + return rs, mg, rsErr + } + rs = newRS + mg, mgErr = rs.Graph(ctx) + } + + return rs, mg, mgErr +} + +// EditBuildList edits the global build list by first adding every module in add +// to the existing build list, then adjusting versions (and adding or removing +// requirements as needed) until every module in mustSelect is selected at the +// given version. +// +// (Note that the newly-added modules might not be selected in the resulting +// build list: they could be lower than existing requirements or conflict with +// versions in mustSelect.) +// +// If the versions listed in mustSelect are mutually incompatible (due to one of +// the listed modules requiring a higher version of another), EditBuildList +// returns a *ConstraintError and leaves the build list in its previous state. +// +// On success, EditBuildList reports whether the selected version of any module +// in the build list may have been changed (possibly to or from "none") as a +// result. +func EditBuildList(ctx context.Context, add, mustSelect []module.Version) (changed bool, err error) { + rs, changed, err := editRequirements(ctx, LoadModFile(ctx), add, mustSelect) + if err != nil { + return false, err + } + commitRequirements(ctx, modFileGoVersion(), rs) + return changed, err } // A ConstraintError describes inconsistent constraints in EditBuildList @@ -213,66 +542,580 @@ type Conflict struct { Constraint module.Version } -// ReloadBuildList resets the state of loaded packages, then loads and returns -// the build list set by EditBuildList. -func ReloadBuildList() []module.Version { - loaded = loadFromRoots(loaderParams{ - PackageOpts: PackageOpts{ - Tags: imports.Tags(), - }, - listRoots: func() []string { return nil }, - allClosesOverTests: index.allPatternClosesOverTests(), // but doesn't matter because the root list is empty. - }) - return capVersionSlice(buildList) +// tidyRoots trims the root dependencies to the minimal requirements needed to +// both retain the same versions of all packages in pkgs and satisfy the +// lazy loading invariants (if applicable). +func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) { + if rs.depth == eager { + return tidyEagerRoots(ctx, rs.direct, pkgs) + } + return tidyLazyRoots(ctx, rs.direct, pkgs) } -// TidyBuildList trims the build list to the minimal requirements needed to -// retain the same versions of all packages from the preceding call to -// LoadPackages. -func TidyBuildList() { - used := map[module.Version]bool{Target: true} - for _, pkg := range loaded.pkgs { - used[pkg.mod] = true +func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) { + if rs.depth == eager { + return updateEagerRoots(ctx, direct, rs, add) } + return updateLazyRoots(ctx, direct, rs, pkgs, add, rootsImported) +} - keep := []module.Version{Target} - var direct []string - for _, m := range buildList[1:] { - if used[m] { - keep = append(keep, m) - if loaded.direct[m.Path] { - direct = append(direct, m.Path) +// tidyLazyRoots returns a minimal set of root requirements that maintains the +// "lazy loading" invariants of the go.mod file for the given packages: +// +// 1. For each package marked with pkgInAll, the module path that provided that +// package is included as a root. +// 2. For all packages, the module that provided that package either remains +// selected at the same version or is upgraded by the dependencies of a +// root. +// +// If any module that provided a package has been upgraded above its previous, +// version, the caller may need to reload and recompute the package graph. +// +// To ensure that the loading process eventually converges, the caller should +// add any needed roots from the tidy root set (without removing existing untidy +// roots) until the set of roots has converged. +func tidyLazyRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) { + var ( + roots []module.Version + pathIncluded = map[string]bool{Target.Path: true} + ) + // We start by adding roots for every package in "all". + // + // Once that is done, we may still need to add more roots to cover upgraded or + // otherwise-missing test dependencies for packages in "all". For those test + // dependencies, we prefer to add roots for packages with shorter import + // stacks first, on the theory that the module requirements for those will + // tend to fill in the requirements for their transitive imports (which have + // deeper import stacks). So we add the missing dependencies for one depth at + // a time, starting with the packages actually in "all" and expanding outwards + // until we have scanned every package that was loaded. + var ( + queue []*loadPkg + queued = map[*loadPkg]bool{} + ) + for _, pkg := range pkgs { + if !pkg.flags.has(pkgInAll) { + continue + } + if pkg.fromExternalModule() && !pathIncluded[pkg.mod.Path] { + roots = append(roots, pkg.mod) + pathIncluded[pkg.mod.Path] = true + } + queue = append(queue, pkg) + queued[pkg] = true + } + module.Sort(roots) + tidy := newRequirements(lazy, roots, direct) + + for len(queue) > 0 { + roots = tidy.rootModules + mg, err := tidy.Graph(ctx) + if err != nil { + return nil, err + } + + prevQueue := queue + queue = nil + for _, pkg := range prevQueue { + m := pkg.mod + if m.Path == "" { + continue } - } else if cfg.BuildV { - if _, ok := index.require[m]; ok { - fmt.Fprintf(os.Stderr, "unused %s\n", m.Path) + for _, dep := range pkg.imports { + if !queued[dep] { + queue = append(queue, dep) + queued[dep] = true + } + } + if pkg.test != nil && !queued[pkg.test] { + queue = append(queue, pkg.test) + queued[pkg.test] = true + } + if !pathIncluded[m.Path] { + if s := mg.Selected(m.Path); cmpVersion(s, m.Version) < 0 { + roots = append(roots, m) + } + pathIncluded[m.Path] = true } } + + if len(roots) > len(tidy.rootModules) { + module.Sort(roots) + tidy = newRequirements(lazy, roots, tidy.direct) + } } - min, err := mvs.Req(Target, direct, &mvsReqs{buildList: keep}) + _, err := tidy.Graph(ctx) if err != nil { - base.Fatalf("go: %v", err) + return nil, err + } + return tidy, nil +} + +// updateLazyRoots returns a set of root requirements that maintains the “lazy +// loading” invariants of the go.mod file: +// +// 1. The selected version of the module providing each package marked with +// either pkgInAll or pkgIsRoot is included as a root. +// Note that certain root patterns (such as '...') may explode the root set +// to contain every module that provides any package imported (or merely +// required) by any other module. +// 2. Each root appears only once, at the selected version of its path +// (if rs.graph is non-nil) or at the highest version otherwise present as a +// root (otherwise). +// 3. Every module path that appears as a root in rs remains a root. +// 4. Every version in add is selected at its given version unless upgraded by +// (the dependencies of) an existing root or another module in add. +// +// The packages in pkgs are assumed to have been loaded from either the roots of +// rs or the modules selected in the graph of rs. +// +// The above invariants together imply the “lazy loading” invariants for the +// go.mod file: +// +// 1. (The import invariant.) Every module that provides a package transitively +// imported by any package or test in the main module is included as a root. +// This follows by induction from (1) and (3) above. Transitively-imported +// packages loaded during this invocation are marked with pkgInAll (1), +// and by hypothesis any transitively-imported packages loaded in previous +// invocations were already roots in rs (3). +// +// 2. (The argument invariant.) Every module that provides a package matching +// an explicit package pattern is included as a root. This follows directly +// from (1): packages matching explicit package patterns are marked with +// pkgIsRoot. +// +// 3. (The completeness invariant.) Every module that contributed any package +// to the build is required by either the main module or one of the modules +// it requires explicitly. This invariant is left up to the caller, who must +// not load packages from outside the module graph but may add roots to the +// graph, but is facilited by (3). If the caller adds roots to the graph in +// order to resolve missing packages, then updateLazyRoots will retain them, +// the selected versions of those roots cannot regress, and they will +// eventually be written back to the main module's go.mod file. +// +// (See https://golang.org/design/36460-lazy-module-loading#invariants for more +// detail.) +func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) { + roots := rs.rootModules + rootsUpgraded := false + + spotCheckRoot := map[module.Version]bool{} + + // “The selected version of the module providing each package marked with + // either pkgInAll or pkgIsRoot is included as a root.” + needSort := false + for _, pkg := range pkgs { + if !pkg.fromExternalModule() { + // pkg was not loaded from a module dependency, so we don't need + // to do anything special to maintain that dependency. + continue + } + + switch { + case pkg.flags.has(pkgInAll): + // pkg is transitively imported by a package or test in the main module. + // We need to promote the module that maintains it to a root: if some + // other module depends on the main module, and that other module also + // uses lazy loading, it will expect to find all of our transitive + // dependencies by reading just our go.mod file, not the go.mod files of + // everything we depend on. + // + // (This is the “import invariant” that makes lazy loading possible.) + + case rootsImported && pkg.flags.has(pkgFromRoot): + // pkg is a transitive dependency of some root, and we are treating the + // roots as if they are imported by the main module (as in 'go get'). + + case pkg.flags.has(pkgIsRoot): + // pkg is a root of the package-import graph. (Generally this means that + // it matches a command-line argument.) We want future invocations of the + // 'go' command — such as 'go test' on the same package — to continue to + // use the same versions of its dependencies that we are using right now. + // So we need to bring this package's dependencies inside the lazy-loading + // horizon. + // + // Making the module containing this package a root of the module graph + // does exactly that: if the module containing the package is lazy it + // should satisfy the import invariant itself, so all of its dependencies + // should be in its go.mod file, and if the module containing the package + // is eager then if we make it a root we will load all of its transitive + // dependencies into the module graph. + // + // (This is the “argument invariant” of lazy loading, and is important for + // reproducibility.) + + default: + // pkg is a dependency of some other package outside of the main module. + // As far as we know it's not relevant to the main module (and thus not + // relevant to consumers of the main module either), and its dependencies + // should already be in the module graph — included in the dependencies of + // the package that imported it. + continue + } + + if _, ok := rs.rootSelected(pkg.mod.Path); ok { + // It is possible that the main module's go.mod file is incomplete or + // otherwise erroneous — for example, perhaps the author forgot to 'git + // add' their updated go.mod file after adding a new package import, or + // perhaps they made an edit to the go.mod file using a third-party tool + // ('git merge'?) that doesn't maintain consistency for module + // dependencies. If that happens, ideally we want to detect the missing + // requirements and fix them up here. + // + // However, we also need to be careful not to be too aggressive. For + // transitive dependencies of external tests, the go.mod file for the + // module containing the test itself is expected to provide all of the + // relevant dependencies, and we explicitly don't want to pull in + // requirements on *irrelevant* requirements that happen to occur in the + // go.mod files for these transitive-test-only dependencies. (See the test + // in mod_lazy_test_horizon.txt for a concrete example. + // + // The “goldilocks zone” seems to be to spot-check exactly the same + // modules that we promote to explicit roots: namely, those that provide + // packages transitively imported by the main module, and those that + // provide roots of the package-import graph. That will catch erroneous + // edits to the main module's go.mod file and inconsistent requirements in + // dependencies that provide imported packages, but will ignore erroneous + // or misleading requirements in dependencies that aren't obviously + // relevant to the packages in the main module. + spotCheckRoot[pkg.mod] = true + } else { + roots = append(roots, pkg.mod) + rootsUpgraded = true + // The roots slice was initially sorted because rs.rootModules was sorted, + // but the root we just added could be out of order. + needSort = true + } + } + + for _, m := range add { + if v, ok := rs.rootSelected(m.Path); !ok || cmpVersion(v, m.Version) < 0 { + roots = append(roots, m) + rootsUpgraded = true + needSort = true + } + } + if needSort { + module.Sort(roots) + } + + // "Each root appears only once, at the selected version of its path ….” + for { + var mg *ModuleGraph + if rootsUpgraded { + // We've added or upgraded one or more roots, so load the full module + // graph so that we can update those roots to be consistent with other + // requirements. + if cfg.BuildMod != "mod" { + // Our changes to the roots may have moved dependencies into or out of + // the lazy-loading horizon, which could in turn change the selected + // versions of other modules. (Unlike for eager modules, for lazy + // modules adding or removing an explicit root is a semantic change, not + // just a cosmetic one.) + return rs, errGoModDirty + } + + rs = newRequirements(lazy, roots, direct) + var err error + mg, err = rs.Graph(ctx) + if err != nil { + return rs, err + } + } else { + // Since none of the roots have been upgraded, we have no reason to + // suspect that they are inconsistent with the requirements of any other + // roots. Only look at the full module graph if we've already loaded it; + // otherwise, just spot-check the explicit requirements of the roots from + // which we loaded packages. + if rs.graph.Load() != nil { + // We've already loaded the full module graph, which includes the + // requirements of all of the root modules — even the transitive + // requirements, if they are eager! + mg, _ = rs.Graph(ctx) + } else if cfg.BuildMod == "vendor" { + // We can't spot-check the requirements of other modules because we + // don't in general have their go.mod files available in the vendor + // directory. (Fortunately this case is impossible, because mg.graph is + // always non-nil in vendor mode!) + panic("internal error: rs.graph is unexpectedly nil with -mod=vendor") + } else if !spotCheckRoots(ctx, rs, spotCheckRoot) { + // We spot-checked the explicit requirements of the roots that are + // relevant to the packages we've loaded. Unfortunately, they're + // inconsistent in some way; we need to load the full module graph + // so that we can fix the roots properly. + var err error + mg, err = rs.Graph(ctx) + if err != nil { + return rs, err + } + } + } + + roots = make([]module.Version, 0, len(rs.rootModules)) + rootsUpgraded = false + inRootPaths := make(map[string]bool, len(rs.rootModules)+1) + inRootPaths[Target.Path] = true + for _, m := range rs.rootModules { + if inRootPaths[m.Path] { + // This root specifies a redundant path. We already retained the + // selected version of this path when we saw it before, so omit the + // redundant copy regardless of its version. + // + // When we read the full module graph, we include the dependencies of + // every root even if that root is redundant. That better preserves + // reproducibility if, say, some automated tool adds a redundant + // 'require' line and then runs 'go mod tidy' to try to make everything + // consistent, since the requirements of the older version are carried + // over. + // + // So omitting a root that was previously present may *reduce* the + // selected versions of non-roots, but merely removing a requirement + // cannot *increase* the selected versions of other roots as a result — + // we don't need to mark this change as an upgrade. (This particular + // change cannot invalidate any other roots.) + continue + } + + var v string + if mg == nil { + v, _ = rs.rootSelected(m.Path) + } else { + v = mg.Selected(m.Path) + } + roots = append(roots, module.Version{Path: m.Path, Version: v}) + inRootPaths[m.Path] = true + if v != m.Version { + rootsUpgraded = true + } + } + // Note that rs.rootModules was already sorted by module path and version, + // and we appended to the roots slice in the same order and guaranteed that + // each path has only one version, so roots is also sorted by module path + // and (trivially) version. + + if !rootsUpgraded { + if cfg.BuildMod != "mod" { + // The only changes to the root set (if any) were to remove duplicates. + // The requirements are consistent (if perhaps redundant), so keep the + // original rs to preserve its ModuleGraph. + return rs, nil + } + // The root set has converged: every root going into this iteration was + // already at its selected version, although we have have removed other + // (redundant) roots for the same path. + break + } } - buildList = append([]module.Version{Target}, min...) + + if rs.depth == lazy && reflect.DeepEqual(roots, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) { + // The root set is unchanged and rs was already lazy, so keep rs to + // preserve its cached ModuleGraph (if any). + return rs, nil + } + return newRequirements(lazy, roots, direct), nil } -// checkMultiplePaths verifies that a given module path is used as itself -// or as a replacement for another module, but not both at the same time. +// spotCheckRoots reports whether the versions of the roots in rs satisfy the +// explicit requirements of the modules in mods. +func spotCheckRoots(ctx context.Context, rs *Requirements, mods map[module.Version]bool) bool { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + work := par.NewQueue(runtime.GOMAXPROCS(0)) + for m := range mods { + m := m + work.Add(func() { + if ctx.Err() != nil { + return + } + + summary, err := goModSummary(m) + if err != nil { + cancel() + return + } + + for _, r := range summary.require { + if v, ok := rs.rootSelected(r.Path); ok && cmpVersion(v, r.Version) < 0 { + cancel() + return + } + } + }) + } + <-work.Idle() + + if ctx.Err() != nil { + // Either we failed a spot-check, or the caller no longer cares about our + // answer anyway. + return false + } + + return true +} + +// tidyEagerRoots returns a minimal set of root requirements that maintains the +// selected version of every module that provided a package in pkgs, and +// includes the selected version of every such module in direct as a root. +func tidyEagerRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) { + var ( + keep []module.Version + keptPath = map[string]bool{} + ) + var ( + rootPaths []string // module paths that should be included as roots + inRootPaths = map[string]bool{} + ) + for _, pkg := range pkgs { + if !pkg.fromExternalModule() { + continue + } + if m := pkg.mod; !keptPath[m.Path] { + keep = append(keep, m) + keptPath[m.Path] = true + if direct[m.Path] && !inRootPaths[m.Path] { + rootPaths = append(rootPaths, m.Path) + inRootPaths[m.Path] = true + } + } + } + + min, err := mvs.Req(Target, rootPaths, &mvsReqs{roots: keep}) + if err != nil { + return nil, err + } + return newRequirements(eager, min, direct), nil +} + +// updateEagerRoots returns a set of root requirements that includes the selected +// version of every module path in direct as a root, and maintains the selected +// version of every module selected in the graph of rs. +// +// The roots are updated such that: // -// (See https://golang.org/issue/26607 and https://golang.org/issue/34650.) -func checkMultiplePaths() { - firstPath := make(map[module.Version]string, len(buildList)) - for _, mod := range buildList { - src := mod - if rep := Replacement(mod); rep.Path != "" { - src = rep +// 1. The selected version of every module path in direct is included as a root +// (if it is not "none"). +// 2. Each root is the selected version of its path. (We say that such a root +// set is “consistent”.) +// 3. Every version selected in the graph of rs remains selected unless upgraded +// by a dependency in add. +// 4. Every version in add is selected at its given version unless upgraded by +// (the dependencies of) an existing root or another module in add. +func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requirements, add []module.Version) (*Requirements, error) { + mg, err := rs.Graph(ctx) + if err != nil { + // We can't ignore errors in the module graph even if the user passed the -e + // flag to try to push past them. If we can't load the complete module + // dependencies, then we can't reliably compute a minimal subset of them. + return rs, err + } + + if cfg.BuildMod != "mod" { + // Instead of actually updating the requirements, just check that no updates + // are needed. + if rs == nil { + // We're being asked to reconstruct the requirements from scratch, + // but we aren't even allowed to modify them. + return rs, errGoModDirty + } + for _, m := range rs.rootModules { + if m.Version != mg.Selected(m.Path) { + // The root version v is misleading: the actual selected version is higher. + return rs, errGoModDirty + } + } + for _, m := range add { + if m.Version != mg.Selected(m.Path) { + return rs, errGoModDirty + } + } + for mPath := range direct { + if _, ok := rs.rootSelected(mPath); !ok { + // Module m is supposed to be listed explicitly, but isn't. + // + // Note that this condition is also detected (and logged with more + // detail) earlier during package loading, so it shouldn't actually be + // possible at this point — this is just a defense in depth. + return rs, errGoModDirty + } + } + + // No explicit roots are missing and all roots are already at the versions + // we want to keep. Any other changes we would make are purely cosmetic, + // such as pruning redundant indirect dependencies. Per issue #34822, we + // ignore cosmetic changes when we cannot update the go.mod file. + return rs, nil + } + + var ( + rootPaths []string // module paths that should be included as roots + inRootPaths = map[string]bool{} + ) + for _, root := range rs.rootModules { + // If the selected version of the root is the same as what was already + // listed in the go.mod file, retain it as a root (even if redundant) to + // avoid unnecessary churn. (See https://golang.org/issue/34822.) + // + // We do this even for indirect requirements, since we don't know why they + // were added and they could become direct at any time. + if !inRootPaths[root.Path] && mg.Selected(root.Path) == root.Version { + rootPaths = append(rootPaths, root.Path) + inRootPaths[root.Path] = true } - if prev, ok := firstPath[src]; !ok { - firstPath[src] = mod.Path - } else if prev != mod.Path { - base.Errorf("go: %s@%s used for two different module paths (%s and %s)", src.Path, src.Version, prev, mod.Path) + } + + // “The selected version of every module path in direct is included as a root.” + // + // This is only for convenience and clarity for end users: in an eager module, + // the choice of explicit vs. implicit dependency has no impact on MVS + // selection (for itself or any other module). + keep := append(mg.BuildList()[1:], add...) + for _, m := range keep { + if direct[m.Path] && !inRootPaths[m.Path] { + rootPaths = append(rootPaths, m.Path) + inRootPaths[m.Path] = true } } - base.ExitIfErrors() + + min, err := mvs.Req(Target, rootPaths, &mvsReqs{roots: keep}) + if err != nil { + return rs, err + } + if rs.depth == eager && reflect.DeepEqual(min, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) { + // The root set is unchanged and rs was already eager, so keep rs to + // preserve its cached ModuleGraph (if any). + return rs, nil + } + return newRequirements(eager, min, direct), nil +} + +// convertDepth returns a version of rs with the given depth. +// If rs already has the given depth, convertDepth returns rs unmodified. +func convertDepth(ctx context.Context, rs *Requirements, depth modDepth) (*Requirements, error) { + if rs.depth == depth { + return rs, nil + } + + if depth == eager { + // We are converting a lazy module to an eager one. The roots of an eager + // module graph are a superset of the roots of a lazy graph, so we don't + // need to add any new roots — we just need to prune away the ones that are + // redundant given eager loading, which is exactly what updateEagerRoots + // does. + return updateEagerRoots(ctx, rs.direct, rs, nil) + } + + // We are converting an eager module to a lazy one. The module graph of an + // eager module includes the transitive dependencies of every module in the + // build list. + // + // Hey, we can express that as a lazy root set! “Include the transitive + // dependencies of every module in the build list” is exactly what happens in + // a lazy module if we promote every module in the build list to a root! + mg, err := rs.Graph(ctx) + if err != nil { + return rs, err + } + return newRequirements(lazy, mg.BuildList()[1:], rs.direct), nil } diff --git a/src/cmd/go/internal/modload/edit.go b/src/cmd/go/internal/modload/edit.go new file mode 100644 index 0000000000000000000000000000000000000000..c350b9d1b5c571ed0e0754a07ad58fa3c9b26fe9 --- /dev/null +++ b/src/cmd/go/internal/modload/edit.go @@ -0,0 +1,569 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modload + +import ( + "cmd/go/internal/mvs" + "context" + "reflect" + "sort" + + "golang.org/x/mod/module" + "golang.org/x/mod/semver" +) + +// editRequirements returns an edited version of rs such that: +// +// 1. Each module version in mustSelect is selected. +// +// 2. Each module version in tryUpgrade is upgraded toward the indicated +// version as far as can be done without violating (1). +// +// 3. Each module version in rs.rootModules (or rs.graph, if rs.depth is eager) +// is downgraded from its original version only to the extent needed to +// satisfy (1), or upgraded only to the extent needed to satisfy (1) and +// (2). +// +// 4. No module is upgraded above the maximum version of its path found in the +// dependency graph of rs, the combined dependency graph of the versions in +// mustSelect, or the dependencies of each individual module version in +// tryUpgrade. +// +// Generally, the module versions in mustSelect are due to the module or a +// package within the module matching an explicit command line argument to 'go +// get', and the versions in tryUpgrade are transitive dependencies that are +// either being upgraded by 'go get -u' or being added to satisfy some +// otherwise-missing package import. +func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSelect []module.Version) (edited *Requirements, changed bool, err error) { + limiter, err := limiterForEdit(ctx, rs, tryUpgrade, mustSelect) + if err != nil { + return rs, false, err + } + + var conflicts []Conflict + for _, m := range mustSelect { + conflict, err := limiter.Select(m) + if err != nil { + return rs, false, err + } + if conflict.Path != "" { + conflicts = append(conflicts, Conflict{ + Source: m, + Dep: conflict, + Constraint: module.Version{ + Path: conflict.Path, + Version: limiter.max[conflict.Path], + }, + }) + } + } + if len(conflicts) > 0 { + return rs, false, &ConstraintError{Conflicts: conflicts} + } + + mods, changed, err := selectPotentiallyImportedModules(ctx, limiter, rs, tryUpgrade) + if err != nil { + return rs, false, err + } + + var roots []module.Version + if rs.depth == eager { + // In an eager module, modules that provide packages imported by the main + // module may either be explicit roots or implicit transitive dependencies. + // We promote the modules in mustSelect to be explicit requirements. + var rootPaths []string + for _, m := range mustSelect { + if m.Version != "none" && m.Path != Target.Path { + rootPaths = append(rootPaths, m.Path) + } + } + if !changed && len(rootPaths) == 0 { + // The build list hasn't changed and we have no new roots to add. + // We don't need to recompute the minimal roots for the module. + return rs, false, nil + } + + for _, m := range mods { + if v, ok := rs.rootSelected(m.Path); ok && (v == m.Version || rs.direct[m.Path]) { + // m.Path was formerly a root, and either its version hasn't changed or + // we believe that it provides a package directly imported by a package + // or test in the main module. For now we'll assume that it is still + // relevant enough to remain a root. If we actually load all of the + // packages and tests in the main module (which we are not doing here), + // we can revise the explicit roots at that point. + rootPaths = append(rootPaths, m.Path) + } + } + + roots, err = mvs.Req(Target, rootPaths, &mvsReqs{roots: mods}) + if err != nil { + return nil, false, err + } + } else { + // In a lazy module, every module that provides a package imported by the + // main module must be retained as a root. + roots = mods + if !changed { + // Because the roots we just computed are unchanged, the entire graph must + // be the same as it was before. Save the original rs, since we have + // probably already loaded its requirement graph. + return rs, false, nil + } + } + + // A module that is not even in the build list necessarily cannot provide + // any imported packages. Mark as direct only the direct modules that are + // still in the build list. + // + // TODO(bcmills): Would it make more sense to leave the direct map as-is + // but allow it to refer to modules that are no longer in the build list? + // That might complicate updateRoots, but it may be cleaner in other ways. + direct := make(map[string]bool, len(rs.direct)) + for _, m := range roots { + if rs.direct[m.Path] { + direct[m.Path] = true + } + } + return newRequirements(rs.depth, roots, direct), changed, nil +} + +// limiterForEdit returns a versionLimiter with its max versions set such that +// the max version for every module path in mustSelect is the version listed +// there, and the max version for every other module path is the maximum version +// of its path found in the dependency graph of rs, the combined dependency +// graph of the versions in mustSelect, or the dependencies of each individual +// module version in tryUpgrade. +func limiterForEdit(ctx context.Context, rs *Requirements, tryUpgrade, mustSelect []module.Version) (*versionLimiter, error) { + mg, err := rs.Graph(ctx) + if err != nil { + return nil, err + } + + maxVersion := map[string]string{} // module path → version + restrictTo := func(m module.Version) { + v, ok := maxVersion[m.Path] + if !ok || cmpVersion(v, m.Version) > 0 { + maxVersion[m.Path] = m.Version + } + } + + if rs.depth == eager { + // Eager go.mod files don't indicate which transitive dependencies are + // actually relevant to the main module, so we have to assume that any module + // that could have provided any package — that is, any module whose selected + // version was not "none" — may be relevant. + for _, m := range mg.BuildList() { + restrictTo(m) + } + } else { + // The go.mod file explicitly records every module that provides a package + // imported by the main module. + // + // If we need to downgrade an existing root or a new root found in + // tryUpgrade, we don't want to allow that downgrade to incidentally upgrade + // a module imported by the main module to some arbitrary version. + // However, we don't particularly care about arbitrary upgrades to modules + // that are (at best) only providing packages imported by tests of + // dependencies outside the main module. + for _, m := range rs.rootModules { + restrictTo(module.Version{ + Path: m.Path, + Version: mg.Selected(m.Path), + }) + } + } + + if err := raiseLimitsForUpgrades(ctx, maxVersion, rs.depth, tryUpgrade, mustSelect); err != nil { + return nil, err + } + + // The versions in mustSelect override whatever we would naively select — + // we will downgrade other modules as needed in order to meet them. + for _, m := range mustSelect { + restrictTo(m) + } + + return newVersionLimiter(rs.depth, maxVersion), nil +} + +// raiseLimitsForUpgrades increases the module versions in maxVersions to the +// versions that would be needed to allow each of the modules in tryUpgrade +// (individually) and all of the modules in mustSelect (simultaneously) to be +// added as roots. +// +// Versions not present in maxVersion are unrestricted, and it is assumed that +// they will not be promoted to root requirements (and thus will not contribute +// their own dependencies if the main module is lazy). +// +// These limits provide an upper bound on how far a module may be upgraded as +// part of an incidental downgrade, if downgrades are needed in order to select +// the versions in mustSelect. +func raiseLimitsForUpgrades(ctx context.Context, maxVersion map[string]string, depth modDepth, tryUpgrade []module.Version, mustSelect []module.Version) error { + // allow raises the limit for m.Path to at least m.Version. + // If m.Path was already unrestricted, it remains unrestricted. + allow := func(m module.Version) { + v, ok := maxVersion[m.Path] + if !ok { + return // m.Path is unrestricted. + } + if cmpVersion(v, m.Version) < 0 { + maxVersion[m.Path] = m.Version + } + } + + var eagerUpgrades []module.Version + if depth == eager { + eagerUpgrades = tryUpgrade + } else { + for _, m := range tryUpgrade { + if m.Path == Target.Path { + // Target is already considered to be higher than any possible m, so we + // won't be upgrading to it anyway and there is no point scanning its + // dependencies. + continue + } + + summary, err := goModSummary(m) + if err != nil { + return err + } + if summary.depth == eager { + // For efficiency, we'll load all of the eager upgrades as one big + // graph, rather than loading the (potentially-overlapping) subgraph for + // each upgrade individually. + eagerUpgrades = append(eagerUpgrades, m) + continue + } + + for _, r := range summary.require { + allow(r) + } + } + } + + if len(eagerUpgrades) > 0 { + // Compute the max versions for eager upgrades all together. + // Since these modules are eager, we'll end up scanning all of their + // transitive dependencies no matter which versions end up selected, + // and since we have a large dependency graph to scan we might get + // a significant benefit from not revisiting dependencies that are at + // common versions among multiple upgrades. + upgradeGraph, err := readModGraph(ctx, eager, eagerUpgrades) + if err != nil { + if go117LazyTODO { + // Compute the requirement path from a module path in tryUpgrade to the + // error, and the requirement path (if any) from rs.rootModules to the + // tryUpgrade module path. Return a *mvs.BuildListError showing the + // concatenation of the paths (with an upgrade in the middle). + } + return err + } + + for _, r := range upgradeGraph.BuildList() { + // Upgrading to m would upgrade to r, and the caller requested that we + // try to upgrade to m, so it's ok to upgrade to r. + allow(r) + } + } + + if len(mustSelect) > 0 { + mustGraph, err := readModGraph(ctx, depth, mustSelect) + if err != nil { + return err + } + + for _, r := range mustGraph.BuildList() { + // Some module in mustSelect requires r, so we must allow at least r.Version + // unless it conflicts with an entry in mustSelect. + allow(r) + } + } + + return nil +} + +// selectPotentiallyImportedModules increases the limiter-selected version of +// every module in rs that potentially provides a package imported (directly or +// indirectly) by the main module, and every module in tryUpgrade, toward the +// highest version seen in rs or tryUpgrade, but not above the maximums enforced +// by the limiter. +// +// It returns the list of module versions selected by the limiter, sorted by +// path, along with a boolean indicating whether that list is different from the +// list of modules read from rs. +func selectPotentiallyImportedModules(ctx context.Context, limiter *versionLimiter, rs *Requirements, tryUpgrade []module.Version) (mods []module.Version, changed bool, err error) { + for _, m := range tryUpgrade { + if err := limiter.UpgradeToward(ctx, m); err != nil { + return nil, false, err + } + } + + var initial []module.Version + if rs.depth == eager { + mg, err := rs.Graph(ctx) + if err != nil { + return nil, false, err + } + initial = mg.BuildList()[1:] + } else { + initial = rs.rootModules + } + for _, m := range initial { + if err := limiter.UpgradeToward(ctx, m); err != nil { + return nil, false, err + } + } + + mods = make([]module.Version, 0, len(limiter.selected)) + for path, v := range limiter.selected { + if v != "none" && path != Target.Path { + mods = append(mods, module.Version{Path: path, Version: v}) + } + } + + // We've identified acceptable versions for each of the modules, but those + // versions are not necessarily consistent with each other: one upgraded or + // downgraded module may require a higher (but still allowed) version of + // another. The lower version may require extraneous dependencies that aren't + // actually relevant, so we need to compute the actual selected versions. + mg, err := readModGraph(ctx, rs.depth, mods) + if err != nil { + return nil, false, err + } + mods = make([]module.Version, 0, len(limiter.selected)) + for path, _ := range limiter.selected { + if path != Target.Path { + if v := mg.Selected(path); v != "none" { + mods = append(mods, module.Version{Path: path, Version: v}) + } + } + } + module.Sort(mods) + + changed = !reflect.DeepEqual(mods, initial) + + return mods, changed, err +} + +// A versionLimiter tracks the versions that may be selected for each module +// subject to constraints on the maximum versions of transitive dependencies. +type versionLimiter struct { + // depth is the depth at which the dependencies of the modules passed to + // Select and UpgradeToward are loaded. + depth modDepth + + // max maps each module path to the maximum version that may be selected for + // that path. + // + // Paths with no entry are unrestricted, and we assume that they will not be + // promoted to root dependencies (so will not contribute dependencies if the + // main module is lazy). + max map[string]string + + // selected maps each module path to a version of that path (if known) whose + // transitive dependencies do not violate any max version. The version kept + // is the highest one found during any call to UpgradeToward for the given + // module path. + // + // If a higher acceptable version is found during a call to UpgradeToward for + // some *other* module path, that does not update the selected version. + // Ignoring those versions keeps the downgrades computed for two modules + // together close to the individual downgrades that would be computed for each + // module in isolation. (The only way one module can affect another is if the + // final downgraded version of the one module explicitly requires a higher + // version of the other.) + // + // Version "none" of every module is always known not to violate any max + // version, so paths at version "none" are omitted. + selected map[string]string + + // dqReason records whether and why each each encountered version is + // disqualified. + dqReason map[module.Version]dqState + + // requiring maps each not-yet-disqualified module version to the versions + // that directly require it. If that version becomes disqualified, the + // disqualification will be propagated to all of the versions in the list. + requiring map[module.Version][]module.Version +} + +// A dqState indicates whether and why a module version is “disqualified” from +// being used in a way that would incorporate its requirements. +// +// The zero dqState indicates that the module version is not known to be +// disqualified, either because it is ok or because we are currently traversing +// a cycle that includes it. +type dqState struct { + err error // if non-nil, disqualified because the requirements of the module could not be read + conflict module.Version // disqualified because the module (transitively) requires dep, which exceeds the maximum version constraint for its path +} + +func (dq dqState) isDisqualified() bool { + return dq != dqState{} +} + +// newVersionLimiter returns a versionLimiter that restricts the module paths +// that appear as keys in max. +// +// max maps each module path to its maximum version; paths that are not present +// in the map are unrestricted. The limiter assumes that unrestricted paths will +// not be promoted to root dependencies. +// +// If depth is lazy, then if a module passed to UpgradeToward or Select is +// itself lazy, its unrestricted dependencies are skipped when scanning +// requirements. +func newVersionLimiter(depth modDepth, max map[string]string) *versionLimiter { + return &versionLimiter{ + depth: depth, + max: max, + selected: map[string]string{Target.Path: Target.Version}, + dqReason: map[module.Version]dqState{}, + requiring: map[module.Version][]module.Version{}, + } +} + +// UpgradeToward attempts to upgrade the selected version of m.Path as close as +// possible to m.Version without violating l's maximum version limits. +// +// If depth is lazy and m itself is lazy, the the dependencies of unrestricted +// dependencies of m will not be followed. +func (l *versionLimiter) UpgradeToward(ctx context.Context, m module.Version) error { + selected, ok := l.selected[m.Path] + if ok { + if cmpVersion(selected, m.Version) >= 0 { + // The selected version is already at least m, so no upgrade is needed. + return nil + } + } else { + selected = "none" + } + + if l.check(m, l.depth).isDisqualified() { + candidates, err := versions(ctx, m.Path, CheckAllowed) + if err != nil { + // This is likely a transient error reaching the repository, + // rather than a permanent error with the retrieved version. + // + // TODO(golang.org/issue/31730, golang.org/issue/30134): + // decode what to do based on the actual error. + return err + } + + // Skip to candidates < m.Version. + i := sort.Search(len(candidates), func(i int) bool { + return semver.Compare(candidates[i], m.Version) >= 0 + }) + candidates = candidates[:i] + + for l.check(m, l.depth).isDisqualified() { + n := len(candidates) + if n == 0 || cmpVersion(selected, candidates[n-1]) >= 0 { + // We couldn't find a suitable candidate above the already-selected version. + // Retain that version unmodified. + return nil + } + m.Version, candidates = candidates[n-1], candidates[:n-1] + } + } + + l.selected[m.Path] = m.Version + return nil +} + +// Select attempts to set the selected version of m.Path to exactly m.Version. +func (l *versionLimiter) Select(m module.Version) (conflict module.Version, err error) { + dq := l.check(m, l.depth) + if !dq.isDisqualified() { + l.selected[m.Path] = m.Version + } + return dq.conflict, dq.err +} + +// check determines whether m (or its transitive dependencies) would violate l's +// maximum version limits if added to the module requirement graph. +// +// If depth is lazy and m itself is lazy, then the dependencies of unrestricted +// dependencies of m will not be followed. If the lazy loading invariants hold +// for the main module up to this point, the packages in those modules are at +// best only imported by tests of dependencies that are themselves loaded from +// outside modules. Although we would like to keep 'go test all' as reproducible +// as is feasible, we don't want to retain test dependencies that are only +// marginally relevant at best. +func (l *versionLimiter) check(m module.Version, depth modDepth) dqState { + if m.Version == "none" || m == Target { + // version "none" has no requirements, and the dependencies of Target are + // tautological. + return dqState{} + } + + if dq, seen := l.dqReason[m]; seen { + return dq + } + l.dqReason[m] = dqState{} + + if max, ok := l.max[m.Path]; ok && cmpVersion(m.Version, max) > 0 { + return l.disqualify(m, dqState{conflict: m}) + } + + summary, err := goModSummary(m) + if err != nil { + // If we can't load the requirements, we couldn't load the go.mod file. + // There are a number of reasons this can happen, but this usually + // means an older version of the module had a missing or invalid + // go.mod file. For example, if example.com/mod released v2.0.0 before + // migrating to modules (v2.0.0+incompatible), then added a valid go.mod + // in v2.0.1, downgrading from v2.0.1 would cause this error. + // + // TODO(golang.org/issue/31730, golang.org/issue/30134): if the error + // is transient (we couldn't download go.mod), return the error from + // Downgrade. Currently, we can't tell what kind of error it is. + return l.disqualify(m, dqState{err: err}) + } + + if summary.depth == eager { + depth = eager + } + for _, r := range summary.require { + if depth == lazy { + if _, restricted := l.max[r.Path]; !restricted { + // r.Path is unrestricted, so we don't care at what version it is + // selected. We assume that r.Path will not become a root dependency, so + // since m is lazy, r's dependencies won't be followed. + continue + } + } + + if dq := l.check(r, depth); dq.isDisqualified() { + return l.disqualify(m, dq) + } + + // r and its dependencies are (perhaps provisionally) ok. + // + // However, if there are cycles in the requirement graph, we may have only + // checked a portion of the requirement graph so far, and r (and thus m) may + // yet be disqualified by some path we have not yet visited. Remember this edge + // so that we can disqualify m and its dependents if that occurs. + l.requiring[r] = append(l.requiring[r], m) + } + + return dqState{} +} + +// disqualify records that m (or one of its transitive dependencies) +// violates l's maximum version limits. +func (l *versionLimiter) disqualify(m module.Version, dq dqState) dqState { + if dq := l.dqReason[m]; dq.isDisqualified() { + return dq + } + l.dqReason[m] = dq + + for _, p := range l.requiring[m] { + l.disqualify(p, dqState{conflict: m}) + } + // Now that we have disqualified the modules that depend on m, we can forget + // about them — we won't need to disqualify them again. + delete(l.requiring, m) + return dq +} diff --git a/src/cmd/go/internal/modload/help.go b/src/cmd/go/internal/modload/help.go index fd39ddd94ec62d6f21533a7ea7dff02c8eeb2ecb..886ad62bd90cb6a64113732336d9affe117a98ef 100644 --- a/src/cmd/go/internal/modload/help.go +++ b/src/cmd/go/internal/modload/help.go @@ -46,7 +46,7 @@ marking the root of the main (current) module. The go.mod file format is described in detail at https://golang.org/ref/mod#go-mod-file. -To create a new go.mod file, use 'go help init'. For details see +To create a new go.mod file, use 'go mod init'. For details see 'go help mod init' or https://golang.org/ref/mod#go-mod-init. To add missing module requirements or remove unneeded requirements, diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go index 995641c9f1f36d8a65192f6448e0c6f18c502fe4..d2bbe5cbe0b1eae58ac5a041792d0b4d0e8a3c85 100644 --- a/src/cmd/go/internal/modload/import.go +++ b/src/cmd/go/internal/modload/import.go @@ -12,6 +12,7 @@ import ( "internal/goroot" "io/fs" "os" + pathpkg "path" "path/filepath" "sort" "strings" @@ -60,7 +61,7 @@ func (e *ImportMissingError) Error() string { if e.replaced.Path != "" { suggestArg := e.replaced.Path - if !modfetch.IsZeroPseudoVersion(e.replaced.Version) { + if !module.IsZeroPseudoVersion(e.replaced.Version) { suggestArg = e.replaced.String() } return fmt.Sprintf("module %s provides package %s and is replaced but not required; to add it:\n\tgo get %s", e.replaced.Path, e.Path, suggestArg) @@ -127,6 +128,23 @@ func (e *AmbiguousImportError) Error() string { return buf.String() } +// A DirectImportFromImplicitDependencyError indicates a package directly +// imported by a package or test in the main module that is satisfied by a +// dependency that is not explicit in the main module's go.mod file. +type DirectImportFromImplicitDependencyError struct { + ImporterPath string + ImportedPath string + Module module.Version +} + +func (e *DirectImportFromImplicitDependencyError) Error() string { + return fmt.Sprintf("package %s imports %s from implicitly required module; to add missing requirements, run:\n\tgo get %s@%s", e.ImporterPath, e.ImportedPath, e.Module.Path, e.Module.Version) +} + +func (e *DirectImportFromImplicitDependencyError) ImportPath() string { + return e.ImporterPath +} + // ImportMissingSumError is reported in readonly mode when we need to check // if a module contains a package, but we don't have a sum for its .zip file. // We might need sums for multiple modules to verify the package is unique. @@ -160,11 +178,13 @@ func (e *ImportMissingSumError) Error() string { // Importing package is unknown, or the missing package was named on the // command line. Recommend 'go mod download' for the modules that could // provide the package, since that shouldn't change go.mod. - args := make([]string, len(e.mods)) - for i, mod := range e.mods { - args[i] = mod.Path + if len(e.mods) > 0 { + args := make([]string, len(e.mods)) + for i, mod := range e.mods { + args[i] = mod.Path + } + hint = fmt.Sprintf("; to add:\n\tgo mod download %s", strings.Join(args, " ")) } - hint = fmt.Sprintf("; to add:\n\tgo mod download %s", strings.Join(args, " ")) } else { // Importing package is known (common case). Recommend 'go get' on the // current version of the importing package. @@ -202,28 +222,31 @@ func (e *invalidImportError) Unwrap() error { return e.err } -// importFromBuildList finds the module and directory in the build list -// containing the package with the given import path. The answer must be unique: -// importFromBuildList returns an error if multiple modules attempt to provide -// the same package. +// importFromModules finds the module and directory in the dependency graph of +// rs containing the package with the given import path. If mg is nil, +// importFromModules attempts to locate the module using only the main module +// and the roots of rs before it loads the full graph. // -// importFromBuildList can return a module with an empty m.Path, for packages in +// The answer must be unique: importFromModules returns an error if multiple +// modules are observed to provide the same package. +// +// importFromModules can return a module with an empty m.Path, for packages in // the standard library. // -// importFromBuildList can return an empty directory string, for fake packages +// importFromModules can return an empty directory string, for fake packages // like "C" and "unsafe". // -// If the package cannot be found in buildList, -// importFromBuildList returns an *ImportMissingError. -func importFromBuildList(ctx context.Context, path string, buildList []module.Version) (m module.Version, dir string, err error) { +// If the package is not present in any module selected from the requirement +// graph, importFromModules returns an *ImportMissingError. +func importFromModules(ctx context.Context, path string, rs *Requirements, mg *ModuleGraph) (m module.Version, dir string, err error) { if strings.Contains(path, "@") { return module.Version{}, "", fmt.Errorf("import path should not have @version") } if build.IsLocalImport(path) { return module.Version{}, "", fmt.Errorf("relative import not supported") } - if path == "C" || path == "unsafe" { - // There's no directory for import "C" or import "unsafe". + if path == "C" { + // There's no directory for import "C". return module.Version{}, "", nil } // Before any further lookup, check that the path is valid. @@ -269,58 +292,114 @@ func importFromBuildList(ctx context.Context, path string, buildList []module.Ve // Check each module on the build list. var dirs []string var mods []module.Version - var sumErrMods []module.Version - for _, m := range buildList { - if !maybeInModule(path, m.Path) { - // Avoid possibly downloading irrelevant modules. - continue - } - needSum := true - root, isLocal, err := fetch(ctx, m, needSum) - if err != nil { - if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) { - // We are missing a sum needed to fetch a module in the build list. - // We can't verify that the package is unique, and we may not find - // the package at all. Keep checking other modules to decide which - // error to report. Multiple sums may be missing if we need to look in - // multiple nested modules to resolve the import. - sumErrMods = append(sumErrMods, m) + + // Iterate over possible modules for the path, not all selected modules. + // Iterating over selected modules would make the overall loading time + // O(M × P) for M modules providing P imported packages, whereas iterating + // over path prefixes is only O(P × k) with maximum path depth k. For + // large projects both M and P may be very large (note that M ≤ P), but k + // will tend to remain smallish (if for no other reason than filesystem + // path limitations). + // + // We perform this iteration either one or two times. If mg is initially nil, + // then we first attempt to load the package using only the main module and + // its root requirements. If that does not identify the package, or if mg is + // already non-nil, then we attempt to load the package using the full + // requirements in mg. + for { + var sumErrMods []module.Version + for prefix := path; prefix != "."; prefix = pathpkg.Dir(prefix) { + var ( + v string + ok bool + ) + if mg == nil { + v, ok = rs.rootSelected(prefix) + } else { + v, ok = mg.Selected(prefix), true + } + if !ok || v == "none" { continue } - // Report fetch error. - // Note that we don't know for sure this module is necessary, - // but it certainly _could_ provide the package, and even if we - // continue the loop and find the package in some other module, - // we need to look at this module to make sure the import is - // not ambiguous. - return module.Version{}, "", err + m := module.Version{Path: prefix, Version: v} + + needSum := true + root, isLocal, err := fetch(ctx, m, needSum) + if err != nil { + if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) { + // We are missing a sum needed to fetch a module in the build list. + // We can't verify that the package is unique, and we may not find + // the package at all. Keep checking other modules to decide which + // error to report. Multiple sums may be missing if we need to look in + // multiple nested modules to resolve the import; we'll report them all. + sumErrMods = append(sumErrMods, m) + continue + } + // Report fetch error. + // Note that we don't know for sure this module is necessary, + // but it certainly _could_ provide the package, and even if we + // continue the loop and find the package in some other module, + // we need to look at this module to make sure the import is + // not ambiguous. + return module.Version{}, "", err + } + if dir, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil { + return module.Version{}, "", err + } else if ok { + mods = append(mods, m) + dirs = append(dirs, dir) + } } - if dir, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil { - return module.Version{}, "", err - } else if ok { - mods = append(mods, m) - dirs = append(dirs, dir) + + if len(mods) > 1 { + // We produce the list of directories from longest to shortest candidate + // module path, but the AmbiguousImportError should report them from + // shortest to longest. Reverse them now. + for i := 0; i < len(mods)/2; i++ { + j := len(mods) - 1 - i + mods[i], mods[j] = mods[j], mods[i] + dirs[i], dirs[j] = dirs[j], dirs[i] + } + return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods} } - } - if len(mods) > 1 { - return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods} - } - if len(sumErrMods) > 0 { - return module.Version{}, "", &ImportMissingSumError{ - importPath: path, - mods: sumErrMods, - found: len(mods) > 0, + + if len(sumErrMods) > 0 { + for i := 0; i < len(sumErrMods)/2; i++ { + j := len(sumErrMods) - 1 - i + sumErrMods[i], sumErrMods[j] = sumErrMods[j], sumErrMods[i] + } + return module.Version{}, "", &ImportMissingSumError{ + importPath: path, + mods: sumErrMods, + found: len(mods) > 0, + } } - } - if len(mods) == 1 { - return mods[0], dirs[0], nil - } - var queryErr error - if !HasModRoot() { - queryErr = ErrNoModRoot + if len(mods) == 1 { + return mods[0], dirs[0], nil + } + + if mg != nil { + // We checked the full module graph and still didn't find the + // requested package. + var queryErr error + if !HasModRoot() { + queryErr = ErrNoModRoot + } + return module.Version{}, "", &ImportMissingError{Path: path, QueryErr: queryErr, isStd: pathIsStd} + } + + // So far we've checked the root dependencies. + // Load the full module graph and try again. + mg, err = rs.Graph(ctx) + if err != nil { + // We might be missing one or more transitive (implicit) dependencies from + // the module graph, so we can't return an ImportMissingError here — one + // of the missing modules might actually contain the package in question, + // in which case we shouldn't go looking for it in some new dependency. + return module.Version{}, "", err + } } - return module.Version{}, "", &ImportMissingError{Path: path, QueryErr: queryErr, isStd: pathIsStd} } // queryImport attempts to locate a module that can be added to the current @@ -328,7 +407,7 @@ func importFromBuildList(ctx context.Context, path string, buildList []module.Ve // // Unlike QueryPattern, queryImport prefers to add a replaced version of a // module *before* checking the proxies for a version to add. -func queryImport(ctx context.Context, path string) (module.Version, error) { +func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) { // To avoid spurious remote fetches, try the latest replacement for each // module (golang.org/issue/26241). if index != nil { @@ -344,11 +423,20 @@ func queryImport(ctx context.Context, path string) (module.Version, error) { // used from within some other module, the user will be able to upgrade // the requirement to any real version they choose. if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 { - mv = modfetch.ZeroPseudoVersion(pathMajor[1:]) + mv = module.ZeroPseudoVersion(pathMajor[1:]) } else { - mv = modfetch.ZeroPseudoVersion("v0") + mv = module.ZeroPseudoVersion("v0") } } + mg, err := rs.Graph(ctx) + if err != nil { + return module.Version{}, err + } + if cmpVersion(mg.Selected(mp), mv) >= 0 { + // We can't resolve the import by adding mp@mv to the module graph, + // because the selected version of mp is already at least mv. + continue + } mods = append(mods, module.Version{Path: mp, Version: mv}) } @@ -417,7 +505,12 @@ func queryImport(ctx context.Context, path string) (module.Version, error) { // and return m, dir, ImpportMissingError. fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path) - candidates, err := QueryPackages(ctx, path, "latest", Selected, CheckAllowed) + mg, err := rs.Graph(ctx) + if err != nil { + return module.Version{}, err + } + + candidates, err := QueryPackages(ctx, path, "latest", mg.Selected, CheckAllowed) if err != nil { if errors.Is(err, fs.ErrNotExist) { // Return "cannot find module providing package […]" instead of whatever @@ -430,28 +523,21 @@ func queryImport(ctx context.Context, path string) (module.Version, error) { candidate0MissingVersion := "" for i, c := range candidates { - cm := c.Mod - canAdd := true - for _, bm := range buildList { - if bm.Path == cm.Path && semver.Compare(bm.Version, cm.Version) > 0 { - // QueryPattern proposed that we add module cm to provide the package, - // but we already depend on a newer version of that module (and we don't - // have the package). - // - // This typically happens when a package is present at the "@latest" - // version (e.g., v1.0.0) of a module, but we have a newer version - // of the same module in the build list (e.g., v1.0.1-beta), and - // the package is not present there. - canAdd = false - if i == 0 { - candidate0MissingVersion = bm.Version - } - break + if v := mg.Selected(c.Mod.Path); semver.Compare(v, c.Mod.Version) > 0 { + // QueryPattern proposed that we add module c.Mod to provide the package, + // but we already depend on a newer version of that module (and that + // version doesn't have the package). + // + // This typically happens when a package is present at the "@latest" + // version (e.g., v1.0.0) of a module, but we have a newer version + // of the same module in the build list (e.g., v1.0.1-beta), and + // the package is not present there. + if i == 0 { + candidate0MissingVersion = v } + continue } - if canAdd { - return cm, nil - } + return c.Mod, nil } return module.Version{}, &ImportMissingError{ Path: path, diff --git a/src/cmd/go/internal/modload/import_test.go b/src/cmd/go/internal/modload/import_test.go index 9420dc56460d1060b7723d6763c66e816956408b..98145887e9dcc5c6b18aa071f08c27ce7c4681ca 100644 --- a/src/cmd/go/internal/modload/import_test.go +++ b/src/cmd/go/internal/modload/import_test.go @@ -69,11 +69,12 @@ func TestQueryImport(t *testing.T) { RootMode = NoRoot ctx := context.Background() + rs := newRequirements(eager, nil, nil) for _, tt := range importTests { t.Run(strings.ReplaceAll(tt.path, "/", "_"), func(t *testing.T) { // Note that there is no build list, so Import should always fail. - m, err := queryImport(ctx, tt.path) + m, err := queryImport(ctx, tt.path, rs) if tt.err == "" { if err != nil { diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index 8ec1c8681a9c8f2376c16c2e070dc533cee0ba49..45f724d5e3658f5d21d757d7a955b24af6b89be9 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -15,10 +15,8 @@ import ( "os" "path" "path/filepath" - "sort" "strconv" "strings" - "sync" "cmd/go/internal/base" "cmd/go/internal/cfg" @@ -26,20 +24,37 @@ import ( "cmd/go/internal/lockedfile" "cmd/go/internal/modconv" "cmd/go/internal/modfetch" - "cmd/go/internal/mvs" "cmd/go/internal/search" - "cmd/go/internal/str" "golang.org/x/mod/modfile" "golang.org/x/mod/module" "golang.org/x/mod/semver" ) +// Variables set by other packages. +// +// TODO(#40775): See if these can be plumbed as explicit parameters. +var ( + // RootMode determines whether a module root is needed. + RootMode Root + + // ForceUseModules may be set to force modules to be enabled when + // GO111MODULE=auto or to report an error when GO111MODULE=off. + ForceUseModules bool + + allowMissingModuleImports bool +) + +// Variables set in Init. var ( initialized bool + modRoot string + gopath string +) - modRoot string - Target module.Version +// Variables set in initTarget (during {Load,Create}ModFile). +var ( + Target module.Version // targetPrefix is the path prefix for packages in Target, without a trailing // slash. For most modules, targetPrefix is just Target.Path, but the @@ -49,17 +64,6 @@ var ( // targetInGorootSrc caches whether modRoot is within GOROOT/src. // The "std" module is special within GOROOT/src, but not otherwise. targetInGorootSrc bool - - gopath string - - // RootMode determines whether a module root is needed. - RootMode Root - - // ForceUseModules may be set to force modules to be enabled when - // GO111MODULE=auto or to report an error when GO111MODULE=off. - ForceUseModules bool - - allowMissingModuleImports bool ) type Root int @@ -67,7 +71,7 @@ type Root int const ( // AutoRoot is the default for most commands. modload.Init will look for // a go.mod file in the current directory or any parent. If none is found, - // modules may be disabled (GO111MODULE=on) or commands may run in a + // modules may be disabled (GO111MODULE=auto) or commands may run in a // limited module mode. AutoRoot Root = iota @@ -82,7 +86,7 @@ const ( // ModFile returns the parsed go.mod file. // -// Note that after calling LoadPackages or LoadAllModules, +// Note that after calling LoadPackages or LoadModGraph, // the require statements in the modfile.File are no longer // the source of truth and will be ignored: edits made directly // will be lost at the next call to WriteGoMod. @@ -131,7 +135,7 @@ func Init() { return } - if err := fsys.Init(base.Cwd); err != nil { + if err := fsys.Init(base.Cwd()); err != nil { base.Fatalf("go: %v", err) } @@ -159,7 +163,11 @@ func Init() { // assume they know what they are doing and don't step on it. // But default to turning off ControlMaster. if os.Getenv("GIT_SSH") == "" && os.Getenv("GIT_SSH_COMMAND") == "" { - os.Setenv("GIT_SSH_COMMAND", "ssh -o ControlMaster=no") + os.Setenv("GIT_SSH_COMMAND", "ssh -o ControlMaster=no -o BatchMode=yes") + } + + if os.Getenv("GCM_INTERACTIVE") == "" { + os.Setenv("GCM_INTERACTIVE", "never") } if modRoot != "" { @@ -171,7 +179,7 @@ func Init() { } modRoot = "" } else { - modRoot = findModuleRoot(base.Cwd) + modRoot = findModuleRoot(base.Cwd()) if modRoot == "" { if cfg.ModFile != "" { base.Fatalf("go: cannot find main module, but -modfile was set.\n\t-modfile cannot be used to set the module root directory.") @@ -268,7 +276,7 @@ func WillBeEnabled() bool { return false } - if modRoot := findModuleRoot(base.Cwd); modRoot == "" { + if modRoot := findModuleRoot(base.Cwd()); modRoot == "" { // GO111MODULE is 'auto', and we can't find a module root. // Stay in GOPATH mode. return false @@ -327,8 +335,8 @@ func die() { if cfg.Getenv("GO111MODULE") == "off" { base.Fatalf("go: modules disabled by GO111MODULE=off; see 'go help modules'") } - if dir, name := findAltConfig(base.Cwd); dir != "" { - rel, err := filepath.Rel(base.Cwd, dir) + if dir, name := findAltConfig(base.Cwd()); dir != "" { + rel, err := filepath.Rel(base.Cwd(), dir) if err != nil { rel = dir } @@ -343,30 +351,77 @@ func die() { var ErrNoModRoot = errors.New("go.mod file not found in current directory or any parent directory; see 'go help modules'") +type goModDirtyError struct{} + +func (goModDirtyError) Error() string { + if cfg.BuildModExplicit { + return fmt.Sprintf("updates to go.mod needed, disabled by -mod=%v; to update it:\n\tgo mod tidy", cfg.BuildMod) + } + if cfg.BuildModReason != "" { + return fmt.Sprintf("updates to go.mod needed, disabled by -mod=%s\n\t(%s)\n\tto update it:\n\tgo mod tidy", cfg.BuildMod, cfg.BuildModReason) + } + return "updates to go.mod needed; to update it:\n\tgo mod tidy" +} + +var errGoModDirty error = goModDirtyError{} + // LoadModFile sets Target and, if there is a main module, parses the initial // build list from its go.mod file. // // LoadModFile may make changes in memory, like adding a go directive and -// ensuring requirements are consistent. WriteGoMod should be called later to -// write changes out to disk or report errors in readonly mode. +// ensuring requirements are consistent, and will write those changes back to +// disk unless DisallowWriteGoMod is in effect. // // As a side-effect, LoadModFile may change cfg.BuildMod to "vendor" if // -mod wasn't set explicitly and automatic vendoring should be enabled. -func LoadModFile(ctx context.Context) { - if len(buildList) > 0 { - return +// +// If LoadModFile or CreateModFile has already been called, LoadModFile returns +// the existing in-memory requirements (rather than re-reading them from disk). +// +// LoadModFile checks the roots of the module graph for consistency with each +// other, but unlike LoadModGraph does not load the full module graph or check +// it for global consistency. Most callers outside of the modload package should +// use LoadModGraph instead. +func LoadModFile(ctx context.Context) *Requirements { + rs, needCommit := loadModFile(ctx) + if needCommit { + commitRequirements(ctx, modFileGoVersion(), rs) + } + return rs +} + +// loadModFile is like LoadModFile, but does not implicitly commit the +// requirements back to disk after fixing inconsistencies. +// +// If needCommit is true, after the caller makes any other needed changes to the +// returned requirements they should invoke commitRequirements to fix any +// inconsistencies that may be present in the on-disk go.mod file. +func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) { + if requirements != nil { + return requirements, false } Init() if modRoot == "" { Target = module.Version{Path: "command-line-arguments"} targetPrefix = "command-line-arguments" - buildList = []module.Version{Target} - return + goVersion := LatestGoVersion() + rawGoVersion.Store(Target, goVersion) + requirements = newRequirements(modDepthFromGoVersion(goVersion), nil, nil) + return requirements, false } gomod := ModFilePath() - data, err := lockedfile.Read(gomod) + var data []byte + var err error + if gomodActual, ok := fsys.OverlayPath(gomod); ok { + // Don't lock go.mod if it's part of the overlay. + // On Plan 9, locking requires chmod, and we don't want to modify any file + // in the overlay. See #44700. + data, err = os.ReadFile(gomodActual) + } else { + data, err = lockedfile.Read(gomodActual) + } if err != nil { base.Fatalf("go: %v", err) } @@ -377,24 +432,62 @@ func LoadModFile(ctx context.Context) { // Errors returned by modfile.Parse begin with file:line. base.Fatalf("go: errors parsing go.mod:\n%s\n", err) } - modFile = f - index = indexModFile(data, f, fixed) - if f.Module == nil { // No module declaration. Must add module path. base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod") } - if err := checkModulePathLax(f.Module.Mod.Path); err != nil { + modFile = f + initTarget(f.Module.Mod) + index = indexModFile(data, f, fixed) + + if err := module.CheckImportPath(f.Module.Mod.Path); err != nil { + if pathErr, ok := err.(*module.InvalidPathError); ok { + pathErr.Kind = "module" + } base.Fatalf("go: %v", err) } setDefaultBuildMod() // possibly enable automatic vendoring - modFileToBuildList() + rs = requirementsFromModFile() if cfg.BuildMod == "vendor" { readVendorList() checkVendorConsistency() + rs.initVendor(vendorList) + } + if rs.hasRedundantRoot() { + // If any module path appears more than once in the roots, we know that the + // go.mod file needs to be updated even though we have not yet loaded any + // transitive dependencies. + rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false) + if err != nil { + base.Fatalf("go: %v", err) + } } + + if index.goVersionV == "" { + // TODO(#45551): Do something more principled instead of checking + // cfg.CmdName directly here. + if cfg.BuildMod == "mod" && cfg.CmdName != "mod graph" && cfg.CmdName != "mod why" { + addGoStmt(LatestGoVersion()) + if go117EnableLazyLoading { + // We need to add a 'go' version to the go.mod file, but we must assume + // that its existing contents match something between Go 1.11 and 1.16. + // Go 1.11 through 1.16 have eager requirements, but the latest Go + // version uses lazy requirements instead — so we need to cnvert the + // requirements to be lazy. + rs, err = convertDepth(ctx, rs, lazy) + if err != nil { + base.Fatalf("go: %v", err) + } + } + } else { + rawGoVersion.Store(Target, modFileGoVersion()) + } + } + + requirements = rs + return requirements, true } // CreateModFile initializes a new module by creating a go.mod file. @@ -407,7 +500,7 @@ func LoadModFile(ctx context.Context) { // exactly the same as in the legacy configuration (for example, we can't get // packages at multiple versions from the same module). func CreateModFile(ctx context.Context, modPath string) { - modRoot = base.Cwd + modRoot = base.Cwd() Init() modFilePath := ModFilePath() if _, err := fsys.Stat(modFilePath); err == nil { @@ -420,14 +513,23 @@ func CreateModFile(ctx context.Context, modPath string) { if err != nil { base.Fatalf("go: %v", err) } - } else if err := checkModulePathLax(modPath); err != nil { + } else if err := module.CheckImportPath(modPath); err != nil { + if pathErr, ok := err.(*module.InvalidPathError); ok { + pathErr.Kind = "module" + // Same as build.IsLocalPath() + if pathErr.Path == "." || pathErr.Path == ".." || + strings.HasPrefix(pathErr.Path, "./") || strings.HasPrefix(pathErr.Path, "../") { + pathErr.Err = errors.New("is a local import path") + } + } base.Fatalf("go: %v", err) } fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath) modFile = new(modfile.File) modFile.AddModuleStmt(modPath) - addGoStmt() // Add the go directive before converted module requirements. + initTarget(modFile.Module.Mod) + addGoStmt(LatestGoVersion()) // Add the go directive before converted module requirements. convertedFrom, err := convertLegacyConfig(modPath) if convertedFrom != "" { @@ -437,8 +539,12 @@ func CreateModFile(ctx context.Context, modPath string) { base.Fatalf("go: %v", err) } - modFileToBuildList() - WriteGoMod() + rs := requirementsFromModFile() + rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false) + if err != nil { + base.Fatalf("go: %v", err) + } + commitRequirements(ctx, modFileGoVersion(), rs) // Suggest running 'go mod tidy' unless the project is empty. Even if we // imported all the correct requirements above, we're probably missing @@ -464,49 +570,6 @@ func CreateModFile(ctx context.Context, modPath string) { } } -// checkModulePathLax checks that the path meets some minimum requirements -// to avoid confusing users or the module cache. The requirements are weaker -// than those of module.CheckPath to allow room for weakening module path -// requirements in the future, but strong enough to help users avoid significant -// problems. -func checkModulePathLax(p string) error { - // TODO(matloob): Replace calls of this function in this CL with calls - // to module.CheckImportPath once it's been laxened, if it becomes laxened. - // See golang.org/issue/29101 for a discussion about whether to make CheckImportPath - // more lax or more strict. - - errorf := func(format string, args ...interface{}) error { - return fmt.Errorf("invalid module path %q: %s", p, fmt.Sprintf(format, args...)) - } - - // Disallow shell characters " ' * < > ? ` | to avoid triggering bugs - // with file systems and subcommands. Disallow file path separators : and \ - // because path separators other than / will confuse the module cache. - // See fileNameOK in golang.org/x/mod/module/module.go. - shellChars := "`" + `\"'*<>?|` - fsChars := `\:` - if i := strings.IndexAny(p, shellChars); i >= 0 { - return errorf("contains disallowed shell character %q", p[i]) - } - if i := strings.IndexAny(p, fsChars); i >= 0 { - return errorf("contains disallowed path separator character %q", p[i]) - } - - // Ensure path.IsAbs and build.IsLocalImport are false, and that the path is - // invariant under path.Clean, also to avoid confusing the module cache. - if path.IsAbs(p) { - return errorf("is an absolute path") - } - if build.IsLocalImport(p) { - return errorf("is a local import path") - } - if path.Clean(p) != p { - return errorf("is not clean") - } - - return nil -} - // fixVersion returns a modfile.VersionFixer implemented using the Query function. // // It resolves commit hashes and branch names to versions, @@ -559,22 +622,42 @@ func fixVersion(ctx context.Context, fixed *bool) modfile.VersionFixer { // when there is no module root. Normally, this is forbidden because it's slow // and there's no way to make the result reproducible, but some commands // like 'go get' are expected to do this. +// +// This function affects the default cfg.BuildMod when outside of a module, +// so it can only be called prior to Init. func AllowMissingModuleImports() { + if initialized { + panic("AllowMissingModuleImports after Init") + } allowMissingModuleImports = true } -// modFileToBuildList initializes buildList from the modFile. -func modFileToBuildList() { - Target = modFile.Module.Mod - targetPrefix = Target.Path - if rel := search.InDir(base.Cwd, cfg.GOROOTsrc); rel != "" { +// initTarget sets Target and associated variables according to modFile, +func initTarget(m module.Version) { + Target = m + targetPrefix = m.Path + + if rel := search.InDir(base.Cwd(), cfg.GOROOTsrc); rel != "" { targetInGorootSrc = true - if Target.Path == "std" { + if m.Path == "std" { + // The "std" module in GOROOT/src is the Go standard library. Unlike other + // modules, the packages in the "std" module have no import-path prefix. + // + // Modules named "std" outside of GOROOT/src do not receive this special + // treatment, so it is possible to run 'go test .' in other GOROOTs to + // test individual packages using a combination of the modified package + // and the ordinary standard library. + // (See https://golang.org/issue/30756.) targetPrefix = "" } } +} - list := []module.Version{Target} +// requirementsFromModFile returns the set of non-excluded requirements from +// the global modFile. +func requirementsFromModFile() *Requirements { + roots := make([]module.Version, 0, len(modFile.Require)) + direct := map[string]bool{} for _, r := range modFile.Require { if index != nil && index.exclude[r.Mod] { if cfg.BuildMod == "mod" { @@ -582,11 +665,17 @@ func modFileToBuildList() { } else { fmt.Fprintf(os.Stderr, "go: ignoring requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version) } - } else { - list = append(list, r.Mod) + continue + } + + roots = append(roots, r.Mod) + if !r.Indirect { + direct[r.Mod.Path] = true } } - buildList = list + module.Sort(roots) + rs := newRequirements(modDepthFromGoVersion(modFileGoVersion()), roots, direct) + return rs } // setDefaultBuildMod sets a default value for cfg.BuildMod if the -mod flag @@ -605,7 +694,11 @@ func setDefaultBuildMod() { return } if modRoot == "" { - cfg.BuildMod = "readonly" + if allowMissingModuleImports { + cfg.BuildMod = "mod" + } else { + cfg.BuildMod = "readonly" + } return } @@ -634,6 +727,17 @@ func setDefaultBuildMod() { // convertLegacyConfig imports module requirements from a legacy vendoring // configuration file, if one is present. func convertLegacyConfig(modPath string) (from string, err error) { + noneSelected := func(path string) (version string) { return "none" } + queryPackage := func(path, rev string) (module.Version, error) { + pkgMods, modOnly, err := QueryPattern(context.Background(), path, rev, noneSelected, nil) + if err != nil { + return module.Version{}, err + } + if len(pkgMods) > 0 { + return pkgMods[0].Mod, nil + } + return modOnly.Mod, nil + } for _, name := range altConfigs { cfg := filepath.Join(modRoot, name) data, err := os.ReadFile(cfg) @@ -643,27 +747,57 @@ func convertLegacyConfig(modPath string) (from string, err error) { return "", nil } cfg = filepath.ToSlash(cfg) - err := modconv.ConvertLegacyConfig(modFile, cfg, data) + err := modconv.ConvertLegacyConfig(modFile, cfg, data, queryPackage) return name, err } } return "", nil } -// addGoStmt adds a go directive to the go.mod file if it does not already include one. -// The 'go' version added, if any, is the latest version supported by this toolchain. -func addGoStmt() { +// addGoStmt adds a go directive to the go.mod file if it does not already +// include one. The 'go' version added, if any, is the latest version supported +// by this toolchain. +func addGoStmt(v string) { if modFile.Go != nil && modFile.Go.Version != "" { return } + if err := modFile.AddGoStmt(v); err != nil { + base.Fatalf("go: internal error: %v", err) + } + rawGoVersion.Store(Target, v) +} + +// LatestGoVersion returns the latest version of the Go language supported by +// this toolchain, like "1.17". +func LatestGoVersion() string { tags := build.Default.ReleaseTags version := tags[len(tags)-1] if !strings.HasPrefix(version, "go") || !modfile.GoVersionRE.MatchString(version[2:]) { - base.Fatalf("go: unrecognized default version %q", version) + base.Fatalf("go: internal error: unrecognized default version %q", version) } - if err := modFile.AddGoStmt(version[2:]); err != nil { - base.Fatalf("go: internal error: %v", err) + return version[2:] +} + +// priorGoVersion returns the Go major release immediately preceding v, +// or v itself if v is the first Go major release (1.0) or not a supported +// Go version. +func priorGoVersion(v string) string { + vTag := "go" + v + tags := build.Default.ReleaseTags + for i, tag := range tags { + if tag == vTag { + if i == 0 { + return v + } + + version := tags[i-1] + if !strings.HasPrefix(version, "go") || !modfile.GoVersionRE.MatchString(version[2:]) { + base.Fatalf("go: internal error: unrecognized version %q", version) + } + return version[2:] + } } + return v } var altConfigs = []string{ @@ -780,14 +914,8 @@ func findModulePath(dir string) (string, error) { } if rel := search.InDir(dir, filepath.Join(gpdir, "src")); rel != "" && rel != "." { path := filepath.ToSlash(rel) - // TODO(matloob): replace this with module.CheckImportPath - // once it's been laxened. - // Only checkModulePathLax here. There are some unpublishable - // module names that are compatible with checkModulePathLax - // but they already work in GOPATH so don't break users - // trying to do a build with modules. gorelease will alert users - // publishing their modules to fix their paths. - if err := checkModulePathLax(path); err != nil { + // gorelease will alert users publishing their modules to fix their paths. + if err := module.CheckImportPath(path); err != nil { badPathErr = err break } @@ -847,71 +975,51 @@ func AllowWriteGoMod() { allowWriteGoMod = true } -// MinReqs returns a Reqs with minimal additional dependencies of Target, -// as will be written to go.mod. -func MinReqs() mvs.Reqs { - retain := append([]string{}, additionalExplicitRequirements...) - for _, m := range buildList[1:] { - _, explicit := index.require[m] - if explicit || loaded.direct[m.Path] { - retain = append(retain, m.Path) - } - } - sort.Strings(retain) - str.Uniq(&retain) - min, err := mvs.Req(Target, retain, &mvsReqs{buildList: buildList}) - if err != nil { - base.Fatalf("go: %v", err) +// WriteGoMod writes the current build list back to go.mod. +func WriteGoMod(ctx context.Context) { + if !allowWriteGoMod { + panic("WriteGoMod called while disallowed") } - return &mvsReqs{buildList: append([]module.Version{Target}, min...)} + commitRequirements(ctx, modFileGoVersion(), LoadModFile(ctx)) } -// WriteGoMod writes the current build list back to go.mod. -func WriteGoMod() { - // If we're using -mod=vendor we basically ignored - // go.mod, so definitely don't try to write back our - // incomplete view of the world. - if !allowWriteGoMod || cfg.BuildMod == "vendor" { +// commitRequirements writes sets the global requirements variable to rs and +// writes its contents back to the go.mod file on disk. +func commitRequirements(ctx context.Context, goVersion string, rs *Requirements) { + requirements = rs + + if !allowWriteGoMod { + // Some package outside of modload promised to update the go.mod file later. return } - // If we aren't in a module, we don't have anywhere to write a go.mod file. if modRoot == "" { + // We aren't in a module, so we don't have anywhere to write a go.mod file. return } - if cfg.BuildMod != "readonly" { - addGoStmt() + var list []*modfile.Require + for _, m := range rs.rootModules { + list = append(list, &modfile.Require{ + Mod: m, + Indirect: !rs.direct[m.Path], + }) } - - if loaded != nil { - reqs := MinReqs() - min, err := reqs.Required(Target) - if err != nil { - base.Fatalf("go: %v", err) - } - var list []*modfile.Require - for _, m := range min { - list = append(list, &modfile.Require{ - Mod: m, - Indirect: !loaded.direct[m.Path], - }) - } + if goVersion != "" { + modFile.AddGoStmt(goVersion) + } + if semver.Compare("v"+modFileGoVersion(), separateIndirectVersionV) < 0 { modFile.SetRequire(list) + } else { + modFile.SetRequireSeparateIndirect(list) } modFile.Cleanup() dirty := index.modFileIsDirty(modFile) - if dirty && cfg.BuildMod == "readonly" { + if dirty && cfg.BuildMod != "mod" { // If we're about to fail due to -mod=readonly, // prefer to report a dirty go.mod over a dirty go.sum - if cfg.BuildModExplicit { - base.Fatalf("go: updates to go.mod needed, disabled by -mod=readonly") - } else if cfg.BuildModReason != "" { - base.Fatalf("go: updates to go.mod needed, disabled by -mod=readonly\n\t(%s)", cfg.BuildModReason) - } else { - base.Fatalf("go: updates to go.mod needed; to update it:\n\tgo mod tidy") - } + base.Fatalf("go: %v", errGoModDirty) } if !dirty && cfg.CmdName != "mod tidy" { @@ -920,7 +1028,14 @@ func WriteGoMod() { // Don't write go.mod, but write go.sum in case we added or trimmed sums. // 'go mod init' shouldn't write go.sum, since it will be incomplete. if cfg.CmdName != "mod init" { - modfetch.WriteGoSum(keepSums(true)) + modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums)) + } + return + } + gomod := ModFilePath() + if _, ok := fsys.OverlayPath(gomod); ok { + if dirty { + base.Fatalf("go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay") } return } @@ -936,7 +1051,7 @@ func WriteGoMod() { // Update go.sum after releasing the side lock and refreshing the index. // 'go mod init' shouldn't write go.sum, since it will be incomplete. if cfg.CmdName != "mod init" { - modfetch.WriteGoSum(keepSums(true)) + modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums)) } }() @@ -973,100 +1088,99 @@ func WriteGoMod() { } } -// keepSums returns a set of module sums to preserve in go.sum. The set -// includes entries for all modules used to load packages (according to -// the last load function such as LoadPackages or ImportFromFiles). -// It also contains entries for go.mod files needed for MVS (the version -// of these entries ends with "/go.mod"). -// -// If keepBuildListZips is true, the set also includes sums for zip files for -// all modules in the build list with replacements applied. 'go get' and -// 'go mod download' may add sums to this set when adding a requirement on a -// module without a root package or when downloading a direct or indirect -// dependency. -func keepSums(keepBuildListZips bool) map[module.Version]bool { - // Re-derive the build list using the current list of direct requirements. - // Keep the sum for the go.mod of each visited module version (or its - // replacement). - modkey := func(m module.Version) module.Version { - return module.Version{Path: m.Path, Version: m.Version + "/go.mod"} - } +// keepSums returns the set of modules (and go.mod file entries) for which +// checksums would be needed in order to reload the same set of packages +// loaded by the most recent call to LoadPackages or ImportFromFiles, +// including any go.mod files needed to reconstruct the MVS result, +// in addition to the checksums for every module in keepMods. +func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums) map[module.Version]bool { + // Every module in the full module graph contributes its requirements, + // so in order to ensure that the build list itself is reproducible, + // we need sums for every go.mod in the graph (regardless of whether + // that version is selected). keep := make(map[module.Version]bool) - var mu sync.Mutex - reqs := &keepSumReqs{ - Reqs: &mvsReqs{buildList: buildList}, - visit: func(m module.Version) { - // If we build using a replacement module, keep the sum for the replacement, - // since that's the code we'll actually use during a build. - mu.Lock() - r := Replacement(m) - if r.Path == "" { - keep[modkey(m)] = true - } else { - keep[modkey(r)] = true - } - mu.Unlock() - }, - } - buildList, err := mvs.BuildList(Target, reqs) - if err != nil { - panic(fmt.Sprintf("unexpected error reloading build list: %v", err)) - } - - actualMods := make(map[string]module.Version) - for _, m := range buildList[1:] { - if r := Replacement(m); r.Path != "" { - actualMods[m.Path] = r - } else { - actualMods[m.Path] = m - } - } // Add entries for modules in the build list with paths that are prefixes of - // paths of loaded packages. We need to retain sums for modules needed to - // report ambiguous import errors. We use our re-derived build list, - // since the global build list may have been tidied. - if loaded != nil { - for _, pkg := range loaded.pkgs { - if pkg.testOf != nil || pkg.inStd || module.CheckImportPath(pkg.path) != nil { + // paths of loaded packages. We need to retain sums for all of these modules — + // not just the modules containing the actual packages — in order to rule out + // ambiguous import errors the next time we load the package. + if ld != nil { + for _, pkg := range ld.pkgs { + // We check pkg.mod.Path here instead of pkg.inStd because the + // pseudo-package "C" is not in std, but not provided by any module (and + // shouldn't force loading the whole module graph). + if pkg.testOf != nil || (pkg.mod.Path == "" && pkg.err == nil) || module.CheckImportPath(pkg.path) != nil { continue } + + if rs.depth == lazy && pkg.mod.Path != "" { + if v, ok := rs.rootSelected(pkg.mod.Path); ok && v == pkg.mod.Version { + // pkg was loaded from a root module, and because the main module is + // lazy we do not check non-root modules for conflicts for packages + // that can be found in roots. So we only need the checksums for the + // root modules that may contain pkg, not all possible modules. + for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) { + if v, ok := rs.rootSelected(prefix); ok && v != "none" { + m := module.Version{Path: prefix, Version: v} + keep[resolveReplacement(m)] = true + } + } + continue + } + } + + mg, _ := rs.Graph(ctx) for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) { - if m, ok := actualMods[prefix]; ok { - keep[m] = true + if v := mg.Selected(prefix); v != "none" { + m := module.Version{Path: prefix, Version: v} + keep[resolveReplacement(m)] = true } } } } - // Add entries for the zip of each module in the build list. - // We might not need all of these (tidy does not add them), but they may be - // added by a specific 'go get' or 'go mod download' command to resolve - // missing import sum errors. - if keepBuildListZips { - for _, m := range actualMods { - keep[m] = true + if rs.graph.Load() == nil { + // The module graph was not loaded, possibly because the main module is lazy + // or possibly because we haven't needed to load the graph yet. + // Save sums for the root modules (or their replacements), but don't + // incur the cost of loading the graph just to find and retain the sums. + for _, m := range rs.rootModules { + r := resolveReplacement(m) + keep[modkey(r)] = true + if which == addBuildListZipSums { + keep[r] = true + } + } + } else { + mg, _ := rs.Graph(ctx) + mg.WalkBreadthFirst(func(m module.Version) { + if _, ok := mg.RequiredBy(m); ok { + // The requirements from m's go.mod file are present in the module graph, + // so they are relevant to the MVS result regardless of whether m was + // actually selected. + keep[modkey(resolveReplacement(m))] = true + } + }) + + if which == addBuildListZipSums { + for _, m := range mg.BuildList() { + keep[resolveReplacement(m)] = true + } } } return keep } -// keepSumReqs embeds another Reqs implementation. The Required method -// calls visit for each version in the module graph. -type keepSumReqs struct { - mvs.Reqs - visit func(module.Version) -} +type whichSums int8 -func (r *keepSumReqs) Required(m module.Version) ([]module.Version, error) { - r.visit(m) - return r.Reqs.Required(m) -} +const ( + loadedZipSumsOnly = whichSums(iota) + addBuildListZipSums +) -func TrimGoSum() { - // Don't retain sums for the zip file of every module in the build list. - // We may not need them all to build the main module's packages. - keepBuildListZips := false - modfetch.TrimGoSum(keepSums(keepBuildListZips)) +// modKey returns the module.Version under which the checksum for m's go.mod +// file is stored in the go.sum file. +func modkey(m module.Version) module.Version { + return module.Version{Path: m.Path, Version: m.Version + "/go.mod"} } diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go index 7b1aa7fd41311b966290d1e1f28ce4e2d6ce802a..ccdeb9b1d11e8650ec420169723ba7d1aae4c2ba 100644 --- a/src/cmd/go/internal/modload/list.go +++ b/src/cmd/go/internal/modload/list.go @@ -20,25 +20,42 @@ import ( "golang.org/x/mod/module" ) -func ListModules(ctx context.Context, args []string, listU, listVersions, listRetracted bool) []*modinfo.ModulePublic { - mods := listModules(ctx, args, listVersions, listRetracted) +type ListMode int + +const ( + ListU ListMode = 1 << iota + ListRetracted + ListDeprecated + ListVersions + ListRetractedVersions +) + +// ListModules returns a description of the modules matching args, if known, +// along with any error preventing additional matches from being identified. +// +// The returned slice can be nonempty even if the error is non-nil. +func ListModules(ctx context.Context, args []string, mode ListMode) ([]*modinfo.ModulePublic, error) { + rs, mods, err := listModules(ctx, LoadModFile(ctx), args, mode) type token struct{} sem := make(chan token, runtime.GOMAXPROCS(0)) - if listU || listVersions || listRetracted { + if mode != 0 { for _, m := range mods { add := func(m *modinfo.ModulePublic) { sem <- token{} go func() { - if listU { + if mode&ListU != 0 { addUpdate(ctx, m) } - if listVersions { - addVersions(ctx, m, listRetracted) + if mode&ListVersions != 0 { + addVersions(ctx, m, mode&ListRetractedVersions != 0) } - if listRetracted || listU { + if mode&ListRetracted != 0 { addRetraction(ctx, m) } + if mode&ListDeprecated != 0 { + addDeprecation(ctx, m) + } <-sem }() } @@ -54,17 +71,18 @@ func ListModules(ctx context.Context, args []string, listU, listVersions, listRe sem <- token{} } - return mods + if err == nil { + commitRequirements(ctx, modFileGoVersion(), rs) + } + return mods, err } -func listModules(ctx context.Context, args []string, listVersions, listRetracted bool) []*modinfo.ModulePublic { - LoadAllModules(ctx) +func listModules(ctx context.Context, rs *Requirements, args []string, mode ListMode) (_ *Requirements, mods []*modinfo.ModulePublic, mgErr error) { if len(args) == 0 { - return []*modinfo.ModulePublic{moduleInfo(ctx, buildList[0], true, listRetracted)} + return rs, []*modinfo.ModulePublic{moduleInfo(ctx, rs, Target, mode)}, nil } - var mods []*modinfo.ModulePublic - matchedBuildList := make([]bool, len(buildList)) + needFullGraph := false for _, arg := range args { if strings.Contains(arg, `\`) { base.Fatalf("go: module paths never use backslash") @@ -72,22 +90,62 @@ func listModules(ctx context.Context, args []string, listVersions, listRetracted if search.IsRelativePath(arg) { base.Fatalf("go: cannot use relative path %s to specify module", arg) } - if !HasModRoot() && (arg == "all" || strings.Contains(arg, "...")) { - base.Fatalf("go: cannot match %q: %v", arg, ErrNoModRoot) + if arg == "all" || strings.Contains(arg, "...") { + needFullGraph = true + if !HasModRoot() { + base.Fatalf("go: cannot match %q: %v", arg, ErrNoModRoot) + } + continue } if i := strings.Index(arg, "@"); i >= 0 { path := arg[:i] vers := arg[i+1:] + if vers == "upgrade" || vers == "patch" { + if _, ok := rs.rootSelected(path); !ok || rs.depth == eager { + needFullGraph = true + if !HasModRoot() { + base.Fatalf("go: cannot match %q: %v", arg, ErrNoModRoot) + } + } + } + continue + } + if _, ok := rs.rootSelected(arg); !ok || rs.depth == eager { + needFullGraph = true + if mode&ListVersions == 0 && !HasModRoot() { + base.Fatalf("go: cannot match %q without -versions or an explicit version: %v", arg, ErrNoModRoot) + } + } + } + + var mg *ModuleGraph + if needFullGraph { + rs, mg, mgErr = expandGraph(ctx, rs) + } + + matchedModule := map[module.Version]bool{} + for _, arg := range args { + if i := strings.Index(arg, "@"); i >= 0 { + path := arg[:i] + vers := arg[i+1:] + var current string - for _, m := range buildList { - if m.Path == path { - current = m.Version - break + if mg == nil { + current, _ = rs.rootSelected(path) + } else { + current = mg.Selected(path) + } + if current == "none" && mgErr != nil { + if vers == "upgrade" || vers == "patch" { + // The module graph is incomplete, so we don't know what version we're + // actually upgrading from. + // mgErr is already set, so just skip this module. + continue } } allowed := CheckAllowed - if IsRevisionQuery(vers) || listRetracted { + if IsRevisionQuery(vers) || mode&ListRetracted != 0 { // Allow excluded and retracted versions if the user asked for a // specific revision or used 'go list -retracted'. allowed = nil @@ -101,75 +159,79 @@ func listModules(ctx context.Context, args []string, listVersions, listRetracted }) continue } - mod := moduleInfo(ctx, module.Version{Path: path, Version: info.Version}, false, listRetracted) + + // Indicate that m was resolved from outside of rs by passing a nil + // *Requirements instead. + var noRS *Requirements + + mod := moduleInfo(ctx, noRS, module.Version{Path: path, Version: info.Version}, mode) mods = append(mods, mod) continue } // Module path or pattern. var match func(string) bool - var literal bool if arg == "all" { match = func(string) bool { return true } } else if strings.Contains(arg, "...") { match = search.MatchPattern(arg) } else { - match = func(p string) bool { return arg == p } - literal = true - } - matched := false - for i, m := range buildList { - if i == 0 && !HasModRoot() { - // The root module doesn't actually exist: omit it. + var v string + if mg == nil { + var ok bool + v, ok = rs.rootSelected(arg) + if !ok { + // We checked rootSelected(arg) in the earlier args loop, so if there + // is no such root we should have loaded a non-nil mg. + panic(fmt.Sprintf("internal error: root requirement expected but not found for %v", arg)) + } + } else { + v = mg.Selected(arg) + } + if v == "none" && mgErr != nil { + // mgErr is already set, so just skip this module. continue } + if v != "none" { + mods = append(mods, moduleInfo(ctx, rs, module.Version{Path: arg, Version: v}, mode)) + } else if cfg.BuildMod == "vendor" { + // In vendor mode, we can't determine whether a missing module is “a + // known dependency” because the module graph is incomplete. + // Give a more explicit error message. + mods = append(mods, &modinfo.ModulePublic{ + Path: arg, + Error: modinfoError(arg, "", errors.New("can't resolve module using the vendor directory\n\t(Use -mod=mod or -mod=readonly to bypass.)")), + }) + } else if mode&ListVersions != 0 { + // Don't make the user provide an explicit '@latest' when they're + // explicitly asking what the available versions are. Instead, return a + // module with version "none", to which we can add the requested list. + mods = append(mods, &modinfo.ModulePublic{Path: arg}) + } else { + mods = append(mods, &modinfo.ModulePublic{ + Path: arg, + Error: modinfoError(arg, "", errors.New("not a known dependency")), + }) + } + continue + } + + matched := false + for _, m := range mg.BuildList() { if match(m.Path) { matched = true - if !matchedBuildList[i] { - matchedBuildList[i] = true - mods = append(mods, moduleInfo(ctx, m, true, listRetracted)) + if !matchedModule[m] { + matchedModule[m] = true + mods = append(mods, moduleInfo(ctx, rs, m, mode)) } } } if !matched { - if literal { - if listVersions { - // Don't make the user provide an explicit '@latest' when they're - // explicitly asking what the available versions are. - // Instead, resolve the module, even if it isn't an existing dependency. - info, err := Query(ctx, arg, "latest", "", nil) - if err == nil { - mod := moduleInfo(ctx, module.Version{Path: arg, Version: info.Version}, false, listRetracted) - mods = append(mods, mod) - } else { - mods = append(mods, &modinfo.ModulePublic{ - Path: arg, - Error: modinfoError(arg, "", err), - }) - } - continue - } - if cfg.BuildMod == "vendor" { - // In vendor mode, we can't determine whether a missing module is “a - // known dependency” because the module graph is incomplete. - // Give a more explicit error message. - mods = append(mods, &modinfo.ModulePublic{ - Path: arg, - Error: modinfoError(arg, "", errors.New("can't resolve module using the vendor directory\n\t(Use -mod=mod or -mod=readonly to bypass.)")), - }) - } else { - mods = append(mods, &modinfo.ModulePublic{ - Path: arg, - Error: modinfoError(arg, "", errors.New("not a known dependency")), - }) - } - } else { - fmt.Fprintf(os.Stderr, "warning: pattern %q matched no module dependencies\n", arg) - } + fmt.Fprintf(os.Stderr, "warning: pattern %q matched no module dependencies\n", arg) } } - return mods + return rs, mods, mgErr } // modinfoError wraps an error to create an error message in diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go index 154fc3c6f0afc6bba814240e3504ef0a94d290ed..bce9ad85f42e6c593724c6e05bbfb31db6b57521 100644 --- a/src/cmd/go/internal/modload/load.go +++ b/src/cmd/go/internal/modload/load.go @@ -49,7 +49,7 @@ package modload // Because "go mod vendor" prunes out the tests of vendored packages, the // behavior of the "all" pattern with -mod=vendor in Go 1.11–1.15 is the same // as the "all" pattern (regardless of the -mod flag) in 1.16+. -// The allClosesOverTests parameter to the loader indicates whether the "all" +// The loader uses the GoVersion parameter to determine whether the "all" // pattern should close over tests (as in Go 1.11–1.15) or stop at only those // packages transitively imported by the packages and tests in the main module // ("all" in Go 1.16+ and "go mod vendor" in Go 1.11+). @@ -121,19 +121,49 @@ import ( "cmd/go/internal/str" "golang.org/x/mod/module" + "golang.org/x/mod/semver" ) // loaded is the most recently-used package loader. // It holds details about individual packages. +// +// This variable should only be accessed directly in top-level exported +// functions. All other functions that require or produce a *loader should pass +// or return it as an explicit parameter. var loaded *loader // PackageOpts control the behavior of the LoadPackages function. type PackageOpts struct { + // GoVersion is the Go version to which the go.mod file should be updated + // after packages have been loaded. + // + // An empty GoVersion means to use the Go version already specified in the + // main module's go.mod file, or the latest Go version if there is no main + // module. + GoVersion string + // Tags are the build tags in effect (as interpreted by the // cmd/go/internal/imports package). // If nil, treated as equivalent to imports.Tags(). Tags map[string]bool + // Tidy, if true, requests that the build list and go.sum file be reduced to + // the minimial dependencies needed to reproducibly reload the requested + // packages. + Tidy bool + + // TidyCompatibleVersion is the oldest Go version that must be able to + // reproducibly reload the requested packages. + // + // If empty, the compatible version is the Go version immediately prior to the + // 'go' version listed in the go.mod file. + TidyCompatibleVersion string + + // VendorModulesInGOROOTSrc indicates that if we are within a module in + // GOROOT/src, packages in the module's vendor directory should be resolved as + // actual module dependencies (instead of standard-library packages). + VendorModulesInGOROOTSrc bool + // ResolveMissingImports indicates that we should attempt to add module // dependencies as needed to resolve imports of packages that are not found. // @@ -141,6 +171,11 @@ type PackageOpts struct { // if the flag is set to "readonly" (the default) or "vendor". ResolveMissingImports bool + // AssumeRootsImported indicates that the transitive dependencies of the root + // packages should be treated as if those roots will be imported by the main + // module. + AssumeRootsImported bool + // AllowPackage, if non-nil, is called after identifying the module providing // each package. If AllowPackage returns a non-nil error, that error is set // for the package, and the imports and test of that package will not be @@ -166,9 +201,16 @@ type PackageOpts struct { // an error occurs. AllowErrors bool - // SilenceErrors indicates that LoadPackages should not print errors - // that occur while loading packages. SilenceErrors implies AllowErrors. - SilenceErrors bool + // SilencePackageErrors indicates that LoadPackages should not print errors + // that occur while matching or loading packages, and should not terminate the + // process if such an error occurs. + // + // Errors encountered in the module graph will still be reported. + // + // The caller may retrieve the silenced package errors using the Lookup + // function, and matching errors are still populated in the Errs field of the + // associated search.Match.) + SilencePackageErrors bool // SilenceMissingStdImports indicates that LoadPackages should not print // errors or terminate the process if an imported package is missing, and the @@ -176,6 +218,15 @@ type PackageOpts struct { // future version). SilenceMissingStdImports bool + // SilenceNoGoErrors indicates that LoadPackages should not print + // imports.ErrNoGo errors. + // This allows the caller to invoke LoadPackages (and report other errors) + // without knowing whether the requested packages exist for the given tags. + // + // Note that if a requested package does not exist *at all*, it will fail + // during module resolution and the error will not be suppressed. + SilenceNoGoErrors bool + // SilenceUnmatchedWarnings suppresses the warnings normally emitted for // patterns that did not match any packages. SilenceUnmatchedWarnings bool @@ -184,7 +235,6 @@ type PackageOpts struct { // LoadPackages identifies the set of packages matching the given patterns and // loads the packages in the import graph rooted at that set. func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (matches []*search.Match, loadedPackages []string) { - LoadModFile(ctx) if opts.Tags == nil { opts.Tags = imports.Tags() } @@ -199,13 +249,13 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma } } - updateMatches := func(ld *loader) { + updateMatches := func(rs *Requirements, ld *loader) { for _, m := range matches { switch { case m.IsLocal(): // Evaluate list of file system directories on first iteration. if m.Dirs == nil { - matchLocalDirs(m) + matchLocalDirs(ctx, m, rs) } // Make a copy of the directory list and translate to import paths. @@ -216,7 +266,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma // the loader iterations. m.Pkgs = m.Pkgs[:0] for _, dir := range m.Dirs { - pkg, err := resolveLocalPackage(dir) + pkg, err := resolveLocalPackage(ctx, dir, rs) if err != nil { if !m.IsLiteral() && (err == errPkgIsBuiltin || err == errPkgIsGorootSrc) { continue // Don't include "builtin" or GOROOT/src in wildcard patterns. @@ -239,7 +289,17 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma case strings.Contains(m.Pattern(), "..."): m.Errs = m.Errs[:0] - matchPackages(ctx, m, opts.Tags, includeStd, buildList) + mg, err := rs.Graph(ctx) + if err != nil { + // The module graph is (or may be) incomplete — perhaps we failed to + // load the requirements of some module. This is an error in matching + // the patterns to packages, because we may be missing some packages + // or we may erroneously match packages in the wrong versions of + // modules. However, for cases like 'go list -e', the error should not + // necessarily prevent us from loading the packages we could find. + m.Errs = append(m.Errs, err) + } + matchPackages(ctx, m, opts.Tags, includeStd, mg.BuildList()) case m.Pattern() == "all": if ld == nil { @@ -264,14 +324,16 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma } } - loaded = loadFromRoots(loaderParams{ - PackageOpts: opts, + initialRS, _ := loadModFile(ctx) // Ignore needCommit — we're going to commit at the end regardless. - allClosesOverTests: index.allPatternClosesOverTests() && !opts.UseVendorAll, - allPatternIsRoot: allPatternIsRoot, + ld := loadFromRoots(ctx, loaderParams{ + PackageOpts: opts, + requirements: initialRS, - listRoots: func() (roots []string) { - updateMatches(nil) + allPatternIsRoot: allPatternIsRoot, + + listRoots: func(rs *Requirements) (roots []string) { + updateMatches(rs, nil) for _, m := range matches { roots = append(roots, m.Pkgs...) } @@ -280,47 +342,14 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma }) // One last pass to finalize wildcards. - updateMatches(loaded) - - // Report errors, if any. - checkMultiplePaths() - for _, pkg := range loaded.pkgs { - if pkg.err != nil { - if sumErr := (*ImportMissingSumError)(nil); errors.As(pkg.err, &sumErr) { - if importer := pkg.stack; importer != nil { - sumErr.importer = importer.path - sumErr.importerVersion = importer.mod.Version - sumErr.importerIsTest = importer.testOf != nil - } - } - silence := opts.SilenceErrors - if stdErr := (*ImportMissingError)(nil); errors.As(pkg.err, &stdErr) && - stdErr.isStd && opts.SilenceMissingStdImports { - silence = true - } + updateMatches(ld.requirements, ld) - if !silence { - if opts.AllowErrors { - fmt.Fprintf(os.Stderr, "%s: %v\n", pkg.stackText(), pkg.err) - } else { - base.Errorf("%s: %v", pkg.stackText(), pkg.err) - } - } - } - if !pkg.isTest() { - loadedPackages = append(loadedPackages, pkg.path) - } - } - if !opts.SilenceErrors { - // Also list errors in matching patterns (such as directory permission - // errors for wildcard patterns). + // List errors in matching patterns (such as directory permission + // errors for wildcard patterns). + if !ld.SilencePackageErrors { for _, match := range matches { for _, err := range match.Errs { - if opts.AllowErrors { - fmt.Fprintf(os.Stderr, "%v\n", err) - } else { - base.Errorf("%v", err) - } + ld.errorf("%v\n", err) } } } @@ -330,15 +359,68 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma search.WarnUnmatched(matches) } - // Success! Update go.mod (if needed) and return the results. - WriteGoMod() + if opts.Tidy { + if cfg.BuildV { + mg, _ := ld.requirements.Graph(ctx) + + for _, m := range initialRS.rootModules { + var unused bool + if ld.requirements.depth == eager { + // m is unused if it was dropped from the module graph entirely. If it + // was only demoted from direct to indirect, it may still be in use via + // a transitive import. + unused = mg.Selected(m.Path) == "none" + } else { + // m is unused if it was dropped from the roots. If it is still present + // as a transitive dependency, that transitive dependency is not needed + // by any package or test in the main module. + _, ok := ld.requirements.rootSelected(m.Path) + unused = !ok + } + if unused { + fmt.Fprintf(os.Stderr, "unused %s\n", m.Path) + } + } + } + + keep := keepSums(ctx, ld, ld.requirements, loadedZipSumsOnly) + if compatDepth := modDepthFromGoVersion(ld.TidyCompatibleVersion); compatDepth != ld.requirements.depth { + compatRS := newRequirements(compatDepth, ld.requirements.rootModules, ld.requirements.direct) + ld.checkTidyCompatibility(ctx, compatRS) + + for m := range keepSums(ctx, ld, compatRS, loadedZipSumsOnly) { + keep[m] = true + } + } + + if allowWriteGoMod { + modfetch.TrimGoSum(keep) + + // commitRequirements below will also call WriteGoSum, but the "keep" map + // we have here could be strictly larger: commitRequirements only commits + // loaded.requirements, but here we may have also loaded (and want to + // preserve checksums for) additional entities from compatRS, which are + // only needed for compatibility with ld.TidyCompatibleVersion. + modfetch.WriteGoSum(keep) + } + } + + // Success! Update go.mod and go.sum (if needed) and return the results. + loaded = ld + commitRequirements(ctx, loaded.GoVersion, loaded.requirements) + + for _, pkg := range ld.pkgs { + if !pkg.isTest() { + loadedPackages = append(loadedPackages, pkg.path) + } + } sort.Strings(loadedPackages) return matches, loadedPackages } // matchLocalDirs is like m.MatchDirs, but tries to avoid scanning directories // outside of the standard library and active modules. -func matchLocalDirs(m *search.Match) { +func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) { if !m.IsLocal() { panic(fmt.Sprintf("internal error: resolveLocalDirs on non-local pattern %s", m.Pattern())) } @@ -352,9 +434,9 @@ func matchLocalDirs(m *search.Match) { dir := filepath.Dir(filepath.Clean(m.Pattern()[:i+3])) absDir := dir if !filepath.IsAbs(dir) { - absDir = filepath.Join(base.Cwd, dir) + absDir = filepath.Join(base.Cwd(), dir) } - if search.InDir(absDir, cfg.GOROOTsrc) == "" && search.InDir(absDir, ModRoot()) == "" && pathInModuleCache(absDir) == "" { + if search.InDir(absDir, cfg.GOROOTsrc) == "" && search.InDir(absDir, ModRoot()) == "" && pathInModuleCache(ctx, absDir, rs) == "" { m.Dirs = []string{} m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir))) return @@ -365,12 +447,12 @@ func matchLocalDirs(m *search.Match) { } // resolveLocalPackage resolves a filesystem path to a package path. -func resolveLocalPackage(dir string) (string, error) { +func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (string, error) { var absDir string if filepath.IsAbs(dir) { absDir = filepath.Clean(dir) } else { - absDir = filepath.Join(base.Cwd, dir) + absDir = filepath.Join(base.Cwd(), dir) } bp, err := cfg.BuildContext.ImportDir(absDir, 0) @@ -456,7 +538,7 @@ func resolveLocalPackage(dir string) (string, error) { return pkg, nil } - pkg := pathInModuleCache(absDir) + pkg := pathInModuleCache(ctx, absDir, rs) if pkg == "" { return "", fmt.Errorf("directory %s outside available modules", base.ShortPath(absDir)) } @@ -471,7 +553,7 @@ var ( // pathInModuleCache returns the import path of the directory dir, // if dir is in the module cache copy of a module in our build list. -func pathInModuleCache(dir string) string { +func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string { tryMod := func(m module.Version) (string, bool) { var root string var err error @@ -501,20 +583,48 @@ func pathInModuleCache(dir string) string { return path.Join(m.Path, filepath.ToSlash(sub)), true } - for _, m := range buildList[1:] { - if importPath, ok := tryMod(m); ok { - // checkMultiplePaths ensures that a module can be used for at most one - // requirement, so this must be it. - return importPath + if rs.depth == lazy { + for _, m := range rs.rootModules { + if v, _ := rs.rootSelected(m.Path); v != m.Version { + continue // m is a root, but we have a higher root for the same path. + } + if importPath, ok := tryMod(m); ok { + // checkMultiplePaths ensures that a module can be used for at most one + // requirement, so this must be it. + return importPath + } + } + } + + // None of the roots contained dir, or we're in eager mode and want to load + // the full module graph more aggressively. Either way, check the full graph + // to see if the directory is a non-root dependency. + // + // If the roots are not consistent with the full module graph, the selected + // versions of root modules may differ from what we already checked above. + // Re-check those paths too. + + mg, _ := rs.Graph(ctx) + var importPath string + for _, m := range mg.BuildList() { + var found bool + importPath, found = tryMod(m) + if found { + break } } - return "" + return importPath } // ImportFromFiles adds modules to the build list as needed // to satisfy the imports in the named Go source files. +// +// Errors in missing dependencies are silenced. +// +// TODO(bcmills): Silencing errors seems off. Take a closer look at this and +// figure out what the error-reporting actually ought to be. func ImportFromFiles(ctx context.Context, gofiles []string) { - LoadModFile(ctx) + rs := LoadModFile(ctx) tags := imports.Tags() imports, testImports, err := imports.ScanFiles(gofiles, tags) @@ -522,31 +632,32 @@ func ImportFromFiles(ctx context.Context, gofiles []string) { base.Fatalf("go: %v", err) } - loaded = loadFromRoots(loaderParams{ + loaded = loadFromRoots(ctx, loaderParams{ PackageOpts: PackageOpts{ Tags: tags, ResolveMissingImports: true, + SilencePackageErrors: true, }, - allClosesOverTests: index.allPatternClosesOverTests(), - listRoots: func() (roots []string) { + requirements: rs, + listRoots: func(*Requirements) (roots []string) { roots = append(roots, imports...) roots = append(roots, testImports...) return roots }, }) - WriteGoMod() + commitRequirements(ctx, loaded.GoVersion, loaded.requirements) } // DirImportPath returns the effective import path for dir, // provided it is within the main module, or else returns ".". -func DirImportPath(dir string) string { +func DirImportPath(ctx context.Context, dir string) string { if !HasModRoot() { return "." } - LoadModFile(context.TODO()) + LoadModFile(ctx) // Sets targetPrefix. if !filepath.IsAbs(dir) { - dir = filepath.Join(base.Cwd, dir) + dir = filepath.Join(base.Cwd(), dir) } else { dir = filepath.Clean(dir) } @@ -564,20 +675,6 @@ func DirImportPath(dir string) string { return "." } -// TargetPackages returns the list of packages in the target (top-level) module -// matching pattern, which may be relative to the working directory, under all -// build tag settings. -func TargetPackages(ctx context.Context, pattern string) *search.Match { - // TargetPackages is relative to the main module, so ensure that the main - // module is a thing that can contain packages. - LoadModFile(ctx) - ModRoot() - - m := search.NewMatch(pattern) - matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{Target}) - return m -} - // ImportMap returns the actual package import path // for an import path found in source code. // If the given import path does not appear in the source code @@ -609,29 +706,6 @@ func PackageModule(path string) module.Version { return pkg.mod } -// PackageImports returns the imports for the package named by the import path. -// Test imports will be returned as well if tests were loaded for the package -// (i.e., if "all" was loaded or if LoadTests was set and the path was matched -// by a command line argument). PackageImports will return nil for -// unknown package paths. -func PackageImports(path string) (imports, testImports []string) { - pkg, ok := loaded.pkgCache.Get(path).(*loadPkg) - if !ok { - return nil, nil - } - imports = make([]string, len(pkg.imports)) - for i, p := range pkg.imports { - imports[i] = p.path - } - if pkg.test != nil { - testImports = make([]string, len(pkg.test.imports)) - for i, p := range pkg.test.imports { - testImports[i] = p.path - } - } - return imports, testImports -} - // Lookup returns the source directory, import path, and any loading error for // the package at path as imported from the package in parentDir. // Lookup requires that one of the Load functions in this package has already @@ -670,26 +744,29 @@ func Lookup(parentPath string, parentIsStd bool, path string) (dir, realPath str type loader struct { loaderParams + // allClosesOverTests indicates whether the "all" pattern includes + // dependencies of tests outside the main module (as in Go 1.11–1.15). + // (Otherwise — as in Go 1.16+ — the "all" pattern includes only the packages + // transitively *imported by* the packages and tests in the main module.) + allClosesOverTests bool + work *par.Queue // reset on each iteration roots []*loadPkg pkgCache *par.Cache // package path (string) → *loadPkg pkgs []*loadPkg // transitive closure of loaded packages and tests; populated in buildStacks - - // computed at end of iterations - direct map[string]bool // imported directly by main module } // loaderParams configure the packages loaded by, and the properties reported // by, a loader instance. type loaderParams struct { PackageOpts + requirements *Requirements - allClosesOverTests bool // Does the "all" pattern include the transitive closure of tests of packages in "all"? - allPatternIsRoot bool // Is the "all" pattern an additional root? + allPatternIsRoot bool // Is the "all" pattern an additional root? - listRoots func() []string + listRoots func(rs *Requirements) []string } func (ld *loader) reset() { @@ -704,6 +781,16 @@ func (ld *loader) reset() { ld.pkgs = nil } +// errorf reports an error via either os.Stderr or base.Errorf, +// according to whether ld.AllowErrors is set. +func (ld *loader) errorf(format string, args ...interface{}) { + if ld.AllowErrors { + fmt.Fprintf(os.Stderr, format, args...) + } else { + base.Errorf(format, args...) + } +} + // A loadPkg records information about a single loaded package. type loadPkg struct { // Populated at construction time: @@ -756,6 +843,11 @@ const ( // are also roots (and must be marked pkgIsRoot). pkgIsRoot + // pkgFromRoot indicates that the package is in the transitive closure of + // imports starting at the roots. (Note that every package marked as pkgIsRoot + // is also trivially marked pkgFromRoot.) + pkgFromRoot + // pkgImportsLoaded indicates that the imports and testImports fields of a // loadPkg have been populated. pkgImportsLoaded @@ -796,6 +888,18 @@ func (pkg *loadPkg) isTest() bool { return pkg.testOf != nil } +// fromExternalModule reports whether pkg was loaded from a module other than +// the main module. +func (pkg *loadPkg) fromExternalModule() bool { + if pkg.mod.Path == "" { + return false // loaded from the standard library, not a module + } + if pkg.mod.Path == Target.Path { + return false // loaded from the main module. + } + return true +} + var errMissing = errors.New("cannot find package") // loadFromRoots attempts to load the build graph needed to process a set of @@ -804,20 +908,52 @@ var errMissing = errors.New("cannot find package") // The set of root packages is returned by the params.listRoots function, and // expanded to the full set of packages by tracing imports (and possibly tests) // as needed. -func loadFromRoots(params loaderParams) *loader { +func loadFromRoots(ctx context.Context, params loaderParams) *loader { ld := &loader{ loaderParams: params, work: par.NewQueue(runtime.GOMAXPROCS(0)), } + if ld.GoVersion == "" { + ld.GoVersion = modFileGoVersion() + + if ld.Tidy && semver.Compare("v"+ld.GoVersion, "v"+LatestGoVersion()) > 0 { + ld.errorf("go mod tidy: go.mod file indicates go %s, but maximum supported version is %s\n", ld.GoVersion, LatestGoVersion()) + base.ExitIfErrors() + } + } + + if ld.Tidy { + if ld.TidyCompatibleVersion == "" { + ld.TidyCompatibleVersion = priorGoVersion(ld.GoVersion) + } else if semver.Compare("v"+ld.TidyCompatibleVersion, "v"+ld.GoVersion) > 0 { + // Each version of the Go toolchain knows how to interpret go.mod and + // go.sum files produced by all previous versions, so a compatibility + // version higher than the go.mod version adds nothing. + ld.TidyCompatibleVersion = ld.GoVersion + } + } + + if semver.Compare("v"+ld.GoVersion, narrowAllVersionV) < 0 && !ld.UseVendorAll { + // The module's go version explicitly predates the change in "all" for lazy + // loading, so continue to use the older interpretation. + ld.allClosesOverTests = true + } + var err error - reqs := &mvsReqs{buildList: buildList} - buildList, err = mvs.BuildList(Target, reqs) + ld.requirements, err = convertDepth(ctx, ld.requirements, modDepthFromGoVersion(ld.GoVersion)) if err != nil { - base.Fatalf("go: %v", err) + ld.errorf("go: %v\n", err) + } + + if ld.requirements.depth == eager { + var err error + ld.requirements, _, err = expandGraph(ctx, ld.requirements) + if err != nil { + ld.errorf("go: %v\n", err) + } } - addedModuleFor := make(map[string]bool) for { ld.reset() @@ -825,9 +961,29 @@ func loadFromRoots(params loaderParams) *loader { // Note: the returned roots can change on each iteration, // since the expansion of package patterns depends on the // build list we're using. + rootPkgs := ld.listRoots(ld.requirements) + + if ld.requirements.depth == lazy && cfg.BuildMod == "mod" { + // Before we start loading transitive imports of packages, locate all of + // the root packages and promote their containing modules to root modules + // dependencies. If their go.mod files are tidy (the common case) and the + // set of root packages does not change then we can select the correct + // versions of all transitive imports on the first try and complete + // loading in a single iteration. + changedBuildList := ld.preloadRootModules(ctx, rootPkgs) + if changedBuildList { + // The build list has changed, so the set of root packages may have also + // changed. Start over to pick up the changes. (Preloading roots is much + // cheaper than loading the full import graph, so we would rather pay + // for an extra iteration of preloading than potentially end up + // discarding the result of a full iteration of loading.) + continue + } + } + inRoots := map[*loadPkg]bool{} - for _, path := range ld.listRoots() { - root := ld.pkg(path, pkgIsRoot) + for _, path := range rootPkgs { + root := ld.pkg(ctx, path, pkgIsRoot) if !inRoots[root] { ld.roots = append(ld.roots, root) inRoots[root] = true @@ -843,77 +999,314 @@ func loadFromRoots(params loaderParams) *loader { ld.buildStacks() + changed, err := ld.updateRequirements(ctx) + if err != nil { + ld.errorf("go: %v\n", err) + break + } + if changed { + // Don't resolve missing imports until the module graph have stabilized. + // If the roots are still changing, they may turn out to specify a + // requirement on the missing package(s), and we would rather use a + // version specified by a new root than add a new dependency on an + // unrelated version. + continue + } + if !ld.ResolveMissingImports || (!HasModRoot() && !allowMissingModuleImports) { // We've loaded as much as we can without resolving missing imports. break } - modAddedBy := ld.resolveMissingImports(addedModuleFor) + + modAddedBy := ld.resolveMissingImports(ctx) if len(modAddedBy) == 0 { + // The roots are stable, and we've resolved all of the missing packages + // that we can. break } - // Recompute buildList with all our additions. - reqs = &mvsReqs{buildList: buildList} - buildList, err = mvs.BuildList(Target, reqs) + toAdd := make([]module.Version, 0, len(modAddedBy)) + for m, _ := range modAddedBy { + toAdd = append(toAdd, m) + } + module.Sort(toAdd) // to make errors deterministic + + // We ran updateRequirements before resolving missing imports and it didn't + // make any changes, so we know that the requirement graph is already + // consistent with ld.pkgs: we don't need to pass ld.pkgs to updateRoots + // again. (That would waste time looking for changes that we have already + // applied.) + var noPkgs []*loadPkg + // We also know that we're going to call updateRequirements again next + // iteration so we don't need to also update it here. (That would waste time + // computing a "direct" map that we'll have to recompute later anyway.) + direct := ld.requirements.direct + rs, err := updateRoots(ctx, direct, ld.requirements, noPkgs, toAdd, ld.AssumeRootsImported) if err != nil { // If an error was found in a newly added module, report the package // import stack instead of the module requirement stack. Packages // are more descriptive. if err, ok := err.(*mvs.BuildListError); ok { if pkg := modAddedBy[err.Module()]; pkg != nil { - base.Fatalf("go: %s: %v", pkg.stackText(), err.Err) + ld.errorf("go: %s: %v\n", pkg.stackText(), err.Err) + break } } - base.Fatalf("go: %v", err) + ld.errorf("go: %v\n", err) + break } + if reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) { + // Something is deeply wrong. resolveMissingImports gave us a non-empty + // set of modules to add to the graph, but adding those modules had no + // effect — either they were already in the graph, or updateRoots did not + // add them as requested. + panic(fmt.Sprintf("internal error: adding %v to module graph had no effect on root requirements (%v)", toAdd, rs.rootModules)) + } + ld.requirements = rs } - base.ExitIfErrors() + base.ExitIfErrors() // TODO(bcmills): Is this actually needed? - // Compute directly referenced dependency modules. - ld.direct = make(map[string]bool) - for _, pkg := range ld.pkgs { - if pkg.mod == Target { - for _, dep := range pkg.imports { - if dep.mod.Path != "" && dep.mod.Path != Target.Path && index != nil { - _, explicit := index.require[dep.mod] - if allowWriteGoMod && cfg.BuildMod == "readonly" && !explicit { - // TODO(#40775): attach error to package instead of using - // base.Errorf. Ideally, 'go list' should not fail because of this, - // but today, LoadPackages calls WriteGoMod unconditionally, which - // would fail with a less clear message. - base.Errorf("go: %[1]s: package %[2]s imported from implicitly required module; to add missing requirements, run:\n\tgo get %[2]s@%[3]s", pkg.path, dep.path, dep.mod.Version) - } - ld.direct[dep.mod.Path] = true + // Tidy the build list, if applicable, before we report errors. + // (The process of tidying may remove errors from irrelevant dependencies.) + if ld.Tidy { + rs, err := tidyRoots(ctx, ld.requirements, ld.pkgs) + if err != nil { + ld.errorf("go: %v\n", err) + } + + if ld.requirements.depth == lazy { + // We continuously add tidy roots to ld.requirements during loading, so at + // this point the tidy roots should be a subset of the roots of + // ld.requirements, ensuring that no new dependencies are brought inside + // the lazy-loading horizon. + // If that is not the case, there is a bug in the loading loop above. + for _, m := range rs.rootModules { + if v, ok := ld.requirements.rootSelected(m.Path); !ok || v != m.Version { + ld.errorf("go mod tidy: internal error: a requirement on %v is needed but was not added during package loading\n", m) + base.ExitIfErrors() } } } + ld.requirements = rs } - base.ExitIfErrors() + + // Report errors, if any. + for _, pkg := range ld.pkgs { + if pkg.err == nil { + continue + } + + // Add importer information to checksum errors. + if sumErr := (*ImportMissingSumError)(nil); errors.As(pkg.err, &sumErr) { + if importer := pkg.stack; importer != nil { + sumErr.importer = importer.path + sumErr.importerVersion = importer.mod.Version + sumErr.importerIsTest = importer.testOf != nil + } + } + + if ld.SilencePackageErrors { + continue + } + if stdErr := (*ImportMissingError)(nil); errors.As(pkg.err, &stdErr) && + stdErr.isStd && ld.SilenceMissingStdImports { + continue + } + if ld.SilenceNoGoErrors && errors.Is(pkg.err, imports.ErrNoGo) { + continue + } + + ld.errorf("%s: %v\n", pkg.stackText(), pkg.err) + } + + ld.checkMultiplePaths() + return ld +} + +// updateRequirements ensures that ld.requirements is consistent with the +// information gained from ld.pkgs and includes the modules in add as roots at +// at least the given versions. +// +// In particular: +// +// - Modules that provide packages directly imported from the main module are +// marked as direct, and are promoted to explicit roots. If a needed root +// cannot be promoted due to -mod=readonly or -mod=vendor, the importing +// package is marked with an error. +// +// - If ld scanned the "all" pattern independent of build constraints, it is +// guaranteed to have seen every direct import. Module dependencies that did +// not provide any directly-imported package are then marked as indirect. +// +// - Root dependencies are updated to their selected versions. +// +// The "changed" return value reports whether the update changed the selected +// version of any module that either provided a loaded package or may now +// provide a package that was previously unresolved. +func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err error) { + rs := ld.requirements + + // direct contains the set of modules believed to provide packages directly + // imported by the main module. + var direct map[string]bool // If we didn't scan all of the imports from the main module, or didn't use // imports.AnyTags, then we didn't necessarily load every package that - // contributes “direct” imports — so we can't safely mark existing - // dependencies as indirect-only. - // Conservatively mark those dependencies as direct. - if modFile != nil && (!ld.allPatternIsRoot || !reflect.DeepEqual(ld.Tags, imports.AnyTags())) { - for _, r := range modFile.Require { - if !r.Indirect { - ld.direct[r.Mod.Path] = true + // contributes “direct” imports — so we can't safely mark existing direct + // dependencies in ld.requirements as indirect-only. Propagate them as direct. + loadedDirect := ld.allPatternIsRoot && reflect.DeepEqual(ld.Tags, imports.AnyTags()) + if loadedDirect { + direct = make(map[string]bool) + } else { + // TODO(bcmills): It seems like a shame to allocate and copy a map here when + // it will only rarely actually vary from rs.direct. Measure this cost and + // maybe avoid the copy. + direct = make(map[string]bool, len(rs.direct)) + for mPath := range rs.direct { + direct[mPath] = true + } + } + + for _, pkg := range ld.pkgs { + if pkg.mod != Target { + continue + } + for _, dep := range pkg.imports { + if !dep.fromExternalModule() { + continue + } + + if pkg.err == nil && cfg.BuildMod != "mod" { + if v, ok := rs.rootSelected(dep.mod.Path); !ok || v != dep.mod.Version { + // dep.mod is not an explicit dependency, but needs to be. + // Because we are not in "mod" mode, we will not be able to update it. + // Instead, mark the importing package with an error. + // + // TODO(#41688): The resulting error message fails to include the file + // position of the import statement (because that information is not + // tracked by the module loader). Figure out how to plumb the import + // position through. + pkg.err = &DirectImportFromImplicitDependencyError{ + ImporterPath: pkg.path, + ImportedPath: dep.path, + Module: dep.mod, + } + // cfg.BuildMod does not allow us to change dep.mod to be a direct + // dependency, so don't mark it as such. + continue + } } + + // dep is a package directly imported by a package or test in the main + // module and loaded from some other module (not the standard library). + // Mark its module as a direct dependency. + direct[dep.mod.Path] = true } } - return ld + var addRoots []module.Version + if ld.Tidy { + // When we are tidying a lazy module, we may need to add roots to preserve + // the versions of indirect, test-only dependencies that are upgraded + // above or otherwise missing from the go.mod files of direct + // dependencies. (For example, the direct dependency might be a very + // stable codebase that predates modules and thus lacks a go.mod file, or + // the author of the direct dependency may have forgotten to commit a + // change to the go.mod file, or may have made an erroneous hand-edit that + // causes it to be untidy.) + // + // Promoting an indirect dependency to a root adds the next layer of its + // dependencies to the module graph, which may increase the selected + // versions of other modules from which we have already loaded packages. + // So after we promote an indirect dependency to a root, we need to reload + // packages, which means another iteration of loading. + // + // As an extra wrinkle, the upgrades due to promoting a root can cause + // previously-resolved packages to become unresolved. For example, the + // module providing an unstable package might be upgraded to a version + // that no longer contains that package. If we then resolve the missing + // package, we might add yet another root that upgrades away some other + // dependency. (The tests in mod_tidy_convergence*.txt illustrate some + // particularly worrisome cases.) + // + // To ensure that this process of promoting, adding, and upgrading roots + // eventually terminates, during iteration we only ever add modules to the + // root set — we only remove irrelevant roots at the very end of + // iteration, after we have already added every root that we plan to need + // in the (eventual) tidy root set. + // + // Since we do not remove any roots during iteration, even if they no + // longer provide any imported packages, the selected versions of the + // roots can only increase and the set of roots can only expand. The set + // of extant root paths is finite and the set of versions of each path is + // finite, so the iteration *must* reach a stable fixed-point. + tidy, err := tidyRoots(ctx, rs, ld.pkgs) + if err != nil { + return false, err + } + addRoots = tidy.rootModules + } + + rs, err = updateRoots(ctx, direct, rs, ld.pkgs, addRoots, ld.AssumeRootsImported) + if err != nil { + // We don't actually know what even the root requirements are supposed to be, + // so we can't proceed with loading. Return the error to the caller + return false, err + } + + if rs != ld.requirements && !reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) { + // The roots of the module graph have changed in some way (not just the + // "direct" markings). Check whether the changes affected any of the loaded + // packages. + mg, err := rs.Graph(ctx) + if err != nil { + return false, err + } + for _, pkg := range ld.pkgs { + if pkg.fromExternalModule() && mg.Selected(pkg.mod.Path) != pkg.mod.Version { + changed = true + break + } + if pkg.err != nil { + // Promoting a module to a root may resolve an import that was + // previously missing (by pulling in a previously-prune dependency that + // provides it) or ambiguous (by promoting exactly one of the + // alternatives to a root and ignoring the second-level alternatives) or + // otherwise errored out (by upgrading from a version that cannot be + // fetched to one that can be). + // + // Instead of enumerating all of the possible errors, we'll just check + // whether importFromModules returns nil for the package. + // False-positives are ok: if we have a false-positive here, we'll do an + // extra iteration of package loading this time, but we'll still + // converge when the root set stops changing. + // + // In some sense, we can think of this as ‘upgraded the module providing + // pkg.path from "none" to a version higher than "none"’. + if _, _, err = importFromModules(ctx, pkg.path, rs, nil); err == nil { + changed = true + break + } + } + } + } + + ld.requirements = rs + return changed, nil } -// resolveMissingImports adds module dependencies to the global build list -// in order to resolve missing packages from pkgs. +// resolveMissingImports returns a set of modules that could be added as +// dependencies in order to resolve missing packages from pkgs. // // The newly-resolved packages are added to the addedModuleFor map, and -// resolveMissingImports returns a map from each newly-added module version to -// the first package for which that module was added. -func (ld *loader) resolveMissingImports(addedModuleFor map[string]bool) (modAddedBy map[module.Version]*loadPkg) { - var needPkgs []*loadPkg +// resolveMissingImports returns a map from each new module version to +// the first missing package that module would resolve. +func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[module.Version]*loadPkg) { + type pkgMod struct { + pkg *loadPkg + mod *module.Version + } + var pkgMods []pkgMod for _, pkg := range ld.pkgs { if pkg.err == nil { continue @@ -928,30 +1321,47 @@ func (ld *loader) resolveMissingImports(addedModuleFor map[string]bool) (modAdde continue } - needPkgs = append(needPkgs, pkg) - pkg := pkg + var mod module.Version ld.work.Add(func() { - pkg.mod, pkg.err = queryImport(context.TODO(), pkg.path) + var err error + mod, err = queryImport(ctx, pkg.path, ld.requirements) + if err != nil { + // pkg.err was already non-nil, so we can reasonably attribute the error + // for pkg to either the original error or the one returned by + // queryImport. The existing error indicates only that we couldn't find + // the package, whereas the query error also explains why we didn't fix + // the problem — so we prefer the latter. + pkg.err = err + } + + // err is nil, but we intentionally leave pkg.err non-nil and pkg.mod + // unset: we still haven't satisfied other invariants of a + // successfully-loaded package, such as scanning and loading the imports + // of that package. If we succeed in resolving the new dependency graph, + // the caller can reload pkg and update the error at that point. + // + // Even then, the package might not be loaded from the version we've + // identified here. The module may be upgraded by some other dependency, + // or by a transitive dependency of mod itself, or — less likely — the + // package may be rejected by an AllowPackage hook or rendered ambiguous + // by some other newly-added or newly-upgraded dependency. }) + + pkgMods = append(pkgMods, pkgMod{pkg: pkg, mod: &mod}) } <-ld.work.Idle() modAddedBy = map[module.Version]*loadPkg{} - for _, pkg := range needPkgs { - if pkg.err != nil { + for _, pm := range pkgMods { + pkg, mod := pm.pkg, *pm.mod + if mod.Path == "" { continue } - fmt.Fprintf(os.Stderr, "go: found %s in %s %s\n", pkg.path, pkg.mod.Path, pkg.mod.Version) - if addedModuleFor[pkg.path] { - // TODO(bcmills): This should only be an error if pkg.mod is the same - // version we already tried to add previously. - base.Fatalf("go: %s: looping trying to add package", pkg.stackText()) - } - if modAddedBy[pkg.mod] == nil { - modAddedBy[pkg.mod] = pkg - buildList = append(buildList, pkg.mod) + fmt.Fprintf(os.Stderr, "go: found %s in %s %s\n", pkg.path, mod.Path, mod.Version) + if modAddedBy[mod] == nil { + modAddedBy[mod] = pkg } } @@ -965,7 +1375,7 @@ func (ld *loader) resolveMissingImports(addedModuleFor map[string]bool) (modAdde // ld.work queue, and its test (if requested) will also be populated once // imports have been resolved. When ld.work goes idle, all transitive imports of // the requested package (and its test, if requested) will have been loaded. -func (ld *loader) pkg(path string, flags loadPkgFlags) *loadPkg { +func (ld *loader) pkg(ctx context.Context, path string, flags loadPkgFlags) *loadPkg { if flags.has(pkgImportsLoaded) { panic("internal error: (*loader).pkg called with pkgImportsLoaded flag set") } @@ -974,20 +1384,20 @@ func (ld *loader) pkg(path string, flags loadPkgFlags) *loadPkg { pkg := &loadPkg{ path: path, } - ld.applyPkgFlags(pkg, flags) + ld.applyPkgFlags(ctx, pkg, flags) - ld.work.Add(func() { ld.load(pkg) }) + ld.work.Add(func() { ld.load(ctx, pkg) }) return pkg }).(*loadPkg) - ld.applyPkgFlags(pkg, flags) + ld.applyPkgFlags(ctx, pkg, flags) return pkg } // applyPkgFlags updates pkg.flags to set the given flags and propagate the // (transitive) effects of those flags, possibly loading or enqueueing further // packages as a result. -func (ld *loader) applyPkgFlags(pkg *loadPkg, flags loadPkgFlags) { +func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkgFlags) { if flags == 0 { return } @@ -996,6 +1406,9 @@ func (ld *loader) applyPkgFlags(pkg *loadPkg, flags loadPkgFlags) { // This package matches a root pattern by virtue of being in "all". flags |= pkgIsRoot } + if flags.has(pkgIsRoot) { + flags |= pkgFromRoot + } old := pkg.flags.update(flags) new := old | flags @@ -1039,7 +1452,7 @@ func (ld *loader) applyPkgFlags(pkg *loadPkg, flags loadPkgFlags) { // of packages in "all" if "all" closes over test dependencies. testFlags |= pkgInAll } - ld.pkgTest(pkg, testFlags) + ld.pkgTest(ctx, pkg, testFlags) } } @@ -1047,13 +1460,100 @@ func (ld *loader) applyPkgFlags(pkg *loadPkg, flags loadPkgFlags) { // We have just marked pkg with pkgInAll, or we have just loaded its // imports, or both. Now is the time to propagate pkgInAll to the imports. for _, dep := range pkg.imports { - ld.applyPkgFlags(dep, pkgInAll) + ld.applyPkgFlags(ctx, dep, pkgInAll) } } + + if new.has(pkgFromRoot) && !old.has(pkgFromRoot|pkgImportsLoaded) { + for _, dep := range pkg.imports { + ld.applyPkgFlags(ctx, dep, pkgFromRoot) + } + } +} + +// preloadRootModules loads the module requirements needed to identify the +// selected version of each module providing a package in rootPkgs, +// adding new root modules to the module graph if needed. +func (ld *loader) preloadRootModules(ctx context.Context, rootPkgs []string) (changedBuildList bool) { + needc := make(chan map[module.Version]bool, 1) + needc <- map[module.Version]bool{} + for _, path := range rootPkgs { + path := path + ld.work.Add(func() { + // First, try to identify the module containing the package using only roots. + // + // If the main module is tidy and the package is in "all" — or if we're + // lucky — we can identify all of its imports without actually loading the + // full module graph. + m, _, err := importFromModules(ctx, path, ld.requirements, nil) + if err != nil { + var missing *ImportMissingError + if errors.As(err, &missing) && ld.ResolveMissingImports { + // This package isn't provided by any selected module. + // If we can find it, it will be a new root dependency. + m, err = queryImport(ctx, path, ld.requirements) + } + if err != nil { + // We couldn't identify the root module containing this package. + // Leave it unresolved; we will report it during loading. + return + } + } + if m.Path == "" { + // The package is in std or cmd. We don't need to change the root set. + return + } + + v, ok := ld.requirements.rootSelected(m.Path) + if !ok || v != m.Version { + // We found the requested package in m, but m is not a root, so + // loadModGraph will not load its requirements. We need to promote the + // module to a root to ensure that any other packages this package + // imports are resolved from correct dependency versions. + // + // (This is the “argument invariant” from the lazy loading design.) + need := <-needc + need[m] = true + needc <- need + } + }) + } + <-ld.work.Idle() + + need := <-needc + if len(need) == 0 { + return false // No roots to add. + } + + toAdd := make([]module.Version, 0, len(need)) + for m := range need { + toAdd = append(toAdd, m) + } + module.Sort(toAdd) + + rs, err := updateRoots(ctx, ld.requirements.direct, ld.requirements, nil, toAdd, ld.AssumeRootsImported) + if err != nil { + // We are missing some root dependency, and for some reason we can't load + // enough of the module dependency graph to add the missing root. Package + // loading is doomed to fail, so fail quickly. + ld.errorf("go: %v\n", err) + base.ExitIfErrors() + return false + } + if reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) { + // Something is deeply wrong. resolveMissingImports gave us a non-empty + // set of modules to add to the graph, but adding those modules had no + // effect — either they were already in the graph, or updateRoots did not + // add them as requested. + panic(fmt.Sprintf("internal error: adding %v to module graph had no effect on root requirements (%v)", toAdd, rs.rootModules)) + } + + ld.requirements = rs + return true } // load loads an individual package. -func (ld *loader) load(pkg *loadPkg) { +func (ld *loader) load(ctx context.Context, pkg *loadPkg) { if strings.Contains(pkg.path, "@") { // Leave for error during load. return @@ -1072,7 +1572,24 @@ func (ld *loader) load(pkg *loadPkg) { return } - pkg.mod, pkg.dir, pkg.err = importFromBuildList(context.TODO(), pkg.path, buildList) + var mg *ModuleGraph + if ld.requirements.depth == eager { + var err error + mg, err = ld.requirements.Graph(ctx) + if err != nil { + // We already checked the error from Graph in loadFromRoots and/or + // updateRequirements, so we ignored the error on purpose and we should + // keep trying to push past it. + // + // However, because mg may be incomplete (and thus may select inaccurate + // versions), we shouldn't use it to load packages. Instead, we pass a nil + // *ModuleGraph, which will cause mg to first try loading from only the + // main module and root dependencies. + mg = nil + } + } + + pkg.mod, pkg.dir, pkg.err = importFromModules(ctx, pkg.path, ld.requirements, mg) if pkg.dir == "" { return } @@ -1086,10 +1603,10 @@ func (ld *loader) load(pkg *loadPkg) { // about (by reducing churn on the flag bits of dependencies), and costs // essentially nothing (these atomic flag ops are essentially free compared // to scanning source code for imports). - ld.applyPkgFlags(pkg, pkgInAll) + ld.applyPkgFlags(ctx, pkg, pkgInAll) } if ld.AllowPackage != nil { - if err := ld.AllowPackage(context.TODO(), pkg.path, pkg.mod); err != nil { + if err := ld.AllowPackage(ctx, pkg.path, pkg.mod); err != nil { pkg.err = err } } @@ -1120,11 +1637,11 @@ func (ld *loader) load(pkg *loadPkg) { // GOROOT/src/vendor even when "std" is not the main module. path = ld.stdVendor(pkg.path, path) } - pkg.imports = append(pkg.imports, ld.pkg(path, importFlags)) + pkg.imports = append(pkg.imports, ld.pkg(ctx, path, importFlags)) } pkg.testImports = testImports - ld.applyPkgFlags(pkg, pkgImportsLoaded) + ld.applyPkgFlags(ctx, pkg, pkgImportsLoaded) } // pkgTest locates the test of pkg, creating it if needed, and updates its state @@ -1132,7 +1649,7 @@ func (ld *loader) load(pkg *loadPkg) { // // pkgTest requires that the imports of pkg have already been loaded (flagged // with pkgImportsLoaded). -func (ld *loader) pkgTest(pkg *loadPkg, testFlags loadPkgFlags) *loadPkg { +func (ld *loader) pkgTest(ctx context.Context, pkg *loadPkg, testFlags loadPkgFlags) *loadPkg { if pkg.isTest() { panic("pkgTest called on a test package") } @@ -1147,7 +1664,7 @@ func (ld *loader) pkgTest(pkg *loadPkg, testFlags loadPkgFlags) *loadPkg { err: pkg.err, inStd: pkg.inStd, } - ld.applyPkgFlags(pkg.test, testFlags) + ld.applyPkgFlags(ctx, pkg.test, testFlags) createdTest = true }) @@ -1162,12 +1679,12 @@ func (ld *loader) pkgTest(pkg *loadPkg, testFlags loadPkgFlags) *loadPkg { if pkg.inStd { path = ld.stdVendor(test.path, path) } - test.imports = append(test.imports, ld.pkg(path, importFlags)) + test.imports = append(test.imports, ld.pkg(ctx, path, importFlags)) } pkg.testImports = nil - ld.applyPkgFlags(test, pkgImportsLoaded) + ld.applyPkgFlags(ctx, test, pkgImportsLoaded) } else { - ld.applyPkgFlags(test, testFlags) + ld.applyPkgFlags(ctx, test, testFlags) } return test @@ -1181,13 +1698,13 @@ func (ld *loader) stdVendor(parentPath, path string) string { } if str.HasPathPrefix(parentPath, "cmd") { - if Target.Path != "cmd" { + if !ld.VendorModulesInGOROOTSrc || Target.Path != "cmd" { vendorPath := pathpkg.Join("cmd", "vendor", path) if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil { return vendorPath } } - } else if Target.Path != "std" || str.HasPathPrefix(parentPath, "vendor") { + } else if !ld.VendorModulesInGOROOTSrc || Target.Path != "std" || str.HasPathPrefix(parentPath, "vendor") { // If we are outside of the 'std' module, resolve imports from within 'std' // to the vendor directory. // @@ -1222,6 +1739,242 @@ func (ld *loader) computePatternAll() (all []string) { return all } +// checkMultiplePaths verifies that a given module path is used as itself +// or as a replacement for another module, but not both at the same time. +// +// (See https://golang.org/issue/26607 and https://golang.org/issue/34650.) +func (ld *loader) checkMultiplePaths() { + mods := ld.requirements.rootModules + if cached := ld.requirements.graph.Load(); cached != nil { + if mg := cached.(cachedGraph).mg; mg != nil { + mods = mg.BuildList() + } + } + + firstPath := map[module.Version]string{} + for _, mod := range mods { + src := resolveReplacement(mod) + if prev, ok := firstPath[src]; !ok { + firstPath[src] = mod.Path + } else if prev != mod.Path { + ld.errorf("go: %s@%s used for two different module paths (%s and %s)\n", src.Path, src.Version, prev, mod.Path) + } + } +} + +// checkTidyCompatibility emits an error if any package would be loaded from a +// different module under rs than under ld.requirements. +func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements) { + suggestUpgrade := false + suggestEFlag := false + suggestFixes := func() { + if ld.AllowErrors { + // The user is explicitly ignoring these errors, so don't bother them with + // other options. + return + } + + // We print directly to os.Stderr because this information is advice about + // how to fix errors, not actually an error itself. + // (The actual errors should have been logged already.) + + fmt.Fprintln(os.Stderr) + + goFlag := "" + if ld.GoVersion != modFileGoVersion() { + goFlag = " -go=" + ld.GoVersion + } + + compatFlag := "" + if ld.TidyCompatibleVersion != priorGoVersion(ld.GoVersion) { + compatFlag = " -compat=" + ld.TidyCompatibleVersion + } + if suggestUpgrade { + eDesc := "" + eFlag := "" + if suggestEFlag { + eDesc = ", leaving some packages unresolved" + eFlag = " -e" + } + fmt.Fprintf(os.Stderr, "To upgrade to the versions selected by go %s%s:\n\tgo mod tidy%s -go=%s && go mod tidy%s -go=%s%s\n", ld.TidyCompatibleVersion, eDesc, eFlag, ld.TidyCompatibleVersion, eFlag, ld.GoVersion, compatFlag) + } else if suggestEFlag { + // If some packages are missing but no package is upgraded, then we + // shouldn't suggest upgrading to the Go 1.16 versions explicitly — that + // wouldn't actually fix anything for Go 1.16 users, and *would* break + // something for Go 1.17 users. + fmt.Fprintf(os.Stderr, "To proceed despite packages unresolved in go %s:\n\tgo mod tidy -e%s%s\n", ld.TidyCompatibleVersion, goFlag, compatFlag) + } + + fmt.Fprintf(os.Stderr, "If reproducibility with go %s is not needed:\n\tgo mod tidy%s -compat=%s\n", ld.TidyCompatibleVersion, goFlag, ld.GoVersion) + + // TODO(#46141): Populate the linked wiki page. + fmt.Fprintf(os.Stderr, "For other options, see:\n\thttps://golang.org/doc/modules/pruning\n") + } + + mg, err := rs.Graph(ctx) + if err != nil { + ld.errorf("go mod tidy: error loading go %s module graph: %v\n", ld.TidyCompatibleVersion, err) + suggestFixes() + return + } + + // Re-resolve packages in parallel. + // + // We re-resolve each package — rather than just checking versions — to ensure + // that we have fetched module source code (and, importantly, checksums for + // that source code) for all modules that are necessary to ensure that imports + // are unambiguous. That also produces clearer diagnostics, since we can say + // exactly what happened to the package if it became ambiguous or disappeared + // entirely. + // + // We re-resolve the packages in parallel because this process involves disk + // I/O to check for package sources, and because the process of checking for + // ambiguous imports may require us to download additional modules that are + // otherwise pruned out in Go 1.17 — we don't want to block progress on other + // packages while we wait for a single new download. + type mismatch struct { + mod module.Version + err error + } + mismatchMu := make(chan map[*loadPkg]mismatch, 1) + mismatchMu <- map[*loadPkg]mismatch{} + for _, pkg := range ld.pkgs { + if pkg.mod.Path == "" && pkg.err == nil { + // This package is from the standard library (which does not vary based on + // the module graph). + continue + } + + pkg := pkg + ld.work.Add(func() { + mod, _, err := importFromModules(ctx, pkg.path, rs, mg) + if mod != pkg.mod { + mismatches := <-mismatchMu + mismatches[pkg] = mismatch{mod: mod, err: err} + mismatchMu <- mismatches + } + }) + } + <-ld.work.Idle() + + mismatches := <-mismatchMu + if len(mismatches) == 0 { + // Since we're running as part of 'go mod tidy', the roots of the module + // graph should contain only modules that are relevant to some package in + // the package graph. We checked every package in the package graph and + // didn't find any mismatches, so that must mean that all of the roots of + // the module graph are also consistent. + // + // If we're wrong, Go 1.16 in -mod=readonly mode will error out with + // "updates to go.mod needed", which would be very confusing. So instead, + // we'll double-check that our reasoning above actually holds — if it + // doesn't, we'll emit an internal error and hopefully the user will report + // it as a bug. + for _, m := range ld.requirements.rootModules { + if v := mg.Selected(m.Path); v != m.Version { + fmt.Fprintln(os.Stderr) + base.Fatalf("go: internal error: failed to diagnose selected-version mismatch for module %s: go %s selects %s, but go %s selects %s\n\tPlease report this at https://golang.org/issue.", m.Path, ld.GoVersion, m.Version, ld.TidyCompatibleVersion, v) + } + } + return + } + + // Iterate over the packages (instead of the mismatches map) to emit errors in + // deterministic order. + for _, pkg := range ld.pkgs { + mismatch, ok := mismatches[pkg] + if !ok { + continue + } + + if pkg.isTest() { + // We already did (or will) report an error for the package itself, + // so don't report a duplicate (and more vebose) error for its test. + if _, ok := mismatches[pkg.testOf]; !ok { + base.Fatalf("go: internal error: mismatch recorded for test %s, but not its non-test package", pkg.path) + } + continue + } + + switch { + case mismatch.err != nil: + // pkg resolved successfully, but errors out using the requirements in rs. + // + // This could occur because the import is provided by a single lazy root + // (and is thus unambiguous in lazy mode) and also one or more + // transitive dependencies (and is ambiguous in eager mode). + // + // It could also occur because some transitive dependency upgrades the + // module that previously provided the package to a version that no + // longer does, or to a version for which the module source code (but + // not the go.mod file in isolation) has a checksum error. + if missing := (*ImportMissingError)(nil); errors.As(mismatch.err, &missing) { + selected := module.Version{ + Path: pkg.mod.Path, + Version: mg.Selected(pkg.mod.Path), + } + ld.errorf("%s loaded from %v,\n\tbut go %s would fail to locate it in %s\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, selected) + } else { + if ambiguous := (*AmbiguousImportError)(nil); errors.As(mismatch.err, &ambiguous) { + // TODO: Is this check needed? + } + ld.errorf("%s loaded from %v,\n\tbut go %s would fail to locate it:\n\t%v\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, mismatch.err) + } + + suggestEFlag = true + + // Even if we press ahead with the '-e' flag, the older version will + // error out in readonly mode if it thinks the go.mod file contains + // any *explicit* dependency that is not at its selected version, + // even if that dependency is not relevant to any package being loaded. + // + // We check for that condition here. If all of the roots are consistent + // the '-e' flag suffices, but otherwise we need to suggest an upgrade. + if !suggestUpgrade { + for _, m := range ld.requirements.rootModules { + if v := mg.Selected(m.Path); v != m.Version { + suggestUpgrade = true + break + } + } + } + + case pkg.err != nil: + // pkg had an error in lazy mode (presumably suppressed with the -e flag), + // but not in eager mode. + // + // This is possible, if, say, the import is unresolved in lazy mode + // (because the "latest" version of each candidate module either is + // unavailable or does not contain the package), but is resolved in + // eager mode due to a newer-than-latest dependency that is normally + // runed out of the module graph. + // + // This could also occur if the source code for the module providing the + // package in lazy mode has a checksum error, but eager mode upgrades + // that module to a version with a correct checksum. + // + // pkg.err should have already been logged elsewhere — along with a + // stack trace — so log only the import path and non-error info here. + suggestUpgrade = true + ld.errorf("%s failed to load from any module,\n\tbut go %s would load it from %v\n", pkg.path, ld.TidyCompatibleVersion, mismatch.mod) + + case pkg.mod != mismatch.mod: + // The package is loaded successfully by both Go versions, but from a + // different module in each. This could lead to subtle (and perhaps even + // unnoticed!) variations in behavior between builds with different + // toolchains. + suggestUpgrade = true + ld.errorf("%s loaded from %v,\n\tbut go %s would select %v\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, mismatch.mod.Version) + + default: + base.Fatalf("go: internal error: mismatch recorded for package %s, but no differences found", pkg.path) + } + } + + suggestFixes() + base.ExitIfErrors() +} + // scanDir is like imports.ScanDir but elides known magic imports from the list, // so that we do not go looking for packages that don't really exist. // diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go index c6667d0bf7944b2dcae558005363d648a05c4c5f..03e02e73b63f659f0ec6e3eff89299f691763c6d 100644 --- a/src/cmd/go/internal/modload/modfile.go +++ b/src/cmd/go/internal/modload/modfile.go @@ -8,6 +8,7 @@ import ( "context" "errors" "fmt" + "os" "path/filepath" "strings" "sync" @@ -15,6 +16,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" + "cmd/go/internal/fsys" "cmd/go/internal/lockedfile" "cmd/go/internal/modfetch" "cmd/go/internal/par" @@ -25,14 +27,58 @@ import ( "golang.org/x/mod/semver" ) -// narrowAllVersionV is the Go version (plus leading "v") at which the -// module-module "all" pattern no longer closes over the dependencies of -// tests outside of the main module. -const narrowAllVersionV = "v1.16" -const go116EnableNarrowAll = true +const ( + // narrowAllVersionV is the Go version (plus leading "v") at which the + // module-module "all" pattern no longer closes over the dependencies of + // tests outside of the main module. + narrowAllVersionV = "v1.16" + + // lazyLoadingVersionV is the Go version (plus leading "v") at which a + // module's go.mod file is expected to list explicit requirements on every + // module that provides any package transitively imported by that module. + lazyLoadingVersionV = "v1.17" + + // separateIndirectVersionV is the Go version (plus leading "v") at which + // "// indirect" dependencies are added in a block separate from the direct + // ones. See https://golang.org/issue/45965. + separateIndirectVersionV = "v1.17" +) + +const ( + // go117EnableLazyLoading toggles whether lazy-loading code paths should be + // active. It will be removed once the lazy loading implementation is stable + // and well-tested. + go117EnableLazyLoading = true + + // go1117LazyTODO is a constant that exists only until lazy loading is + // implemented. Its use indicates a condition that will need to change if the + // main module is lazy. + go117LazyTODO = false +) var modFile *modfile.File +// modFileGoVersion returns the (non-empty) Go version at which the requirements +// in modFile are intepreted, or the latest Go version if modFile is nil. +func modFileGoVersion() string { + if modFile == nil { + return LatestGoVersion() + } + if modFile.Go == nil || modFile.Go.Version == "" { + // The main module necessarily has a go.mod file, and that file lacks a + // 'go' directive. The 'go' command has been adding that directive + // automatically since Go 1.12, so this module either dates to Go 1.11 or + // has been erroneously hand-edited. + // + // The semantics of the go.mod file are more-or-less the same from Go 1.11 + // through Go 1.16, changing at 1.17 for lazy loading. So even though a + // go.mod file without a 'go' directive is theoretically a Go 1.11 file, + // scripts may assume that it ends up as a Go 1.16 module. + return "1.16" + } + return modFile.Go.Version +} + // A modFileIndex is an index of data corresponding to a modFile // at a specific point in time. type modFileIndex struct { @@ -53,6 +99,24 @@ type requireMeta struct { indirect bool } +// A modDepth indicates which dependencies should be loaded for a go.mod file. +type modDepth uint8 + +const ( + lazy modDepth = iota // load dependencies only as needed + eager // load all transitive dependencies eagerly +) + +func modDepthFromGoVersion(goVersion string) modDepth { + if !go117EnableLazyLoading { + return eager + } + if semver.Compare("v"+goVersion, lazyLoadingVersionV) < 0 { + return eager + } + return lazy +} + // CheckAllowed returns an error equivalent to ErrDisallowed if m is excluded by // the main module's go.mod or retracted by its author. Most version queries use // this to filter out versions that should not be used. @@ -88,76 +152,53 @@ func (e *excludedError) Is(err error) bool { return err == ErrDisallowed } // CheckRetractions returns an error if module m has been retracted by // its author. -func CheckRetractions(ctx context.Context, m module.Version) error { +func CheckRetractions(ctx context.Context, m module.Version) (err error) { + defer func() { + if retractErr := (*ModuleRetractedError)(nil); err == nil || errors.As(err, &retractErr) { + return + } + // Attribute the error to the version being checked, not the version from + // which the retractions were to be loaded. + if mErr := (*module.ModuleError)(nil); errors.As(err, &mErr) { + err = mErr.Err + } + err = &retractionLoadingError{m: m, err: err} + }() + if m.Version == "" { // Main module, standard library, or file replacement module. // Cannot be retracted. return nil } - - // Look up retraction information from the latest available version of - // the module. Cache retraction information so we don't parse the go.mod - // file repeatedly. - type entry struct { - retract []retraction - err error + if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" { + // All versions of the module were replaced. + // Don't load retractions, since we'd just load the replacement. + return nil } - path := m.Path - e := retractCache.Do(path, func() (v interface{}) { - ctx, span := trace.StartSpan(ctx, "checkRetractions "+path) - defer span.Done() - - if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" { - // All versions of the module were replaced with a local directory. - // Don't load retractions. - return &entry{nil, nil} - } - - // Find the latest version of the module. - // Ignore exclusions from the main module's go.mod. - const ignoreSelected = "" - var allowAll AllowedFunc - rev, err := Query(ctx, path, "latest", ignoreSelected, allowAll) - if err != nil { - return &entry{nil, err} - } - - // Load go.mod for that version. - // If the version is replaced, we'll load retractions from the replacement. - // - // If there's an error loading the go.mod, we'll return it here. - // These errors should generally be ignored by callers of checkRetractions, - // since they happen frequently when we're offline. These errors are not - // equivalent to ErrDisallowed, so they may be distinguished from - // retraction errors. - // - // We load the raw file here: the go.mod file may have a different module - // path that we expect if the module or its repository was renamed. - // We still want to apply retractions to other aliases of the module. - rm := module.Version{Path: path, Version: rev.Version} - if repl := Replacement(rm); repl.Path != "" { - rm = repl - } - summary, err := rawGoModSummary(rm) - if err != nil { - return &entry{nil, err} - } - return &entry{summary.retract, nil} - }).(*entry) - if err := e.err; err != nil { - // Attribute the error to the version being checked, not the version from - // which the retractions were to be loaded. - var mErr *module.ModuleError - if errors.As(err, &mErr) { - err = mErr.Err - } - return &retractionLoadingError{m: m, err: err} + // Find the latest available version of the module, and load its go.mod. If + // the latest version is replaced, we'll load the replacement. + // + // If there's an error loading the go.mod, we'll return it here. These errors + // should generally be ignored by callers since they happen frequently when + // we're offline. These errors are not equivalent to ErrDisallowed, so they + // may be distinguished from retraction errors. + // + // We load the raw file here: the go.mod file may have a different module + // path that we expect if the module or its repository was renamed. + // We still want to apply retractions to other aliases of the module. + rm, err := queryLatestVersionIgnoringRetractions(ctx, m.Path) + if err != nil { + return err + } + summary, err := rawGoModSummary(rm) + if err != nil { + return err } var rationale []string isRetracted := false - for _, r := range e.retract { + for _, r := range summary.retract { if semver.Compare(r.Low, m.Version) <= 0 && semver.Compare(m.Version, r.High) <= 0 { isRetracted = true if r.Rationale != "" { @@ -171,8 +212,6 @@ func CheckRetractions(ctx context.Context, m module.Version) error { return nil } -var retractCache par.Cache - type ModuleRetractedError struct { Rationale []string } @@ -182,7 +221,7 @@ func (e *ModuleRetractedError) Error() string { if len(e.Rationale) > 0 { // This is meant to be a short error printed on a terminal, so just // print the first rationale. - msg += ": " + ShortRetractionRationale(e.Rationale[0]) + msg += ": " + ShortMessage(e.Rationale[0], "retracted by module author") } return msg } @@ -204,28 +243,67 @@ func (e *retractionLoadingError) Unwrap() error { return e.err } -// ShortRetractionRationale returns a retraction rationale string that is safe -// to print in a terminal. It returns hard-coded strings if the rationale -// is empty, too long, or contains non-printable characters. -func ShortRetractionRationale(rationale string) string { - const maxRationaleBytes = 500 - if i := strings.Index(rationale, "\n"); i >= 0 { - rationale = rationale[:i] - } - rationale = strings.TrimSpace(rationale) - if rationale == "" { - return "retracted by module author" - } - if len(rationale) > maxRationaleBytes { - return "(rationale omitted: too long)" - } - for _, r := range rationale { +// ShortMessage returns a string from go.mod (for example, a retraction +// rationale or deprecation message) that is safe to print in a terminal. +// +// If the given string is empty, ShortMessage returns the given default. If the +// given string is too long or contains non-printable characters, ShortMessage +// returns a hard-coded string. +func ShortMessage(message, emptyDefault string) string { + const maxLen = 500 + if i := strings.Index(message, "\n"); i >= 0 { + message = message[:i] + } + message = strings.TrimSpace(message) + if message == "" { + return emptyDefault + } + if len(message) > maxLen { + return "(message omitted: too long)" + } + for _, r := range message { if !unicode.IsGraphic(r) && !unicode.IsSpace(r) { - return "(rationale omitted: contains non-printable characters)" + return "(message omitted: contains non-printable characters)" } } // NOTE: the go.mod parser rejects invalid UTF-8, so we don't check that here. - return rationale + return message +} + +// CheckDeprecation returns a deprecation message from the go.mod file of the +// latest version of the given module. Deprecation messages are comments +// before or on the same line as the module directives that start with +// "Deprecated:" and run until the end of the paragraph. +// +// CheckDeprecation returns an error if the message can't be loaded. +// CheckDeprecation returns "", nil if there is no deprecation message. +func CheckDeprecation(ctx context.Context, m module.Version) (deprecation string, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("loading deprecation for %s: %w", m.Path, err) + } + }() + + if m.Version == "" { + // Main module, standard library, or file replacement module. + // Don't look up deprecation. + return "", nil + } + if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" { + // All versions of the module were replaced. + // We'll look up deprecation separately for the replacement. + return "", nil + } + + latest, err := queryLatestVersionIgnoringRetractions(ctx, m.Path) + if err != nil { + return "", err + } + summary, err := rawGoModSummary(latest) + if err != nil { + return "", err + } + return summary.deprecated, nil } // Replacement returns the replacement for mod, if any, from go.mod. @@ -243,6 +321,15 @@ func Replacement(mod module.Version) module.Version { return module.Version{} } +// resolveReplacement returns the module actually used to load the source code +// for m: either m itself, or the replacement for m (iff m is replaced). +func resolveReplacement(m module.Version) module.Version { + if r := Replacement(m); r.Path != "" { + return r + } + return m +} + // indexModFile rebuilds the index of modFile. // If modFile has been changed since it was first read, // modFile.Cleanup must be called before indexModFile. @@ -257,10 +344,13 @@ func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileInd } i.goVersionV = "" - if modFile.Go != nil { + if modFile.Go == nil { + rawGoVersion.Store(Target, "") + } else { // We're going to use the semver package to compare Go versions, so go ahead // and add the "v" prefix it expects once instead of every time. i.goVersionV = "v" + modFile.Go.Version + rawGoVersion.Store(Target, modFile.Go.Version) } i.require = make(map[module.Version]requireMeta, len(modFile.Require)) @@ -292,23 +382,6 @@ func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileInd return i } -// allPatternClosesOverTests reports whether the "all" pattern includes -// dependencies of tests outside the main module (as in Go 1.11–1.15). -// (Otherwise — as in Go 1.16+ — the "all" pattern includes only the packages -// transitively *imported by* the packages and tests in the main module.) -func (i *modFileIndex) allPatternClosesOverTests() bool { - if !go116EnableNarrowAll { - return true - } - if i != nil && semver.Compare(i.goVersionV, narrowAllVersionV) < 0 { - // The module explicitly predates the change in "all" for lazy loading, so - // continue to use the older interpretation. (If i == nil, we not in any - // module at all and should use the latest semantics.) - return true - } - return false -} - // modFileIsDirty reports whether the go.mod file differs meaningfully // from what was indexed. // If modFile has been changed (even cosmetically) since it was first read, @@ -335,7 +408,7 @@ func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool { return true } } else if "v"+modFile.Go.Version != i.goVersionV { - if i.goVersionV == "" && cfg.BuildMod == "readonly" { + if i.goVersionV == "" && cfg.BuildMod != "mod" { // go.mod files did not always require a 'go' version, so do not error out // if one is missing — we may be inside an older module in the module // cache, and should bias toward providing useful behavior. @@ -391,9 +464,11 @@ var rawGoVersion sync.Map // map[module.Version]string // module. type modFileSummary struct { module module.Version - goVersionV string // GoVersion with "v" prefix + goVersion string + depth modDepth require []module.Version retract []retraction + deprecated string } // A retraction consists of a retracted version interval and rationale. @@ -433,19 +508,13 @@ func goModSummary(m module.Version) (*modFileSummary, error) { // return the full list of modules from modules.txt. readVendorList() - // TODO(#36876): Load the "go" version from vendor/modules.txt and store it - // in rawGoVersion with the appropriate key. - // We don't know what versions the vendored module actually relies on, // so assume that it requires everything. summary.require = vendorList return summary, nil } - actual := Replacement(m) - if actual.Path == "" { - actual = m - } + actual := resolveReplacement(m) if HasModRoot() && cfg.BuildMod == "readonly" && actual.Version != "" { key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"} if !modfetch.HaveSum(key) { @@ -526,45 +595,24 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) { } c := rawGoModSummaryCache.Do(m, func() interface{} { summary := new(modFileSummary) - var f *modfile.File - if m.Version == "" { - // m is a replacement module with only a file path. - dir := m.Path - if !filepath.IsAbs(dir) { - dir = filepath.Join(ModRoot(), dir) - } - gomod := filepath.Join(dir, "go.mod") - - data, err := lockedfile.Read(gomod) - if err != nil { - return cached{nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(gomod), err))} - } - f, err = modfile.ParseLax(gomod, data, nil) - if err != nil { - return cached{nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err))} - } - } else { - if !semver.IsValid(m.Version) { - // Disallow the broader queries supported by fetch.Lookup. - base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version) - } - - data, err := modfetch.GoMod(m.Path, m.Version) - if err != nil { - return cached{nil, err} - } - f, err = modfile.ParseLax("go.mod", data, nil) - if err != nil { - return cached{nil, module.VersionError(m, fmt.Errorf("parsing go.mod: %v", err))} - } + name, data, err := rawGoModData(m) + if err != nil { + return cached{nil, err} + } + f, err := modfile.ParseLax(name, data, nil) + if err != nil { + return cached{nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(name), err))} } - if f.Module != nil { summary.module = f.Module.Mod + summary.deprecated = f.Module.Deprecated } if f.Go != nil && f.Go.Version != "" { rawGoVersion.LoadOrStore(m, f.Go.Version) - summary.goVersionV = "v" + f.Go.Version + summary.goVersion = f.Go.Version + summary.depth = modDepthFromGoVersion(f.Go.Version) + } else { + summary.depth = eager } if len(f.Require) > 0 { summary.require = make([]module.Version, 0, len(f.Require)) @@ -589,3 +637,84 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) { } var rawGoModSummaryCache par.Cache // module.Version → rawGoModSummary result + +// rawGoModData returns the content of the go.mod file for module m, ignoring +// all replacements that may apply to m. +// +// rawGoModData cannot be used on the Target module. +// +// Unlike rawGoModSummary, rawGoModData does not cache its results in memory. +// Use rawGoModSummary instead unless you specifically need these bytes. +func rawGoModData(m module.Version) (name string, data []byte, err error) { + if m.Version == "" { + // m is a replacement module with only a file path. + dir := m.Path + if !filepath.IsAbs(dir) { + dir = filepath.Join(ModRoot(), dir) + } + name = filepath.Join(dir, "go.mod") + if gomodActual, ok := fsys.OverlayPath(name); ok { + // Don't lock go.mod if it's part of the overlay. + // On Plan 9, locking requires chmod, and we don't want to modify any file + // in the overlay. See #44700. + data, err = os.ReadFile(gomodActual) + } else { + data, err = lockedfile.Read(gomodActual) + } + if err != nil { + return "", nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(name), err)) + } + } else { + if !semver.IsValid(m.Version) { + // Disallow the broader queries supported by fetch.Lookup. + base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version) + } + name = "go.mod" + data, err = modfetch.GoMod(m.Path, m.Version) + } + return name, data, err +} + +// queryLatestVersionIgnoringRetractions looks up the latest version of the +// module with the given path without considering retracted or excluded +// versions. +// +// If all versions of the module are replaced, +// queryLatestVersionIgnoringRetractions returns the replacement without making +// a query. +// +// If the queried latest version is replaced, +// queryLatestVersionIgnoringRetractions returns the replacement. +func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) { + type entry struct { + latest module.Version + err error + } + e := latestVersionIgnoringRetractionsCache.Do(path, func() interface{} { + ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path) + defer span.Done() + + if repl := Replacement(module.Version{Path: path}); repl.Path != "" { + // All versions of the module were replaced. + // No need to query. + return &entry{latest: repl} + } + + // Find the latest version of the module. + // Ignore exclusions from the main module's go.mod. + const ignoreSelected = "" + var allowAll AllowedFunc + rev, err := Query(ctx, path, "latest", ignoreSelected, allowAll) + if err != nil { + return &entry{err: err} + } + latest := module.Version{Path: path, Version: rev.Version} + if repl := resolveReplacement(latest); repl.Path != "" { + latest = repl + } + return &entry{latest: latest} + }).(*entry) + return e.latest, e.err +} + +var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersionIgnoringRetractions result diff --git a/src/cmd/go/internal/modload/mvs.go b/src/cmd/go/internal/modload/mvs.go index 31015194f9a0192b055615bd8619b0b7a6249c7e..87619b4ace68e28d40621054ab4d437a4b97cf83 100644 --- a/src/cmd/go/internal/modload/mvs.go +++ b/src/cmd/go/internal/modload/mvs.go @@ -16,17 +16,36 @@ import ( "golang.org/x/mod/semver" ) +// cmpVersion implements the comparison for versions in the module loader. +// +// It is consistent with semver.Compare except that as a special case, +// the version "" is considered higher than all other versions. +// The main module (also known as the target) has no version and must be chosen +// over other versions of the same module in the module dependency graph. +func cmpVersion(v1, v2 string) int { + if v2 == "" { + if v1 == "" { + return 0 + } + return -1 + } + if v1 == "" { + return 1 + } + return semver.Compare(v1, v2) +} + // mvsReqs implements mvs.Reqs for module semantic versions, // with any exclusions or replacements applied internally. type mvsReqs struct { - buildList []module.Version + roots []module.Version } func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) { if mod == Target { // Use the build list as it existed when r was constructed, not the current // global build list. - return r.buildList[1:], nil + return r.roots, nil } if mod.Version == "none" { @@ -47,7 +66,7 @@ func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) { // be chosen over other versions of the same module in the module dependency // graph. func (*mvsReqs) Max(v1, v2 string) string { - if v1 != "" && (v2 == "" || semver.Compare(v1, v2) == -1) { + if cmpVersion(v1, v2) < 0 { return v2 } return v1 @@ -86,12 +105,12 @@ func versions(ctx context.Context, path string, allowed AllowedFunc) ([]string, return versions, err } -// Previous returns the tagged version of m.Path immediately prior to +// previousVersion returns the tagged version of m.Path immediately prior to // m.Version, or version "none" if no prior version is tagged. // // Since the version of Target is not found in the version list, // it has no previous version. -func (*mvsReqs) Previous(m module.Version) (module.Version, error) { +func previousVersion(m module.Version) (module.Version, error) { // TODO(golang.org/issue/38714): thread tracing context through MVS. if m == Target { @@ -111,3 +130,7 @@ func (*mvsReqs) Previous(m module.Version) (module.Version, error) { } return module.Version{Path: m.Path, Version: "none"}, nil } + +func (*mvsReqs) Previous(m module.Version) (module.Version, error) { + return previousVersion(m) +} diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go index 8affd179bb2e9c0b1d51fec1b3b2bd80e2254017..e737ca90fcd79d911f741b7fc43e1aa663b97d4b 100644 --- a/src/cmd/go/internal/modload/query.go +++ b/src/cmd/go/internal/modload/query.go @@ -5,13 +5,13 @@ package modload import ( + "bytes" "context" "errors" "fmt" "io/fs" "os" pathpkg "path" - "path/filepath" "sort" "strings" "sync" @@ -177,7 +177,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed return nil, err } - if (query == "upgrade" || query == "patch") && modfetch.IsPseudoVersion(current) && !rev.Time.IsZero() { + if (query == "upgrade" || query == "patch") && module.IsPseudoVersion(current) && !rev.Time.IsZero() { // Don't allow "upgrade" or "patch" to move from a pseudo-version // to a chronologically older version or pseudo-version. // @@ -196,7 +196,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed // newer but v1.1.0 is still an “upgrade”; or v1.0.2 might be a revert of // an unsuccessful fix in v1.0.1, in which case the v1.0.2 commit may be // older than the v1.0.1 commit despite the tag itself being newer.) - currentTime, err := modfetch.PseudoVersionTime(current) + currentTime, err := module.PseudoVersionTime(current) if err == nil && rev.Time.Before(currentTime) { if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) { return nil, err @@ -325,18 +325,18 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (* if current == "" || current == "none" { qm.mayUseLatest = true } else { - qm.mayUseLatest = modfetch.IsPseudoVersion(current) + qm.mayUseLatest = module.IsPseudoVersion(current) qm.filter = func(mv string) bool { return semver.Compare(mv, current) >= 0 } } case query == "patch": - if current == "none" { + if current == "" || current == "none" { return nil, &NoPatchBaseError{path} } if current == "" { qm.mayUseLatest = true } else { - qm.mayUseLatest = modfetch.IsPseudoVersion(current) + qm.mayUseLatest = module.IsPseudoVersion(current) qm.prefix = semver.MajorMinor(current) + "." qm.filter = func(mv string) bool { return semver.Compare(mv, current) >= 0 } } @@ -695,7 +695,9 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin // modulePrefixesExcludingTarget returns all prefixes of path that may plausibly // exist as a module, excluding targetPrefix but otherwise including path -// itself, sorted by descending length. +// itself, sorted by descending length. Prefixes that are not valid module paths +// but are valid package paths (like "m" or "example.com/.gen") are included, +// since they might be replaced. func modulePrefixesExcludingTarget(path string) []string { prefixes := make([]string, 0, strings.Count(path, "/")+1) @@ -747,6 +749,7 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod noPackage *PackageNotInModuleError noVersion *NoMatchingVersionError noPatchBase *NoPatchBaseError + invalidPath *module.InvalidPathError // see comment in case below notExistErr error ) for _, r := range results { @@ -767,6 +770,17 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod if noPatchBase == nil { noPatchBase = rErr } + case *module.InvalidPathError: + // The prefix was not a valid module path, and there was no replacement. + // Prefixes like this may appear in candidateModules, since we handle + // replaced modules that weren't required in the repo lookup process + // (see lookupRepo). + // + // A shorter prefix may be a valid module path and may contain a valid + // import path, so this is a low-priority error. + if invalidPath == nil { + invalidPath = rErr + } default: if errors.Is(rErr, fs.ErrNotExist) { if notExistErr == nil { @@ -800,6 +814,8 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod err = noVersion case noPatchBase != nil: err = noPatchBase + case invalidPath != nil: + err = invalidPath case notExistErr != nil: err = notExistErr default: @@ -904,8 +920,8 @@ func (e *PackageNotInModuleError) ImportPath() string { return "" } -// ModuleHasRootPackage returns whether module m contains a package m.Path. -func ModuleHasRootPackage(ctx context.Context, m module.Version) (bool, error) { +// moduleHasRootPackage returns whether module m contains a package m.Path. +func moduleHasRootPackage(ctx context.Context, m module.Version) (bool, error) { needSum := false root, isLocal, err := fetch(ctx, m, needSum) if err != nil { @@ -915,14 +931,32 @@ func ModuleHasRootPackage(ctx context.Context, m module.Version) (bool, error) { return ok, err } -func versionHasGoMod(ctx context.Context, m module.Version) (bool, error) { - needSum := false - root, _, err := fetch(ctx, m, needSum) +// versionHasGoMod returns whether a version has a go.mod file. +// +// versionHasGoMod fetches the go.mod file (possibly a fake) and true if it +// contains anything other than a module directive with the same path. When a +// module does not have a real go.mod file, the go command acts as if it had one +// that only contained a module directive. Normal go.mod files created after +// 1.12 at least have a go directive. +// +// This function is a heuristic, since it's possible to commit a file that would +// pass this test. However, we only need a heurstic for determining whether +// +incompatible versions may be "latest", which is what this function is used +// for. +// +// This heuristic is useful for two reasons: first, when using a proxy, +// this lets us fetch from the .mod endpoint which is much faster than the .zip +// endpoint. The .mod file is used anyway, even if the .zip file contains a +// go.mod with different content. Second, if we don't fetch the .zip, then +// we don't need to verify it in go.sum. This makes 'go list -m -u' faster +// and simpler. +func versionHasGoMod(_ context.Context, m module.Version) (bool, error) { + _, data, err := rawGoModData(m) if err != nil { return false, err } - fi, err := os.Stat(filepath.Join(root, "go.mod")) - return err == nil && !fi.IsDir(), nil + isFake := bytes.Equal(data, modfetch.LegacyGoMod(m.Path)) + return !isFake, nil } // A versionRepo is a subset of modfetch.Repo that can report information about @@ -993,7 +1027,7 @@ func (rr *replacementRepo) Versions(prefix string) ([]string, error) { if index != nil && len(index.replace) > 0 { path := rr.ModulePath() for m, _ := range index.replace { - if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !modfetch.IsPseudoVersion(m.Version) { + if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) { versions = append(versions, m.Version) } } @@ -1050,9 +1084,9 @@ func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) { // used from within some other module, the user will be able to upgrade // the requirement to any real version they choose. if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 { - v = modfetch.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000") + v = module.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000") } else { - v = modfetch.PseudoVersion("v0", "", time.Time{}, "000000000000") + v = module.PseudoVersion("v0", "", time.Time{}, "000000000000") } } @@ -1067,9 +1101,9 @@ func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) { func (rr *replacementRepo) replacementStat(v string) (*modfetch.RevInfo, error) { rev := &modfetch.RevInfo{Version: v} - if modfetch.IsPseudoVersion(v) { - rev.Time, _ = modfetch.PseudoVersionTime(v) - rev.Short, _ = modfetch.PseudoVersionRev(v) + if module.IsPseudoVersion(v) { + rev.Time, _ = module.PseudoVersionTime(v) + rev.Short, _ = module.PseudoVersionRev(v) } return rev, nil } diff --git a/src/cmd/go/internal/modload/query_test.go b/src/cmd/go/internal/modload/query_test.go index e225a0e71e7eb2ec34b549cb9e37d7b1e9821c46..a3f2f84505a0f996d48aa6bb91937e63c33ff097 100644 --- a/src/cmd/go/internal/modload/query_test.go +++ b/src/cmd/go/internal/modload/query_test.go @@ -106,7 +106,7 @@ var queryTests = []struct { {path: queryRepo, query: "v1.9.10-pre2+metadata", vers: "v1.9.10-pre2.0.20190513201126-42abcb6df8ee"}, {path: queryRepo, query: "ed5ffdaa", vers: "v1.9.10-pre2.0.20191220134614-ed5ffdaa1f5e"}, - // golang.org/issue/29262: The major version for for a module without a suffix + // golang.org/issue/29262: The major version for a module without a suffix // should be based on the most recent tag (v1 as appropriate, not v0 // unconditionally). {path: queryRepo, query: "42abcb6df8ee", vers: "v1.9.10-pre2.0.20190513201126-42abcb6df8ee"}, @@ -122,7 +122,7 @@ var queryTests = []struct { {path: queryRepo, query: "upgrade", allow: "NOMATCH", err: `no matching versions for query "upgrade"`}, {path: queryRepo, query: "upgrade", current: "v1.9.9", allow: "NOMATCH", err: `vcs-test.golang.org/git/querytest.git@v1.9.9: disallowed module version`}, {path: queryRepo, query: "upgrade", current: "v1.99.99", err: `vcs-test.golang.org/git/querytest.git@v1.99.99: invalid version: unknown revision v1.99.99`}, - {path: queryRepo, query: "patch", current: "", vers: "v1.9.9"}, + {path: queryRepo, query: "patch", current: "", err: `can't query version "patch" of module vcs-test.golang.org/git/querytest.git: no existing version is required`}, {path: queryRepo, query: "patch", current: "v0.1.0", vers: "v0.1.2"}, {path: queryRepo, query: "patch", current: "v1.9.0", vers: "v1.9.9"}, {path: queryRepo, query: "patch", current: "v1.9.10-pre1", vers: "v1.9.10-pre1"}, diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go index 1fe742dc97d51df88db5a33e953223626108c641..658fc6f55a9c9bb127e600f3d5ef51ffeb8c9773 100644 --- a/src/cmd/go/internal/modload/search.go +++ b/src/cmd/go/internal/modload/search.go @@ -86,7 +86,7 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f } if !fi.IsDir() { - if fi.Mode()&fs.ModeSymlink != 0 && want { + if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") { if target, err := fsys.Stat(path); err == nil && target.IsDir() { fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) } @@ -187,7 +187,7 @@ func MatchInModule(ctx context.Context, pattern string, m module.Version, tags m matchPackages(ctx, match, tags, includeStd, nil) } - LoadModFile(ctx) + LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages. if !match.IsLiteral() { matchPackages(ctx, match, tags, omitStd, []module.Version{m}) diff --git a/src/cmd/go/internal/modload/stat_openfile.go b/src/cmd/go/internal/modload/stat_openfile.go index 5842b858f0b1a48a1b2b78b404a574cabf6d58da..368f893198492984b0cc1efaffe988d5f172c5d5 100644 --- a/src/cmd/go/internal/modload/stat_openfile.go +++ b/src/cmd/go/internal/modload/stat_openfile.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build (js && wasm) || plan9 // +build js,wasm plan9 // On plan9, per http://9p.io/magic/man2html/2/access: “Since file permissions diff --git a/src/cmd/go/internal/modload/stat_unix.go b/src/cmd/go/internal/modload/stat_unix.go index f49278ec3a8341668f35711cfc7eaae3f9775e89..e079d7399026feb44091d94973cfe0eeb6da9f05 100644 --- a/src/cmd/go/internal/modload/stat_unix.go +++ b/src/cmd/go/internal/modload/stat_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris package modload diff --git a/src/cmd/go/internal/modload/stat_windows.go b/src/cmd/go/internal/modload/stat_windows.go index 0ac23913475beab944be2a70ecaa3202e165b8ec..825e60b27af89406122aa400b92df706aba508d5 100644 --- a/src/cmd/go/internal/modload/stat_windows.go +++ b/src/cmd/go/internal/modload/stat_windows.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows // +build windows package modload diff --git a/src/cmd/go/internal/modload/vendor.go b/src/cmd/go/internal/modload/vendor.go index d8fd91f1fea2b20339b4c2f61a1a77f5da6ab15f..80713b0812eacce928f0a39b26eba024e655a56b 100644 --- a/src/cmd/go/internal/modload/vendor.go +++ b/src/cmd/go/internal/modload/vendor.go @@ -31,6 +31,7 @@ var ( type vendorMetadata struct { Explicit bool Replacement module.Version + GoVersion string } // readVendorList reads the list of vendored modules from vendor/modules.txt. @@ -104,6 +105,10 @@ func readVendorList() { if entry == "explicit" { meta.Explicit = true } + if strings.HasPrefix(entry, "go ") { + meta.GoVersion = strings.TrimPrefix(entry, "go ") + rawGoVersion.Store(mod, meta.GoVersion) + } // All other tokens are reserved for future use. } vendorMeta[mod] = meta diff --git a/src/cmd/go/internal/mvs/errors.go b/src/cmd/go/internal/mvs/errors.go index 5564965fb51675a33dfd7c303efbacd1009fecda..bf183cea9e88af62b0b8d773297f432bae740b84 100644 --- a/src/cmd/go/internal/mvs/errors.go +++ b/src/cmd/go/internal/mvs/errors.go @@ -31,13 +31,15 @@ type buildListErrorElem struct { // occurred at a module found along the given path of requirements and/or // upgrades, which must be non-empty. // -// The isUpgrade function reports whether a path step is due to an upgrade. -// A nil isUpgrade function indicates that none of the path steps are due to upgrades. -func NewBuildListError(err error, path []module.Version, isUpgrade func(from, to module.Version) bool) *BuildListError { +// The isVersionChange function reports whether a path step is due to an +// explicit upgrade or downgrade (as opposed to an existing requirement in a +// go.mod file). A nil isVersionChange function indicates that none of the path +// steps are due to explicit version changes. +func NewBuildListError(err error, path []module.Version, isVersionChange func(from, to module.Version) bool) *BuildListError { stack := make([]buildListErrorElem, 0, len(path)) for len(path) > 1 { reason := "requires" - if isUpgrade != nil && isUpgrade(path[0], path[1]) { + if isVersionChange != nil && isVersionChange(path[0], path[1]) { reason = "updating to" } stack = append(stack, buildListErrorElem{ diff --git a/src/cmd/go/internal/mvs/graph.go b/src/cmd/go/internal/mvs/graph.go new file mode 100644 index 0000000000000000000000000000000000000000..c5de4866bf467907bafe5d1370b5f2ed39c8c173 --- /dev/null +++ b/src/cmd/go/internal/mvs/graph.go @@ -0,0 +1,223 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mvs + +import ( + "fmt" + + "golang.org/x/mod/module" +) + +// Graph implements an incremental version of the MVS algorithm, with the +// requirements pushed by the caller instead of pulled by the MVS traversal. +type Graph struct { + cmp func(v1, v2 string) int + roots []module.Version + + required map[module.Version][]module.Version + + isRoot map[module.Version]bool // contains true for roots and false for reachable non-roots + selected map[string]string // path → version +} + +// NewGraph returns an incremental MVS graph containing only a set of root +// dependencies and using the given max function for version strings. +// +// The caller must ensure that the root slice is not modified while the Graph +// may be in use. +func NewGraph(cmp func(v1, v2 string) int, roots []module.Version) *Graph { + g := &Graph{ + cmp: cmp, + roots: roots[:len(roots):len(roots)], + required: make(map[module.Version][]module.Version), + isRoot: make(map[module.Version]bool), + selected: make(map[string]string), + } + + for _, m := range roots { + g.isRoot[m] = true + if g.cmp(g.Selected(m.Path), m.Version) < 0 { + g.selected[m.Path] = m.Version + } + } + + return g +} + +// Require adds the information that module m requires all modules in reqs. +// The reqs slice must not be modified after it is passed to Require. +// +// m must be reachable by some existing chain of requirements from g's target, +// and Require must not have been called for it already. +// +// If any of the modules in reqs has the same path as g's target, +// the target must have higher precedence than the version in req. +func (g *Graph) Require(m module.Version, reqs []module.Version) { + // To help catch disconnected-graph bugs, enforce that all required versions + // are actually reachable from the roots (and therefore should affect the + // selected versions of the modules they name). + if _, reachable := g.isRoot[m]; !reachable { + panic(fmt.Sprintf("%v is not reachable from any root", m)) + } + + // Truncate reqs to its capacity to avoid aliasing bugs if it is later + // returned from RequiredBy and appended to. + reqs = reqs[:len(reqs):len(reqs)] + + if _, dup := g.required[m]; dup { + panic(fmt.Sprintf("requirements of %v have already been set", m)) + } + g.required[m] = reqs + + for _, dep := range reqs { + // Mark dep reachable, regardless of whether it is selected. + if _, ok := g.isRoot[dep]; !ok { + g.isRoot[dep] = false + } + + if g.cmp(g.Selected(dep.Path), dep.Version) < 0 { + g.selected[dep.Path] = dep.Version + } + } +} + +// RequiredBy returns the slice of requirements passed to Require for m, if any, +// with its capacity reduced to its length. +// If Require has not been called for m, RequiredBy(m) returns ok=false. +// +// The caller must not modify the returned slice, but may safely append to it +// and may rely on it not to be modified. +func (g *Graph) RequiredBy(m module.Version) (reqs []module.Version, ok bool) { + reqs, ok = g.required[m] + return reqs, ok +} + +// Selected returns the selected version of the given module path. +// +// If no version is selected, Selected returns version "none". +func (g *Graph) Selected(path string) (version string) { + v, ok := g.selected[path] + if !ok { + return "none" + } + return v +} + +// BuildList returns the selected versions of all modules present in the Graph, +// beginning with the selected versions of each module path in the roots of g. +// +// The order of the remaining elements in the list is deterministic +// but arbitrary. +func (g *Graph) BuildList() []module.Version { + seenRoot := make(map[string]bool, len(g.roots)) + + var list []module.Version + for _, r := range g.roots { + if seenRoot[r.Path] { + // Multiple copies of the same root, with the same or different versions, + // are a bit of a degenerate case: we will take the transitive + // requirements of both roots into account, but only the higher one can + // possibly be selected. However — especially given that we need the + // seenRoot map for later anyway — it is simpler to support this + // degenerate case than to forbid it. + continue + } + + if v := g.Selected(r.Path); v != "none" { + list = append(list, module.Version{Path: r.Path, Version: v}) + } + seenRoot[r.Path] = true + } + uniqueRoots := list + + for path, version := range g.selected { + if !seenRoot[path] { + list = append(list, module.Version{Path: path, Version: version}) + } + } + module.Sort(list[len(uniqueRoots):]) + + return list +} + +// WalkBreadthFirst invokes f once, in breadth-first order, for each module +// version other than "none" that appears in the graph, regardless of whether +// that version is selected. +func (g *Graph) WalkBreadthFirst(f func(m module.Version)) { + var queue []module.Version + enqueued := make(map[module.Version]bool) + for _, m := range g.roots { + if m.Version != "none" { + queue = append(queue, m) + enqueued[m] = true + } + } + + for len(queue) > 0 { + m := queue[0] + queue = queue[1:] + + f(m) + + reqs, _ := g.RequiredBy(m) + for _, r := range reqs { + if !enqueued[r] && r.Version != "none" { + queue = append(queue, r) + enqueued[r] = true + } + } + } +} + +// FindPath reports a shortest requirement path starting at one of the roots of +// the graph and ending at a module version m for which f(m) returns true, or +// nil if no such path exists. +func (g *Graph) FindPath(f func(module.Version) bool) []module.Version { + // firstRequires[a] = b means that in a breadth-first traversal of the + // requirement graph, the module version a was first required by b. + firstRequires := make(map[module.Version]module.Version) + + queue := g.roots + for _, m := range g.roots { + firstRequires[m] = module.Version{} + } + + for len(queue) > 0 { + m := queue[0] + queue = queue[1:] + + if f(m) { + // Construct the path reversed (because we're starting from the far + // endpoint), then reverse it. + path := []module.Version{m} + for { + m = firstRequires[m] + if m.Path == "" { + break + } + path = append(path, m) + } + + i, j := 0, len(path)-1 + for i < j { + path[i], path[j] = path[j], path[i] + i++ + j-- + } + + return path + } + + reqs, _ := g.RequiredBy(m) + for _, r := range reqs { + if _, seen := firstRequires[r]; !seen { + queue = append(queue, r) + firstRequires[r] = m + } + } + } + + return nil +} diff --git a/src/cmd/go/internal/mvs/mvs.go b/src/cmd/go/internal/mvs/mvs.go index b630b610f10e03641362acf7b385f34781e58cf5..6969f90f2e681abf23495ab7aab8ba4432453d17 100644 --- a/src/cmd/go/internal/mvs/mvs.go +++ b/src/cmd/go/internal/mvs/mvs.go @@ -10,7 +10,6 @@ import ( "fmt" "sort" "sync" - "sync/atomic" "cmd/go/internal/par" @@ -41,6 +40,11 @@ type Reqs interface { // Note that v1 < v2 can be written Max(v1, v2) != v1 // and similarly v1 <= v2 can be written Max(v1, v2) == v2. Max(v1, v2 string) string +} + +// An UpgradeReqs is a Reqs that can also identify available upgrades. +type UpgradeReqs interface { + Reqs // Upgrade returns the upgraded version of m, // for use during an UpgradeAll operation. @@ -54,6 +58,11 @@ type Reqs interface { // TODO(rsc): Upgrade must be able to return errors, // but should "no latest version" just return m instead? Upgrade(m module.Version) (module.Version, error) +} + +// A DowngradeReqs is a Reqs that can also identify available downgrades. +type DowngradeReqs interface { + Reqs // Previous returns the version of m.Path immediately prior to m.Version, // or "none" if no such version is known. @@ -81,151 +90,91 @@ func BuildList(target module.Version, reqs Reqs) ([]module.Version, error) { } func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) (module.Version, error)) ([]module.Version, error) { - // Explore work graph in parallel in case reqs.Required - // does high-latency network operations. - type modGraphNode struct { - m module.Version - required []module.Version - upgrade module.Version - err error + cmp := func(v1, v2 string) int { + if reqs.Max(v1, v2) != v1 { + return -1 + } + if reqs.Max(v2, v1) != v2 { + return 1 + } + return 0 } + var ( mu sync.Mutex - modGraph = map[module.Version]*modGraphNode{} - min = map[string]string{} // maps module path to minimum required version - haveErr int32 + g = NewGraph(cmp, []module.Version{target}) + upgrades = map[module.Version]module.Version{} + errs = map[module.Version]error{} // (non-nil errors only) ) - setErr := func(n *modGraphNode, err error) { - n.err = err - atomic.StoreInt32(&haveErr, 1) - } + // Explore work graph in parallel in case reqs.Required + // does high-latency network operations. var work par.Work work.Add(target) work.Do(10, func(item interface{}) { m := item.(module.Version) - node := &modGraphNode{m: m} - mu.Lock() - modGraph[m] = node + var required []module.Version + var err error if m.Version != "none" { - if v, ok := min[m.Path]; !ok || reqs.Max(v, m.Version) != v { - min[m.Path] = m.Version - } + required, err = reqs.Required(m) } - mu.Unlock() - if m.Version != "none" { - required, err := reqs.Required(m) - if err != nil { - setErr(node, err) - return - } - node.required = required - for _, r := range node.required { - work.Add(r) + u := m + if upgrade != nil { + upgradeTo, upErr := upgrade(m) + if upErr == nil { + u = upgradeTo + } else if err == nil { + err = upErr } } - if upgrade != nil { - u, err := upgrade(m) - if err != nil { - setErr(node, err) - return - } - if u != m { - node.upgrade = u - work.Add(u) - } + mu.Lock() + if err != nil { + errs[m] = err + } + if u != m { + upgrades[m] = u + required = append([]module.Version{u}, required...) + } + g.Require(m, required) + mu.Unlock() + + for _, r := range required { + work.Add(r) } }) // If there was an error, find the shortest path from the target to the // node where the error occurred so we can report a useful error message. - if haveErr != 0 { - // neededBy[a] = b means a was added to the module graph by b. - neededBy := make(map[*modGraphNode]*modGraphNode) - q := make([]*modGraphNode, 0, len(modGraph)) - q = append(q, modGraph[target]) - for len(q) > 0 { - node := q[0] - q = q[1:] - - if node.err != nil { - pathUpgrade := map[module.Version]module.Version{} - - // Construct the error path reversed (from the error to the main module), - // then reverse it to obtain the usual order (from the main module to - // the error). - errPath := []module.Version{node.m} - for n, prev := neededBy[node], node; n != nil; n, prev = neededBy[n], n { - if n.upgrade == prev.m { - pathUpgrade[n.m] = prev.m - } - errPath = append(errPath, n.m) - } - i, j := 0, len(errPath)-1 - for i < j { - errPath[i], errPath[j] = errPath[j], errPath[i] - i++ - j-- - } - - isUpgrade := func(from, to module.Version) bool { - return pathUpgrade[from] == to - } - - return nil, NewBuildListError(node.err, errPath, isUpgrade) - } + if len(errs) > 0 { + errPath := g.FindPath(func(m module.Version) bool { + return errs[m] != nil + }) + if len(errPath) == 0 { + panic("internal error: could not reconstruct path to module with error") + } - neighbors := node.required - if node.upgrade.Path != "" { - neighbors = append(neighbors, node.upgrade) - } - for _, neighbor := range neighbors { - nn := modGraph[neighbor] - if neededBy[nn] != nil { - continue - } - neededBy[nn] = node - q = append(q, nn) + err := errs[errPath[len(errPath)-1]] + isUpgrade := func(from, to module.Version) bool { + if u, ok := upgrades[from]; ok { + return u == to } + return false } + return nil, NewBuildListError(err.(error), errPath, isUpgrade) } // The final list is the minimum version of each module found in the graph. - - if v := min[target.Path]; v != target.Version { + list := g.BuildList() + if v := list[0]; v != target { // target.Version will be "" for modload, the main client of MVS. // "" denotes the main module, which has no version. However, MVS treats // version strings as opaque, so "" is not a special value here. // See golang.org/issue/31491, golang.org/issue/29773. - panic(fmt.Sprintf("mistake: chose version %q instead of target %+v", v, target)) // TODO: Don't panic. + panic(fmt.Sprintf("mistake: chose version %q instead of target %+v", v, target)) } - - list := []module.Version{target} - for path, vers := range min { - if path != target.Path { - list = append(list, module.Version{Path: path, Version: vers}) - } - - n := modGraph[module.Version{Path: path, Version: vers}] - required := n.required - for _, r := range required { - if r.Version == "none" { - continue - } - v := min[r.Path] - if r.Path != target.Path && reqs.Max(v, r.Version) != v { - panic(fmt.Sprintf("mistake: version %q does not satisfy requirement %+v", v, r)) // TODO: Don't panic. - } - } - } - - tail := list[1:] - sort.Slice(tail, func(i, j int) bool { - return tail[i].Path < tail[j].Path - }) return list, nil } @@ -293,10 +242,15 @@ func Req(target module.Version, base []string, reqs Reqs) ([]module.Version, err } // First walk the base modules that must be listed. var min []module.Version + haveBase := map[string]bool{} for _, path := range base { + if haveBase[path] { + continue + } m := module.Version{Path: path, Version: max[path]} min = append(min, m) walk(m) + haveBase[path] = true } // Now the reverse postorder to bring in anything else. for i := len(postorder) - 1; i >= 0; i-- { @@ -318,7 +272,7 @@ func Req(target module.Version, base []string, reqs Reqs) ([]module.Version, err // UpgradeAll returns a build list for the target module // in which every module is upgraded to its latest version. -func UpgradeAll(target module.Version, reqs Reqs) ([]module.Version, error) { +func UpgradeAll(target module.Version, reqs UpgradeReqs) ([]module.Version, error) { return buildList(target, reqs, func(m module.Version) (module.Version, error) { if m.Path == target.Path { return target, nil @@ -330,7 +284,7 @@ func UpgradeAll(target module.Version, reqs Reqs) ([]module.Version, error) { // Upgrade returns a build list for the target module // in which the given additional modules are upgraded. -func Upgrade(target module.Version, reqs Reqs, upgrade ...module.Version) ([]module.Version, error) { +func Upgrade(target module.Version, reqs UpgradeReqs, upgrade ...module.Version) ([]module.Version, error) { list, err := reqs.Required(target) if err != nil { return nil, err @@ -369,11 +323,20 @@ func Upgrade(target module.Version, reqs Reqs, upgrade ...module.Version) ([]mod // The versions to be downgraded may be unreachable from reqs.Latest and // reqs.Previous, but the methods of reqs must otherwise handle such versions // correctly. -func Downgrade(target module.Version, reqs Reqs, downgrade ...module.Version) ([]module.Version, error) { - list, err := reqs.Required(target) +func Downgrade(target module.Version, reqs DowngradeReqs, downgrade ...module.Version) ([]module.Version, error) { + // Per https://research.swtch.com/vgo-mvs#algorithm_4: + // “To avoid an unnecessary downgrade to E 1.1, we must also add a new + // requirement on E 1.2. We can apply Algorithm R to find the minimal set of + // new requirements to write to go.mod.” + // + // In order to generate those new requirements, we need to identify versions + // for every module in the build list — not just reqs.Required(target). + list, err := BuildList(target, reqs) if err != nil { return nil, err } + list = list[1:] // remove target + max := make(map[string]string) for _, r := range list { max[r.Path] = r.Version @@ -406,6 +369,9 @@ func Downgrade(target module.Version, reqs Reqs, downgrade ...module.Version) ([ } added[m] = true if v, ok := max[m.Path]; ok && reqs.Max(m.Version, v) != v { + // m would upgrade an existing dependency — it is not a strict downgrade, + // and because it was already present as a dependency, it could affect the + // behavior of other relevant packages. exclude(m) return } @@ -422,6 +388,7 @@ func Downgrade(target module.Version, reqs Reqs, downgrade ...module.Version) ([ // is transient (we couldn't download go.mod), return the error from // Downgrade. Currently, we can't tell what kind of error it is. exclude(m) + return } for _, r := range list { add(r) @@ -433,8 +400,8 @@ func Downgrade(target module.Version, reqs Reqs, downgrade ...module.Version) ([ } } - var out []module.Version - out = append(out, target) + downgraded := make([]module.Version, 0, len(list)+1) + downgraded = append(downgraded, target) List: for _, r := range list { add(r) @@ -461,10 +428,49 @@ List: add(p) r = p } - out = append(out, r) + downgraded = append(downgraded, r) } - return out, nil + // The downgrades we computed above only downgrade to versions enumerated by + // reqs.Previous. However, reqs.Previous omits some versions — such as + // pseudo-versions and retracted versions — that may be selected as transitive + // requirements of other modules. + // + // If one of those requirements pulls the version back up above the version + // identified by reqs.Previous, then the transitive dependencies of that that + // initially-downgraded version should no longer matter — in particular, we + // should not add new dependencies on module paths that nothing else in the + // updated module graph even requires. + // + // In order to eliminate those spurious dependencies, we recompute the build + // list with the actual versions of the downgraded modules as selected by MVS, + // instead of our initial downgrades. + // (See the downhiddenartifact and downhiddencross test cases). + actual, err := BuildList(target, &override{ + target: target, + list: downgraded, + Reqs: reqs, + }) + if err != nil { + return nil, err + } + actualVersion := make(map[string]string, len(actual)) + for _, m := range actual { + actualVersion[m.Path] = m.Version + } + + downgraded = downgraded[:0] + for _, m := range list { + if v, ok := actualVersion[m.Path]; ok { + downgraded = append(downgraded, module.Version{Path: m.Path, Version: v}) + } + } + + return BuildList(target, &override{ + target: target, + list: downgraded, + Reqs: reqs, + }) } type override struct { diff --git a/src/cmd/go/internal/mvs/mvs_test.go b/src/cmd/go/internal/mvs/mvs_test.go index 721cd9635c8d84dabead835817dfa9f817b97fc2..598ed666889517a112c01e78d5b6fcb95101fcd2 100644 --- a/src/cmd/go/internal/mvs/mvs_test.go +++ b/src/cmd/go/internal/mvs/mvs_test.go @@ -28,10 +28,11 @@ D4: E2 F1 D5: E2 G1: C4 A2: B1 C4 D4 -build A: A B1 C2 D4 E2 F1 -upgrade* A: A B1 C4 D5 E2 F1 G1 -upgrade A C4: A B1 C4 D4 E2 F1 G1 -downgrade A2 D2: A2 C4 D2 +build A: A B1 C2 D4 E2 F1 +upgrade* A: A B1 C4 D5 E2 F1 G1 +upgrade A C4: A B1 C4 D4 E2 F1 G1 +build A2: A2 B1 C4 D4 E2 F1 G1 +downgrade A2 D2: A2 C4 D2 E2 F1 G1 name: trim A: B1 C2 @@ -68,7 +69,7 @@ B2: D1 C: D2 D1: E2 D2: E1 -build A: A B1 C D2 E1 +build A: A B1 C D2 E1 upgrade A B2: A B2 C D2 E2 name: cross1R @@ -136,17 +137,17 @@ name: cross5 A: D1 D1: E2 D2: E1 -build A: A D1 E2 -upgrade* A: A D2 E2 -upgrade A D2: A D2 E2 +build A: A D1 E2 +upgrade* A: A D2 E2 +upgrade A D2: A D2 E2 upgradereq A D2: D2 E2 name: cross6 A: D2 D1: E2 D2: E1 -build A: A D2 E1 -upgrade* A: A D2 E2 +build A: A D2 E1 +upgrade* A: A D2 E2 upgrade A E2: A D2 E2 name: cross7 @@ -175,7 +176,7 @@ B1: D1 B2: C2: D2: -build A: A B1 C1 D1 +build A: A B1 C1 D1 upgrade* A: A B2 C2 D2 name: simplify @@ -194,7 +195,7 @@ B4: B5.hidden: C2: C3: -build A: A B1 C1 +build A: A B1 C1 upgrade* A: A B4 C3 name: up2 @@ -206,15 +207,15 @@ B4: B5.hidden: C2: C3: -build A: A B5.hidden C1 +build A: A B5.hidden C1 upgrade* A: A B5.hidden C3 name: down1 A: B2 B1: C1 B2: C2 -build A: A B2 C2 -downgrade A C1: A B1 +build A: A B2 C2 +downgrade A C1: A B1 C1 name: down2 A: B2 E2 @@ -227,19 +228,113 @@ D2: B2 E2: D2 E1: F1: -downgrade A F1: A B1 E1 +build A: A B2 C2 D2 E2 F2 +downgrade A F1: A B1 C1 D1 E1 F1 + +# https://research.swtch.com/vgo-mvs#algorithm_4: +# “[D]owngrades are constrained to only downgrade packages, not also upgrade +# them; if an upgrade before downgrade is needed, the user must ask for it +# explicitly.” +# +# Here, downgrading B2 to B1 upgrades C1 to C2, and C2 does not depend on D2. +# However, C2 would be an upgrade — not a downgrade — so B1 must also be +# rejected. +name: downcross1 +A: B2 C1 +B1: C2 +B2: C1 +C1: D2 +C2: +D1: +D2: +build A: A B2 C1 D2 +downgrade A D1: A D1 + +# https://research.swtch.com/vgo-mvs#algorithm_4: +# “Unlike upgrades, downgrades must work by removing requirements, not adding +# them.” +# +# However, downgrading a requirement may introduce a new requirement on a +# previously-unrequired module. If each dependency's requirements are complete +# (“tidy”), that can't change the behavior of any other package whose version is +# not also being downgraded, so we should allow it. +name: downcross2 +A: B2 +B1: C1 +B2: D2 +C1: +D1: +D2: +build A: A B2 D2 +downgrade A D1: A B1 C1 D1 name: downcycle A: A B2 B2: A B1: +build A: A B2 downgrade A B1: A B1 +# Both B3 and C2 require D2. +# If we downgrade D to D1, then in isolation B3 would downgrade to B1, +# because B2 is hidden — B1 is the next-highest version that is not hidden. +# However, if we downgrade D, we will also downgrade C to C1. +# And C1 requires B2.hidden, and B2.hidden also meets our requirements: +# it is compatible with D1 and a strict downgrade from B3. +# +# Since neither the initial nor the final build list includes B1, +# and the nothing in the final downgraded build list requires E at all, +# no dependency on E1 (required by only B1) should be introduced. +# +name: downhiddenartifact +A: B3 C2 +A1: B3 +B1: E1 +B2.hidden: +B3: D2 +C1: B2.hidden +C2: D2 +D1: +D2: +build A1: A1 B3 D2 +downgrade A1 D1: A1 B1 D1 E1 +build A: A B3 C2 D2 +downgrade A D1: A B2.hidden C1 D1 + +# Both B3 and C3 require D2. +# If we downgrade D to D1, then in isolation B3 would downgrade to B1, +# and C3 would downgrade to C1. +# But C1 requires B2.hidden, and B1 requires C2.hidden, so we can't +# downgrade to either of those without pulling the other back up a little. +# +# B2.hidden and C2.hidden are both compatible with D1, so that still +# meets our requirements — but then we're in an odd state in which +# B and C have both been downgraded to hidden versions, without any +# remaining requirements to explain how those hidden versions got there. +# +# TODO(bcmills): Would it be better to force downgrades to land on non-hidden +# versions? +# In this case, that would remove the dependencies on B and C entirely. +# +name: downhiddencross +A: B3 C3 +B1: C2.hidden +B2.hidden: +B3: D2 +C1: B2.hidden +C2.hidden: +C3: D2 +D1: +D2: +build A: A B3 C3 D2 +downgrade A D1: A B2.hidden C2.hidden D1 + # golang.org/issue/25542. name: noprev1 A: B4 C2 B2.hidden: C2: +build A: A B4 C2 downgrade A B2.hidden: A B2.hidden C2 name: noprev2 @@ -247,6 +342,7 @@ A: B4 C2 B2.hidden: B1: C2: +build A: A B4 C2 downgrade A B2.hidden: A B2.hidden C2 name: noprev3 @@ -254,6 +350,7 @@ A: B4 C2 B3: B2.hidden: C2: +build A: A B4 C2 downgrade A B2.hidden: A B2.hidden C2 # Cycles involving the target. @@ -264,9 +361,9 @@ A: B1 B1: A1 B2: A2 B3: A3 -build A: A B1 +build A: A B1 upgrade A B2: A B2 -upgrade* A: A B3 +upgrade* A: A B3 # golang.org/issue/29773: # Requirements of older versions of the target @@ -280,7 +377,7 @@ B2: A2 C1: A2 C2: D2: -build A: A B1 C1 D1 +build A: A B1 C1 D1 upgrade* A: A B2 C2 D2 # Cycles with multiple possible solutions. @@ -293,23 +390,23 @@ B2: C2 C1: C2: B2 build M: M A1 B2 C2 -req M: A1 B2 -req M A: A1 B2 -req M C: A1 C2 +req M: A1 B2 +req M A: A1 B2 +req M C: A1 C2 # Requirement minimization. name: req1 A: B1 C1 D1 E1 F1 B1: C1 E1 F1 -req A: B1 D1 +req A: B1 D1 req A C: B1 C1 D1 name: req2 A: G1 H1 G1: H1 H1: G1 -req A: G1 +req A: G1 req A G: G1 req A H: H1 @@ -326,7 +423,20 @@ M: Anone B1 D1 E1 B1: Cnone D1 E1: Fnone build M: M B1 D1 E1 -req M: B1 E1 +req M: B1 E1 + +name: reqdup +M: A1 B1 +A1: B1 +B1: +req M A A: A1 + +name: reqcross +M: A1 B1 C1 +A1: B1 C1 +B1: C1 +C1: +req M A B: A1 B1 ` func Test(t *testing.T) { diff --git a/src/cmd/go/internal/renameio/renameio.go b/src/cmd/go/internal/renameio/renameio.go deleted file mode 100644 index 9788171d6e200d6c5648772cf3729a54ae606d66..0000000000000000000000000000000000000000 --- a/src/cmd/go/internal/renameio/renameio.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package renameio writes files atomically by renaming temporary files. -package renameio - -import ( - "bytes" - "io" - "io/fs" - "math/rand" - "os" - "path/filepath" - "strconv" - - "cmd/go/internal/robustio" -) - -const patternSuffix = ".tmp" - -// Pattern returns a glob pattern that matches the unrenamed temporary files -// created when writing to filename. -func Pattern(filename string) string { - return filepath.Join(filepath.Dir(filename), filepath.Base(filename)+patternSuffix) -} - -// WriteFile is like os.WriteFile, but first writes data to an arbitrary -// file in the same directory as filename, then renames it atomically to the -// final name. -// -// That ensures that the final location, if it exists, is always a complete file. -func WriteFile(filename string, data []byte, perm fs.FileMode) (err error) { - return WriteToFile(filename, bytes.NewReader(data), perm) -} - -// WriteToFile is a variant of WriteFile that accepts the data as an io.Reader -// instead of a slice. -func WriteToFile(filename string, data io.Reader, perm fs.FileMode) (err error) { - f, err := tempFile(filepath.Dir(filename), filepath.Base(filename), perm) - if err != nil { - return err - } - defer func() { - // Only call os.Remove on f.Name() if we failed to rename it: otherwise, - // some other process may have created a new file with the same name after - // that. - if err != nil { - f.Close() - os.Remove(f.Name()) - } - }() - - if _, err := io.Copy(f, data); err != nil { - return err - } - // Sync the file before renaming it: otherwise, after a crash the reader may - // observe a 0-length file instead of the actual contents. - // See https://golang.org/issue/22397#issuecomment-380831736. - if err := f.Sync(); err != nil { - return err - } - if err := f.Close(); err != nil { - return err - } - - return robustio.Rename(f.Name(), filename) -} - -// ReadFile is like os.ReadFile, but on Windows retries spurious errors that -// may occur if the file is concurrently replaced. -// -// Errors are classified heuristically and retries are bounded, so even this -// function may occasionally return a spurious error on Windows. -// If so, the error will likely wrap one of: -// - syscall.ERROR_ACCESS_DENIED -// - syscall.ERROR_FILE_NOT_FOUND -// - internal/syscall/windows.ERROR_SHARING_VIOLATION -func ReadFile(filename string) ([]byte, error) { - return robustio.ReadFile(filename) -} - -// tempFile creates a new temporary file with given permission bits. -func tempFile(dir, prefix string, perm fs.FileMode) (f *os.File, err error) { - for i := 0; i < 10000; i++ { - name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix) - f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm) - if os.IsExist(err) { - continue - } - break - } - return -} diff --git a/src/cmd/go/internal/renameio/renameio_test.go b/src/cmd/go/internal/renameio/renameio_test.go deleted file mode 100644 index 5b2ed836242a4a8399566514d1c13a716dbd29dc..0000000000000000000000000000000000000000 --- a/src/cmd/go/internal/renameio/renameio_test.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !plan9 - -package renameio - -import ( - "encoding/binary" - "errors" - "internal/testenv" - "math/rand" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - "sync/atomic" - "syscall" - "testing" - "time" - - "cmd/go/internal/robustio" -) - -func TestConcurrentReadsAndWrites(t *testing.T) { - if runtime.GOOS == "darwin" && strings.HasSuffix(testenv.Builder(), "-10_14") { - testenv.SkipFlaky(t, 33041) - } - - dir, err := os.MkdirTemp("", "renameio") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - path := filepath.Join(dir, "blob.bin") - - const chunkWords = 8 << 10 - buf := make([]byte, 2*chunkWords*8) - for i := uint64(0); i < 2*chunkWords; i++ { - binary.LittleEndian.PutUint64(buf[i*8:], i) - } - - var attempts int64 = 128 - if !testing.Short() { - attempts *= 16 - } - const parallel = 32 - - var sem = make(chan bool, parallel) - - var ( - writeSuccesses, readSuccesses int64 // atomic - writeErrnoSeen, readErrnoSeen sync.Map - ) - - for n := attempts; n > 0; n-- { - sem <- true - go func() { - defer func() { <-sem }() - - time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond) - offset := rand.Intn(chunkWords) - chunk := buf[offset*8 : (offset+chunkWords)*8] - if err := WriteFile(path, chunk, 0666); err == nil { - atomic.AddInt64(&writeSuccesses, 1) - } else if robustio.IsEphemeralError(err) { - var ( - errno syscall.Errno - dup bool - ) - if errors.As(err, &errno) { - _, dup = writeErrnoSeen.LoadOrStore(errno, true) - } - if !dup { - t.Logf("ephemeral error: %v", err) - } - } else { - t.Errorf("unexpected error: %v", err) - } - - time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond) - data, err := ReadFile(path) - if err == nil { - atomic.AddInt64(&readSuccesses, 1) - } else if robustio.IsEphemeralError(err) { - var ( - errno syscall.Errno - dup bool - ) - if errors.As(err, &errno) { - _, dup = readErrnoSeen.LoadOrStore(errno, true) - } - if !dup { - t.Logf("ephemeral error: %v", err) - } - return - } else { - t.Errorf("unexpected error: %v", err) - return - } - - if len(data) != 8*chunkWords { - t.Errorf("read %d bytes, but each write is a %d-byte file", len(data), 8*chunkWords) - return - } - - u := binary.LittleEndian.Uint64(data) - for i := 1; i < chunkWords; i++ { - next := binary.LittleEndian.Uint64(data[i*8:]) - if next != u+1 { - t.Errorf("wrote sequential integers, but read integer out of sequence at offset %d", i) - return - } - u = next - } - }() - } - - for n := parallel; n > 0; n-- { - sem <- true - } - - var minWriteSuccesses int64 = attempts - if runtime.GOOS == "windows" { - // Windows produces frequent "Access is denied" errors under heavy rename load. - // As long as those are the only errors and *some* of the writes succeed, we're happy. - minWriteSuccesses = attempts / 4 - } - - if writeSuccesses < minWriteSuccesses { - t.Errorf("%d (of %d) writes succeeded; want ≥ %d", writeSuccesses, attempts, minWriteSuccesses) - } else { - t.Logf("%d (of %d) writes succeeded (ok: ≥ %d)", writeSuccesses, attempts, minWriteSuccesses) - } - - var minReadSuccesses int64 = attempts - - switch runtime.GOOS { - case "windows": - // Windows produces frequent "Access is denied" errors under heavy rename load. - // As long as those are the only errors and *some* of the reads succeed, we're happy. - minReadSuccesses = attempts / 4 - - case "darwin", "ios": - // The filesystem on certain versions of macOS (10.14) and iOS (affected - // versions TBD) occasionally fail with "no such file or directory" errors. - // See https://golang.org/issue/33041 and https://golang.org/issue/42066. - // The flake rate is fairly low, so ensure that at least 75% of attempts - // succeed. - minReadSuccesses = attempts - (attempts / 4) - } - - if readSuccesses < minReadSuccesses { - t.Errorf("%d (of %d) reads succeeded; want ≥ %d", readSuccesses, attempts, minReadSuccesses) - } else { - t.Logf("%d (of %d) reads succeeded (ok: ≥ %d)", readSuccesses, attempts, minReadSuccesses) - } -} diff --git a/src/cmd/go/internal/renameio/umask_test.go b/src/cmd/go/internal/renameio/umask_test.go deleted file mode 100644 index 65e4fa587b7943d740ee024df4eca4c611163993..0000000000000000000000000000000000000000 --- a/src/cmd/go/internal/renameio/umask_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !plan9,!windows,!js - -package renameio - -import ( - "io/fs" - "os" - "path/filepath" - "syscall" - "testing" -) - -func TestWriteFileModeAppliesUmask(t *testing.T) { - dir, err := os.MkdirTemp("", "renameio") - if err != nil { - t.Fatalf("Failed to create temporary directory: %v", err) - } - defer os.RemoveAll(dir) - - const mode = 0644 - const umask = 0007 - defer syscall.Umask(syscall.Umask(umask)) - - file := filepath.Join(dir, "testWrite") - err = WriteFile(file, []byte("go-build"), mode) - if err != nil { - t.Fatalf("Failed to write file: %v", err) - } - - fi, err := os.Stat(file) - if err != nil { - t.Fatalf("Stat %q (looking for mode %#o): %s", file, mode, err) - } - - if fi.Mode()&fs.ModePerm != 0640 { - t.Errorf("Stat %q: mode %#o want %#o", file, fi.Mode()&fs.ModePerm, 0640) - } -} diff --git a/src/cmd/go/internal/robustio/robustio_flaky.go b/src/cmd/go/internal/robustio/robustio_flaky.go index 5bd44bd345338f31f7e278c0eaf66042f955ac51..d5c241857b476c47cfed2c9bb1c5b1f739426ee3 100644 --- a/src/cmd/go/internal/robustio/robustio_flaky.go +++ b/src/cmd/go/internal/robustio/robustio_flaky.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows || darwin // +build windows darwin package robustio diff --git a/src/cmd/go/internal/robustio/robustio_other.go b/src/cmd/go/internal/robustio/robustio_other.go index 6fe7b7e4e4e9df3e4812f73a598e2073be43f96d..3a20cac6cf88aef9882a484439c2298489a80e2f 100644 --- a/src/cmd/go/internal/robustio/robustio_other.go +++ b/src/cmd/go/internal/robustio/robustio_other.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !windows && !darwin // +build !windows,!darwin package robustio diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go index 666b1a0e560c5156d39c56e7ae5a2a5baedaeaa6..784f7162dfd3f5b01bbd7c60a89975a9e3e5f1aa 100644 --- a/src/cmd/go/internal/run/run.go +++ b/src/cmd/go/internal/run/run.go @@ -8,13 +8,16 @@ package run import ( "context" "fmt" + "go/build" "os" "path" + "path/filepath" "strings" "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/load" + "cmd/go/internal/modload" "cmd/go/internal/str" "cmd/go/internal/work" ) @@ -24,10 +27,21 @@ var CmdRun = &base.Command{ Short: "compile and run Go program", Long: ` Run compiles and runs the named main Go package. -Typically the package is specified as a list of .go source files from a single directory, -but it may also be an import path, file system path, or pattern +Typically the package is specified as a list of .go source files from a single +directory, but it may also be an import path, file system path, or pattern matching a single known package, as in 'go run .' or 'go run my/cmd'. +If the package argument has a version suffix (like @latest or @v1.0.0), +"go run" builds the program in module-aware mode, ignoring the go.mod file in +the current directory or any parent directory, if there is one. This is useful +for running programs without affecting the dependencies of the main module. + +If the package argument doesn't have a version suffix, "go run" may run in +module-aware mode or GOPATH mode, depending on the GO111MODULE environment +variable and the presence of a go.mod file. See 'go help modules' for details. +If module-aware mode is enabled, "go run" runs in the context of the main +module. + By default, 'go run' runs the compiled binary directly: 'a.out arguments...'. If the -exec flag is given, 'go run' invokes the binary using xprog: 'xprog a.out arguments...'. @@ -59,14 +73,26 @@ func printStderr(args ...interface{}) (int, error) { } func runRun(ctx context.Context, cmd *base.Command, args []string) { + if shouldUseOutsideModuleMode(args) { + // Set global module flags for 'go run cmd@version'. + // This must be done before modload.Init, but we need to call work.BuildInit + // before loading packages, since it affects package locations, e.g., + // for -race and -msan. + modload.ForceUseModules = true + modload.RootMode = modload.NoRoot + modload.AllowMissingModuleImports() + modload.Init() + } work.BuildInit() var b work.Builder b.Init() b.Print = printStderr + i := 0 for i < len(args) && strings.HasSuffix(args[i], ".go") { i++ } + pkgOpts := load.PackageOpts{MainOnly: true} var p *load.Package if i > 0 { files := args[:i] @@ -77,18 +103,29 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) { base.Fatalf("go run: cannot run *_test.go files (%s)", file) } } - p = load.GoFilesPackage(ctx, files) + p = load.GoFilesPackage(ctx, pkgOpts, files) } else if len(args) > 0 && !strings.HasPrefix(args[0], "-") { - pkgs := load.PackagesAndErrors(ctx, args[:1]) + arg := args[0] + var pkgs []*load.Package + if strings.Contains(arg, "@") && !build.IsLocalImport(arg) && !filepath.IsAbs(arg) { + var err error + pkgs, err = load.PackagesAndErrorsOutsideModule(ctx, pkgOpts, args[:1]) + if err != nil { + base.Fatalf("go run: %v", err) + } + } else { + pkgs = load.PackagesAndErrors(ctx, pkgOpts, args[:1]) + } + if len(pkgs) == 0 { - base.Fatalf("go run: no packages loaded from %s", args[0]) + base.Fatalf("go run: no packages loaded from %s", arg) } if len(pkgs) > 1 { var names []string for _, p := range pkgs { names = append(names, p.ImportPath) } - base.Fatalf("go run: pattern %s matches multiple packages:\n\t%s", args[0], strings.Join(names, "\n\t")) + base.Fatalf("go run: pattern %s matches multiple packages:\n\t%s", arg, strings.Join(names, "\n\t")) } p = pkgs[0] i++ @@ -98,9 +135,6 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) { cmdArgs := args[i:] load.CheckPackageErrors([]*load.Package{p}) - if p.Name != "main" { - base.Fatalf("go run: cannot run non-main package") - } p.Internal.OmitDebug = true p.Target = "" // must build - not up to date if p.Internal.CmdlineFiles { @@ -123,11 +157,34 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) { } else { p.Internal.ExeName = path.Base(p.ImportPath) } + a1 := b.LinkAction(work.ModeBuild, work.ModeBuild, p) a := &work.Action{Mode: "go run", Func: buildRunProgram, Args: cmdArgs, Deps: []*work.Action{a1}} b.Do(ctx, a) } +// shouldUseOutsideModuleMode returns whether 'go run' will load packages in +// module-aware mode, ignoring the go.mod file in the current directory. It +// returns true if the first argument contains "@", does not begin with "-" +// (resembling a flag) or end with ".go" (a file). The argument must not be a +// local or absolute file path. +// +// These rules are slightly different than other commands. Whether or not +// 'go run' uses this mode, it interprets arguments ending with ".go" as files +// and uses arguments up to the last ".go" argument to comprise the package. +// If there are no ".go" arguments, only the first argument is interpreted +// as a package path, since there can be only one package. +func shouldUseOutsideModuleMode(args []string) bool { + // NOTE: "@" not allowed in import paths, but it is allowed in non-canonical + // versions. + return len(args) > 0 && + !strings.HasSuffix(args[0], ".go") && + !strings.HasPrefix(args[0], "-") && + strings.Contains(args[0], "@") && + !build.IsLocalImport(args[0]) && + !filepath.IsAbs(args[0]) +} + // buildRunProgram is the action for running a binary that has already // been compiled. We ignore exit status. func buildRunProgram(b *work.Builder, ctx context.Context, a *work.Action) error { diff --git a/src/cmd/go/internal/search/search.go b/src/cmd/go/internal/search/search.go index 18738cf59ec8d3c48dc30339c869c40ef698bde0..a0c806a259329c27c2f45739e1250550bec04f06 100644 --- a/src/cmd/go/internal/search/search.go +++ b/src/cmd/go/internal/search/search.go @@ -155,7 +155,7 @@ func (m *Match) MatchPackages() { } if !fi.IsDir() { - if fi.Mode()&fs.ModeSymlink != 0 && want { + if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.pattern, "...") { if target, err := fsys.Stat(path); err == nil && target.IsDir() { fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) } @@ -445,7 +445,7 @@ func ImportPathsQuiet(patterns []string) []*Match { for i, dir := range m.Dirs { absDir := dir if !filepath.IsAbs(dir) { - absDir = filepath.Join(base.Cwd, dir) + absDir = filepath.Join(base.Cwd(), dir) } if bp, _ := cfg.BuildContext.ImportDir(absDir, build.FindOnly); bp.ImportPath != "" && bp.ImportPath != "." { m.Pkgs[i] = bp.ImportPath @@ -571,7 +571,6 @@ func IsRelativePath(pattern string) bool { // If so, InDir returns an equivalent path relative to dir. // If not, InDir returns an empty string. // InDir makes some effort to succeed even in the presence of symbolic links. -// TODO(rsc): Replace internal/test.inDir with a call to this function for Go 1.12. func InDir(path, dir string) string { if rel := inDirLex(path, dir); rel != "" { return rel diff --git a/src/cmd/go/internal/test/cover.go b/src/cmd/go/internal/test/cover.go index 9841791552dbbc125afa5b3fa4e193a62d4aaad1..657d22a6b4d2ff8a7a1d69c6e2ed76e6c869c3e6 100644 --- a/src/cmd/go/internal/test/cover.go +++ b/src/cmd/go/internal/test/cover.go @@ -26,8 +26,8 @@ func initCoverProfile() { if testCoverProfile == "" || testC { return } - if !filepath.IsAbs(testCoverProfile) && testOutputDir != "" { - testCoverProfile = filepath.Join(testOutputDir, testCoverProfile) + if !filepath.IsAbs(testCoverProfile) { + testCoverProfile = filepath.Join(testOutputDir.getAbs(), testCoverProfile) } // No mutex - caller's responsibility to call with no racing goroutines. diff --git a/src/cmd/go/internal/test/flagdefs.go b/src/cmd/go/internal/test/flagdefs.go index 8a0a07683b7cc9778ee96d5011db4baefadc6f3a..37ac81c26782ae226be515bc2f65a42700de978d 100644 --- a/src/cmd/go/internal/test/flagdefs.go +++ b/src/cmd/go/internal/test/flagdefs.go @@ -28,6 +28,7 @@ var passFlagToTest = map[string]bool{ "parallel": true, "run": true, "short": true, + "shuffle": true, "timeout": true, "trace": true, "v": true, diff --git a/src/cmd/go/internal/test/genflags.go b/src/cmd/go/internal/test/genflags.go index 30334b0f305ce495cc0475a43f6c08da063c90be..9277de7fee839e216f8c70b31b8720d839f16c5e 100644 --- a/src/cmd/go/internal/test/genflags.go +++ b/src/cmd/go/internal/test/genflags.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore package main diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go index 7fc9e8fbdcda1afb5837d5b2932329890b49b6a2..59ea1ef5445178f052006dd37af2d8b0b209b9ef 100644 --- a/src/cmd/go/internal/test/test.go +++ b/src/cmd/go/internal/test/test.go @@ -29,6 +29,7 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/load" "cmd/go/internal/lockedfile" + "cmd/go/internal/search" "cmd/go/internal/str" "cmd/go/internal/trace" "cmd/go/internal/work" @@ -117,8 +118,8 @@ elapsed time in the summary line. The rule for a match in the cache is that the run involves the same test binary and the flags on the command line come entirely from a -restricted set of 'cacheable' test flags, defined as -cpu, -list, --parallel, -run, -short, and -v. If a run of go test has any test +restricted set of 'cacheable' test flags, defined as -benchtime, -cpu, +-list, -parallel, -run, -short, and -v. If a run of go test has any test or non-test flags outside this set, the result is not cached. To disable test caching, use any test flag or argument other than the cacheable flags. The idiomatic way to disable test caching explicitly @@ -271,6 +272,13 @@ control the execution of any test: the Go tree can run a sanity check but not spend time running exhaustive tests. + -shuffle off,on,N + Randomize the execution order of tests and benchmarks. + It is off by default. If -shuffle is set to on, then it will seed + the randomizer using the system clock. If -shuffle is set to an + integer N, then N will be used as the seed value. In both cases, + the seed will be reported for reproducibility. + -timeout d If a test binary runs longer than duration d, panic. If d is 0, the timeout is disabled. @@ -478,7 +486,8 @@ var ( testJSON bool // -json flag testList string // -list flag testO string // -o flag - testOutputDir = base.Cwd // -outputdir flag + testOutputDir outputdirFlag // -outputdir flag + testShuffle shuffleFlag // -shuffle flag testTimeout time.Duration // -timeout flag testV bool // -v flag testVet = vetFlag{flags: defaultVetFlags} // -vet flag @@ -568,8 +577,6 @@ var defaultVetFlags = []string{ } func runTest(ctx context.Context, cmd *base.Command, args []string) { - load.ModResolveTests = true - pkgArgs, testArgs = testFlags(args) if cfg.DebugTrace != "" { @@ -595,7 +602,8 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) { work.VetFlags = testVet.flags work.VetExplicit = testVet.explicit - pkgs = load.PackagesAndErrors(ctx, pkgArgs) + pkgOpts := load.PackageOpts{ModResolveTests: true} + pkgs = load.PackagesAndErrors(ctx, pkgOpts, pkgArgs) load.CheckPackageErrors(pkgs) if len(pkgs) == 0 { base.Fatalf("no packages to test") @@ -679,7 +687,7 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) { sort.Strings(all) a := &work.Action{Mode: "go test -i"} - pkgs := load.PackagesAndErrors(ctx, all) + pkgs := load.PackagesAndErrors(ctx, pkgOpts, all) load.CheckPackageErrors(pkgs) for _, p := range pkgs { if cfg.BuildToolchainName == "gccgo" && p.Standard { @@ -702,11 +710,11 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) { match := make([]func(*load.Package) bool, len(testCoverPaths)) matched := make([]bool, len(testCoverPaths)) for i := range testCoverPaths { - match[i] = load.MatchPackage(testCoverPaths[i], base.Cwd) + match[i] = load.MatchPackage(testCoverPaths[i], base.Cwd()) } // Select for coverage all dependencies matching the testCoverPaths patterns. - for _, p := range load.TestPackageList(ctx, pkgs) { + for _, p := range load.TestPackageList(ctx, pkgOpts, pkgs) { haveMatch := false for i := range testCoverPaths { if match[i](p) { @@ -715,6 +723,12 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) { } } + // A package which only has test files can't be imported + // as a dependency, nor can it be instrumented for coverage. + if len(p.GoFiles)+len(p.CgoFiles) == 0 { + continue + } + // Silently ignore attempts to run coverage on // sync/atomic when using atomic coverage mode. // Atomic coverage mode uses sync/atomic, so @@ -768,7 +782,7 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) { ensureImport(p, "sync/atomic") } - buildTest, runTest, printTest, err := builderTest(&b, ctx, p) + buildTest, runTest, printTest, err := builderTest(&b, ctx, pkgOpts, p) if err != nil { str := err.Error() str = strings.TrimPrefix(str, "\n") @@ -835,7 +849,7 @@ var windowsBadWords = []string{ "update", } -func builderTest(b *work.Builder, ctx context.Context, p *load.Package) (buildAction, runAction, printAction *work.Action, err error) { +func builderTest(b *work.Builder, ctx context.Context, pkgOpts load.PackageOpts, p *load.Package) (buildAction, runAction, printAction *work.Action, err error) { if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 { build := b.CompileAction(work.ModeBuild, work.ModeBuild, p) run := &work.Action{Mode: "test run", Package: p, Deps: []*work.Action{build}} @@ -858,7 +872,7 @@ func builderTest(b *work.Builder, ctx context.Context, p *load.Package) (buildAc DeclVars: declareCoverVars, } } - pmain, ptest, pxtest, err := load.TestPackagesFor(ctx, p, cover) + pmain, ptest, pxtest, err := load.TestPackagesFor(ctx, pkgOpts, p, cover) if err != nil { return nil, nil, nil, err } @@ -931,11 +945,11 @@ func builderTest(b *work.Builder, ctx context.Context, p *load.Package) (buildAc var installAction, cleanAction *work.Action if testC || testNeedBinary() { // -c or profiling flag: create action to copy binary to ./test.out. - target := filepath.Join(base.Cwd, testBinary+cfg.ExeSuffix) + target := filepath.Join(base.Cwd(), testBinary+cfg.ExeSuffix) if testO != "" { target = testO if !filepath.IsAbs(target) { - target = filepath.Join(base.Cwd, target) + target = filepath.Join(base.Cwd(), target) } } if target == os.DevNull { @@ -1326,7 +1340,8 @@ func (c *runCache) tryCacheWithID(b *work.Builder, a *work.Action, id string) bo return false } switch arg[:i] { - case "-test.cpu", + case "-test.benchtime", + "-test.cpu", "-test.list", "-test.parallel", "-test.run", @@ -1499,7 +1514,7 @@ func computeTestInputsID(a *work.Action, testlog []byte) (cache.ActionID, error) if !filepath.IsAbs(name) { name = filepath.Join(pwd, name) } - if a.Package.Root == "" || !inDir(name, a.Package.Root) { + if a.Package.Root == "" || search.InDir(name, a.Package.Root) == "" { // Do not recheck files outside the module, GOPATH, or GOROOT root. break } @@ -1508,7 +1523,7 @@ func computeTestInputsID(a *work.Action, testlog []byte) (cache.ActionID, error) if !filepath.IsAbs(name) { name = filepath.Join(pwd, name) } - if a.Package.Root == "" || !inDir(name, a.Package.Root) { + if a.Package.Root == "" || search.InDir(name, a.Package.Root) == "" { // Do not recheck files outside the module, GOPATH, or GOROOT root. break } @@ -1526,18 +1541,6 @@ func computeTestInputsID(a *work.Action, testlog []byte) (cache.ActionID, error) return sum, nil } -func inDir(path, dir string) bool { - if str.HasFilePathPrefix(path, dir) { - return true - } - xpath, err1 := filepath.EvalSymlinks(path) - xdir, err2 := filepath.EvalSymlinks(dir) - if err1 == nil && err2 == nil && str.HasFilePathPrefix(xpath, xdir) { - return true - } - return false -} - func hashGetenv(name string) cache.ActionID { h := cache.NewHash("getenv") v, ok := os.LookupEnv(name) diff --git a/src/cmd/go/internal/test/testflag.go b/src/cmd/go/internal/test/testflag.go index 10e6604da5f44b3b1c41f56f0653a667b7019196..08f1efa2c0d26a0cf398f2b778c0adde90fc97ae 100644 --- a/src/cmd/go/internal/test/testflag.go +++ b/src/cmd/go/internal/test/testflag.go @@ -10,6 +10,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "time" @@ -61,15 +62,16 @@ func init() { cf.String("memprofilerate", "", "") cf.StringVar(&testMutexProfile, "mutexprofile", "", "") cf.String("mutexprofilefraction", "", "") - cf.Var(outputdirFlag{&testOutputDir}, "outputdir", "") + cf.Var(&testOutputDir, "outputdir", "") cf.Int("parallel", 0, "") cf.String("run", "", "") cf.Bool("short", false, "") cf.DurationVar(&testTimeout, "timeout", 10*time.Minute, "") cf.StringVar(&testTrace, "trace", "", "") cf.BoolVar(&testV, "v", false, "") + cf.Var(&testShuffle, "shuffle", "") - for name, _ := range passFlagToTest { + for name := range passFlagToTest { cf.Var(cf.Lookup(name).Value, "test."+name, "") } } @@ -126,19 +128,26 @@ func (f stringFlag) Set(value string) error { // outputdirFlag implements the -outputdir flag. // It interprets an empty value as the working directory of the 'go' command. type outputdirFlag struct { - resolved *string + abs string } -func (f outputdirFlag) String() string { return *f.resolved } -func (f outputdirFlag) Set(value string) (err error) { +func (f *outputdirFlag) String() string { + return f.abs +} +func (f *outputdirFlag) Set(value string) (err error) { if value == "" { - // The empty string implies the working directory of the 'go' command. - *f.resolved = base.Cwd + f.abs = "" } else { - *f.resolved, err = filepath.Abs(value) + f.abs, err = filepath.Abs(value) } return err } +func (f *outputdirFlag) getAbs() string { + if f.abs == "" { + return base.Cwd() + } + return f.abs +} // vetFlag implements the special parsing logic for the -vet flag: // a comma-separated list, with a distinguished value "off" and @@ -194,6 +203,41 @@ func (f *vetFlag) Set(value string) error { return nil } +type shuffleFlag struct { + on bool + seed *int64 +} + +func (f *shuffleFlag) String() string { + if !f.on { + return "off" + } + if f.seed == nil { + return "on" + } + return fmt.Sprintf("%d", *f.seed) +} + +func (f *shuffleFlag) Set(value string) error { + if value == "off" { + *f = shuffleFlag{on: false} + return nil + } + + if value == "on" { + *f = shuffleFlag{on: true} + return nil + } + + seed, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return fmt.Errorf(`-shuffle argument must be "on", "off", or an int64: %v`, err) + } + + *f = shuffleFlag{on: true, seed: &seed} + return nil +} + // testFlags processes the command line, grabbing -x and -c, rewriting known flags // to have "test" before them, and reading the command line for the test binary. // Unfortunately for us, we need to do our own flag processing because go test @@ -367,7 +411,7 @@ func testFlags(args []string) (packageNames, passToTest []string) { // command. Set it explicitly if it is needed due to some other flag that // requests output. if testProfile() != "" && !outputDirSet { - injectedFlags = append(injectedFlags, "-test.outputdir="+testOutputDir) + injectedFlags = append(injectedFlags, "-test.outputdir="+testOutputDir.getAbs()) } // If the user is explicitly passing -help or -h, show output diff --git a/src/cmd/go/internal/vcs/vcs.go b/src/cmd/go/internal/vcs/vcs.go index 9feffe07656e84fa7fc821e5b52250d85077bedf..91485f6f745b1ffcc64fcbd95f3449cbcb2b7cb6 100644 --- a/src/cmd/go/internal/vcs/vcs.go +++ b/src/cmd/go/internal/vcs/vcs.go @@ -1176,7 +1176,7 @@ func expand(match map[string]string, s string) string { // and import paths referring to a fully-qualified importPath // containing a VCS type (foo.com/repo.git/dir) var vcsPaths = []*vcsPath{ - // Github + // GitHub { pathPrefix: "github.com", regexp: lazyregexp.New(`^(?Pgithub\.com/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)*$`), diff --git a/src/cmd/go/internal/vet/vet.go b/src/cmd/go/internal/vet/vet.go index 4257c90c97a193ca5a80b8a5a82abb04a98a1a81..1d419dddb98d6cdcc53ef5e3e6eb67b477531988 100644 --- a/src/cmd/go/internal/vet/vet.go +++ b/src/cmd/go/internal/vet/vet.go @@ -53,8 +53,6 @@ See also: go fmt, go fix. } func runVet(ctx context.Context, cmd *base.Command, args []string) { - load.ModResolveTests = true - vetFlags, pkgArgs := vetFlags(args) if cfg.DebugTrace != "" { @@ -87,7 +85,8 @@ func runVet(ctx context.Context, cmd *base.Command, args []string) { } } - pkgs := load.PackagesAndErrors(ctx, pkgArgs) + pkgOpts := load.PackageOpts{ModResolveTests: true} + pkgs := load.PackagesAndErrors(ctx, pkgOpts, pkgArgs) load.CheckPackageErrors(pkgs) if len(pkgs) == 0 { base.Fatalf("no packages to vet") @@ -98,7 +97,7 @@ func runVet(ctx context.Context, cmd *base.Command, args []string) { root := &work.Action{Mode: "go vet"} for _, p := range pkgs { - _, ptest, pxtest, err := load.TestPackagesFor(ctx, p, nil) + _, ptest, pxtest, err := load.TestPackagesFor(ctx, pkgOpts, p, nil) if err != nil { base.Errorf("%v", err) continue diff --git a/src/cmd/go/internal/web/bootstrap.go b/src/cmd/go/internal/web/bootstrap.go index 781702100a09e8b596ce9d3a9095e7dc88ada578..08686cdfcf9f4c11e96587a99738a09aa62b4324 100644 --- a/src/cmd/go/internal/web/bootstrap.go +++ b/src/cmd/go/internal/web/bootstrap.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cmd_go_bootstrap // +build cmd_go_bootstrap // This code is compiled only into the bootstrap 'go' binary. diff --git a/src/cmd/go/internal/web/http.go b/src/cmd/go/internal/web/http.go index 72fa2b2ca6aa349241d1e7ec8a0b96c23d3ed3fc..f177278eba1e7d9088014ee0ae246e2efb0dca39 100644 --- a/src/cmd/go/internal/web/http.go +++ b/src/cmd/go/internal/web/http.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !cmd_go_bootstrap // +build !cmd_go_bootstrap // This code is compiled into the real 'go' binary, but it is not @@ -27,7 +28,7 @@ import ( "cmd/internal/browser" ) -// impatientInsecureHTTPClient is used in -insecure mode, +// impatientInsecureHTTPClient is used with GOINSECURE, // when we're connecting to https servers that might not be there // or might be using self-signed certificates. var impatientInsecureHTTPClient = &http.Client{ diff --git a/src/cmd/go/internal/web/url_other.go b/src/cmd/go/internal/web/url_other.go index 2641ee62bfa5518b7d4500f9df55ffa8207afe3c..453af402b43dd9eb9208af234701127cfa887f25 100644 --- a/src/cmd/go/internal/web/url_other.go +++ b/src/cmd/go/internal/web/url_other.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !windows // +build !windows package web diff --git a/src/cmd/go/internal/web/url_other_test.go b/src/cmd/go/internal/web/url_other_test.go index aa5663355efb9c262012ed3b31d7954a73c6bc75..4d6ed2ec7f8c1dd0d9ab90f83f4478057c36b46d 100644 --- a/src/cmd/go/internal/web/url_other_test.go +++ b/src/cmd/go/internal/web/url_other_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !windows // +build !windows package web diff --git a/src/cmd/go/internal/work/action.go b/src/cmd/go/internal/work/action.go index 9d141ae233dfcdb1410d3633007f5698ea2a0365..69940cb00159b64c60024a9f248e5a0db337b266 100644 --- a/src/cmd/go/internal/work/action.go +++ b/src/cmd/go/internal/work/action.go @@ -344,7 +344,7 @@ func readpkglist(shlibpath string) (pkgs []*load.Package) { if strings.HasPrefix(t, "pkgpath ") { t = strings.TrimPrefix(t, "pkgpath ") t = strings.TrimSuffix(t, ";") - pkgs = append(pkgs, load.LoadImportWithFlags(t, base.Cwd, nil, &stk, nil, 0)) + pkgs = append(pkgs, load.LoadImportWithFlags(t, base.Cwd(), nil, &stk, nil, 0)) } } } else { @@ -355,7 +355,7 @@ func readpkglist(shlibpath string) (pkgs []*load.Package) { scanner := bufio.NewScanner(bytes.NewBuffer(pkglistbytes)) for scanner.Scan() { t := scanner.Text() - pkgs = append(pkgs, load.LoadImportWithFlags(t, base.Cwd, nil, &stk, nil, 0)) + pkgs = append(pkgs, load.LoadImportWithFlags(t, base.Cwd(), nil, &stk, nil, 0)) } } return @@ -776,7 +776,7 @@ func (b *Builder) linkSharedAction(mode, depMode BuildMode, shlib string, a1 *Ac } } var stk load.ImportStack - p := load.LoadImportWithFlags(pkg, base.Cwd, nil, &stk, nil, 0) + p := load.LoadImportWithFlags(pkg, base.Cwd(), nil, &stk, nil, 0) if p.Error != nil { base.Fatalf("load %s: %v", pkg, p.Error) } diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index f024b07b229e03927acb59f4a83ee4dd682690dd..0ed2389cd5a81af6f3ff862dd275b9fb9ba0109e 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -10,9 +10,7 @@ import ( "fmt" "go/build" exec "internal/execabs" - "internal/goroot" "os" - "path" "path/filepath" "runtime" "strings" @@ -21,13 +19,9 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/fsys" "cmd/go/internal/load" - "cmd/go/internal/modfetch" "cmd/go/internal/modload" "cmd/go/internal/search" "cmd/go/internal/trace" - - "golang.org/x/mod/modfile" - "golang.org/x/mod/module" ) var CmdBuild = &base.Command{ @@ -71,7 +65,7 @@ and test commands: -p n the number of programs, such as build commands or test binaries, that can be run in parallel. - The default is the number of CPUs available. + The default is GOMAXPROCS, normally the number of CPUs available. -race enable data race detection. Supported only on linux/amd64, freebsd/amd64, darwin/amd64, windows/amd64, @@ -134,8 +128,8 @@ and test commands: a build will run as if the disk file path exists with the contents given by the backing file paths, or as if the disk file path does not exist if its backing file path is empty. Support for the -overlay flag - has some limitations:importantly, cgo files included from outside the - include path must be in the same directory as the Go package they are + has some limitations: importantly, cgo files included from outside the + include path must be in the same directory as the Go package they are included from, and overlays will not appear when binaries and tests are run through go run and go test respectively. -pkgdir dir @@ -158,6 +152,8 @@ and test commands: a program to use to invoke toolchain programs like vet and asm. For example, instead of running asm, the go command will run 'cmd args /path/to/asm '. + The TOOLEXEC_IMPORTPATH environment variable will be set, + matching 'go list -f {{.ImportPath}}' for the package being built. The -asmflags, -gccgoflags, -gcflags, and -ldflags flags accept a space-separated list of arguments to pass to an underlying tool @@ -372,7 +368,7 @@ func runBuild(ctx context.Context, cmd *base.Command, args []string) { var b Builder b.Init() - pkgs := load.PackagesAndErrors(ctx, args) + pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{}, args) load.CheckPackageErrors(pkgs) explicitO := len(cfg.BuildO) > 0 @@ -592,7 +588,7 @@ func runInstall(ctx context.Context, cmd *base.Command, args []string) { } BuildInit() - pkgs := load.PackagesAndErrors(ctx, args) + pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{}, args) if cfg.ModulesEnabled && !modload.HasModRoot() { haveErrors := false allMissingErrors := true @@ -762,145 +758,27 @@ func installOutsideModule(ctx context.Context, args []string) { modload.RootMode = modload.NoRoot modload.AllowMissingModuleImports() modload.Init() - - // Check that the arguments satisfy syntactic constraints. - var version string - for _, arg := range args { - if i := strings.Index(arg, "@"); i >= 0 { - version = arg[i+1:] - if version == "" { - base.Fatalf("go install %s: version must not be empty", arg) - } - break - } - } - patterns := make([]string, len(args)) - for i, arg := range args { - if !strings.HasSuffix(arg, "@"+version) { - base.Errorf("go install %s: all arguments must have the same version (@%s)", arg, version) - continue - } - p := arg[:len(arg)-len(version)-1] - switch { - case build.IsLocalImport(p): - base.Errorf("go install %s: argument must be a package path, not a relative path", arg) - case filepath.IsAbs(p): - base.Errorf("go install %s: argument must be a package path, not an absolute path", arg) - case search.IsMetaPackage(p): - base.Errorf("go install %s: argument must be a package path, not a meta-package", arg) - case path.Clean(p) != p: - base.Errorf("go install %s: argument must be a clean package path", arg) - case !strings.Contains(p, "...") && search.IsStandardImportPath(p) && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, p): - base.Errorf("go install %s: argument must not be a package in the standard library", arg) - default: - patterns[i] = p - } - } - base.ExitIfErrors() BuildInit() - // Query the module providing the first argument, load its go.mod file, and - // check that it doesn't contain directives that would cause it to be - // interpreted differently if it were the main module. - // - // If multiple modules match the first argument, accept the longest match - // (first result). It's possible this module won't provide packages named by - // later arguments, and other modules would. Let's not try to be too - // magical though. - allowed := modload.CheckAllowed - if modload.IsRevisionQuery(version) { - // Don't check for retractions if a specific revision is requested. - allowed = nil - } - noneSelected := func(path string) (version string) { return "none" } - qrs, err := modload.QueryPackages(ctx, patterns[0], version, noneSelected, allowed) - if err != nil { - base.Fatalf("go install %s: %v", args[0], err) - } - installMod := qrs[0].Mod - data, err := modfetch.GoMod(installMod.Path, installMod.Version) - if err != nil { - base.Fatalf("go install %s: %v", args[0], err) - } - f, err := modfile.Parse("go.mod", data, nil) - if err != nil { - base.Fatalf("go install %s: %s: %v", args[0], installMod, err) - } - directiveFmt := "go install %s: %s\n" + - "\tThe go.mod file for the module providing named packages contains one or\n" + - "\tmore %s directives. It must not contain directives that would cause\n" + - "\tit to be interpreted differently than if it were the main module." - if len(f.Replace) > 0 { - base.Fatalf(directiveFmt, args[0], installMod, "replace") - } - if len(f.Exclude) > 0 { - base.Fatalf(directiveFmt, args[0], installMod, "exclude") - } - - // Since we are in NoRoot mode, the build list initially contains only - // the dummy command-line-arguments module. Add a requirement on the - // module that provides the packages named on the command line. - if err := modload.EditBuildList(ctx, nil, []module.Version{installMod}); err != nil { - base.Fatalf("go install %s: %v", args[0], err) - } - - // Load packages for all arguments. Ignore non-main packages. + // Load packages. Ignore non-main packages. // Print a warning if an argument contains "..." and matches no main packages. // PackagesAndErrors already prints warnings for patterns that don't match any // packages, so be careful not to double print. - matchers := make([]func(string) bool, len(patterns)) - for i, p := range patterns { - if strings.Contains(p, "...") { - matchers[i] = search.MatchPattern(p) - } - } - // TODO(golang.org/issue/40276): don't report errors loading non-main packages // matched by a pattern. - pkgs := load.PackagesAndErrors(ctx, patterns) - load.CheckPackageErrors(pkgs) - mainPkgs := make([]*load.Package, 0, len(pkgs)) - mainCount := make([]int, len(patterns)) - nonMainCount := make([]int, len(patterns)) - for _, pkg := range pkgs { - if pkg.Name == "main" { - mainPkgs = append(mainPkgs, pkg) - for i := range patterns { - if matchers[i] != nil && matchers[i](pkg.ImportPath) { - mainCount[i]++ - } - } - } else { - for i := range patterns { - if matchers[i] == nil && patterns[i] == pkg.ImportPath { - base.Errorf("go install: package %s is not a main package", pkg.ImportPath) - } else if matchers[i] != nil && matchers[i](pkg.ImportPath) { - nonMainCount[i]++ - } - } - } - } - base.ExitIfErrors() - for i, p := range patterns { - if matchers[i] != nil && mainCount[i] == 0 && nonMainCount[i] > 0 { - fmt.Fprintf(os.Stderr, "go: warning: %q matched no main packages\n", p) - } + pkgOpts := load.PackageOpts{MainOnly: true} + pkgs, err := load.PackagesAndErrorsOutsideModule(ctx, pkgOpts, args) + if err != nil { + base.Fatalf("go install: %v", err) } - - // Check that named packages are all provided by the same module. - for _, pkg := range mainPkgs { - if pkg.Module == nil { - // Packages in std, cmd, and their vendored dependencies - // don't have this field set. - base.Errorf("go install: package %s not provided by module %s", pkg.ImportPath, installMod) - } else if pkg.Module.Path != installMod.Path || pkg.Module.Version != installMod.Version { - base.Errorf("go install: package %s provided by module %s@%s\n\tAll packages must be provided by the same module (%s).", pkg.ImportPath, pkg.Module.Path, pkg.Module.Version, installMod) - } + load.CheckPackageErrors(pkgs) + patterns := make([]string, len(args)) + for i, arg := range args { + patterns[i] = arg[:strings.Index(arg, "@")] } - base.ExitIfErrors() // Build and install the packages. - InstallPackages(ctx, patterns, mainPkgs) + InstallPackages(ctx, patterns, pkgs) } // ExecCmd is the command to use to run user binaries. diff --git a/src/cmd/go/internal/work/build_test.go b/src/cmd/go/internal/work/build_test.go index eaf2639e9e0deaa5e43f1acadb279bc146c4d1b7..600fc3083f0d506228fe1e26797013f94e11607b 100644 --- a/src/cmd/go/internal/work/build_test.go +++ b/src/cmd/go/internal/work/build_test.go @@ -173,10 +173,11 @@ func TestSharedLibName(t *testing.T) { if err != nil { t.Fatal(err) } + cwd := base.Cwd() oldGopath := cfg.BuildContext.GOPATH defer func() { cfg.BuildContext.GOPATH = oldGopath - os.Chdir(base.Cwd) + os.Chdir(cwd) err := os.RemoveAll(tmpGopath) if err != nil { t.Error(err) diff --git a/src/cmd/go/internal/work/buildid.go b/src/cmd/go/internal/work/buildid.go index c555d4a9f1ed036367fbfc0a0ed4d4e748919e09..4e9189a36320ace016890c567fd241ffb92a7a87 100644 --- a/src/cmd/go/internal/work/buildid.go +++ b/src/cmd/go/internal/work/buildid.go @@ -204,7 +204,7 @@ func (b *Builder) toolID(name string) string { // In order to get reproducible builds for released compilers, we // detect a released compiler by the absence of "experimental" in the // --version output, and in that case we just use the version string. -func (b *Builder) gccgoToolID(name, language string) (string, error) { +func (b *Builder) gccToolID(name, language string) (string, error) { key := name + "." + language b.id.Lock() id := b.toolIDCache[key] diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 422e83c224f84c7ae43f2e7036ca0eab5b9b90d2..5a225fb9f1f62666ca959add62e75a458fc8f279 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -8,11 +8,11 @@ package work import ( "bytes" - "cmd/go/internal/fsys" "context" "encoding/json" "errors" "fmt" + "internal/buildcfg" exec "internal/execabs" "internal/lazyregexp" "io" @@ -31,6 +31,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cache" "cmd/go/internal/cfg" + "cmd/go/internal/fsys" "cmd/go/internal/load" "cmd/go/internal/modload" "cmd/go/internal/str" @@ -245,17 +246,37 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID { if p.Internal.ForceLibrary { fmt.Fprintf(h, "forcelibrary\n") } - if len(p.CgoFiles)+len(p.SwigFiles) > 0 { + if len(p.CgoFiles)+len(p.SwigFiles)+len(p.SwigCXXFiles) > 0 { fmt.Fprintf(h, "cgo %q\n", b.toolID("cgo")) cppflags, cflags, cxxflags, fflags, ldflags, _ := b.CFlags(p) - fmt.Fprintf(h, "CC=%q %q %q %q\n", b.ccExe(), cppflags, cflags, ldflags) - if len(p.CXXFiles)+len(p.SwigFiles) > 0 { - fmt.Fprintf(h, "CXX=%q %q\n", b.cxxExe(), cxxflags) + + ccExe := b.ccExe() + fmt.Fprintf(h, "CC=%q %q %q %q\n", ccExe, cppflags, cflags, ldflags) + // Include the C compiler tool ID so that if the C + // compiler changes we rebuild the package. + // But don't do that for standard library packages like net, + // so that the prebuilt .a files from a Go binary install + // don't need to be rebuilt with the local compiler. + if !p.Standard { + if ccID, err := b.gccToolID(ccExe[0], "c"); err == nil { + fmt.Fprintf(h, "CC ID=%q\n", ccID) + } + } + if len(p.CXXFiles)+len(p.SwigCXXFiles) > 0 { + cxxExe := b.cxxExe() + fmt.Fprintf(h, "CXX=%q %q\n", cxxExe, cxxflags) + if cxxID, err := b.gccToolID(cxxExe[0], "c++"); err == nil { + fmt.Fprintf(h, "CXX ID=%q\n", cxxID) + } } if len(p.FFiles) > 0 { - fmt.Fprintf(h, "FC=%q %q\n", b.fcExe(), fflags) + fcExe := b.fcExe() + fmt.Fprintf(h, "FC=%q %q\n", fcExe, fflags) + if fcID, err := b.gccToolID(fcExe[0], "f95"); err == nil { + fmt.Fprintf(h, "FC ID=%q\n", fcID) + } } - // TODO(rsc): Should we include the SWIG version or Fortran/GCC/G++/Objective-C compiler versions? + // TODO(rsc): Should we include the SWIG version? } if p.Internal.CoverMode != "" { fmt.Fprintf(h, "cover %q %q\n", p.Internal.CoverMode, b.toolID("cover")) @@ -276,6 +297,10 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID { key, val := cfg.GetArchEnv() fmt.Fprintf(h, "%s=%s\n", key, val) + if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" { + fmt.Fprintf(h, "GOEXPERIMENT=%q\n", goexperiment) + } + // TODO(rsc): Convince compiler team not to add more magic environment variables, // or perhaps restrict the environment variables passed to subprocesses. // Because these are clumsy, undocumented special-case hacks @@ -284,7 +309,7 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID { magic := []string{ "GOCLOBBERDEADHASH", "GOSSAFUNC", - "GO_SSA_PHI_LOC_CUTOFF", + "GOSSADIR", "GOSSAHASH", } for _, env := range magic { @@ -311,7 +336,7 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID { } case "gccgo": - id, err := b.gccgoToolID(BuildToolchain.compiler(), "go") + id, err := b.gccToolID(BuildToolchain.compiler(), "go") if err != nil { base.Fatalf("%v", err) } @@ -319,7 +344,7 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID { fmt.Fprintf(h, "pkgpath %s\n", gccgoPkgpath(p)) fmt.Fprintf(h, "ar %q\n", BuildToolchain.(gccgoToolchain).ar()) if len(p.SFiles) > 0 { - id, _ = b.gccgoToolID(BuildToolchain.compiler(), "assembler-with-cpp") + id, _ = b.gccToolID(BuildToolchain.compiler(), "assembler-with-cpp") // Ignore error; different assembler versions // are unlikely to make any difference anyhow. fmt.Fprintf(h, "asm %q\n", id) @@ -649,6 +674,10 @@ OverlayLoop: } outGo, outObj, err := b.cgo(a, base.Tool("cgo"), objdir, pcCFLAGS, pcLDFLAGS, mkAbsFiles(a.Package.Dir, cgofiles), gccfiles, cxxfiles, a.Package.MFiles, a.Package.FFiles) + + // The files in cxxfiles have now been handled by b.cgo. + cxxfiles = nil + if err != nil { return err } @@ -1246,6 +1275,10 @@ func (b *Builder) printLinkerConfig(h io.Writer, p *load.Package) { key, val := cfg.GetArchEnv() fmt.Fprintf(h, "%s=%s\n", key, val) + if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" { + fmt.Fprintf(h, "GOEXPERIMENT=%q\n", goexperiment) + } + // The linker writes source file paths that say GOROOT_FINAL, but // only if -trimpath is not specified (see ld() in gc.go). gorootFinal := cfg.GOROOT_FINAL @@ -1261,7 +1294,7 @@ func (b *Builder) printLinkerConfig(h io.Writer, p *load.Package) { // Or external linker settings and flags? case "gccgo": - id, err := b.gccgoToolID(BuildToolchain.linker(), "go") + id, err := b.gccToolID(BuildToolchain.linker(), "go") if err != nil { base.Fatalf("%v", err) } @@ -1841,6 +1874,7 @@ var objectMagic = [][]byte{ {0xCE, 0xFA, 0xED, 0xFE}, // Mach-O little-endian 32-bit {0xCF, 0xFA, 0xED, 0xFE}, // Mach-O little-endian 64-bit {0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00}, // PE (Windows) as generated by 6l/8l and gcc + {0x4d, 0x5a, 0x78, 0x00, 0x01, 0x00}, // PE (Windows) as generated by llvm for dll {0x00, 0x00, 0x01, 0xEB}, // Plan 9 i386 {0x00, 0x00, 0x8a, 0x97}, // Plan 9 amd64 {0x00, 0x00, 0x06, 0x47}, // Plan 9 arm @@ -2057,8 +2091,11 @@ func (b *Builder) runOut(a *Action, dir string, env []string, cmdargs ...interfa // Add the TOOLEXEC_IMPORTPATH environment variable for -toolexec tools. // It doesn't really matter if -toolexec isn't being used. + // Note that a.Package.Desc is not really an import path, + // but this is consistent with 'go list -f {{.ImportPath}}'. + // Plus, it is useful to uniquely identify packages in 'go list -json'. if a != nil && a.Package != nil { - cmd.Env = append(cmd.Env, "TOOLEXEC_IMPORTPATH="+a.Package.ImportPath) + cmd.Env = append(cmd.Env, "TOOLEXEC_IMPORTPATH="+a.Package.Desc()) } cmd.Env = append(cmd.Env, env...) @@ -2347,7 +2384,7 @@ func (b *Builder) gccld(a *Action, p *load.Package, objdir, outfile string, flag cmdargs := []interface{}{cmd, "-o", outfile, objs, flags} dir := p.Dir - out, err := b.runOut(a, base.Cwd, b.cCompilerEnv(), cmdargs...) + out, err := b.runOut(a, base.Cwd(), b.cCompilerEnv(), cmdargs...) if len(out) > 0 { // Filter out useless linker warnings caused by bugs outside Go. @@ -2598,9 +2635,19 @@ func (b *Builder) gccArchArgs() []string { case "s390x": return []string{"-m64", "-march=z196"} case "mips64", "mips64le": - return []string{"-mabi=64"} + args := []string{"-mabi=64"} + if cfg.GOMIPS64 == "hardfloat" { + return append(args, "-mhard-float") + } else if cfg.GOMIPS64 == "softfloat" { + return append(args, "-msoft-float") + } case "mips", "mipsle": - return []string{"-mabi=32", "-march=mips32"} + args := []string{"-mabi=32", "-march=mips32"} + if cfg.GOMIPS == "hardfloat" { + return append(args, "-mhard-float", "-mfp32", "-mno-odd-spreg") + } else if cfg.GOMIPS == "softfloat" { + return append(args, "-msoft-float") + } case "ppc64": if cfg.Goos == "aix" { return []string{"-maix64"} @@ -2951,7 +2998,7 @@ func (b *Builder) dynimport(a *Action, p *load.Package, objdir, importGo, cgoExe if p.Standard && p.ImportPath == "runtime/cgo" { cgoflags = []string{"-dynlinker"} // record path to dynamic linker } - return b.run(a, base.Cwd, p.ImportPath, b.cCompilerEnv(), cfg.BuildToolexec, cgoExe, "-dynpackage", p.Name, "-dynimport", dynobj, "-dynout", importGo, cgoflags) + return b.run(a, base.Cwd(), p.ImportPath, b.cCompilerEnv(), cfg.BuildToolexec, cgoExe, "-dynpackage", p.Name, "-dynimport", dynobj, "-dynout", importGo, cgoflags) } // Run SWIG on all SWIG input files. @@ -3085,7 +3132,7 @@ func (b *Builder) swigDoIntSize(objdir string) (intsize string, err error) { } srcs := []string{src} - p := load.GoFilesPackage(context.TODO(), srcs) + p := load.GoFilesPackage(context.TODO(), load.PackageOpts{}, srcs) if _, _, e := BuildToolchain.gc(b, &Action{Mode: "swigDoIntSize", Package: p, Objdir: objdir}, "", nil, nil, "", false, srcs); e != nil { return "32", nil diff --git a/src/cmd/go/internal/work/gc.go b/src/cmd/go/internal/work/gc.go index cc4e2b2b2b96553f0d899ca2f92e8f367bd4dea5..85da4f89f991f48e4ad9a3aad4b145f622363cb1 100644 --- a/src/cmd/go/internal/work/gc.go +++ b/src/cmd/go/internal/work/gc.go @@ -8,6 +8,7 @@ import ( "bufio" "bytes" "fmt" + "internal/buildcfg" "io" "log" "os" @@ -63,15 +64,33 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg pkgpath := pkgPath(a) gcargs := []string{"-p", pkgpath} - if p.Module != nil && p.Module.GoVersion != "" && allowedVersion(p.Module.GoVersion) { - gcargs = append(gcargs, "-lang=go"+p.Module.GoVersion) + if p.Module != nil { + v := p.Module.GoVersion + if v == "" { + // We started adding a 'go' directive to the go.mod file unconditionally + // as of Go 1.12, so any module that still lacks such a directive must + // either have been authored before then, or have a hand-edited go.mod + // file that hasn't been updated by cmd/go since that edit. + // + // Unfortunately, through at least Go 1.16 we didn't add versions to + // vendor/modules.txt. So this could also be a vendored 1.16 dependency. + // + // Fortunately, there were no breaking changes to the language between Go + // 1.11 and 1.16, so if we assume Go 1.16 semantics we will not introduce + // any spurious errors — we will only mask errors, and not particularly + // important ones at that. + v = "1.16" + } + if allowedVersion(v) { + gcargs = append(gcargs, "-lang=go"+v) + } } if p.Standard { gcargs = append(gcargs, "-std") } compilingRuntime := p.Standard && (p.ImportPath == "runtime" || strings.HasPrefix(p.ImportPath, "runtime/internal")) // The runtime package imports a couple of general internal packages. - if p.Standard && (p.ImportPath == "internal/cpu" || p.ImportPath == "internal/bytealg") { + if p.Standard && (p.ImportPath == "internal/cpu" || p.ImportPath == "internal/bytealg" || p.ImportPath == "internal/abi") { compilingRuntime = true } if compilingRuntime { @@ -129,7 +148,11 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg } } - args := []interface{}{cfg.BuildToolexec, base.Tool("compile"), "-o", ofile, "-trimpath", a.trimpath(), gcflags, gcargs, "-D", p.Internal.LocalPrefix} + args := []interface{}{cfg.BuildToolexec, base.Tool("compile"), "-o", ofile, "-trimpath", a.trimpath(), gcflags, gcargs} + if p.Internal.LocalPrefix != "" { + // Workaround #43883. + args = append(args, "-D", p.Internal.LocalPrefix) + } if importcfg != nil { if err := b.writeFile(objdir+"importcfg", importcfg); err != nil { return "", nil, err @@ -174,7 +197,7 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg args = append(args, f) } - output, err = b.runOut(a, base.Cwd, nil, args...) + output, err = b.runOut(a, base.Cwd(), nil, args...) return ofile, output, err } @@ -209,7 +232,7 @@ CheckFlags: } // TODO: Test and delete these conditions. - if objabi.Fieldtrack_enabled != 0 || objabi.Preemptibleloops_enabled != 0 { + if buildcfg.Experiment.FieldTrack || buildcfg.Experiment.PreemptibleLoops { canDashC = false } @@ -235,16 +258,19 @@ CheckFlags: // - it has no successor packages to compile (usually package main) // - all paths through the build graph pass through it // - critical path scheduling says it is high priority - // and in such a case, set c to runtime.NumCPU. + // and in such a case, set c to runtime.GOMAXPROCS(0). + // By default this is the same as runtime.NumCPU. // We do this now when p==1. + // To limit parallelism, set GOMAXPROCS below numCPU; this may be useful + // on a low-memory builder, or if a deterministic build order is required. + c := runtime.GOMAXPROCS(0) if cfg.BuildP == 1 { - // No process parallelism. Max out c. - return runtime.NumCPU() + // No process parallelism, do not cap compiler parallelism. + return c } - // Some process parallelism. Set c to min(4, numcpu). - c := 4 - if ncpu := runtime.NumCPU(); ncpu < c { - c = ncpu + // Some process parallelism. Set c to min(4, maxprocs). + if c > 4 { + c = 4 } return c } @@ -265,10 +291,11 @@ func (a *Action) trimpath() string { rewriteDir := a.Package.Dir if cfg.BuildTrimpath { + importPath := a.Package.Internal.OrigImportPath if m := a.Package.Module; m != nil && m.Version != "" { - rewriteDir = m.Path + "@" + m.Version + strings.TrimPrefix(a.Package.ImportPath, m.Path) + rewriteDir = m.Path + "@" + m.Version + strings.TrimPrefix(importPath, m.Path) } else { - rewriteDir = a.Package.ImportPath + rewriteDir = importPath } rewrite += a.Package.Dir + "=>" + rewriteDir + ";" } @@ -336,18 +363,6 @@ func asmArgs(a *Action, p *load.Package) []interface{} { } if objabi.IsRuntimePackagePath(pkgpath) { args = append(args, "-compiling-runtime") - if objabi.Regabi_enabled != 0 { - // In order to make it easier to port runtime assembly - // to the register ABI, we introduce a macro - // indicating the experiment is enabled. - // - // Note: a similar change also appears in - // cmd/dist/build.go. - // - // TODO(austin): Remove this once we commit to the - // register ABI (#40724). - args = append(args, "-D=GOEXPERIMENT_REGABI=1") - } } if cfg.Goarch == "mips" || cfg.Goarch == "mipsle" { diff --git a/src/cmd/go/internal/work/gccgo.go b/src/cmd/go/internal/work/gccgo.go index b58c8aa8854a459a53f5226a322eee342af296ab..1499536932d2cf94fef56715aec3b08c39be0022 100644 --- a/src/cmd/go/internal/work/gccgo.go +++ b/src/cmd/go/internal/work/gccgo.go @@ -102,7 +102,7 @@ func (tools gccgoToolchain) gc(b *Builder, a *Action, archive string, importcfg, if b.gccSupportsFlag(args[:1], "-ffile-prefix-map=a=b") { if cfg.BuildTrimpath { - args = append(args, "-ffile-prefix-map="+base.Cwd+"=.") + args = append(args, "-ffile-prefix-map="+base.Cwd()+"=.") args = append(args, "-ffile-prefix-map="+b.WorkDir+"=/tmp/go-build") } if fsys.OverlayFile != "" { @@ -114,9 +114,9 @@ func (tools gccgoToolchain) gc(b *Builder, a *Action, archive string, importcfg, } toPath := absPath // gccgo only applies the last matching rule, so also handle the case where - // BuildTrimpath is true and the path is relative to base.Cwd. - if cfg.BuildTrimpath && str.HasFilePathPrefix(toPath, base.Cwd) { - toPath = "." + toPath[len(base.Cwd):] + // BuildTrimpath is true and the path is relative to base.Cwd(). + if cfg.BuildTrimpath && str.HasFilePathPrefix(toPath, base.Cwd()) { + toPath = "." + toPath[len(base.Cwd()):] } args = append(args, "-ffile-prefix-map="+overlayPath+"="+toPath) } @@ -572,7 +572,7 @@ func (tools gccgoToolchain) cc(b *Builder, a *Action, ofile, cfile string) error } defs = tools.maybePIC(defs) if b.gccSupportsFlag(compiler, "-ffile-prefix-map=a=b") { - defs = append(defs, "-ffile-prefix-map="+base.Cwd+"=.") + defs = append(defs, "-ffile-prefix-map="+base.Cwd()+"=.") defs = append(defs, "-ffile-prefix-map="+b.WorkDir+"=/tmp/go-build") } else if b.gccSupportsFlag(compiler, "-fdebug-prefix-map=a=b") { defs = append(defs, "-fdebug-prefix-map="+b.WorkDir+"=/tmp/go-build") diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go index ba7c7c2fbb18d95b456e6e0973006ec3d48ab411..37a3e2d0ffd1232368863992603432fbd783e828 100644 --- a/src/cmd/go/internal/work/init.go +++ b/src/cmd/go/internal/work/init.go @@ -11,21 +11,19 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/fsys" "cmd/go/internal/modload" - "cmd/internal/objabi" "cmd/internal/sys" "flag" "fmt" "os" "path/filepath" "runtime" - "strings" ) func BuildInit() { modload.Init() instrumentInit() buildModeInit() - if err := fsys.Init(base.Cwd); err != nil { + if err := fsys.Init(base.Cwd()); err != nil { base.Fatalf("go: %v", err) } @@ -47,20 +45,6 @@ func BuildInit() { base.Fatalf("go %s: %s environment variable is relative; must be absolute path: %s\n", flag.Args()[0], key, path) } } - - // For each experiment that has been enabled in the toolchain, define a - // build tag with the same name but prefixed by "goexperiment." which can be - // used for compiling alternative files for the experiment. This allows - // changes for the experiment, like extra struct fields in the runtime, - // without affecting the base non-experiment code at all. [2:] strips the - // leading "X:" from objabi.Expstring(). - exp := objabi.Expstring()[2:] - if exp != "none" { - experiments := strings.Split(exp, ",") - for _, expt := range experiments { - cfg.BuildContext.BuildTags = append(cfg.BuildContext.BuildTags, "goexperiment."+expt) - } - } } func instrumentInit() { diff --git a/src/cmd/go/internal/work/security.go b/src/cmd/go/internal/work/security.go index 36bbab37ee23e89943f26a543e5a230ae1fb70a0..e9b9f6c6c0f24417652309b4d2b13b098677db73 100644 --- a/src/cmd/go/internal/work/security.go +++ b/src/cmd/go/internal/work/security.go @@ -208,8 +208,8 @@ var validLinkerFlags = []*lazyregexp.Regexp{ re(`-Wl,-z,(no)?execstack`), re(`-Wl,-z,relro`), - re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o) - re(`\./.*\.(a|o|obj|dll|dylib|so)`), + re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so|tbd)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o) + re(`\./.*\.(a|o|obj|dll|dylib|so|tbd)`), } var validLinkerFlagsWithNextArg = []string{ diff --git a/src/cmd/go/internal/work/security_test.go b/src/cmd/go/internal/work/security_test.go index 4f2e0eb21ab842dd80c4425fa806ca2998000f73..8d4be0abfc09e6c21c9a51ea6a32696c4fa6e259 100644 --- a/src/cmd/go/internal/work/security_test.go +++ b/src/cmd/go/internal/work/security_test.go @@ -164,6 +164,8 @@ var goodLinkerFlags = [][]string{ {"-Wl,-framework", "-Wl,Chocolate"}, {"-Wl,-framework,Chocolate"}, {"-Wl,-unresolved-symbols=ignore-all"}, + {"libcgotbdtest.tbd"}, + {"./libcgotbdtest.tbd"}, } var badLinkerFlags = [][]string{ diff --git a/src/cmd/go/internal/work/testgo.go b/src/cmd/go/internal/work/testgo.go index 931f49a0691d278b490dad919b40e0e1e7e0716b..8b77871b23f26922b4612c9017522710f1511af5 100644 --- a/src/cmd/go/internal/work/testgo.go +++ b/src/cmd/go/internal/work/testgo.go @@ -4,6 +4,7 @@ // This file contains extra hooks for testing the go command. +//go:build testgo // +build testgo package work diff --git a/src/cmd/go/main.go b/src/cmd/go/main.go index 9cc44da84db6a378de4156df4ba87bb0082537d9..16361e02ca7f7c4d1cadee8a6f93aa3d2b3d46ed 100644 --- a/src/cmd/go/main.go +++ b/src/cmd/go/main.go @@ -10,6 +10,7 @@ import ( "context" "flag" "fmt" + "internal/buildcfg" "log" "os" "path/filepath" @@ -144,19 +145,6 @@ func main() { os.Exit(2) } - // Set environment (GOOS, GOARCH, etc) explicitly. - // In theory all the commands we invoke should have - // the same default computation of these as we do, - // but in practice there might be skew - // This makes sure we all agree. - cfg.OrigEnv = os.Environ() - cfg.CmdEnv = envcmd.MkEnv() - for _, env := range cfg.CmdEnv { - if os.Getenv(env.Name) != env.Value { - os.Setenv(env.Name, env.Value) - } - } - BigCmdLoop: for bigCmd := base.Go; ; { for _, cmd := range bigCmd.Commands { @@ -182,18 +170,7 @@ BigCmdLoop: if !cmd.Runnable() { continue } - cmd.Flag.Usage = func() { cmd.Usage() } - if cmd.CustomFlags { - args = args[1:] - } else { - base.SetFromGOFLAGS(&cmd.Flag) - cmd.Flag.Parse(args[1:]) - args = cmd.Flag.Args() - } - ctx := maybeStartTrace(context.Background()) - ctx, span := trace.StartSpan(ctx, fmt.Sprint("Running ", cmd.Name(), " command")) - cmd.Run(ctx, cmd, args) - span.Done() + invoke(cmd, args) base.Exit() return } @@ -207,6 +184,39 @@ BigCmdLoop: } } +func invoke(cmd *base.Command, args []string) { + // 'go env' handles checking the build config + if cmd != envcmd.CmdEnv { + buildcfg.Check() + } + + // Set environment (GOOS, GOARCH, etc) explicitly. + // In theory all the commands we invoke should have + // the same default computation of these as we do, + // but in practice there might be skew + // This makes sure we all agree. + cfg.OrigEnv = os.Environ() + cfg.CmdEnv = envcmd.MkEnv() + for _, env := range cfg.CmdEnv { + if os.Getenv(env.Name) != env.Value { + os.Setenv(env.Name, env.Value) + } + } + + cmd.Flag.Usage = func() { cmd.Usage() } + if cmd.CustomFlags { + args = args[1:] + } else { + base.SetFromGOFLAGS(&cmd.Flag) + cmd.Flag.Parse(args[1:]) + args = cmd.Flag.Args() + } + ctx := maybeStartTrace(context.Background()) + ctx, span := trace.StartSpan(ctx, fmt.Sprint("Running ", cmd.Name(), " command")) + cmd.Run(ctx, cmd, args) + span.Done() +} + func init() { base.Usage = mainUsage } diff --git a/src/cmd/go/proxy_test.go b/src/cmd/go/proxy_test.go index e390c73a9cfa06aa71975c13fade24b2d8c60812..74bfecc08dbc6de1ca9768c536113eb878d7f3da 100644 --- a/src/cmd/go/proxy_test.go +++ b/src/cmd/go/proxy_test.go @@ -23,7 +23,6 @@ import ( "sync" "testing" - "cmd/go/internal/modfetch" "cmd/go/internal/modfetch/codehost" "cmd/go/internal/par" "cmd/go/internal/txtar" @@ -229,7 +228,7 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) { if m.Path != modPath { continue } - if modfetch.IsPseudoVersion(m.Version) && (latestPseudo == "" || semver.Compare(latestPseudo, m.Version) > 0) { + if module.IsPseudoVersion(m.Version) && (latestPseudo == "" || semver.Compare(latestPseudo, m.Version) > 0) { latestPseudo = m.Version } else if semver.Prerelease(m.Version) != "" && (latestPrerelease == "" || semver.Compare(latestPrerelease, m.Version) > 0) { latestPrerelease = m.Version @@ -282,7 +281,7 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) { continue } found = true - if !modfetch.IsPseudoVersion(m.Version) { + if !module.IsPseudoVersion(m.Version) { if err := module.Check(m.Path, m.Version); err == nil { fmt.Fprintf(w, "%s\n", m.Version) } @@ -315,7 +314,7 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) { for _, m := range modList { if m.Path == path && semver.Compare(best, m.Version) < 0 { var hash string - if modfetch.IsPseudoVersion(m.Version) { + if module.IsPseudoVersion(m.Version) { hash = m.Version[strings.LastIndex(m.Version, "-")+1:] } else { hash = findHash(m) @@ -362,7 +361,7 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) { var buf bytes.Buffer z := zip.NewWriter(&buf) for _, f := range a.Files { - if strings.HasPrefix(f.Name, ".") { + if f.Name == ".info" || f.Name == ".mod" || f.Name == ".zip" { continue } var zipName string diff --git a/src/cmd/go/script_test.go b/src/cmd/go/script_test.go index dfaa40548e44d8df9f10ef873d566f1a684cdbd5..639e907db075159dd5aba227d38e7ec9261127bf 100644 --- a/src/cmd/go/script_test.go +++ b/src/cmd/go/script_test.go @@ -32,7 +32,6 @@ import ( "cmd/go/internal/robustio" "cmd/go/internal/txtar" "cmd/go/internal/work" - "cmd/internal/objabi" "cmd/internal/sys" ) @@ -41,6 +40,33 @@ func TestScript(t *testing.T) { testenv.MustHaveGoBuild(t) testenv.SkipIfShortAndSlow(t) + var ( + ctx = context.Background() + gracePeriod = 100 * time.Millisecond + ) + if deadline, ok := t.Deadline(); ok { + timeout := time.Until(deadline) + + // If time allows, increase the termination grace period to 5% of the + // remaining time. + if gp := timeout / 20; gp > gracePeriod { + gracePeriod = gp + } + + // When we run commands that execute subprocesses, we want to reserve two + // grace periods to clean up. We will send the first termination signal when + // the context expires, then wait one grace period for the process to + // produce whatever useful output it can (such as a stack trace). After the + // first grace period expires, we'll escalate to os.Kill, leaving the second + // grace period for the test function to record its output before the test + // process itself terminates. + timeout -= 2 * gracePeriod + + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, timeout) + t.Cleanup(cancel) + } + files, err := filepath.Glob("testdata/script/*.txt") if err != nil { t.Fatal(err) @@ -50,40 +76,51 @@ func TestScript(t *testing.T) { name := strings.TrimSuffix(filepath.Base(file), ".txt") t.Run(name, func(t *testing.T) { t.Parallel() - ts := &testScript{t: t, name: name, file: file} + ctx, cancel := context.WithCancel(ctx) + ts := &testScript{ + t: t, + ctx: ctx, + cancel: cancel, + gracePeriod: gracePeriod, + name: name, + file: file, + } ts.setup() if !*testWork { defer removeAll(ts.workdir) } ts.run() + cancel() }) } } // A testScript holds execution state for a single test script. type testScript struct { - t *testing.T - workdir string // temporary work dir ($WORK) - log bytes.Buffer // test execution log (printed at end of test) - mark int // offset of next log truncation - cd string // current directory during test execution; initially $WORK/gopath/src - name string // short name of test ("foo") - file string // full file name ("testdata/script/foo.txt") - lineno int // line number currently executing - line string // line currently executing - env []string // environment list (for os/exec) - envMap map[string]string // environment mapping (matches env) - stdout string // standard output from last 'go' command; for 'stdout' command - stderr string // standard error from last 'go' command; for 'stderr' command - stopped bool // test wants to stop early - start time.Time // time phase started - background []*backgroundCmd // backgrounded 'exec' and 'go' commands + t *testing.T + ctx context.Context + cancel context.CancelFunc + gracePeriod time.Duration + workdir string // temporary work dir ($WORK) + log bytes.Buffer // test execution log (printed at end of test) + mark int // offset of next log truncation + cd string // current directory during test execution; initially $WORK/gopath/src + name string // short name of test ("foo") + file string // full file name ("testdata/script/foo.txt") + lineno int // line number currently executing + line string // line currently executing + env []string // environment list (for os/exec) + envMap map[string]string // environment mapping (matches env) + stdout string // standard output from last 'go' command; for 'stdout' command + stderr string // standard error from last 'go' command; for 'stderr' command + stopped bool // test wants to stop early + start time.Time // time phase started + background []*backgroundCmd // backgrounded 'exec' and 'go' commands } type backgroundCmd struct { want simpleStatus args []string - cancel context.CancelFunc done <-chan struct{} err error stdout, stderr strings.Builder @@ -109,6 +146,10 @@ var extraEnvKeys = []string{ // setup sets up the test execution temporary directory and environment. func (ts *testScript) setup() { + if err := ts.ctx.Err(); err != nil { + ts.t.Fatalf("test interrupted during setup: %v", err) + } + StartProxy() ts.workdir = filepath.Join(testTmpDir, "script-"+ts.name) ts.check(os.MkdirAll(filepath.Join(ts.workdir, "tmp"), 0777)) @@ -123,13 +164,13 @@ func (ts *testScript) setup() { "GOCACHE=" + testGOCACHE, "GODEBUG=" + os.Getenv("GODEBUG"), "GOEXE=" + cfg.ExeSuffix, - "GOEXPSTRING=" + objabi.Expstring()[2:], "GOOS=" + runtime.GOOS, "GOPATH=" + filepath.Join(ts.workdir, "gopath"), "GOPROXY=" + proxyURL, "GOPRIVATE=", "GOROOT=" + testGOROOT, "GOROOT_FINAL=" + os.Getenv("GOROOT_FINAL"), // causes spurious rebuilds and breaks the "stale" built-in if not propagated + "GOTRACEBACK=system", "TESTGO_GOROOT=" + testGOROOT, "GOSUMDB=" + testSumDBVerifierKey, "GONOPROXY=", @@ -200,9 +241,7 @@ func (ts *testScript) run() { // On a normal exit from the test loop, background processes are cleaned up // before we print PASS. If we return early (e.g., due to a test failure), // don't print anything about the processes that were still running. - for _, bg := range ts.background { - bg.cancel() - } + ts.cancel() for _, bg := range ts.background { <-bg.done } @@ -275,6 +314,10 @@ Script: fmt.Fprintf(&ts.log, "> %s\n", line) for _, cond := range parsed.conds { + if err := ts.ctx.Err(); err != nil { + ts.fatalf("test interrupted: %v", err) + } + // Known conds are: $GOOS, $GOARCH, runtime.Compiler, and 'short' (for testing.Short). // // NOTE: If you make changes here, update testdata/script/README too! @@ -356,9 +399,7 @@ Script: } } - for _, bg := range ts.background { - bg.cancel() - } + ts.cancel() ts.cmdWait(success, nil) // Final phase ended. @@ -476,7 +517,7 @@ func (ts *testScript) cmdCd(want simpleStatus, args []string) { ts.fatalf("usage: cd dir") } - dir := args[0] + dir := filepath.FromSlash(args[0]) if !filepath.IsAbs(dir) { dir = filepath.Join(ts.cd, dir) } @@ -798,9 +839,7 @@ func (ts *testScript) cmdSkip(want simpleStatus, args []string) { // Before we mark the test as skipped, shut down any background processes and // make sure they have returned the correct status. - for _, bg := range ts.background { - bg.cancel() - } + ts.cancel() ts.cmdWait(success, nil) if len(args) == 1 { @@ -1065,38 +1104,9 @@ func (ts *testScript) exec(command string, args ...string) (stdout, stderr strin func (ts *testScript) startBackground(want simpleStatus, command string, args ...string) (*backgroundCmd, error) { done := make(chan struct{}) bg := &backgroundCmd{ - want: want, - args: append([]string{command}, args...), - done: done, - cancel: func() {}, - } - - ctx := context.Background() - gracePeriod := 100 * time.Millisecond - if deadline, ok := ts.t.Deadline(); ok { - timeout := time.Until(deadline) - // If time allows, increase the termination grace period to 5% of the - // remaining time. - if gp := timeout / 20; gp > gracePeriod { - gracePeriod = gp - } - - // Send the first termination signal with two grace periods remaining. - // If it still hasn't finished after the first period has elapsed, - // we'll escalate to os.Kill with a second period remaining until the - // test deadline.. - timeout -= 2 * gracePeriod - - if timeout <= 0 { - // The test has less than the grace period remaining. There is no point in - // even starting the command, because it will be terminated immediately. - // Save the expense of starting it in the first place. - bg.err = context.DeadlineExceeded - close(done) - return bg, nil - } - - ctx, bg.cancel = context.WithTimeout(ctx, timeout) + want: want, + args: append([]string{command}, args...), + done: done, } cmd := exec.Command(command, args...) @@ -1105,29 +1115,16 @@ func (ts *testScript) startBackground(want simpleStatus, command string, args .. cmd.Stdout = &bg.stdout cmd.Stderr = &bg.stderr if err := cmd.Start(); err != nil { - bg.cancel() return nil, err } go func() { - bg.err = waitOrStop(ctx, cmd, stopSignal(), gracePeriod) + bg.err = waitOrStop(ts.ctx, cmd, quitSignal(), ts.gracePeriod) close(done) }() return bg, nil } -// stopSignal returns the appropriate signal to use to request that a process -// stop execution. -func stopSignal() os.Signal { - if runtime.GOOS == "windows" { - // Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on - // Windows; using it with os.Process.Signal will return an error.” - // Fall back to Kill instead. - return os.Kill - } - return os.Interrupt -} - // waitOrStop waits for the already-started command cmd by calling its Wait method. // // If cmd does not return before ctx is done, waitOrStop sends it the given interrupt signal. diff --git a/src/cmd/go/stop_other_test.go b/src/cmd/go/stop_other_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e1cc6cf8ba755fffd719f14a2e0455b2ae7874e5 --- /dev/null +++ b/src/cmd/go/stop_other_test.go @@ -0,0 +1,33 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !(aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris) +// +build !aix +// +build !darwin +// +build !dragonfly +// +build !freebsd +// +build !js !wasm +// +build !linux +// +build !netbsd +// +build !openbsd +// +build !solaris + +package main_test + +import ( + "os" + "runtime" +) + +// quitSignal returns the appropriate signal to use to request that a process +// quit execution. +func quitSignal() os.Signal { + if runtime.GOOS == "windows" { + // Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on + // Windows; using it with os.Process.Signal will return an error.” + // Fall back to Kill instead. + return os.Kill + } + return os.Interrupt +} diff --git a/src/cmd/go/stop_unix_test.go b/src/cmd/go/stop_unix_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ac35b240f0ad9727845fed622a5112f704b95909 --- /dev/null +++ b/src/cmd/go/stop_unix_test.go @@ -0,0 +1,17 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris +// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris + +package main_test + +import ( + "os" + "syscall" +) + +func quitSignal() os.Signal { + return syscall.SIGQUIT +} diff --git a/src/cmd/go/testdata/addmod.go b/src/cmd/go/testdata/addmod.go index 09fc8e713bcbcfbc9ad226567369df1f2387d446..03869e68defe0845c5cbdef60fa08f2fd3fdf42d 100644 --- a/src/cmd/go/testdata/addmod.go +++ b/src/cmd/go/testdata/addmod.go @@ -22,10 +22,10 @@ import ( "bytes" "flag" "fmt" + exec "internal/execabs" "io/fs" "log" "os" - exec "internal/execabs" "path/filepath" "strings" diff --git a/src/cmd/go/testdata/mod/example.com_cmd_v1.0.0.txt b/src/cmd/go/testdata/mod/example.com_cmd_v1.0.0.txt index ee439384d25817bb23ad7e6f09cc835ca8a9eae8..c1981391a13c74a57b1a326f4cd915b236594208 100644 --- a/src/cmd/go/testdata/mod/example.com_cmd_v1.0.0.txt +++ b/src/cmd/go/testdata/mod/example.com_cmd_v1.0.0.txt @@ -16,11 +16,15 @@ go 1.16 -- a/a.go -- package main -func main() {} +import "fmt" + +func main() { fmt.Println("a@v1.0.0") } -- b/b.go -- package main -func main() {} +import "fmt" + +func main() { fmt.Println("b@v1.0.0") } -- err/err.go -- package err diff --git a/src/cmd/go/testdata/mod/example.com_deprecated_a_v1.0.0.txt b/src/cmd/go/testdata/mod/example.com_deprecated_a_v1.0.0.txt new file mode 100644 index 0000000000000000000000000000000000000000..7c29621e83db969f78eb860c4fd5457a2f1b2223 --- /dev/null +++ b/src/cmd/go/testdata/mod/example.com_deprecated_a_v1.0.0.txt @@ -0,0 +1,12 @@ +-- .info -- +{"Version":"v1.0.0"} +-- .mod -- +module example.com/deprecated/a + +go 1.17 +-- go.mod -- +module example.com/deprecated/a + +go 1.17 +-- a.go -- +package a diff --git a/src/cmd/go/testdata/mod/example.com_deprecated_a_v1.9.0.txt b/src/cmd/go/testdata/mod/example.com_deprecated_a_v1.9.0.txt new file mode 100644 index 0000000000000000000000000000000000000000..0613389d1f3874ffe6a1bbd981f41d679ee8748a --- /dev/null +++ b/src/cmd/go/testdata/mod/example.com_deprecated_a_v1.9.0.txt @@ -0,0 +1,14 @@ +-- .info -- +{"Version":"v1.9.0"} +-- .mod -- +// Deprecated: in example.com/deprecated/a@v1.9.0 +module example.com/deprecated/a + +go 1.17 +-- go.mod -- +// Deprecated: in example.com/deprecated/a@v1.9.0 +module example.com/deprecated/a + +go 1.17 +-- a.go -- +package a diff --git a/src/cmd/go/testdata/mod/example.com_deprecated_b_v1.0.0.txt b/src/cmd/go/testdata/mod/example.com_deprecated_b_v1.0.0.txt new file mode 100644 index 0000000000000000000000000000000000000000..50006aefb5f3aabeddc1763a1f39f7c74b276252 --- /dev/null +++ b/src/cmd/go/testdata/mod/example.com_deprecated_b_v1.0.0.txt @@ -0,0 +1,12 @@ +-- .info -- +{"Version":"v1.0.0"} +-- .mod -- +module example.com/deprecated/b + +go 1.17 +-- go.mod -- +module example.com/deprecated/b + +go 1.17 +-- b.go -- +package b diff --git a/src/cmd/go/testdata/mod/example.com_deprecated_b_v1.9.0.txt b/src/cmd/go/testdata/mod/example.com_deprecated_b_v1.9.0.txt new file mode 100644 index 0000000000000000000000000000000000000000..163d6b543eb7a8ff08f4ad19debf24c4aea21024 --- /dev/null +++ b/src/cmd/go/testdata/mod/example.com_deprecated_b_v1.9.0.txt @@ -0,0 +1,14 @@ +-- .info -- +{"Version":"v1.9.0"} +-- .mod -- +// Deprecated: in example.com/deprecated/b@v1.9.0 +module example.com/deprecated/b + +go 1.17 +-- go.mod -- +// Deprecated: in example.com/deprecated/b@v1.9.0 +module example.com/deprecated/b + +go 1.17 +-- b.go -- +package b diff --git a/src/cmd/go/testdata/mod/example.com_dotname_v1.0.0.txt b/src/cmd/go/testdata/mod/example.com_dotname_v1.0.0.txt new file mode 100644 index 0000000000000000000000000000000000000000..2ada3a3f812af60861577b2383c619fde64c4126 --- /dev/null +++ b/src/cmd/go/testdata/mod/example.com_dotname_v1.0.0.txt @@ -0,0 +1,12 @@ +-- .info -- +{"Version":"v1.0.0"} +-- .mod -- +module example.com/dotname + +go 1.16 +-- go.mod -- +module example.com/dotname + +go 1.16 +-- .dot/dot.go -- +package dot diff --git a/src/cmd/go/testdata/mod/example.com_split-incompatible_subpkg_v0.1.0.txt b/src/cmd/go/testdata/mod/example.com_split-incompatible_subpkg_v0.1.0.txt index 8f9e49176c7182994cb1645557be8122178c79f2..edf5d487885d4293a7178464953ebfa3a9dcd3a5 100644 --- a/src/cmd/go/testdata/mod/example.com_split-incompatible_subpkg_v0.1.0.txt +++ b/src/cmd/go/testdata/mod/example.com_split-incompatible_subpkg_v0.1.0.txt @@ -1,6 +1,6 @@ Written by hand. Test case for getting a package that has been moved to a nested module, -with a +incompatible verison (and thus no go.mod file) at the root module. +with a +incompatible version (and thus no go.mod file) at the root module. -- .mod -- module example.com/split-incompatible/subpkg diff --git a/src/cmd/go/testdata/mod/example.com_undeprecated_v1.0.0.txt b/src/cmd/go/testdata/mod/example.com_undeprecated_v1.0.0.txt new file mode 100644 index 0000000000000000000000000000000000000000..a68588eedb4ae267a1990f95f93c0e2833dc9df3 --- /dev/null +++ b/src/cmd/go/testdata/mod/example.com_undeprecated_v1.0.0.txt @@ -0,0 +1,14 @@ +-- .info -- +{"Version":"v1.0.0"} +-- .mod -- +// Deprecated: in v1.0.0 +module example.com/undeprecated + +go 1.17 +-- go.mod -- +// Deprecated: in v1.0.0 +module example.com/undeprecated + +go 1.17 +-- undeprecated.go -- +package undeprecated diff --git a/src/cmd/go/testdata/mod/example.com_undeprecated_v1.0.1.txt b/src/cmd/go/testdata/mod/example.com_undeprecated_v1.0.1.txt new file mode 100644 index 0000000000000000000000000000000000000000..ecabf322ec486fa99f1455f04241d559a5bfb6a4 --- /dev/null +++ b/src/cmd/go/testdata/mod/example.com_undeprecated_v1.0.1.txt @@ -0,0 +1,14 @@ +-- .info -- +{"Version":"v1.0.1"} +-- .mod -- +// no longer deprecated +module example.com/undeprecated + +go 1.17 +-- go.mod -- +// no longer deprecated +module example.com/undeprecated + +go 1.17 +-- undeprecated.go -- +package undeprecated diff --git a/src/cmd/go/testdata/mod/rsc.io_sampler_v1.2.1.txt b/src/cmd/go/testdata/mod/rsc.io_sampler_v1.2.1.txt index 00b71bf0d59468aec1b6ea12dec809f8f755e67f..7982cccea100c0a523aa9e668ed45aa2d6dfada6 100644 --- a/src/cmd/go/testdata/mod/rsc.io_sampler_v1.2.1.txt +++ b/src/cmd/go/testdata/mod/rsc.io_sampler_v1.2.1.txt @@ -5,7 +5,7 @@ module "rsc.io/sampler" require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c -- .info -- -{"Version":"v1.2.1","Name":"cac3af4f8a0ab40054fa6f8d423108a63a1255bb","Short":"cac3af4f8a0a","Time":"2018-02-13T18:16:22Z"}EOF +{"Version":"v1.2.1","Name":"cac3af4f8a0ab40054fa6f8d423108a63a1255bb","Short":"cac3af4f8a0a","Time":"2018-02-13T18:16:22Z"} -- hello.go -- // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/cmd/go/testdata/script/README b/src/cmd/go/testdata/script/README index d658cebfce0170e8fcd2956b2bd2e11d74a2555c..48e4055b0bdb971397be7dd2b0b909205a46a588 100644 --- a/src/cmd/go/testdata/script/README +++ b/src/cmd/go/testdata/script/README @@ -29,7 +29,6 @@ Scripts also have access to these other environment variables: GOARCH= GOCACHE= GOEXE= - GOEXPSTRING= GOOS= GOPATH=$WORK/gopath GOPROXY= @@ -103,6 +102,7 @@ The commands are: - cd dir Change to the given directory for future commands. + The directory must use slashes as path separator. - chmod perm path... Change the permissions of the files or directories named by the path arguments diff --git a/src/cmd/go/testdata/script/badgo.txt b/src/cmd/go/testdata/script/badgo.txt new file mode 100644 index 0000000000000000000000000000000000000000..cf4e2584d656508ef0505e568b1d6b0866371160 --- /dev/null +++ b/src/cmd/go/testdata/script/badgo.txt @@ -0,0 +1,50 @@ +go get example.net/badgo@v1.0.0 +go get example.net/badgo@v1.1.0 +go get example.net/badgo@v1.2.0 +go get example.net/badgo@v1.3.0 +go get example.net/badgo@v1.4.0 +go get example.net/badgo@v1.5.0 +! go get example.net/badgo@v1.6.0 +stderr 'invalid go version .X.Y.: must match format 1.23' + +-- go.mod -- +module m + +replace ( + example.net/badgo v1.0.0 => ./v1.0.0 + example.net/badgo v1.1.0 => ./v1.1.0 + example.net/badgo v1.2.0 => ./v1.2.0 + example.net/badgo v1.3.0 => ./v1.3.0 + example.net/badgo v1.4.0 => ./v1.4.0 + example.net/badgo v1.5.0 => ./v1.5.0 + example.net/badgo v1.6.0 => ./v1.6.0 +) + +-- v1.0.0/go.mod -- +module example.net/badgo +go 1.17.0 + +-- v1.1.0/go.mod -- +module example.net/badgo +go 1.17rc2 + +-- v1.2.0/go.mod -- +module example.net/badgo +go 1.17.1 + +-- v1.3.0/go.mod -- +module example.net/badgo +go v1.17.0 + +-- v1.4.0/go.mod -- +module example.net/badgo +go v1.17.0-rc.2 + +-- v1.5.0/go.mod -- +module example.net/badgo +go v1.17.1 + +-- v1.6.0/go.mod -- +module example.net/badgo +go X.Y + diff --git a/src/cmd/go/testdata/script/bug.txt b/src/cmd/go/testdata/script/bug.txt index b9bbaaad335ffa587d253a1d43feb0e98659458c..571d507358ade486cfdc46b59dbe07925fbcc6c9 100644 --- a/src/cmd/go/testdata/script/bug.txt +++ b/src/cmd/go/testdata/script/bug.txt @@ -1,9 +1,11 @@ # Verify that go bug creates the appropriate URL issue body [!linux] skip +[short] skip go install -env BROWSER=$GOPATH/bin/browser +go build -o $TMPDIR/go ./go +env BROWSER=$GOPATH/bin/browser PATH=$TMPDIR:$PATH go bug exists $TMPDIR/browser grep '^go version' $TMPDIR/browser @@ -44,3 +46,13 @@ func main() { } } +-- go/main.go -- +package main + +import ( + "os" +) + +func main() { + os.Exit(1) +} diff --git a/src/cmd/go/testdata/script/build_ignore_leading_bom.txt b/src/cmd/go/testdata/script/build_ignore_leading_bom.txt new file mode 100644 index 0000000000000000000000000000000000000000..37141f3466b3e7d7ac785d6e5cd909157bb2febe --- /dev/null +++ b/src/cmd/go/testdata/script/build_ignore_leading_bom.txt @@ -0,0 +1,27 @@ +# Per https://golang.org/ref/spec#Source_code_representation: +# a compiler may ignore a UTF-8-encoded byte order mark (U+FEFF) +# if it is the first Unicode code point in the source text. + +go list -f 'Imports: {{.Imports}} EmbedFiles: {{.EmbedFiles}}' . +stdout '^Imports: \[embed m/hello\] EmbedFiles: \[.*file\]$' + +-- go.mod -- +module m + +go 1.16 +-- m.go -- +package main + +import ( + _ "embed" + + "m/hello" +) + +//go:embed file +var s string + +-- hello/hello.go -- +package hello + +-- file -- diff --git a/src/cmd/go/testdata/script/build_overlay.txt b/src/cmd/go/testdata/script/build_overlay.txt index b11cd960141f8815972d7fae268102f85c01b9c4..2932b94e6c42c70cdec8884c1e36dc281e063369 100644 --- a/src/cmd/go/testdata/script/build_overlay.txt +++ b/src/cmd/go/testdata/script/build_overlay.txt @@ -238,7 +238,7 @@ void say_hello(); void say_hello() { puts("hello cgo\n"); fflush(stdout); } -- m/overlay/asm_gc.s -- -// +build !gccgo +// +build gc TEXT ·foo(SB),0,$0 RET diff --git a/src/cmd/go/testdata/script/build_package_not_stale_trailing_slash.txt b/src/cmd/go/testdata/script/build_package_not_stale_trailing_slash.txt deleted file mode 100644 index 38a151ef1f68df727a87610a6bae87261de824cb..0000000000000000000000000000000000000000 --- a/src/cmd/go/testdata/script/build_package_not_stale_trailing_slash.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Tests Issue #12690 - -[gccgo] skip 'gccgo does not have GOROOT' - -! stale runtime -! stale os -! stale io - -env GOROOT=$GOROOT'/' - -! stale runtime -! stale os -! stale io \ No newline at end of file diff --git a/src/cmd/go/testdata/script/build_tag_goexperiment.txt b/src/cmd/go/testdata/script/build_tag_goexperiment.txt index 26ad029845ee6edf58e459725e60d98303b4b696..bee218f4c1fff290b993cf68930a4753ecbce708 100644 --- a/src/cmd/go/testdata/script/build_tag_goexperiment.txt +++ b/src/cmd/go/testdata/script/build_tag_goexperiment.txt @@ -1,85 +1,20 @@ -# compile_ext will fail if the buildtags that are enabled (or not enabled) for the -# framepointer and fieldtrack experiments are not consistent with the value of -# GOEXPSTRING (which comes from objabi.Expstring()). - [short] skip +# Reset all experiments so fieldtrack is definitely off. +env GOEXPERIMENT=none go run m - --- expt_main.go -- -package main - -import ( - "os" - "strings" -) - -func main() { - fp() - ft() -} - -func hasExpEntry(s string) bool { - // script_test.go defines GOEXPSTRING to be the value of - // objabi.Expstring(), which gives the enabled experiments baked into the - // toolchain. - g := os.Getenv("GOEXPSTRING") - for _, f := range strings.Split(g, ",") { - if f == s { - return true - } - } - return false -} - --- fp_off.go -- -// +build !goexperiment.framepointer - -package main - -import ( - "fmt" - "os" -) - -func fp() { - if hasExpEntry("framepointer") { - fmt.Println("in !framepointer build, but objabi.Expstring() has 'framepointer'") - os.Exit(1) - } -} - --- fp_on.go -- -// +build goexperiment.framepointer - -package main - -import ( - "fmt" - "os" -) - -func fp() { - if !hasExpEntry("framepointer") { - fmt.Println("in framepointer build, but objabi.Expstring() does not have 'framepointer', is", os.Getenv("GOEXPSTRING")) - os.Exit(1) - } -} +stderr 'fieldtrack off' +# Turn fieldtrack on. +env GOEXPERIMENT=none,fieldtrack +go run m +stderr 'fieldtrack on' -- ft_off.go -- // +build !goexperiment.fieldtrack package main -import ( - "fmt" - "os" -) - -func ft() { - if hasExpEntry("fieldtrack") { - fmt.Println("in !fieldtrack build, but objabi.Expstring() has 'fieldtrack'") - os.Exit(1) - } +func main() { + println("fieldtrack off") } -- ft_on.go -- @@ -87,16 +22,8 @@ func ft() { package main -import ( - "fmt" - "os" -) - -func ft() { - if !hasExpEntry("fieldtrack") { - fmt.Println("in fieldtrack build, but objabi.Expstring() does not have 'fieldtrack', is", os.Getenv("GOEXPSTRING")) - os.Exit(1) - } +func main() { + println("fieldtrack on") } -- go.mod -- diff --git a/src/cmd/go/testdata/script/cgo_stale.txt b/src/cmd/go/testdata/script/cgo_stale.txt new file mode 100644 index 0000000000000000000000000000000000000000..9e46855eadb1e687458cbb035705a76324185a86 --- /dev/null +++ b/src/cmd/go/testdata/script/cgo_stale.txt @@ -0,0 +1,39 @@ +# golang.org/issue/46347: a stale runtime/cgo should only force a single rebuild + +[!cgo] skip +[short] skip + + +# If we set a unique CGO_CFLAGS, the installed copy of runtime/cgo +# should be reported as stale. + +env CGO_CFLAGS=-DTestScript_cgo_stale=true +stale runtime/cgo + + +# If we then build a package that uses cgo, runtime/cgo should be rebuilt and +# cached with the new flag, but not installed to GOROOT (and thus still stale). + +env GOCACHE=$WORK/cache # Use a fresh cache to avoid interference between runs. + +go build -x . +stderr '[/\\]cgo'$GOEXE'["]? .* -importpath runtime/cgo' +stale runtime/cgo + + +# After runtime/cgo has been rebuilt and cached, it should not be rebuilt again +# even though it is still reported as stale. + +go build -x . +! stderr '[/\\]cgo'$GOEXE'["]? .* -importpath runtime/cgo' +stale runtime/cgo + + +-- go.mod -- +module example.com/m + +go 1.17 +-- m.go -- +package m + +import "C" diff --git a/src/cmd/go/testdata/script/cover_pkgall_imports.txt b/src/cmd/go/testdata/script/cover_pkgall_imports.txt new file mode 100644 index 0000000000000000000000000000000000000000..4e51726b29c4db2ea7c740ba2a5176e94eedaeb5 --- /dev/null +++ b/src/cmd/go/testdata/script/cover_pkgall_imports.txt @@ -0,0 +1,48 @@ +# This test checks that -coverpkg=all can be used +# when the package pattern includes packages +# which only have tests. +# Verifies golang.org/issue/27333, golang.org/issue/43242. + +[short] skip +cd $GOPATH/src/example.com/cov + +env GO111MODULE=on +go test -coverpkg=all ./... + +env GO111MODULE=off +go test -coverpkg=all ./... + +-- $GOPATH/src/example.com/cov/go.mod -- +module example.com/cov + +-- $GOPATH/src/example.com/cov/notest/notest.go -- +package notest + +func Foo() {} + +-- $GOPATH/src/example.com/cov/onlytest/onlytest_test.go -- +package onlytest_test + +import ( + "testing" + + "example.com/cov/notest" +) + +func TestFoo(t *testing.T) { + notest.Foo() +} + +-- $GOPATH/src/example.com/cov/withtest/withtest.go -- +package withtest + +func Bar() {} + +-- $GOPATH/src/example.com/cov/withtest/withtest_test.go -- +package withtest + +import "testing" + +func TestBar(t *testing.T) { + Bar() +} diff --git a/src/cmd/go/testdata/script/embed.txt b/src/cmd/go/testdata/script/embed.txt index 6ad42e9cd18c3894931eeb25c47bba17fde519b2..04b17cd62b385999188cfa7e6795496bbddd0c50 100644 --- a/src/cmd/go/testdata/script/embed.txt +++ b/src/cmd/go/testdata/script/embed.txt @@ -107,3 +107,4 @@ import _ "m" -- go.mod -- module m +go 1.16 diff --git a/src/cmd/go/testdata/script/env_cross_build.txt b/src/cmd/go/testdata/script/env_cross_build.txt new file mode 100644 index 0000000000000000000000000000000000000000..3feeba6b1412d463264614ab53bed41eb135980e --- /dev/null +++ b/src/cmd/go/testdata/script/env_cross_build.txt @@ -0,0 +1,29 @@ +# Test that the corect default GOEXPERIMENT is used when cross +# building with GOENV (#46815). + +# Unset variables set by the TestScript harness. Users typically won't +# explicitly configure these, and #46815 doesn't repro if they are. +env GOOS= +env GOARCH= +env GOEXPERIMENT= + +env GOENV=windows-amd64 +go build internal/abi + +env GOENV=ios-arm64 +go build internal/abi + +env GOENV=linux-mips +go build internal/abi + +-- windows-amd64 -- +GOOS=windows +GOARCH=amd64 + +-- ios-arm64 -- +GOOS=ios +GOARCH=arm64 + +-- linux-mips -- +GOOS=linux +GOARCH=mips diff --git a/src/cmd/go/testdata/script/env_exp.txt b/src/cmd/go/testdata/script/env_exp.txt new file mode 100644 index 0000000000000000000000000000000000000000..681512d87c612181a81d2f7f152e98b39115112c --- /dev/null +++ b/src/cmd/go/testdata/script/env_exp.txt @@ -0,0 +1,17 @@ +# Test GOEXPERIMENT variable + +# go env shows default empty GOEXPERIMENT +go env +stdout GOEXPERIMENT= + +# go env shows valid experiments +env GOEXPERIMENT=fieldtrack,staticlockranking +go env GOEXPERIMENT +stdout '.*fieldtrack.*staticlockranking.*' +go env +stdout 'GOEXPERIMENT=.*fieldtrack.*staticlockranking.*' + +# go env rejects unknown experiments +env GOEXPERIMENT=bad +! go env GOEXPERIMENT +stderr 'unknown GOEXPERIMENT bad' diff --git a/src/cmd/go/testdata/script/env_unset.txt b/src/cmd/go/testdata/script/env_unset.txt new file mode 100644 index 0000000000000000000000000000000000000000..4e0f249509873ed7355470f5bd0a1326defbb1a0 --- /dev/null +++ b/src/cmd/go/testdata/script/env_unset.txt @@ -0,0 +1,30 @@ +# Test that we can unset variables, even if initially invalid, +# as long as resulting config is valid. + +env GOENV=badenv +env GOOS= +env GOARCH= +env GOEXPERIMENT= + +! go env +stderr '^go(\.exe)?: unknown GOEXPERIMENT badexp$' + +go env -u GOEXPERIMENT + +! go env +stderr '^cmd/go: unsupported GOOS/GOARCH pair bados/badarch$' + +! go env -u GOOS +stderr '^go env -u: unsupported GOOS/GOARCH pair \w+/badarch$' + +! go env -u GOARCH +stderr '^go env -u: unsupported GOOS/GOARCH pair bados/\w+$' + +go env -u GOOS GOARCH + +go env + +-- badenv -- +GOOS=bados +GOARCH=badarch +GOEXPERIMENT=badexp diff --git a/src/cmd/go/testdata/script/env_write.txt b/src/cmd/go/testdata/script/env_write.txt index bda1e57826ee1bb9dbea4cac1e3571f533fda501..b5e97391679cc52442322e972c7a82fbed0e30bc 100644 --- a/src/cmd/go/testdata/script/env_write.txt +++ b/src/cmd/go/testdata/script/env_write.txt @@ -173,3 +173,15 @@ go env -w GOOS=linux GOARCH=mips env GOOS=windows ! go env -u GOOS stderr 'unsupported GOOS/GOARCH.*windows/mips$' + +# go env -w should reject relative paths in GOMODCACHE environment. +! go env -w GOMODCACHE=~/test +stderr 'go env -w: GOMODCACHE entry is relative; must be absolute path: "~/test"' +! go env -w GOMODCACHE=./test +stderr 'go env -w: GOMODCACHE entry is relative; must be absolute path: "./test"' + +# go env -w checks validity of GOEXPERIMENT +env GOEXPERIMENT= +! go env -w GOEXPERIMENT=badexp +stderr 'unknown GOEXPERIMENT badexp' +go env -w GOEXPERIMENT=fieldtrack diff --git a/src/cmd/go/testdata/script/fmt_load_errors.txt b/src/cmd/go/testdata/script/fmt_load_errors.txt index 297ec0fe3c7c28d533065de426e99e229388f54b..84bf41cfbafacd175597b77c26b9525e8f4c6e03 100644 --- a/src/cmd/go/testdata/script/fmt_load_errors.txt +++ b/src/cmd/go/testdata/script/fmt_load_errors.txt @@ -6,6 +6,17 @@ go fmt -n exclude stdout 'exclude[/\\]x\.go' stdout 'exclude[/\\]x_linux\.go' +# Test edge cases with gofmt. +# Note that this execs GOROOT/bin/gofmt. + +! exec gofmt does-not-exist + +exec gofmt gofmt-dir/no-extension +stdout 'package x' + +exec gofmt gofmt-dir +! stdout 'package x' + -- exclude/empty/x.txt -- -- exclude/ignore/_x.go -- package x @@ -17,3 +28,5 @@ package x // +build windows package x +-- gofmt-dir/no-extension -- +package x diff --git a/src/cmd/go/testdata/script/generate.txt b/src/cmd/go/testdata/script/generate.txt index c3c563e5f4a1d157f34f2b113431b88a71cf4dd9..73f5bbd57a91d35177b5ccaed75e0d1496cebffe 100644 --- a/src/cmd/go/testdata/script/generate.txt +++ b/src/cmd/go/testdata/script/generate.txt @@ -26,6 +26,10 @@ stdout 'yes' # flag.go should select yes go generate './generate/env_test.go' stdout 'main_test' +# Test go generate provides the right "$PWD" +go generate './generate/env_pwd.go' +stdout $WORK'[/\\]gopath[/\\]src[/\\]generate' + -- echo.go -- package main @@ -88,4 +92,8 @@ package p -- generate/env_test.go -- package main_test -//go:generate echo $GOPACKAGE \ No newline at end of file +//go:generate echo $GOPACKAGE +-- generate/env_pwd.go -- +package p + +//go:generate echo $PWD diff --git a/src/cmd/go/testdata/script/get_404_meta.txt b/src/cmd/go/testdata/script/get_404_meta.txt index b71cc7fe010a1e182342d70ed003aced170fee88..ec4f8d32432179bc97c5fe9fccb56d1527c664e3 100644 --- a/src/cmd/go/testdata/script/get_404_meta.txt +++ b/src/cmd/go/testdata/script/get_404_meta.txt @@ -3,9 +3,10 @@ [!net] skip [!exec:git] skip +env GONOSUMDB=bazil.org,github.com,golang.org env GO111MODULE=off -go get -d -insecure bazil.org/fuse/fs/fstestutil +go get -d bazil.org/fuse/fs/fstestutil env GO111MODULE=on env GOPROXY=direct -go get -d -insecure bazil.org/fuse/fs/fstestutil +go get -d bazil.org/fuse/fs/fstestutil diff --git a/src/cmd/go/testdata/script/get_insecure.txt b/src/cmd/go/testdata/script/get_insecure.txt index 36ad2c05b7b997e30ba667a863f37f315e311bbd..69930f7107a58b9ddeb4729b313ccb8cc999969e 100644 --- a/src/cmd/go/testdata/script/get_insecure.txt +++ b/src/cmd/go/testdata/script/get_insecure.txt @@ -12,10 +12,12 @@ env GO111MODULE=off # GOPATH: Try go get -d of HTTP-only repo (should fail). ! go get -d insecure.go-get-issue-15410.appspot.com/pkg/p -# GOPATH: Try again with -insecure (should succeed). -go get -d -insecure insecure.go-get-issue-15410.appspot.com/pkg/p +# GOPATH: Try again with GOINSECURE (should succeed). +env GOINSECURE=insecure.go-get-issue-15410.appspot.com +go get -d insecure.go-get-issue-15410.appspot.com/pkg/p -# GOPATH: Try updating without -insecure (should fail). +# GOPATH: Try updating without GOINSECURE (should fail). +env GOINSECURE='' ! go get -d -u -f insecure.go-get-issue-15410.appspot.com/pkg/p # Modules: Set up @@ -29,10 +31,14 @@ env GOPROXY='' # Modules: Try go get -d of HTTP-only repo (should fail). ! go get -d insecure.go-get-issue-15410.appspot.com/pkg/p -# Modules: Try again with -insecure (should succeed). -go get -d -insecure insecure.go-get-issue-15410.appspot.com/pkg/p +# Modules: Try again with GOINSECURE (should succeed). +env GOINSECURE=insecure.go-get-issue-15410.appspot.com +env GONOSUMDB=insecure.go-get-issue-15410.appspot.com +go get -d insecure.go-get-issue-15410.appspot.com/pkg/p -# Modules: Try updating without -insecure (should fail). +# Modules: Try updating without GOINSECURE (should fail). +env GOINSECURE='' +env GONOSUMDB='' ! go get -d -u -f insecure.go-get-issue-15410.appspot.com/pkg/p go list -m ... @@ -48,4 +54,4 @@ func main() { os.Exit(1) } -- module_file -- -module m \ No newline at end of file +module m diff --git a/src/cmd/go/testdata/script/get_insecure_custom_domain.txt b/src/cmd/go/testdata/script/get_insecure_custom_domain.txt index a4a6fd428f7c2fde2a5ed5689e3dd083f04c863a..7eba42e873e011a8c7d479e53a2e04e360bbf20e 100644 --- a/src/cmd/go/testdata/script/get_insecure_custom_domain.txt +++ b/src/cmd/go/testdata/script/get_insecure_custom_domain.txt @@ -3,4 +3,6 @@ env GO111MODULE=off ! go get -d insecure.go-get-issue-15410.appspot.com/pkg/p -go get -d -insecure insecure.go-get-issue-15410.appspot.com/pkg/p + +env GOINSECURE=insecure.go-get-issue-15410.appspot.com +go get -d insecure.go-get-issue-15410.appspot.com/pkg/p diff --git a/src/cmd/go/testdata/script/get_insecure_deprecated.txt b/src/cmd/go/testdata/script/get_insecure_deprecated.txt deleted file mode 100644 index 7f5f5c7877e6a3b84e40f9544491393bf823f087..0000000000000000000000000000000000000000 --- a/src/cmd/go/testdata/script/get_insecure_deprecated.txt +++ /dev/null @@ -1,21 +0,0 @@ -# GOPATH: Set up -env GO111MODULE=off - -# GOPATH: Fetch without insecure, no warning -! go get test -! stderr 'go get: -insecure flag is deprecated; see ''go help get'' for details' - -# GOPATH: Fetch with insecure, should warn -! go get -insecure test -stderr 'go get: -insecure flag is deprecated; see ''go help get'' for details' - -# Modules: Set up -env GO111MODULE=on - -# Modules: Fetch without insecure, no warning -! go get test -! stderr 'go get: -insecure flag is deprecated; see ''go help get'' for details' - -# Modules: Fetch with insecure, should warn -! go get -insecure test -stderr 'go get: -insecure flag is deprecated; see ''go help get'' for details' diff --git a/src/cmd/go/testdata/script/get_insecure_no_longer_supported.txt b/src/cmd/go/testdata/script/get_insecure_no_longer_supported.txt new file mode 100644 index 0000000000000000000000000000000000000000..2517664dd02308901fe338e5daac43a73c16b306 --- /dev/null +++ b/src/cmd/go/testdata/script/get_insecure_no_longer_supported.txt @@ -0,0 +1,13 @@ +# GOPATH: Set up +env GO111MODULE=off + +# GOPATH: Fetch with insecure, should error +! go get -insecure test +stderr 'go get: -insecure flag is no longer supported; use GOINSECURE instead' + +# Modules: Set up +env GO111MODULE=on + +# Modules: Fetch with insecure, should error +! go get -insecure test +stderr 'go get: -insecure flag is no longer supported; use GOINSECURE instead' diff --git a/src/cmd/go/testdata/script/get_insecure_redirect.txt b/src/cmd/go/testdata/script/get_insecure_redirect.txt index 0478d1f75dea2f99b0281dd7b2ec3e76688d732d..fb5f26951cdad9dfdd72f67775db707149f7e9a4 100644 --- a/src/cmd/go/testdata/script/get_insecure_redirect.txt +++ b/src/cmd/go/testdata/script/get_insecure_redirect.txt @@ -1,4 +1,4 @@ -# golang.org/issue/29591: 'go get' was following plain-HTTP redirects even without -insecure. +# golang.org/issue/29591: 'go get' was following plain-HTTP redirects even without -insecure (now replaced by GOINSECURE). # golang.org/issue/34049: 'go get' would panic in case of an insecure redirect in GOPATH mode [!net] skip @@ -9,4 +9,5 @@ env GO111MODULE=off ! go get -d vcs-test.golang.org/insecure/go/insecure stderr 'redirected .* to insecure URL' -go get -d -insecure vcs-test.golang.org/insecure/go/insecure +env GOINSECURE=vcs-test.golang.org/insecure/go/insecure +go get -d vcs-test.golang.org/insecure/go/insecure diff --git a/src/cmd/go/testdata/script/get_insecure_update.txt b/src/cmd/go/testdata/script/get_insecure_update.txt index 4511c98c568458884a97af5e3c441a47f9d0ff62..e1a1a23d47e475a34927e55bd570389a0b27dc09 100644 --- a/src/cmd/go/testdata/script/get_insecure_update.txt +++ b/src/cmd/go/testdata/script/get_insecure_update.txt @@ -5,8 +5,10 @@ env GO111MODULE=off # Clone the repo via HTTP manually. exec git clone -q http://github.com/golang/example github.com/golang/example -# Update without -insecure should fail. -# Update with -insecure should succeed. +# Update without GOINSECURE should fail. # We need -f to ignore import comments. ! go get -d -u -f github.com/golang/example/hello -go get -d -u -f -insecure github.com/golang/example/hello + +# Update with GOINSECURE should succeed. +env GOINSECURE=github.com/golang/example/hello +go get -d -u -f github.com/golang/example/hello diff --git a/src/cmd/go/testdata/script/list_cgo_compiled_importmap.txt b/src/cmd/go/testdata/script/list_cgo_compiled_importmap.txt new file mode 100644 index 0000000000000000000000000000000000000000..3d68ef3055972e5310191f86942248817685d7d6 --- /dev/null +++ b/src/cmd/go/testdata/script/list_cgo_compiled_importmap.txt @@ -0,0 +1,38 @@ +# Regression test for https://golang.org/issue/46462. +# +# The "runtime/cgo" import found in synthesized .go files (reported in +# the CompiledGoFiles field) should have a corresponding entry in the +# ImportMap field when a runtime/cgo variant (such as a test variant) +# will be used. + +[short] skip # -compiled can be slow (because it compiles things) +[!cgo] skip + +env CGO_ENABLED=1 +env GOFLAGS=-tags=netcgo # Force net to use cgo even on Windows. + + +# "runtime/cgo [runtime.test]" appears in the the test dependencies of "runtime", +# because "runtime/cgo" itself depends on "runtime" + +go list -deps -test -compiled -f '{{if eq .ImportPath "net [runtime.test]"}}{{printf "%q" .Imports}}{{end}}' runtime + + # Control case: the explicitly-imported package "sync" is a test variant, + # because "sync" depends on "runtime". +stdout '"sync \[runtime\.test\]"' +! stdout '"sync"' + + # Experiment: the implicitly-imported package "runtime/cgo" is also a test variant, + # because "runtime/cgo" also depends on "runtime". +stdout '"runtime/cgo \[runtime\.test\]"' +! stdout '"runtime/cgo"' + + +# Because the import of "runtime/cgo" in the cgo-generated file actually refers +# to "runtime/cgo [runtime.test]", the latter should be listed in the ImportMap. +# BUG(#46462): Today, it is not. + +go list -deps -test -compiled -f '{{if eq .ImportPath "net [runtime.test]"}}{{printf "%q" .ImportMap}}{{end}}' runtime + +stdout '"sync":"sync \[runtime\.test\]"' # control +stdout '"runtime/cgo":"runtime/cgo \[runtime\.test\]"' # experiment diff --git a/src/cmd/go/testdata/script/list_err_cycle.txt b/src/cmd/go/testdata/script/list_err_cycle.txt new file mode 100644 index 0000000000000000000000000000000000000000..44b82a62b0b0ddbcb04a9756739c914a246e5e8f --- /dev/null +++ b/src/cmd/go/testdata/script/list_err_cycle.txt @@ -0,0 +1,15 @@ +# Check that we don't get infinite recursion when loading a package with +# an import cycle and another error. Verifies #25830. +! go list +stderr 'found packages a \(a.go\) and b \(b.go\)' + +-- go.mod -- +module errcycle + +go 1.16 +-- a.go -- +package a + +import _ "errcycle" +-- b.go -- +package b \ No newline at end of file diff --git a/src/cmd/go/testdata/script/list_find_nodeps.txt b/src/cmd/go/testdata/script/list_find_nodeps.txt new file mode 100644 index 0000000000000000000000000000000000000000..e08ce7895093c890355418fa6afa3b7c188ebbe0 --- /dev/null +++ b/src/cmd/go/testdata/script/list_find_nodeps.txt @@ -0,0 +1,49 @@ +# Issue #46092 +# go list -find should always return a package with an empty Deps list + +# The linker loads implicit dependencies +go list -find -f {{.Deps}} ./cmd +stdout '\[\]' + +# Cgo translation may add imports of "unsafe", "runtime/cgo" and "syscall" +go list -find -f {{.Deps}} ./cgo +stdout '\[\]' + +# SWIG adds imports of some standard packages +go list -find -f {{.Deps}} ./swig +stdout '\[\]' + +-- go.mod -- +module listfind + +-- cmd/main.go -- +package main + +func main() {} + +-- cgo/pkg.go -- +package cgopkg + +/* +#include +*/ +import "C" + +func F() { + println(C.INT_MAX) +} + +-- cgo/pkg_notcgo.go -- +//go:build !cgo +// +build !cgo + +package cgopkg + +func F() { + println(0) +} + +-- swig/pkg.go -- +package swigpkg + +-- swig/a.swigcxx -- diff --git a/src/cmd/go/testdata/script/list_gomod_in_gopath.txt b/src/cmd/go/testdata/script/list_gomod_in_gopath.txt new file mode 100644 index 0000000000000000000000000000000000000000..064f33adc36a69f9c551ca07aece88e1a67e8002 --- /dev/null +++ b/src/cmd/go/testdata/script/list_gomod_in_gopath.txt @@ -0,0 +1,23 @@ +# Issue 46119 + +# When a module is inside a GOPATH workspace, Package.Root should be set to +# Module.Dir instead of $GOPATH/src. + +env GOPATH=$WORK/tmp +cd $WORK/tmp/src/test + +go list -f {{.Root}} +stdout ^$PWD$ + +# Were we really inside a GOPATH workspace? +env GO111MODULE=off +go list -f {{.Root}} +stdout ^$WORK/tmp$ + +-- $WORK/tmp/src/test/go.mod -- +module test + +-- $WORK/tmp/src/test/main.go -- +package main + +func main() {} diff --git a/src/cmd/go/testdata/script/list_json_with_f.txt b/src/cmd/go/testdata/script/list_json_with_f.txt new file mode 100644 index 0000000000000000000000000000000000000000..2011a6e808bff68fb7bcace57f3486252a921d92 --- /dev/null +++ b/src/cmd/go/testdata/script/list_json_with_f.txt @@ -0,0 +1,20 @@ +[short] skip + +# list -json should generate output on stdout +go list -json ./... +stdout . +# list -f should generate output on stdout +go list -f '{{.}}' ./... +stdout . + +# test passing first -json then -f +! go list -json -f '{{.}}' ./... +stderr '^go list -f cannot be used with -json$' + +# test passing first -f then -json +! go list -f '{{.}}' -json ./... +stderr '^go list -f cannot be used with -json$' +-- go.mod -- +module m +-- list_test.go -- +package list_test diff --git a/src/cmd/go/testdata/script/list_load_err.txt b/src/cmd/go/testdata/script/list_load_err.txt index b3b72713e5499284eaf9803b2d236221b8473bd8..0cfa7fbed2fe437592a18238916b4b4a52fc617d 100644 --- a/src/cmd/go/testdata/script/list_load_err.txt +++ b/src/cmd/go/testdata/script/list_load_err.txt @@ -2,26 +2,42 @@ # other files in the same package cause go/build.Import to return an error. # Verfifies golang.org/issue/38568 - go list -e -deps ./scan stdout m/want - go list -e -deps ./multi stdout m/want - go list -e -deps ./constraint stdout m/want - [cgo] go list -e -test -deps ./cgotest [cgo] stdout m/want - [cgo] go list -e -deps ./cgoflag [cgo] stdout m/want + +# go list -e should include files with errors in GoFiles, TestGoFiles, and +# other lists, assuming they match constraints. +# Verifies golang.org/issue/39986 +go list -e -f '{{range .GoFiles}}{{.}},{{end}}' ./scan +stdout '^good.go,scan.go,$' + +go list -e -f '{{range .GoFiles}}{{.}},{{end}}' ./multi +stdout '^a.go,b.go,$' + +go list -e -f '{{range .GoFiles}}{{.}},{{end}}' ./constraint +stdout '^good.go,$' +go list -e -f '{{range .IgnoredGoFiles}}{{.}},{{end}}' ./constraint +stdout '^constraint.go,$' + +[cgo] go list -e -f '{{range .XTestGoFiles}}{{.}},{{end}}' ./cgotest +[cgo] stdout '^cgo_test.go,$' + +[cgo] go list -e -f '{{range .GoFiles}}{{.}},{{end}}' ./cgoflag +[cgo] stdout '^cgoflag.go,$' + -- go.mod -- module m diff --git a/src/cmd/go/testdata/script/list_module_when_error.txt b/src/cmd/go/testdata/script/list_module_when_error.txt new file mode 100644 index 0000000000000000000000000000000000000000..844164cd6a27628c9061dc3c59ffb989e8e714a4 --- /dev/null +++ b/src/cmd/go/testdata/script/list_module_when_error.txt @@ -0,0 +1,19 @@ +# The Module field should be populated even if there is an error loading the package. + +env GO111MODULE=on + +go list -e -f {{.Module}} +stdout '^mod.com$' + +-- go.mod -- +module mod.com + +go 1.16 + +-- blah.go -- +package blah + +import _ "embed" + +//go:embed README.md +var readme string diff --git a/src/cmd/go/testdata/script/list_std_vendor.txt b/src/cmd/go/testdata/script/list_std_vendor.txt new file mode 100644 index 0000000000000000000000000000000000000000..8f27cc1e8d80fa76037b0ef29a454118304d0bc1 --- /dev/null +++ b/src/cmd/go/testdata/script/list_std_vendor.txt @@ -0,0 +1,32 @@ +# https://golang.org/issue/44725: packages in std should have the same +# dependencies regardless of whether they are listed from within or outside +# GOROOT/src. + +# Control case: net, viewed from outside the 'std' module, +# should depend on vendor/golang.org/… instead of golang.org/…. + +go list -deps net +stdout '^vendor/golang.org/x/net' +! stdout '^golang.org/x/net' +cp stdout $WORK/net-deps.txt + + +# It should still report the same package dependencies when viewed from +# within GOROOT/src. + +cd $GOROOT/src + +go list -deps net +stdout '^vendor/golang.org/x/net' +! stdout '^golang.org/x/net' +cmp stdout $WORK/net-deps.txt + + +# However, 'go mod' and 'go get' subcommands should report the original module +# dependencies, not the vendored packages. + +[!net] stop + +env GOPROXY= +go mod why -m golang.org/x/net +stdout '^# golang.org/x/net\nnet\ngolang.org/x/net' diff --git a/src/cmd/go/testdata/script/list_swigcxx.txt b/src/cmd/go/testdata/script/list_swigcxx.txt new file mode 100644 index 0000000000000000000000000000000000000000..c6acd9ecdbabf4b467c37fd262344be45dac8fec --- /dev/null +++ b/src/cmd/go/testdata/script/list_swigcxx.txt @@ -0,0 +1,27 @@ +# go list should not report SWIG-generated C++ files in CompiledGoFiles. + +[!exec:swig] skip +[!exec:g++] skip + +# CompiledGoFiles should contain 4 files: +# a.go +# a.swigcxx.go +# _cgo_gotypes.go +# a.cgo1.go + +go list -f '{{.CompiledGoFiles}}' -compiled=true example/swig + +# These names we see here, other than a.go, will be from the build cache, +# so we just count them. +stdout a\.go +stdout -count=3 $GOCACHE + +-- go.mod -- +module example + +go 1.16 + +-- swig/a.go -- +package swig + +-- swig/a.swigcxx -- diff --git a/src/cmd/go/testdata/script/list_symlink_issue35941.txt b/src/cmd/go/testdata/script/list_symlink_issue35941.txt new file mode 100644 index 0000000000000000000000000000000000000000..eb12bde6ceff5c919c3c49a03be0958653f73c7a --- /dev/null +++ b/src/cmd/go/testdata/script/list_symlink_issue35941.txt @@ -0,0 +1,18 @@ +[!symlink] skip +env GO111MODULE=off + +# Issue 35941: suppress symlink warnings when running 'go list all'. +symlink goproj/css -> $GOPATH/src/css + +go list all +! stderr 'warning: ignoring symlink' + +# Show symlink warnings when patterns contain '...'. +go list goproj/... +stderr 'warning: ignoring symlink' + +-- goproj/a.go -- +package a + +-- css/index.css -- +body {} diff --git a/src/cmd/go/testdata/script/mod_all.txt b/src/cmd/go/testdata/script/mod_all.txt index aac66292d66fbd8a055962725804ac3c96b2d88b..090eeee22df263579b99075bb227f5f32372e0dd 100644 --- a/src/cmd/go/testdata/script/mod_all.txt +++ b/src/cmd/go/testdata/script/mod_all.txt @@ -189,19 +189,22 @@ stdout '^example.com/main/testonly_test \[example.com/main/testonly.test\]$' rm vendor -# Convert all modules to go 1.16 to enable lazy loading. -go mod edit -go=1.16 a/go.mod -go mod edit -go=1.16 b/go.mod -go mod edit -go=1.16 c/go.mod -go mod edit -go=1.16 d/go.mod -go mod edit -go=1.16 q/go.mod -go mod edit -go=1.16 r/go.mod -go mod edit -go=1.16 s/go.mod -go mod edit -go=1.16 t/go.mod -go mod edit -go=1.16 u/go.mod -go mod edit -go=1.16 w/go.mod -go mod edit -go=1.16 x/go.mod -go mod edit -go=1.16 +# Convert all modules to go 1.17 to enable lazy loading. +go mod edit -go=1.17 a/go.mod +go mod edit -go=1.17 b/go.mod +go mod edit -go=1.17 c/go.mod +go mod edit -go=1.17 d/go.mod +go mod edit -go=1.17 q/go.mod +go mod edit -go=1.17 r/go.mod +go mod edit -go=1.17 s/go.mod +go mod edit -go=1.17 t/go.mod +go mod edit -go=1.17 u/go.mod +go mod edit -go=1.17 w/go.mod +go mod edit -go=1.17 x/go.mod +go mod edit -go=1.17 +cp go.mod go.mod.orig +go mod tidy +cmp go.mod go.mod.orig # With lazy loading, 'go list all' with neither -mod=vendor nor -test should # match -mod=vendor without -test in 1.15. @@ -282,20 +285,41 @@ stdout '^example.com/t_test \[example.com/t.test\]$' stdout '^example.com/u.test$' stdout '^example.com/u_test \[example.com/u.test\]$' +# 'go list -m all' should cover all of the modules providing packages in +# 'go list -test -deps all', but should exclude modules d and x, +# which are not relevant to the main module and are outside of the +# lazy-loading horizon. -# TODO(#36460): -# 'go list -m all' should exactly cover the packages in 'go list -test all'. +go list -m -f $MODFMT all +stdout -count=10 '^.' +stdout '^example.com/a$' +stdout '^example.com/b$' +stdout '^example.com/c$' +! stdout '^example.com/d$' +stdout '^example.com/main$' +stdout '^example.com/q$' +stdout '^example.com/r$' +stdout '^example.com/s$' +stdout '^example.com/t$' +stdout '^example.com/u$' +stdout '^example.com/w$' +! stdout '^example.com/x$' -- go.mod -- module example.com/main +// Note: this go.mod file initially specifies go 1.15, +// but includes some redundant roots so that it +// also already obeys the 1.17 lazy loading invariants. go 1.15 require ( example.com/a v0.1.0 example.com/b v0.1.0 example.com/q v0.1.0 + example.com/r v0.1.0 // indirect example.com/t v0.1.0 + example.com/u v0.1.0 // indirect ) replace ( diff --git a/src/cmd/go/testdata/script/mod_cache_dir.txt b/src/cmd/go/testdata/script/mod_cache_dir.txt new file mode 100644 index 0000000000000000000000000000000000000000..7284ccf8bab735aa5613f08608228f41ec778c9f --- /dev/null +++ b/src/cmd/go/testdata/script/mod_cache_dir.txt @@ -0,0 +1,11 @@ +env GO111MODULE=on + +# Go should reject relative paths in GOMODCACHE environment. + +env GOMODCACHE="~/test" +! go get example.com/tools/cmd/hello +stderr 'must be absolute path' + +env GOMODCACHE="./test" +! go get example.com/tools/cmd/hello +stderr 'must be absolute path' diff --git a/src/cmd/go/testdata/script/mod_convert.txt b/src/cmd/go/testdata/script/mod_convert.txt new file mode 100644 index 0000000000000000000000000000000000000000..f60fe87637cb3e0c4389c3cac54db09b933216c6 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_convert.txt @@ -0,0 +1,66 @@ +[short] skip +[!net] skip +[!exec:git] skip + +env GO111MODULE=on +env GOPROXY= +env GOSUMDB= + +go mod download github.com/docker/distribution@v0.0.0-20150410205453-85de3967aa93 +mkdir x/Godeps +cp $GOPATH/pkg/mod/github.com/docker/distribution@v0.0.0-20150410205453-85de3967aa93/Godeps/Godeps.json x/Godeps +cd x +go mod init github.com/docker/distribution +cmpenv go.mod go.mod.want + +go mod download github.com/fishy/gcsbucket@v0.0.0-20180217031846-618d60fe84e0 +cp $GOPATH/pkg/mod/github.com/fishy/gcsbucket@v0.0.0-20180217031846-618d60fe84e0/Gopkg.lock ../y +cd ../y +go mod init github.com/fishy/gcsbucket +cmpenv go.mod go.mod.want + +-- x/go.mod.want -- +module github.com/docker/distribution + +go $goversion + +require ( + github.com/AdRoll/goamz v0.0.0-20150130162828-d3664b76d905 + github.com/MSOpenTech/azure-sdk-for-go v0.0.0-20150323223030-d90753bcad2e + github.com/Sirupsen/logrus v0.7.3 + github.com/bugsnag/bugsnag-go v1.0.3-0.20141110184014-b1d153021fcd + github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b + github.com/bugsnag/panicwrap v0.0.0-20141110184334-e5f9854865b9 + github.com/codegangsta/cli v1.4.2-0.20150131031259-6086d7927ec3 + github.com/docker/docker v1.4.2-0.20150204013315-165ea5c158cf + github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 + github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 + github.com/gorilla/context v0.0.0-20140604161150-14f550f51af5 + github.com/gorilla/handlers v0.0.0-20140825150757-0e84b7d810c1 + github.com/gorilla/mux v0.0.0-20140926153814-e444e69cbd2e + github.com/jlhawn/go-crypto v0.0.0-20150401213827-cd738dde20f0 + github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 + github.com/yvasiyarov/gorelic v0.0.7-0.20141212073537-a9bba5b9ab50 + github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f + golang.org/x/net v0.0.0-20150202051010-1dfe7915deaf + gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789 + gopkg.in/yaml.v2 v2.0.0-20150116202057-bef53efd0c76 +) +-- y/go.mod.want -- +module github.com/fishy/gcsbucket + +go $goversion + +require ( + cloud.google.com/go v0.18.0 + github.com/fishy/fsdb v0.0.0-20180217030800-5527ded01371 + github.com/golang/protobuf v1.0.0 + github.com/googleapis/gax-go v2.0.0+incompatible + golang.org/x/net v0.0.0-20180216171745-136a25c244d3 + golang.org/x/oauth2 v0.0.0-20180207181906-543e37812f10 + golang.org/x/text v0.3.1-0.20180208041248-4e4a3210bb54 + google.golang.org/api v0.0.0-20180217000815-c7a403bb5fe1 + google.golang.org/appengine v1.0.0 + google.golang.org/genproto v0.0.0-20180206005123-2b5a72b8730b + google.golang.org/grpc v1.10.0 +) diff --git a/src/cmd/go/testdata/script/mod_convert_tsv_insecure.txt b/src/cmd/go/testdata/script/mod_convert_tsv_insecure.txt index ddb0c081996ed7653b1adfac836b3d017007d7ce..283e2d99366bb8c914f9b4428943355fdd286077 100644 --- a/src/cmd/go/testdata/script/mod_convert_tsv_insecure.txt +++ b/src/cmd/go/testdata/script/mod_convert_tsv_insecure.txt @@ -1,4 +1,6 @@ env GO111MODULE=on +env GOPROXY=direct +env GOSUMDB=off [!net] skip [!exec:git] skip diff --git a/src/cmd/go/testdata/script/mod_deprecate_message.txt b/src/cmd/go/testdata/script/mod_deprecate_message.txt new file mode 100644 index 0000000000000000000000000000000000000000..567027935dc338c32ec73644c594253b640c3cc6 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_deprecate_message.txt @@ -0,0 +1,73 @@ +# When there is a short single-line message, 'go get' should print it all. +go get -d short +stderr '^go: module short is deprecated: short$' +go list -m -u -f '{{.Deprecated}}' short +stdout '^short$' + +# When there is a multi-line message, 'go get' should print the first line. +go get -d multiline +stderr '^go: module multiline is deprecated: first line$' +! stderr 'second line' +go list -m -u -f '{{.Deprecated}}' multiline +stdout '^first line\nsecond line.$' + +# When there is a long message, 'go get' should print a placeholder. +go get -d long +stderr '^go: module long is deprecated: \(message omitted: too long\)$' +go list -m -u -f '{{.Deprecated}}' long +stdout '^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$' + +# When a message contains unprintable chracters, 'go get' should say that +# without printing the message. +go get -d unprintable +stderr '^go: module unprintable is deprecated: \(message omitted: contains non-printable characters\)$' +go list -m -u -f '{{.Deprecated}}' unprintable +stdout '^message contains ASCII BEL\x07$' + +-- go.mod -- +module use + +go 1.16 + +require ( + short v0.0.0 + multiline v0.0.0 + long v0.0.0 + unprintable v0.0.0 +) + +replace ( + short v0.0.0 => ./short + multiline v0.0.0 => ./multiline + long v0.0.0 => ./long + unprintable v0.0.0 => ./unprintable +) +-- short/go.mod -- +// Deprecated: short +module short + +go 1.16 +-- short/short.go -- +package short +-- multiline/go.mod -- +// Deprecated: first line +// second line. +module multiline + +go 1.16 +-- multiline/multiline.go -- +package multiline +-- long/go.mod -- +// Deprecated: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +module long + +go 1.16 +-- long/long.go -- +package long +-- unprintable/go.mod -- +// Deprecated: message contains ASCII BEL +module unprintable + +go 1.16 +-- unprintable/unprintable.go -- +package unprintable diff --git a/src/cmd/go/testdata/script/mod_download.txt b/src/cmd/go/testdata/script/mod_download.txt index 8a9faffe4e009beedf0d080b6643030f880bb6bf..c2b72b2a02cab8f681872c637d1d9990c9131db5 100644 --- a/src/cmd/go/testdata/script/mod_download.txt +++ b/src/cmd/go/testdata/script/mod_download.txt @@ -107,13 +107,28 @@ stderr '^go mod download: skipping argument m that resolves to the main module\n ! go mod download m@latest stderr '^go mod download: m@latest: malformed module path "m": missing dot in first path element$' -# download updates go.mod and populates go.sum +# download without arguments updates go.mod and go.sum after loading the +# build list, but does not save sums for downloaded zips. cd update +cp go.mod.orig go.mod ! exists go.sum go mod download +cmp go.mod.update go.mod +cmp go.sum.update go.sum +cp go.mod.orig go.mod +rm go.sum + +# download with arguments (even "all") does update go.mod and go.sum. +go mod download rsc.io/sampler +cmp go.mod.update go.mod grep '^rsc.io/sampler v1.3.0 ' go.sum -go list -m rsc.io/sampler -stdout '^rsc.io/sampler v1.3.0$' +cp go.mod.orig go.mod +rm go.sum + +go mod download all +cmp go.mod.update go.mod +grep '^rsc.io/sampler v1.3.0 ' go.sum +cd .. # allow go mod download without go.mod env GO111MODULE=auto @@ -131,7 +146,7 @@ stderr 'get '$GOPROXY -- go.mod -- module m --- update/go.mod -- +-- update/go.mod.orig -- module m go 1.16 @@ -140,3 +155,16 @@ require ( rsc.io/quote v1.5.2 rsc.io/sampler v1.2.1 // older version than in build list ) +-- update/go.mod.update -- +module m + +go 1.16 + +require ( + rsc.io/quote v1.5.2 + rsc.io/sampler v1.3.0 // older version than in build list +) +-- update/go.sum.update -- +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/src/cmd/go/testdata/script/mod_edit.txt b/src/cmd/go/testdata/script/mod_edit.txt index 9da69306dacc4032e73c7610770c252732d7178b..5aa5ca1ffc02c520d79eefa46e4d24f3b980dba4 100644 --- a/src/cmd/go/testdata/script/mod_edit.txt +++ b/src/cmd/go/testdata/script/mod_edit.txt @@ -46,6 +46,10 @@ cmpenv stdout $WORK/go.mod.json go mod edit -json $WORK/go.mod.retractrationale cmp stdout $WORK/go.mod.retractrationale.json +# go mod edit -json (deprecation) +go mod edit -json $WORK/go.mod.deprecation +cmp stdout $WORK/go.mod.deprecation.json + # go mod edit -json (empty mod file) go mod edit -json $WORK/go.mod.empty cmp stdout $WORK/go.mod.empty.json @@ -290,6 +294,20 @@ retract ( } ] } +-- $WORK/go.mod.deprecation -- +// Deprecated: and the new one is not ready yet +module m +-- $WORK/go.mod.deprecation.json -- +{ + "Module": { + "Path": "m", + "Deprecated": "and the new one is not ready yet" + }, + "Require": null, + "Exclude": null, + "Replace": null, + "Retract": null +} -- $WORK/go.mod.empty -- -- $WORK/go.mod.empty.json -- { diff --git a/src/cmd/go/testdata/script/mod_edit_no_modcache.txt b/src/cmd/go/testdata/script/mod_edit_no_modcache.txt new file mode 100644 index 0000000000000000000000000000000000000000..ced15bb3018aa2e9e71eab690f6de7e2a4af349d --- /dev/null +++ b/src/cmd/go/testdata/script/mod_edit_no_modcache.txt @@ -0,0 +1,15 @@ +# 'go mod edit' opportunistically locks the side-lock file in the module cache, +# for compatibility with older versions of the 'go' command. +# It does not otherwise depend on the module cache, so it should not +# fail if the module cache directory cannot be created. + +[root] skip + +mkdir $WORK/readonly +chmod 0555 $WORK/readonly +env GOPATH=$WORK/readonly/nonexist + +go mod edit -go=1.17 + +-- go.mod -- +module example.com/m diff --git a/src/cmd/go/testdata/script/mod_empty_err.txt b/src/cmd/go/testdata/script/mod_empty_err.txt index 982e6b2e518d9670fff09fb896f02acdc065dce5..c4359bccccf49cffd05862ee3dc3b41b60c26222 100644 --- a/src/cmd/go/testdata/script/mod_empty_err.txt +++ b/src/cmd/go/testdata/script/mod_empty_err.txt @@ -1,4 +1,4 @@ -# This test checks error messages for non-existant packages in module mode. +# This test checks error messages for non-existent packages in module mode. # Veries golang.org/issue/35414 env GO111MODULE=on cd $WORK diff --git a/src/cmd/go/testdata/script/mod_get_deprecate_install.txt b/src/cmd/go/testdata/script/mod_get_deprecate_install.txt new file mode 100644 index 0000000000000000000000000000000000000000..63cd27a42d2bc76911d0e36f589adb4f97259f22 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_get_deprecate_install.txt @@ -0,0 +1,39 @@ +[short] skip + +env GO111MODULE=on + +# 'go get' outside a module with an executable prints a deprecation message. +go get example.com/cmd/a +stderr '^go get: installing executables with ''go get'' in module mode is deprecated.$' +stderr 'Use ''go install pkg@version'' instead.' + +cp go.mod.orig go.mod + +# 'go get' inside a module with a non-main package does not print a message. +# This will stop building in the future, but it's the command we want to use. +go get rsc.io/quote +! stderr deprecated +cp go.mod.orig go.mod + +# 'go get' inside a module with an executable prints a different +# deprecation message. +go get example.com/cmd/a +stderr '^go get: installing executables with ''go get'' in module mode is deprecated.$' +stderr 'To adjust and download dependencies of the current module, use ''go get -d''' +cp go.mod.orig go.mod + +# 'go get' should not print a warning for a main package inside the main module. +# The intent is most likely to update the dependencies of that package. +# 'go install' would be used otherwise. +go get m +! stderr . +cp go.mod.orig go.mod + +-- go.mod.orig -- +module m + +go 1.17 +-- main.go -- +package main + +func main() {} diff --git a/src/cmd/go/testdata/script/mod_get_deprecated.txt b/src/cmd/go/testdata/script/mod_get_deprecated.txt new file mode 100644 index 0000000000000000000000000000000000000000..7bdd7a58a89b5d15c43520055daf3eb7b4c1d8f8 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_get_deprecated.txt @@ -0,0 +1,66 @@ +# 'go get pkg' should not show a deprecation message for an unrelated module. +go get -d ./use/nothing +! stderr 'module.*is deprecated' + +# 'go get pkg' should show a deprecation message for the module providing pkg. +go get -d example.com/deprecated/a +stderr '^go: module example.com/deprecated/a is deprecated: in example.com/deprecated/a@v1.9.0$' +go get -d example.com/deprecated/a@v1.0.0 +stderr '^go: module example.com/deprecated/a is deprecated: in example.com/deprecated/a@v1.9.0$' + +# 'go get pkg' should show a deprecation message for a module providing +# packages directly imported by pkg. +go get -d ./use/a +stderr '^go: module example.com/deprecated/a is deprecated: in example.com/deprecated/a@v1.9.0$' + +# 'go get pkg' may show a deprecation message for an indirectly required module +# if it provides a package named on the command line. +go get -d ./use/b +! stderr 'module.*is deprecated' +go get -d local/use +! stderr 'module.*is deprecated' +go get -d example.com/deprecated/b +stderr '^go: module example.com/deprecated/b is deprecated: in example.com/deprecated/b@v1.9.0$' + +# 'go get pkg' does not show a deprecation message for a module providing a +# directly imported package if the module is no longer deprecated in its +# latest version, even if the module is deprecated in its current version. +go get -d ./use/undeprecated +! stderr 'module.*is deprecated' + +-- go.mod -- +module m + +go 1.17 + +require ( + example.com/deprecated/a v1.0.0 + example.com/undeprecated v1.0.0 + local v0.0.0 +) + +replace local v0.0.0 => ./local +-- use/nothing/nothing.go -- +package nothing +-- use/a/a.go -- +package a + +import _ "example.com/deprecated/a" +-- use/b/b.go -- +package b + +import _ "local/use" +-- use/undeprecated/undeprecated.go -- +package undeprecated + +import _ "example.com/undeprecated" +-- local/go.mod -- +module local + +go 1.17 + +require example.com/deprecated/b v1.0.0 +-- local/use/use.go -- +package use + +import _ "example.com/deprecated/b" diff --git a/src/cmd/go/testdata/script/mod_get_downadd_indirect.txt b/src/cmd/go/testdata/script/mod_get_downadd_indirect.txt new file mode 100644 index 0000000000000000000000000000000000000000..efc38f77c85d37b6d94840acc771bc06c2febf9e --- /dev/null +++ b/src/cmd/go/testdata/script/mod_get_downadd_indirect.txt @@ -0,0 +1,81 @@ +# This test illustrates a case where downgrading one module may upgrade another. +# Compare to the downcross2 test case in cmd/go/internal/mvs/mvs_test.go. + +# The initial package import graph used in this test looks like: +# +# a ---- b ---- d +# +# The module dependency graph originally looks like: +# +# a ---- b.2 ---- d.2 +# +# b.1 ---- c.1 +# +# If we downgrade module d to version 1, we must downgrade b as well. +# If that downgrade selects b version 1, we will add a new dependency on module c. + +cp go.mod go.mod.orig +go mod tidy +cmp go.mod.orig go.mod + +go get -d example.com/d@v0.1.0 +go list -m all +stdout '^example.com/b v0.1.0 ' +stdout '^example.com/c v0.1.0 ' +stdout '^example.com/d v0.1.0 ' + +-- go.mod -- +module example.com/a + +go 1.15 + +require example.com/b v0.2.0 + +replace ( + example.com/b v0.1.0 => ./b1 + example.com/b v0.2.0 => ./b2 + example.com/c v0.1.0 => ./c + example.com/d v0.1.0 => ./d + example.com/d v0.2.0 => ./d +) +-- a.go -- +package a + +import _ "example.com/b" + +-- b1/go.mod -- +module example.com/b + +go 1.15 + +require example.com/c v0.1.0 +-- b1/b.go -- +package b + +import _ "example.com/c" + +-- b2/go.mod -- +module example.com/b + +go 1.15 + +require example.com/d v0.2.0 +-- b2/b.go -- +package b + +import _ "example.com/d" + +-- c/go.mod -- +module example.com/c + +go 1.15 + +-- c/c.go -- +package c + +-- d/go.mod -- +module example.com/d + +go 1.15 +-- d/d.go -- +package d diff --git a/src/cmd/go/testdata/script/mod_get_downgrade.txt b/src/cmd/go/testdata/script/mod_get_downgrade.txt index a954c10344bbafd33266e59c8efb4bb0a2115db3..c26c5e1c21013ada363965a4b768d36d921c6552 100644 --- a/src/cmd/go/testdata/script/mod_get_downgrade.txt +++ b/src/cmd/go/testdata/script/mod_get_downgrade.txt @@ -20,8 +20,8 @@ stdout 'rsc.io/quote v1.5.1' stdout 'rsc.io/sampler v1.3.0' ! go get -d rsc.io/sampler@v1.0.0 rsc.io/quote@v1.5.2 golang.org/x/text@none +stderr -count=1 '^go get:' stderr '^go get: rsc.io/quote@v1.5.2 requires rsc.io/sampler@v1.3.0, not rsc.io/sampler@v1.0.0$' -stderr '^go get: rsc.io/quote@v1.5.2 requires golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c, not golang.org/x/text@none$' go list -m all stdout 'rsc.io/quote v1.5.1' diff --git a/src/cmd/go/testdata/script/mod_get_downup_artifact.txt b/src/cmd/go/testdata/script/mod_get_downup_artifact.txt new file mode 100644 index 0000000000000000000000000000000000000000..c20583b22a136822effc89c7844217ab4ea8c8e4 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_get_downup_artifact.txt @@ -0,0 +1,159 @@ +# This test illustrates a case where an upgrade–downgrade–upgrade cycle can +# result in upgrades of otherwise-irrelevant dependencies. +# +# This case has no corresponding test in the mvs package, because it is an +# artifact that results from the composition of *multiple* MVS operations. + +# The initial package import graph used in the test looks like: +# +# m ---- a +# | | +# +----- b +# | | +# +----- c +# | +# +----- d +# +# b version 2 adds its own import of package d. +# +# The module dependency graph initially looks like: +# +# m ---- a.1 +# | | +# +----- b.1 +# | | +# +----- c.1 +# | +# +----- d.1 +# +# b.2 ---- c.2 +# | +# +------ d.2 +# | +# +------ e.1 +# +# If we upgrade module b to version 2, we will upgrade c and d and add a new +# dependency on e. If b version 2 is disallowed because of any of those +# dependencies, the other dependencies should not be upgraded as a side-effect. + +cp go.mod go.mod.orig +go mod tidy +cmp go.mod go.mod.orig + +go list -m all +stdout '^example.com/a v0.1.0 ' +stdout '^example.com/b v0.1.0 ' +stdout '^example.com/c v0.1.0 ' +stdout '^example.com/d v0.1.0 ' +! stdout '^example.com/e ' + +# b is imported by a, so the -u flag would normally upgrade it to v0.2.0. +# However, that would conflict with the explicit c@v0.1.0 constraint, +# so b must remain at v0.1.0. +# +# If we're not careful, we might temporarily add b@v0.2.0 and pull in its +# upgrades of module d and addition of module e, which are not relevant to +# b@v0.1.0 and should not be added to the main module's dependencies. + +go get -u -d example.com/a@latest example.com/c@v0.1.0 + +go list -m all +stdout '^example.com/a v0.1.0 ' +stdout '^example.com/b v0.1.0 ' +stdout '^example.com/c v0.1.0 ' +stdout '^example.com/d v0.1.0 ' +! stdout '^example.com/e ' + +-- go.mod -- +module example.com/m + +go 1.16 + +require ( + example.com/a v0.1.0 + example.com/b v0.1.0 + example.com/c v0.1.0 + example.com/d v0.1.0 +) + +replace ( + example.com/a v0.1.0 => ./a1 + example.com/b v0.1.0 => ./b1 + example.com/b v0.2.0 => ./b2 + example.com/c v0.1.0 => ./c + example.com/c v0.2.0 => ./c + example.com/d v0.1.0 => ./d + example.com/d v0.2.0 => ./d + example.com/e v0.1.0 => ./e +) +-- m.go -- +package m + +import ( + _ "example.com/a" + _ "example.com/b" + _ "example.com/c" + _ "example.com/d" +) + +-- a1/go.mod -- +module example.com/a + +go 1.16 + +require example.com/b v0.1.0 +-- a1/a.go -- +package a + +import _ "example.com/b" + +-- b1/go.mod -- +module example.com/b + +go 1.16 + +require example.com/c v0.1.0 +-- b1/b.go -- +package b + +import _ "example.com/c" + +-- b2/go.mod -- +module example.com/b + +go 1.16 + +require ( + example.com/c v0.2.0 + example.com/d v0.2.0 + example.com/e v0.1.0 +) +-- b2/b.go -- +package b + +import ( + "example.com/c" + "example.com/d" + "example.com/e" +) + +-- c/go.mod -- +module example.com/c + +go 1.16 +-- c/c.go -- +package c + +-- d/go.mod -- +module example.com/d + +go 1.16 +-- d/d.go -- +package d + +-- e/go.mod -- +module example.com/e + +go 1.16 +-- e/e.go -- +package e diff --git a/src/cmd/go/testdata/script/mod_get_downup_indirect.txt b/src/cmd/go/testdata/script/mod_get_downup_indirect.txt new file mode 100644 index 0000000000000000000000000000000000000000..ced1dcd6b14e37a528f1a9e494c3e93a6acbf804 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_get_downup_indirect.txt @@ -0,0 +1,101 @@ +# This test illustrates a case where downgrading one module may upgrade another. +# Compare to the downcross1 test case in cmd/go/internal/mvs/mvs_test.go. + +# The package import graph used in this test looks like: +# +# a ---- b +# \ \ +# \ \ +# ----- c ---- d +# +# The module dependency graph originally looks like: +# +# a ---- b.2 +# \ \ +# \ \ +# ----- c.1 ---- d.2 +# +# b.1 ---- c.2 +# +# If we downgrade module d to version 1, we must downgrade b as well. +# If that downgrade selects b version 1, we will upgrade module c to version 2. +# So 'go get d@1' should instead downgrade both b and c to "none". + +cp go.mod go.mod.orig +go mod tidy +cmp go.mod.orig go.mod + +go get -d example.com/d@v0.1.0 +go list -m all +! stdout '^example.com/b ' +! stdout '^example.com/c ' +stdout '^example.com/d v0.1.0 ' + +-- go.mod -- +module example.com/a + +go 1.15 + +require ( + example.com/b v0.2.0 + example.com/c v0.1.0 +) + +replace ( + example.com/b v0.1.0 => ./b1 + example.com/b v0.2.0 => ./b2 + example.com/c v0.1.0 => ./c1 + example.com/c v0.2.0 => ./c2 + example.com/d v0.1.0 => ./d + example.com/d v0.2.0 => ./d +) +-- a.go -- +package a + +import ( + _ "example.com/b" + _ "example.com/c" +) + +-- b1/go.mod -- +module example.com/b + +go 1.15 + +require example.com/c v0.2.0 +-- b1/b.go -- +package b + +import _ "example.com/c" + +-- b2/go.mod -- +module example.com/b + +go 1.15 + +require example.com/c v0.1.0 +-- b2/b.go -- +package b + +import _ "example.com/c" + +-- c1/go.mod -- +module example.com/c + +go 1.15 + +require example.com/d v0.2.0 +-- c1/c.go -- +package c + +-- c2/go.mod -- +module example.com/c + +go 1.15 +-- c2/c.go -- +package c + +-- d/go.mod -- +module example.com/d + +go 1.15 diff --git a/src/cmd/go/testdata/script/mod_get_downup_pseudo_artifact.txt b/src/cmd/go/testdata/script/mod_get_downup_pseudo_artifact.txt new file mode 100644 index 0000000000000000000000000000000000000000..c49615cecb34d22ee114a974af6ecc2469814f4e --- /dev/null +++ b/src/cmd/go/testdata/script/mod_get_downup_pseudo_artifact.txt @@ -0,0 +1,129 @@ +# This test illustrates a case where an upgrade–downgrade–upgrade cycle could +# add extraneous dependencies due to another module depending on an +# otherwise-unlisted version (such as a pseudo-version). +# +# This case corresponds to the "downhiddenartifact" test in the mvs package. + +# The initial package import graph used in the test looks like: +# +# a --- b +# \ \ +# \ \ +# c --- d +# +# The module dependency graph initially looks like: +# +# a --- b.3 +# \ \ +# \ \ +# c.2 --- d.2 +# +# c.1 --- b.2 (pseudo) +# +# b.1 --- e.1 + +cp go.mod go.mod.orig +go mod tidy +cmp go.mod.orig go.mod + +# When we downgrade d.2 to d.1, no dependency on e should be added +# because nothing else in the module or import graph requires it. +go get -d example.net/d@v0.1.0 + +go list -m all +stdout '^example.net/b v0.2.1-0.20210219000000-000000000000 ' +stdout '^example.net/c v0.1.0 ' +stdout '^example.net/d v0.1.0 ' +! stdout '^example.net/e ' + +-- go.mod -- +module example.net/a + +go 1.16 + +require ( + example.net/b v0.3.0 + example.net/c v0.2.0 +) + +replace ( + example.net/b v0.1.0 => ./b1 + example.net/b v0.2.1-0.20210219000000-000000000000 => ./b2 + example.net/b v0.3.0 => ./b3 + example.net/c v0.1.0 => ./c1 + example.net/c v0.2.0 => ./c2 + example.net/d v0.1.0 => ./d + example.net/d v0.2.0 => ./d + example.net/e v0.1.0 => ./e +) +-- a.go -- +package a + +import ( + _ "example.net/b" + _ "example.net/c" +) + +-- b1/go.mod -- +module example.net/b + +go 1.16 + +require example.net/e v0.1.0 +-- b1/b.go -- +package b + +import _ "example.net/e" + +-- b2/go.mod -- +module example.net/b + +go 1.16 +-- b2/b.go -- +package b + +-- b3/go.mod -- +module example.net/b + +go 1.16 + +require example.net/d v0.2.0 +-- b3/b.go -- +package b + +import _ "example.net/d" +-- c1/go.mod -- +module example.net/c + +go 1.16 + +require example.net/b v0.2.1-0.20210219000000-000000000000 +-- c1/c.go -- +package c + +import _ "example.net/b" + +-- c2/go.mod -- +module example.net/c + +go 1.16 + +require example.net/d v0.2.0 +-- c2/c.go -- +package c + +import _ "example.net/d" + +-- d/go.mod -- +module example.net/d + +go 1.16 +-- d/d.go -- +package d + +-- e/go.mod -- +module example.net/e + +go 1.16 +-- e/e.go -- +package e diff --git a/src/cmd/go/testdata/script/mod_get_insecure_redirect.txt b/src/cmd/go/testdata/script/mod_get_insecure_redirect.txt index 3755f1763321e66a41cc0b04dcc6b66296404dd7..2e1283449554072c43509f293f8e64c11221e30d 100644 --- a/src/cmd/go/testdata/script/mod_get_insecure_redirect.txt +++ b/src/cmd/go/testdata/script/mod_get_insecure_redirect.txt @@ -1,4 +1,4 @@ -# golang.org/issue/29591: 'go get' was following plain-HTTP redirects even without -insecure. +# golang.org/issue/29591: 'go get' was following plain-HTTP redirects even without -insecure (now replaced by GOINSECURE). [!net] skip [!exec:git] skip @@ -10,8 +10,6 @@ env GOSUMDB=off ! go get -d vcs-test.golang.org/insecure/go/insecure stderr 'redirected .* to insecure URL' -go get -d -insecure vcs-test.golang.org/insecure/go/insecure - # insecure host env GOINSECURE=vcs-test.golang.org go clean -modcache diff --git a/src/cmd/go/testdata/script/mod_get_lazy_indirect.txt b/src/cmd/go/testdata/script/mod_get_lazy_indirect.txt new file mode 100644 index 0000000000000000000000000000000000000000..1cef9d1c0cf1e21bd8b5875c3940827573618190 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_get_lazy_indirect.txt @@ -0,0 +1,44 @@ +# https://golang.org/issue/45979: after 'go get' on a package, +# that package should be importable without error. + + +# We start out with an unresolved dependency. +# 'go list' suggests that we run 'go get' on that dependency. + +! go list -deps . +stderr '^m.go:3:8: no required module provides package rsc\.io/quote; to add it:\n\tgo get rsc.io/quote$' + + +# When we run the suggested 'go get' command, the new dependency can be used +# immediately. +# +# 'go get' marks the new dependency as 'indirect', because it doesn't scan +# enough source code to know whether it is direct, and it is easier and less +# invasive to remove an incorrect indirect mark (e.g. using 'go get') than to +# add one that is missing ('go mod tidy' or 'go mod vendor'). + +go get rsc.io/quote +grep 'rsc.io/quote v\d+\.\d+\.\d+ // indirect$' go.mod +! grep 'rsc.io/quote v\d+\.\d+\.\d+$' go.mod + +go list -deps . +! stderr . +[!short] go build . +[!short] ! stderr . + + +# 'go get .' (or 'go mod tidy') removes the indirect mark. + +go get . +grep 'rsc.io/quote v\d+\.\d+\.\d+$' go.mod +! grep 'rsc.io/quote v\d+\.\d+\.\d+ // indirect$' go.mod + + +-- go.mod -- +module example.com/m + +go 1.17 +-- m.go -- +package m + +import _ "rsc.io/quote" diff --git a/src/cmd/go/testdata/script/mod_get_missing_ziphash.txt b/src/cmd/go/testdata/script/mod_get_missing_ziphash.txt index 8f6793edf58678abc2bdf121e5b62eff42ddb495..789d42d24dbca7184ff3572e50162f448acc5ab4 100644 --- a/src/cmd/go/testdata/script/mod_get_missing_ziphash.txt +++ b/src/cmd/go/testdata/script/mod_get_missing_ziphash.txt @@ -29,7 +29,7 @@ go build -n use -- go.mod -- module use -go 1.17 +go 1.16 require rsc.io/quote v1.5.2 -- go.sum.tidy -- diff --git a/src/cmd/go/testdata/script/mod_get_pkgtags.txt b/src/cmd/go/testdata/script/mod_get_pkgtags.txt index c0a57f3fab2dfd70bc972e04fd42c979f7843622..0c79ec71b7b41d628d6a412e34816399d4599007 100644 --- a/src/cmd/go/testdata/script/mod_get_pkgtags.txt +++ b/src/cmd/go/testdata/script/mod_get_pkgtags.txt @@ -16,6 +16,7 @@ go mod edit -droprequire example.net/tools # error out if dependencies of tag-guarded files are missing. go get -d example.net/tools@v0.1.0 +! stderr 'no Go source files' ! go list example.net/tools stderr '^package example.net/tools: build constraints exclude all Go files in .*[/\\]tools$' @@ -30,6 +31,19 @@ go list -deps example.net/cmd/tool stderr '^no required module provides package example.net/missing; to add it:\n\tgo get example.net/missing$' +# https://golang.org/issue/33526: 'go get' without '-d' should succeed +# for a module whose root is a constrained-out package. +# +# Ideally it should silently succeed, but today it logs the "no Go source files" +# error and succeeds anyway. + +go get example.net/tools@v0.1.0 +! stderr . + +! go build example.net/tools +stderr '^package example.net/tools: build constraints exclude all Go files in .*[/\\]tools$' + + # https://golang.org/issue/29268 # 'go get' should fetch modules whose roots contain test-only packages, but # without the -t flag shouldn't error out if the test has missing dependencies. diff --git a/src/cmd/go/testdata/script/mod_get_private_vcs.txt b/src/cmd/go/testdata/script/mod_get_private_vcs.txt index 514b0a7a5314df19b2b8d0d25610858a1d40f0c2..75c776a7fa29c78d1856d381fa2a938b15348034 100644 --- a/src/cmd/go/testdata/script/mod_get_private_vcs.txt +++ b/src/cmd/go/testdata/script/mod_get_private_vcs.txt @@ -9,3 +9,35 @@ env GOPROXY=direct stderr 'Confirm the import path was entered correctly.' stderr 'If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.' ! stdout . + +# Fetching a nonexistent commit should return an "unknown revision" +# error message. +! go get github.com/golang/term@86186f3aba07ed0212cfb944f3398997d2d07c6b +stderr '^go get: github.com/golang/term@86186f3aba07ed0212cfb944f3398997d2d07c6b: invalid version: unknown revision 86186f3aba07ed0212cfb944f3398997d2d07c6b$' +! stdout . + +! go get github.com/golang/nonexist@master +stderr '^Confirm the import path was entered correctly.$' +stderr '^If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.$' +! stderr 'unknown revision' +! stdout . + +[!linux] stop # Needs XDG_CONFIG_HOME. +[!exec:false] stop + +# Test that Git clone errors will be shown to the user instead of a generic +# "unknown revision" error. To do this we want to force git ls-remote to return +# an error we don't already have special handling for. See golang/go#42751. +# +# Set XDG_CONFIG_HOME to tell Git where to look for the git config file listed +# below, which turns on ssh. +env XDG_CONFIG_HOME=$TMPDIR +env GIT_SSH_COMMAND=false +! go install github.com/golang/nonexist@master +stderr 'fatal: Could not read from remote repository.' +! stderr 'unknown revision' +! stdout . + +-- $TMPDIR/git/config -- +[url "git@github.com:"] + insteadOf = https://github.com/ diff --git a/src/cmd/go/testdata/script/mod_get_promote_implicit.txt b/src/cmd/go/testdata/script/mod_get_promote_implicit.txt index 10ca6594e4c6e33e8abd5c1b38624d50e04b9ff7..9eec2013210350fdd7b4234eeea6160abaa70ec7 100644 --- a/src/cmd/go/testdata/script/mod_get_promote_implicit.txt +++ b/src/cmd/go/testdata/script/mod_get_promote_implicit.txt @@ -6,7 +6,7 @@ cp go.mod.orig go.mod go list -m indirect-with-pkg stdout '^indirect-with-pkg v1.0.0 => ./indirect-with-pkg$' ! go list ./use-indirect -stderr '^go: m/use-indirect: package indirect-with-pkg imported from implicitly required module; to add missing requirements, run:\n\tgo get indirect-with-pkg@v1.0.0$' +stderr '^package m/use-indirect imports indirect-with-pkg from implicitly required module; to add missing requirements, run:\n\tgo get indirect-with-pkg@v1.0.0$' # We can promote the implicit requirement by getting the importing package. # NOTE: the hint recommends getting the imported package (tested below) since diff --git a/src/cmd/go/testdata/script/mod_get_trailing_slash.txt b/src/cmd/go/testdata/script/mod_get_trailing_slash.txt index 3b38d8ba7d346d926b2cb9058ab9d4f5b4b0fbb0..c53669353794ade0e4eb2039d0f91f48a89ad1d2 100644 --- a/src/cmd/go/testdata/script/mod_get_trailing_slash.txt +++ b/src/cmd/go/testdata/script/mod_get_trailing_slash.txt @@ -1,6 +1,3 @@ -# Populate go.sum -go mod download - # go list should succeed to load a package ending with ".go" if the path does # not correspond to an existing local file. Listing a pattern ending with # ".go/" should try to list a package regardless of whether a file exists at the @@ -31,3 +28,10 @@ module m go 1.13 require example.com/dotgo.go v1.0.0 +-- go.sum -- +example.com/dotgo.go v1.0.0 h1:XKJfs0V8x2PvY2tX8bJBCEbCDLnt15ma2onwhVpew/I= +example.com/dotgo.go v1.0.0/go.mod h1:Qi6z/X3AC5vHiuMt6HF2ICx3KhIBGrMdrA7YoPDKqR0= +-- use.go -- +package use + +import _ "example.com/dotgo.go" diff --git a/src/cmd/go/testdata/script/mod_get_update_unrelated_sum.txt b/src/cmd/go/testdata/script/mod_get_update_unrelated_sum.txt new file mode 100644 index 0000000000000000000000000000000000000000..0093c0eda0c8270eabe147901828f994f88122af --- /dev/null +++ b/src/cmd/go/testdata/script/mod_get_update_unrelated_sum.txt @@ -0,0 +1,120 @@ +# Check that 'go get' adds sums for updated modules if we had sums before, +# even if we didn't load packages from them. +# Verifies #44129. + +env fmt='{{.ImportPath}}: {{if .Error}}{{.Error.Err}}{{else}}ok{{end}}' + +# Control case: before upgrading, we have the sums we need. +# go list -deps -e -f $fmt . +# stdout '^rsc.io/quote: ok$' +# ! stdout rsc.io/sampler # not imported by quote in this version +cp go.mod.orig go.mod +cp go.sum.orig go.sum +go mod tidy +cmp go.mod.orig go.mod +cmp go.sum.orig go.sum + + +# Upgrade a module. This also upgrades rsc.io/quote, and though we didn't load +# a package from it, we had the sum for its old version, so we need the +# sum for the new version, too. +go get -d example.com/upgrade@v0.0.2 +grep '^rsc.io/quote v1.5.2 ' go.sum + +# The upgrade still breaks the build because the new version of quote imports +# rsc.io/sampler, and we don't have its zip sum. +go list -deps -e -f $fmt +stdout 'rsc.io/quote: ok' +stdout 'rsc.io/sampler: missing go.sum entry for module providing package rsc.io/sampler' +cp go.mod.orig go.mod +cp go.sum.orig go.sum + + +# Replace the old version with a directory before upgrading. +# We didn't need a sum for it before (even though we had one), so we won't +# fetch a new sum. +go mod edit -replace rsc.io/quote@v1.0.0=./dummy +go get -d example.com/upgrade@v0.0.2 +! grep '^rsc.io/quote v1.5.2 ' go.sum +cp go.mod.orig go.mod +cp go.sum.orig go.sum + + +# Replace the new version with a directory before upgrading. +# We can't get a sum for a directory. +go mod edit -replace rsc.io/quote@v1.5.2=./dummy +go get -d example.com/upgrade@v0.0.2 +! grep '^rsc.io/quote v1.5.2 ' go.sum +cp go.mod.orig go.mod +cp go.sum.orig go.sum + + +# Replace the new version with a different version. +# We should get a sum for that version. +go mod edit -replace rsc.io/quote@v1.5.2=rsc.io/quote@v1.5.1 +go get -d example.com/upgrade@v0.0.2 +! grep '^rsc.io/quote v1.5.2 ' go.sum +grep '^rsc.io/quote v1.5.1 ' go.sum +cp go.mod.orig go.mod +cp go.sum.orig go.sum + + +# Delete the new version's zip (but not mod) from the cache and go offline. +# 'go get' should fail when fetching the zip. +rm $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.zip +env GOPROXY=off +! go get -d example.com/upgrade@v0.0.2 +stderr '^go: upgraded rsc.io/quote v1.0.0 => v1.5.2: error finding sum for rsc.io/quote@v1.5.2: module lookup disabled by GOPROXY=off$' + +-- go.mod.orig -- +module m + +go 1.16 + +require ( + example.com/upgrade v0.0.1 + rsc.io/quote v1.0.0 +) + +replace ( + example.com/upgrade v0.0.1 => ./upgrade1 + example.com/upgrade v0.0.2 => ./upgrade2 +) +-- go.sum.orig -- +rsc.io/quote v1.0.0 h1:kQ3IZQzPTiDJxSZI98YaWgxFEhlNdYASHvh+MplbViw= +rsc.io/quote v1.0.0/go.mod h1:v83Ri/njykPcgJltBc/gEkJTmjTsNgtO1Y7vyIK1CQA= +-- go.sum.want -- +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +rsc.io/quote v1.0.0 h1:kQ3IZQzPTiDJxSZI98YaWgxFEhlNdYASHvh+MplbViw= +rsc.io/quote v1.0.0/go.mod h1:v83Ri/njykPcgJltBc/gEkJTmjTsNgtO1Y7vyIK1CQA= +rsc.io/quote v1.5.2 h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0= +rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +-- use.go -- +package use + +import ( + _ "example.com/upgrade" + _ "rsc.io/quote" +) +-- upgrade1/go.mod -- +module example.com/upgrade + +go 1.16 +-- upgrade1/upgrade.go -- +package upgrade +-- upgrade2/go.mod -- +module example.com/upgrade + +go 1.16 + +require rsc.io/quote v1.5.2 // indirect +-- upgrade2/upgrade.go -- +package upgrade +-- dummy/go.mod -- +module rsc.io/quote + +go 1.16 +-- dummy/quote.go -- +package quote + diff --git a/src/cmd/go/testdata/script/mod_go_version_missing.txt b/src/cmd/go/testdata/script/mod_go_version_missing.txt new file mode 100644 index 0000000000000000000000000000000000000000..d704816729b8c30e64bb64fd04e234ab3e829a5c --- /dev/null +++ b/src/cmd/go/testdata/script/mod_go_version_missing.txt @@ -0,0 +1,122 @@ +cp go.mod go.mod.orig + +# For modules whose go.mod file does not include a 'go' directive, +# we assume the language and dependency semantics of Go 1.16, +# but do not trigger “automatic vendoring” mode (-mod=vendor), +# which was added in Go 1.14 and was not triggered +# under the same conditions in Go 1.16 (which would instead +# default to -mod=readonly when no 'go' directive is present). + +# For Go 1.16 modules, 'all' should prune out dependencies of tests, +# even if the 'go' directive is missing. + +go list -mod=readonly all +stdout '^example.com/dep$' +! stdout '^example.com/testdep$' +cp stdout list-1.txt +cmp go.mod go.mod.orig + +# We should only default to -mod=vendor if the 'go' directive is explicit in the +# go.mod file. Otherwise, we don't actually know whether the module was written +# against Go 1.11 or 1.16. We would have to update the go.mod file to clarify, +# and as of Go 1.16 we don't update the go.mod file by default. +# +# If we set -mod=vendor explicitly, we shouldn't apply the Go 1.14 +# consistency check, because — again — we don't know whether we're in a 1.11 +# module or a bad-script-edited 1.16 module. + +! go list -mod=vendor all +! stderr '^go: inconsistent vendoring' +stderr 'cannot find package "\." in:\n\t.*[/\\]vendor[/\\]example.com[/\\]badedit$' + +# When we set -mod=mod, the go version should be updated immediately, +# to the current version, converting the requirements from eager to lazy. +# +# Since we don't know which requirements are actually relevant to the main +# module, all requirements are added as roots, making the requirements untidy. + +go list -mod=mod all +! stdout '^example.com/testdep$' +cmp stdout list-1.txt +cmpenv go.mod go.mod.untidy + +go mod tidy +cmpenv go.mod go.mod.tidy + +# On the other hand, if we jump straight to 'go mod tidy', +# the requirements remain tidy from the start. + +cp go.mod.orig go.mod +go mod tidy +cmpenv go.mod go.mod.tidy + + +# The updated version should have been written back to go.mod, so now the 'go' +# directive is explicit. -mod=vendor should trigger by default, and the stronger +# Go 1.14 consistency check should apply. +! go list all +stderr '^go: inconsistent vendoring' +! stderr badedit + + +-- go.mod -- +module example.com/m + +require example.com/dep v0.1.0 + +replace ( + example.com/dep v0.1.0 => ./dep + example.com/testdep v0.1.0 => ./testdep +) +-- go.mod.untidy -- +module example.com/m + +go $goversion + +require example.com/dep v0.1.0 + +require example.com/testdep v0.1.0 // indirect + +replace ( + example.com/dep v0.1.0 => ./dep + example.com/testdep v0.1.0 => ./testdep +) +-- go.mod.tidy -- +module example.com/m + +go $goversion + +require example.com/dep v0.1.0 + +replace ( + example.com/dep v0.1.0 => ./dep + example.com/testdep v0.1.0 => ./testdep +) +-- vendor/example.com/dep/dep.go -- +package dep +import _ "example.com/badedit" +-- vendor/modules.txt -- +HAHAHA this is broken. + +-- m.go -- +package m + +import _ "example.com/dep" + +const x = 1_000 + +-- dep/go.mod -- +module example.com/dep + +require example.com/testdep v0.1.0 +-- dep/dep.go -- +package dep +-- dep/dep_test.go -- +package dep_test + +import _ "example.com/testdep" + +-- testdep/go.mod -- +module example.com/testdep +-- testdep/testdep.go -- +package testdep diff --git a/src/cmd/go/testdata/script/mod_gomodcache.txt b/src/cmd/go/testdata/script/mod_gomodcache.txt index b2143e2093989719a2d000b12c415b5030a6c97e..74a3c79622f1f5ff916edd64cb39facd386edee4 100644 --- a/src/cmd/go/testdata/script/mod_gomodcache.txt +++ b/src/cmd/go/testdata/script/mod_gomodcache.txt @@ -47,6 +47,11 @@ env GOMODCACHE=$WORK/modcache go mod download rsc.io/quote@v1.0.0 exists $WORK/modcache/cache/download/rsc.io/quote/@v/v1.0.0.info +# Test error when cannot create GOMODCACHE directory +env GOMODCACHE=$WORK/modcachefile +! go install example.com/cmd/a@v1.0.0 +stderr 'go: could not create module cache' + # Test that the following work even with GO111MODULE=off env GO111MODULE=off @@ -58,3 +63,5 @@ go clean -modcache -- go.mod -- module m + +-- $WORK/modcachefile -- diff --git a/src/cmd/go/testdata/script/mod_graph_version.txt b/src/cmd/go/testdata/script/mod_graph_version.txt new file mode 100644 index 0000000000000000000000000000000000000000..f9a73f4617b34366cb2ffd146ad969614034f641 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_graph_version.txt @@ -0,0 +1,101 @@ +# For this module, Go 1.17 prunes out a (transitive and otherwise-irrelevant) +# requirement on a retracted higher version of a dependency. +# However, when Go 1.16 reads the same requirements from the go.mod file, +# it does not prune out that requirement, and selects the retracted version. +# +# The Go 1.16 module graph looks like: +# +# m ---- lazy v0.1.0 ---- requireincompatible v0.1.0 ---- incompatible v2.0.0+incompatible +# | | +# + -------+------------- incompatible v1.0.0 +# +# The Go 1.17 module graph is the same except that the dependencies of +# requireincompatible are pruned out (because the module that requires +# it — lazy v0.1.0 — specifies 'go 1.17', and it is not otherwise relevant to +# the main module). + +cp go.mod go.mod.orig + +go mod graph +cp stdout graph-1.17.txt +stdout '^example\.com/m example\.com/retract/incompatible@v1\.0\.0$' +stdout '^example\.net/lazy@v0\.1\.0 example\.com/retract/incompatible@v1\.0\.0$' +! stdout 'example\.com/retract/incompatible@v2\.0\.0\+incompatible' + +go mod graph -go=1.17 +cmp stdout graph-1.17.txt + +cmp go.mod go.mod.orig + + +# Setting -go=1.16 should report the graph as viewed by Go 1.16, +# but should not edit the go.mod file. + +go mod graph -go=1.16 +cp stdout graph-1.16.txt +stdout '^example\.com/m example\.com/retract/incompatible@v1\.0\.0$' +stdout '^example\.net/lazy@v0\.1\.0 example.com/retract/incompatible@v1\.0\.0$' +stdout '^example.net/requireincompatible@v0.1.0 example.com/retract/incompatible@v2\.0\.0\+incompatible$' + +cmp go.mod go.mod.orig + + +# If we actually update the go.mod file to the requested go version, +# we should get the same selected versions, but the roots of the graph +# may be updated. +# +# TODO(#45551): The roots should not be updated. + +go mod edit -go=1.16 +go mod graph +! stdout '^example\.com/m example\.com/retract/incompatible@v1\.0\.0$' +stdout '^example\.net/lazy@v0.1.0 example.com/retract/incompatible@v1\.0\.0$' +stdout '^example.net/requireincompatible@v0.1.0 example.com/retract/incompatible@v2\.0\.0\+incompatible$' + # TODO(#45551): cmp stdout graph-1.16.txt + + +# Unsupported go versions should be rejected, since we don't know +# what versions they would report. +! go mod graph -go=1.99999999999 +stderr '^invalid value "1\.99999999999" for flag -go: maximum supported Go version is '$goversion'\nusage: go mod graph \[-go=version\]\nRun ''go help mod graph'' for details.$' + + +-- go.mod -- +// Module m indirectly imports a package from +// example.com/retract/incompatible. Its selected version of +// that module is lower under Go 1.17 semantics than under Go 1.16. +module example.com/m + +go 1.17 + +replace ( + example.net/lazy v0.1.0 => ./lazy + example.net/requireincompatible v0.1.0 => ./requireincompatible +) + +require ( + example.com/retract/incompatible v1.0.0 // indirect + example.net/lazy v0.1.0 +) +-- lazy/go.mod -- +// Module lazy requires example.com/retract/incompatible v1.0.0. +// +// When viewed from the outside it also has a transitive dependency +// on v2.0.0+incompatible, but in lazy mode that transitive dependency +// is pruned out. +module example.net/lazy + +go 1.17 + +exclude example.com/retract/incompatible v2.0.0+incompatible + +require ( + example.com/retract/incompatible v1.0.0 + example.net/requireincompatible v0.1.0 +) +-- requireincompatible/go.mod -- +module example.net/requireincompatible + +go 1.15 + +require example.com/retract/incompatible v2.0.0+incompatible diff --git a/src/cmd/go/testdata/script/mod_indirect_nospace.txt b/src/cmd/go/testdata/script/mod_indirect_nospace.txt new file mode 100644 index 0000000000000000000000000000000000000000..f4fb6a8c1b529fd06701073363030a7d3c96732b --- /dev/null +++ b/src/cmd/go/testdata/script/mod_indirect_nospace.txt @@ -0,0 +1,32 @@ +# https://golang.org/issue/45932: "indirect" comments missing spaces +# should not be corrupted when the comment is removed. + +go mod tidy +cmp go.mod go.mod.direct + +-- go.mod -- +module example.net/m + +go 1.16 + +require example.net/x v0.1.0 //indirect + +replace example.net/x v0.1.0 => ./x +-- go.mod.direct -- +module example.net/m + +go 1.16 + +require example.net/x v0.1.0 + +replace example.net/x v0.1.0 => ./x +-- m.go -- +package m +import _ "example.net/x" + +-- x/go.mod -- +module example.net/x + +go 1.16 +-- x/x.go -- +package x diff --git a/src/cmd/go/testdata/script/mod_init_dep.txt b/src/cmd/go/testdata/script/mod_init_dep.txt index f8cf1d563ab64b11bbee108653cf1b2556b400e0..76b48678604fabe57bda20fde36b770e5728435d 100644 --- a/src/cmd/go/testdata/script/mod_init_dep.txt +++ b/src/cmd/go/testdata/script/mod_init_dep.txt @@ -1,10 +1,6 @@ env GO111MODULE=on env GOFLAGS=-mod=mod -# modconv uses git directly to examine what old 'go get' would -[!net] skip -[!exec:git] skip - # go mod init should populate go.mod from Gopkg.lock go mod init x stderr 'copying requirements from Gopkg.lock' diff --git a/src/cmd/go/testdata/script/mod_init_glide.txt b/src/cmd/go/testdata/script/mod_init_glide.txt index a351a6ae4bce792c59bd25f7eacb9cf9413c5d44..373810c7687275f39d91a82e2ebc739f9421d64e 100644 --- a/src/cmd/go/testdata/script/mod_init_glide.txt +++ b/src/cmd/go/testdata/script/mod_init_glide.txt @@ -3,6 +3,7 @@ env GO111MODULE=on env GOPROXY=direct +env GOSUMDB= # Regression test for golang.org/issue/32161: # 'go mod init' did not locate tags when resolving a commit to a pseudo-version. diff --git a/src/cmd/go/testdata/script/mod_init_path.txt b/src/cmd/go/testdata/script/mod_init_path.txt index ccdfc92317583e96a8176a91daa97c7ab650c784..e5fd4ddbcb92c7d585b1e5b239a6dfc5cfe8089d 100644 --- a/src/cmd/go/testdata/script/mod_init_path.txt +++ b/src/cmd/go/testdata/script/mod_init_path.txt @@ -1,7 +1,7 @@ env GO111MODULE=on ! go mod init . -stderr '^go: invalid module path "\.": is a local import path$' +stderr '^go: malformed module path ".": is a local import path$' cd x go mod init example.com/x diff --git a/src/cmd/go/testdata/script/mod_install_hint.txt b/src/cmd/go/testdata/script/mod_install_hint.txt new file mode 100644 index 0000000000000000000000000000000000000000..ab02840eb8befb1f5579d7704e0fc8f5000f4232 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_install_hint.txt @@ -0,0 +1,5 @@ +# Module is replaced but not required. No hint appears as no module is suggested. +go mod init m +go mod edit -replace=github.com/notrequired@v0.5.0=github.com/doesnotexist@v0.5.0 +! go install github.com/notrequired +! stderr 'to add it:' \ No newline at end of file diff --git a/src/cmd/go/testdata/script/mod_install_pkg_version.txt b/src/cmd/go/testdata/script/mod_install_pkg_version.txt index 6ed600ff7155fbecaafd5dd120c70fb1db3a674e..fd02392af1b29387bcd072db6aa802e8a3738ac5 100644 --- a/src/cmd/go/testdata/script/mod_install_pkg_version.txt +++ b/src/cmd/go/testdata/script/mod_install_pkg_version.txt @@ -16,7 +16,7 @@ env GO111MODULE=auto cd m cp go.mod go.mod.orig ! go list -m all -stderr '^go: example.com/cmd@v1.1.0-doesnotexist: missing go.sum entry; to add it:\n\tgo mod download example.com/cmd$' +stderr '^go list -m: example.com/cmd@v1.1.0-doesnotexist: missing go.sum entry; to add it:\n\tgo mod download example.com/cmd$' go install example.com/cmd/a@latest cmp go.mod go.mod.orig exists $GOPATH/bin/a$GOEXE @@ -67,9 +67,9 @@ cd tmp go mod init tmp go mod edit -require=rsc.io/fortune@v1.0.0 ! go install -mod=readonly $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0 -stderr '^go: rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$' +stderr '^missing go\.sum entry for module providing package rsc\.io/fortune; to add:\n\tgo mod download rsc\.io/fortune$' ! go install -mod=readonly ../../pkg/mod/rsc.io/fortune@v1.0.0 -stderr '^go: rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$' +stderr '^missing go\.sum entry for module providing package rsc\.io/fortune; to add:\n\tgo mod download rsc\.io/fortune$' go get -d rsc.io/fortune@v1.0.0 go install -mod=readonly $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0 exists $GOPATH/bin/fortune$GOEXE @@ -81,17 +81,17 @@ env GO111MODULE=auto # 'go install pkg@version' reports errors for meta packages, std packages, # and directories. ! go install std@v1.0.0 -stderr '^go install std@v1.0.0: argument must be a package path, not a meta-package$' +stderr '^go install: std@v1.0.0: argument must be a package path, not a meta-package$' ! go install fmt@v1.0.0 -stderr '^go install fmt@v1.0.0: argument must not be a package in the standard library$' +stderr '^go install: fmt@v1.0.0: argument must not be a package in the standard library$' ! go install example.com//cmd/a@v1.0.0 -stderr '^go install example.com//cmd/a@v1.0.0: argument must be a clean package path$' +stderr '^go install: example.com//cmd/a@v1.0.0: argument must be a clean package path$' ! go install example.com/cmd/a@v1.0.0 ./x@v1.0.0 -stderr '^go install ./x@v1.0.0: argument must be a package path, not a relative path$' +stderr '^go install: ./x@v1.0.0: argument must be a package path, not a relative path$' ! go install example.com/cmd/a@v1.0.0 $GOPATH/src/x@v1.0.0 -stderr '^go install '$WORK'[/\\]gopath/src/x@v1.0.0: argument must be a package path, not an absolute path$' +stderr '^go install: '$WORK'[/\\]gopath/src/x@v1.0.0: argument must be a package path, not an absolute path$' ! go install example.com/cmd/a@v1.0.0 cmd/...@v1.0.0 -stderr '^go install: package cmd/go not provided by module example.com/cmd@v1.0.0$' +stderr '^package cmd/go not provided by module example.com/cmd@v1.0.0$' # 'go install pkg@version' should accept multiple arguments but report an error # if the version suffixes are different, even if they refer to the same version. @@ -106,19 +106,19 @@ stdout '^example.com/cmd v1.0.0$' env GO111MODULE=auto ! go install example.com/cmd/a@v1.0.0 example.com/cmd/b@latest -stderr '^go install example.com/cmd/b@latest: all arguments must have the same version \(@v1.0.0\)$' +stderr '^go install: example.com/cmd/b@latest: all arguments must have the same version \(@v1.0.0\)$' # 'go install pkg@version' should report an error if the arguments are in # different modules. ! go install example.com/cmd/a@v1.0.0 rsc.io/fortune@v1.0.0 -stderr '^go install: package rsc.io/fortune provided by module rsc.io/fortune@v1.0.0\n\tAll packages must be provided by the same module \(example.com/cmd@v1.0.0\).$' +stderr '^package rsc.io/fortune provided by module rsc.io/fortune@v1.0.0\n\tAll packages must be provided by the same module \(example.com/cmd@v1.0.0\).$' # 'go install pkg@version' should report an error if an argument is not # a main package. ! go install example.com/cmd/a@v1.0.0 example.com/cmd/err@v1.0.0 -stderr '^go install: package example.com/cmd/err is not a main package$' +stderr '^package example.com/cmd/err is not a main package$' # Wildcards should match only main packages. This module has a non-main package # with an error, so we'll know if that gets built. @@ -137,13 +137,13 @@ rm $GOPATH/bin # If a wildcard matches no packages, we should see a warning. ! go install example.com/cmd/nomatch...@v1.0.0 -stderr '^go install example.com/cmd/nomatch\.\.\.@v1.0.0: module example.com/cmd@v1.0.0 found, but does not contain packages matching example.com/cmd/nomatch\.\.\.$' +stderr '^go install: example.com/cmd/nomatch\.\.\.@v1.0.0: module example.com/cmd@v1.0.0 found, but does not contain packages matching example.com/cmd/nomatch\.\.\.$' go install example.com/cmd/a@v1.0.0 example.com/cmd/nomatch...@v1.0.0 stderr '^go: warning: "example.com/cmd/nomatch\.\.\." matched no packages$' # If a wildcard matches only non-main packges, we should see a different warning. go install example.com/cmd/err...@v1.0.0 -stderr '^go: warning: "example.com/cmd/err\.\.\." matched no main packages$' +stderr '^go: warning: "example.com/cmd/err\.\.\." matched only non-main packages$' # 'go install pkg@version' should report errors if the module contains @@ -159,7 +159,7 @@ cmp stderr exclude-err # 'go install pkg@version' should report an error if the module requires a # higher version of itself. ! go install example.com/cmd/a@v1.0.0-newerself -stderr '^go install example.com/cmd/a@v1.0.0-newerself: version constraints conflict:\n\texample.com/cmd@v1.0.0-newerself requires example.com/cmd@v1.0.0, but example.com/cmd@v1.0.0-newerself is requested$' +stderr '^go install: example.com/cmd/a@v1.0.0-newerself: version constraints conflict:\n\texample.com/cmd@v1.0.0-newerself requires example.com/cmd@v1.0.0, but example.com/cmd@v1.0.0-newerself is requested$' # 'go install pkg@version' will only match a retracted version if it's @@ -192,12 +192,12 @@ package main func main() {} -- replace-err -- -go install example.com/cmd/a@v1.0.0-replace: example.com/cmd@v1.0.0-replace +go install: example.com/cmd/a@v1.0.0-replace (in example.com/cmd@v1.0.0-replace): The go.mod file for the module providing named packages contains one or more replace directives. It must not contain directives that would cause it to be interpreted differently than if it were the main module. -- exclude-err -- -go install example.com/cmd/a@v1.0.0-exclude: example.com/cmd@v1.0.0-exclude +go install: example.com/cmd/a@v1.0.0-exclude (in example.com/cmd@v1.0.0-exclude): The go.mod file for the module providing named packages contains one or more exclude directives. It must not contain directives that would cause it to be interpreted differently than if it were the main module. diff --git a/src/cmd/go/testdata/script/mod_invalid_path.txt b/src/cmd/go/testdata/script/mod_invalid_path.txt index c8c075daaefe85a1f26cfb1bfaaac0f38bdfe28e..333a3ffa35cba157e6228157414be59a4cb59b92 100644 --- a/src/cmd/go/testdata/script/mod_invalid_path.txt +++ b/src/cmd/go/testdata/script/mod_invalid_path.txt @@ -8,11 +8,8 @@ stderr '^go: no module declaration in go.mod. To specify the module path:\n\tgo # Test that go mod init in GOPATH doesn't add a module declaration # with a path that can't possibly be a module path, because # it isn't even a valid import path. -# The single quote and backtick are the only characters we don't allow -# in checkModulePathLax, but is allowed in a Windows file name. -# TODO(matloob): choose a different character once -# module.CheckImportPath is laxened and replaces -# checkModulePathLax. +# The single quote and backtick are the only characters which are not allowed +# but are a valid Windows file name. cd $WORK/'gopath/src/m''d' ! go mod init stderr 'cannot determine module path' @@ -21,7 +18,7 @@ stderr 'cannot determine module path' # possibly be a module path, because it isn't even a valid import path cd $WORK/gopath/src/badname ! go list . -stderr 'invalid module path' +stderr 'malformed module path' # Test that an import path containing an element with a leading dot is valid, # but such a module path is not. diff --git a/src/cmd/go/testdata/script/mod_invalid_path_dotname.txt b/src/cmd/go/testdata/script/mod_invalid_path_dotname.txt new file mode 100644 index 0000000000000000000000000000000000000000..85934332d145db7194fc62dc1ddb68aaa49e2e7d --- /dev/null +++ b/src/cmd/go/testdata/script/mod_invalid_path_dotname.txt @@ -0,0 +1,46 @@ +# Test that an import path containing an element with a leading dot +# in another module is valid. + +# 'go get' works with no version query. +cp go.mod.empty go.mod +go get -d example.com/dotname/.dot +go list -m example.com/dotname +stdout '^example.com/dotname v1.0.0$' + +# 'go get' works with a version query. +cp go.mod.empty go.mod +go get -d example.com/dotname/.dot@latest +go list -m example.com/dotname +stdout '^example.com/dotname v1.0.0$' + +# 'go get' works on an importing package. +cp go.mod.empty go.mod +go get -d . +go list -m example.com/dotname +stdout '^example.com/dotname v1.0.0$' + +# 'go list' works on the dotted package. +go list example.com/dotname/.dot +stdout '^example.com/dotname/.dot$' + +# 'go list' works on an importing package. +go list . +stdout '^m$' + +# 'go mod tidy' works. +cp go.mod.empty go.mod +go mod tidy +go list -m example.com/dotname +stdout '^example.com/dotname v1.0.0$' + +-- go.mod.empty -- +module m + +go 1.16 +-- go.sum -- +example.com/dotname v1.0.0 h1:Q0JMAn464CnwFVCshs1n4+f5EFiW/eRhnx/fTWjw2Ag= +example.com/dotname v1.0.0/go.mod h1:7K4VLT7QylRI8H7yZwUkeDH2s19wQnyfp/3oBlItWJ0= +-- use.go -- +package use + +import _ "example.com/dotname/.dot" diff --git a/src/cmd/go/testdata/script/mod_invalid_path_plus.txt b/src/cmd/go/testdata/script/mod_invalid_path_plus.txt index 2f2488f01b62a8d2620a1df870cd133f6727d632..51dbf936888769d0e4b777292ce807b235b918c3 100644 --- a/src/cmd/go/testdata/script/mod_invalid_path_plus.txt +++ b/src/cmd/go/testdata/script/mod_invalid_path_plus.txt @@ -2,18 +2,22 @@ # The '+' character should be disallowed in module paths, but allowed in package # paths within valid modules. +# 'go list' accepts package paths with pluses. +cp go.mod.orig go.mod go get -d example.net/cmd go list example.net/cmd/x++ +# 'go list -m' rejects module paths with pluses. ! go list -versions -m 'example.net/bad++' -stderr '^go list -m: module example.net/bad\+\+: malformed module path "example.net/bad\+\+": invalid char ''\+''$' +stderr '^go list -m: malformed module path "example.net/bad\+\+": invalid char ''\+''$' -# TODO(bcmills): 'go get -d example.net/cmd/x++' should also work, but currently -# it does not. This might be fixed by https://golang.org/cl/297891. -! go get -d example.net/cmd/x++ -stderr '^go get: malformed module path "example.net/cmd/x\+\+": invalid char ''\+''$' +# 'go get' accepts package paths with pluses. +cp go.mod.orig go.mod +go get -d example.net/cmd/x++ +go list -m example.net/cmd +stdout '^example.net/cmd v0.0.0-00010101000000-000000000000 => ./cmd$' --- go.mod -- +-- go.mod.orig -- module example.com/m go 1.16 diff --git a/src/cmd/go/testdata/script/mod_invalid_version.txt b/src/cmd/go/testdata/script/mod_invalid_version.txt index 43b9564356100a02cea4f9c00ae394930748db37..6846a792a5df587e00b2ce7d497333413d4f0dd5 100644 --- a/src/cmd/go/testdata/script/mod_invalid_version.txt +++ b/src/cmd/go/testdata/script/mod_invalid_version.txt @@ -19,7 +19,7 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text@14c0d48ead0c cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 \(replaced by \./\..\): parsing ../go.mod: '$WORK'/gopath/src/go.mod:5: require golang.org/x/text: version "14c0d48ead0c" invalid: must be of the form v1.2.3' +stderr 'go list -m: example.com@v0.0.0 \(replaced by \./\.\.\): parsing ..[/\\]go.mod: '$WORK'[/\\]gopath[/\\]src[/\\]go.mod:5: require golang.org/x/text: version "14c0d48ead0c" invalid: must be of the form v1.2.3' cd .. go list -m golang.org/x/text stdout 'golang.org/x/text v0.1.1-0.20170915032832-14c0d48ead0c' @@ -30,7 +30,7 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text/unicode@v0.0.0-20170915032832-14c0d48ead0c cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text/unicode@v0.0.0-20170915032832-14c0d48ead0c: invalid version: missing golang.org/x/text/unicode/go.mod at revision 14c0d48ead0c' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text/unicode@v0.0.0-20170915032832-14c0d48ead0c: invalid version: missing golang.org/x/text/unicode/go.mod at revision 14c0d48ead0c' cd .. ! go list -m golang.org/x/text stderr 'golang.org/x/text/unicode@v0.0.0-20170915032832-14c0d48ead0c: invalid version: missing golang.org/x/text/unicode/go.mod at revision 14c0d48ead0c' @@ -47,17 +47,17 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text@v2.1.1-0.20170915032832-14c0d48ead0c cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 \(replaced by \./\.\.\): parsing ../go.mod: '$WORK'/gopath/src/go.mod:5: require golang.org/x/text: version "v2.1.1-0.20170915032832-14c0d48ead0c" invalid: should be v0 or v1, not v2' +stderr 'go list -m: example.com@v0.0.0 \(replaced by \./\.\.\): parsing ..[/\\]go.mod: '$WORK'[/\\]gopath[/\\]src[/\\]go.mod:5: require golang.org/x/text: version "v2.1.1-0.20170915032832-14c0d48ead0c" invalid: should be v0 or v1, not v2' cd .. ! go list -m golang.org/x/text -stderr $WORK'/gopath/src/go.mod:5: require golang.org/x/text: version "v2.1.1-0.20170915032832-14c0d48ead0c" invalid: should be v0 or v1, not v2' +stderr $WORK'[/\\]gopath[/\\]src[/\\]go.mod:5: require golang.org/x/text: version "v2.1.1-0.20170915032832-14c0d48ead0c" invalid: should be v0 or v1, not v2' # A pseudo-version with fewer than 12 digits of SHA-1 prefix is invalid. cp go.mod.orig go.mod go mod edit -require golang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0 cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0: invalid pseudo-version: revision is shorter than canonical \(14c0d48ead0c\)' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0: invalid pseudo-version: revision is shorter than canonical \(14c0d48ead0c\)' cd .. ! go list -m golang.org/x/text stderr 'golang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0: invalid pseudo-version: revision is shorter than canonical \(14c0d48ead0c\)' @@ -67,7 +67,7 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0cd47e3104ada247d91be04afc7a5a cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0cd47e3104ada247d91be04afc7a5a: invalid pseudo-version: revision is longer than canonical \(14c0d48ead0c\)' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0cd47e3104ada247d91be04afc7a5a: invalid pseudo-version: revision is longer than canonical \(14c0d48ead0c\)' cd .. ! go list -m golang.org/x/text stderr 'golang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0cd47e3104ada247d91be04afc7a5a: invalid pseudo-version: revision is longer than canonical \(14c0d48ead0c\)' @@ -77,7 +77,7 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text@v0.1.1-0.20190915032832-14c0d48ead0c cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.1.1-0.20190915032832-14c0d48ead0c: invalid pseudo-version: does not match version-control timestamp \(expected 20170915032832\)' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.1.1-0.20190915032832-14c0d48ead0c: invalid pseudo-version: does not match version-control timestamp \(expected 20170915032832\)' cd .. ! go list -m golang.org/x/text stderr 'golang.org/x/text@v0.1.1-0.20190915032832-14c0d48ead0c: invalid pseudo-version: does not match version-control timestamp \(expected 20170915032832\)' @@ -87,7 +87,7 @@ stderr 'golang.org/x/text@v0.1.1-0.20190915032832-14c0d48ead0c: invalid pseudo-v go mod edit -replace golang.org/x/text@v0.1.1-0.20190915032832-14c0d48ead0c=golang.org/x/text@14c0d48ead0c cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.1.1-0.20190915032832-14c0d48ead0c: invalid pseudo-version: does not match version-control timestamp \(expected 20170915032832\)' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.1.1-0.20190915032832-14c0d48ead0c: invalid pseudo-version: does not match version-control timestamp \(expected 20170915032832\)' cd .. go list -m golang.org/x/text stdout 'golang.org/x/text v0.1.1-0.20190915032832-14c0d48ead0c => golang.org/x/text v0.1.1-0.20170915032832-14c0d48ead0c' @@ -97,7 +97,7 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text@v1.999.999-0.20170915032832-14c0d48ead0c cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v1.999.999-0.20170915032832-14c0d48ead0c: invalid pseudo-version: preceding tag \(v1.999.998\) not found' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v1.999.999-0.20170915032832-14c0d48ead0c: invalid pseudo-version: preceding tag \(v1.999.998\) not found' cd .. ! go list -m golang.org/x/text stderr 'golang.org/x/text@v1.999.999-0.20170915032832-14c0d48ead0c: invalid pseudo-version: preceding tag \(v1.999.998\) not found' @@ -109,7 +109,7 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text@v1.0.0-20170915032832-14c0d48ead0c cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v1.0.0-20170915032832-14c0d48ead0c: invalid pseudo-version: major version without preceding tag must be v0, not v1' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v1.0.0-20170915032832-14c0d48ead0c: invalid pseudo-version: major version without preceding tag must be v0, not v1' cd .. ! go list -m golang.org/x/text stderr 'golang.org/x/text@v1.0.0-20170915032832-14c0d48ead0c: invalid pseudo-version: major version without preceding tag must be v0, not v1' @@ -120,7 +120,7 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text@v0.0.0-0.20170915032832-14c0d48ead0c cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.0.0-0.20170915032832-14c0d48ead0c: invalid pseudo-version: version before v0.0.0 would have negative patch number' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.0.0-0.20170915032832-14c0d48ead0c: invalid pseudo-version: version before v0.0.0 would have negative patch number' cd .. ! go list -m golang.org/x/text stderr 'golang.org/x/text@v0.0.0-0.20170915032832-14c0d48ead0c: invalid pseudo-version: version before v0.0.0 would have negative patch number' @@ -130,7 +130,7 @@ stderr 'golang.org/x/text@v0.0.0-0.20170915032832-14c0d48ead0c: invalid pseudo-v go mod edit -replace golang.org/x/text@v0.0.0-0.20170915032832-14c0d48ead0c=golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.0.0-0.20170915032832-14c0d48ead0c: invalid pseudo-version: version before v0.0.0 would have negative patch number' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.0.0-0.20170915032832-14c0d48ead0c: invalid pseudo-version: version before v0.0.0 would have negative patch number' cd .. go list -m golang.org/x/text stdout 'golang.org/x/text v0.0.0-0.20170915032832-14c0d48ead0c => golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c' @@ -153,7 +153,7 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text@v0.2.1-0.20170915032832-14c0d48ead0c cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.2.1-0.20170915032832-14c0d48ead0c: invalid pseudo-version: revision 14c0d48ead0c is not a descendent of preceding tag \(v0.2.0\)' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.2.1-0.20170915032832-14c0d48ead0c: invalid pseudo-version: revision 14c0d48ead0c is not a descendent of preceding tag \(v0.2.0\)' cd .. ! go list -m golang.org/x/text stderr 'golang.org/x/text@v0.2.1-0.20170915032832-14c0d48ead0c: invalid pseudo-version: revision 14c0d48ead0c is not a descendent of preceding tag \(v0.2.0\)' @@ -163,7 +163,7 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text@v0.2.1-0.20171213102548-c4d099d611ac cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.2.1-0.20171213102548-c4d099d611ac: invalid pseudo-version: tag \(v0.2.0\) found on revision c4d099d611ac is already canonical, so should not be replaced with a pseudo-version derived from that tag' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.2.1-0.20171213102548-c4d099d611ac: invalid pseudo-version: tag \(v0.2.0\) found on revision c4d099d611ac is already canonical, so should not be replaced with a pseudo-version derived from that tag' cd .. ! go list -m golang.org/x/text stderr 'golang.org/x/text@v0.2.1-0.20171213102548-c4d099d611ac: invalid pseudo-version: tag \(v0.2.0\) found on revision c4d099d611ac is already canonical, so should not be replaced with a pseudo-version derived from that tag' @@ -173,7 +173,7 @@ cp go.mod.orig go.mod go mod edit -require golang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0c+incompatible cd outside ! go list -m golang.org/x/text -stderr 'go: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0c\+incompatible: invalid version: \+incompatible suffix not allowed: major version v0 is compatible' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgolang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0c\+incompatible: invalid version: \+incompatible suffix not allowed: major version v0 is compatible' cd .. ! go list -m golang.org/x/text stderr 'golang.org/x/text@v0.1.1-0.20170915032832-14c0d48ead0c\+incompatible: invalid version: \+incompatible suffix not allowed: major version v0 is compatible' @@ -194,7 +194,7 @@ cp go.mod.orig go.mod go mod edit -require github.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d+incompatible cd outside ! go list -m github.com/pierrec/lz4 -stderr 'go: example.com@v0.0.0 requires\n\tgithub.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required' +stderr 'go list -m: example.com@v0.0.0 requires\n\tgithub.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required' cd .. ! go list -m github.com/pierrec/lz4 stderr 'github.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required' diff --git a/src/cmd/go/testdata/script/mod_lazy_consistency.txt b/src/cmd/go/testdata/script/mod_lazy_consistency.txt new file mode 100644 index 0000000000000000000000000000000000000000..1bf3e31bfe0799b6f51159332febe315893957a6 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_lazy_consistency.txt @@ -0,0 +1,95 @@ +# If the root requirements in a lazy module are inconsistent +# (for example, due to a bad hand-edit or git merge), +# they can go unnoticed as long as the module with the violated +# requirement is not used. +# When we load a package from that module, we should spot-check its +# requirements and either emit an error or update the go.mod file. + +cp go.mod go.mod.orig + + +# If we load package x from x.1, we only check the requirements of x, +# which are fine: loading succeeds. + +go list -deps ./usex +stdout '^example.net/x$' +cmp go.mod go.mod.orig + + +# However, if we load needx2, we should load the requirements of needx2. +# Those requirements indicate x.2, not x.1, so the module graph is +# inconsistent and needs to be fixed. + +! go list -deps ./useneedx2 +stderr '^go: updates to go.mod needed; to update it:\n\tgo mod tidy$' + +! go list -deps example.net/needx2 +stderr '^go: updates to go.mod needed; to update it:\n\tgo mod tidy$' + + +# The command printed in the error message should fix the problem. + +go mod tidy +go list -deps ./useneedx2 +stdout '^example.net/m/useneedx2$' +stdout '^example.net/needx2$' +stdout '^example.net/x$' + +go list -m all +stdout '^example.net/needx2 v0\.1\.0 ' +stdout '^example.net/x v0\.2\.0 ' + + +-- go.mod -- +module example.net/m + +go 1.17 + +require ( + example.net/needx2 v0.1.0 + example.net/x v0.1.0 +) + +replace ( + example.net/needx2 v0.1.0 => ./needx2.1 + example.net/x v0.1.0 => ./x.1 + example.net/x v0.2.0 => ./x.2 +) +-- useneedx2/useneedx2.go -- +package useneedx2 + +import _ "example.net/needx2" +-- usex/usex.go -- +package usex + +import _ "example.net/x" + +-- x.1/go.mod -- +module example.com/x + +go 1.17 +-- x.1/x.go -- +package x + +-- x.2/go.mod -- +module example.com/x + +go 1.17 +-- x.2/x.go -- +package x + +const AddedInV2 = true + +-- needx2.1/go.mod -- +module example.com/x + +go 1.17 + +require example.net/x v0.2.0 +-- needx2.1/needx2.go -- +// Package needx2 needs x v0.2.0 or higher. +package needx2 + +import "example.net/x" + +var _ = x.AddedInV2 diff --git a/src/cmd/go/testdata/script/mod_lazy_downgrade.txt b/src/cmd/go/testdata/script/mod_lazy_downgrade.txt index 1e84820f811ab89cf13f21e5040118ada0caa752..2f815fef22ff6398d894ba5d4d74475fa728d525 100644 --- a/src/cmd/go/testdata/script/mod_lazy_downgrade.txt +++ b/src/cmd/go/testdata/script/mod_lazy_downgrade.txt @@ -1,5 +1,5 @@ # This test illustrates the interaction between lazy loading and downgrading in -# 'go get. +# 'go get'. # The package import graph used in this test looks like: # @@ -46,7 +46,7 @@ go list -m all # outside of the deepening scan should not affect the downgrade. cp go.mod.orig go.mod -go mod edit -go=1.16 +go mod edit -go=1.17 go list -m all stdout '^example.com/a v0.1.0 ' @@ -59,12 +59,50 @@ stdout '^example.com/a v0.1.0 ' stdout '^example.com/b v0.2.0 ' stdout '^example.com/c v0.1.0 ' +# At this point, b.2 is still an explicit root, so its dependency on c +# is still tracked, and it will still be downgraded away if we remove c. +# ('go get' never makes a root into a non-root. Only 'go mod tidy' does that.) + go get -d example.com/c@none go list -m all -! stdout '^example.com/a ' # TODO(#36460): example.com/a v0.1.0 -! stdout '^example.com/b ' # TODO(#36460): example.com/b v0.1.0 +! stdout '^example.com/a ' +! stdout '^example.com/b ' ! stdout '^example.com/c ' + +# This time, we drop the explicit 'b' root by downgrading it to v0.1.0 +# (the version required by a.1) and running 'go mod tidy'. +# It is still selected at v0.1.0 (as a dependency of a), +# but its dependency on c is now pruned from the module graph, so it doesn't +# result in any downgrades to b or a if we run 'go get c@none'. + +cp go.mod.orig go.mod +go mod edit -go=1.17 + +go list -m all +stdout '^example.com/a v0.1.0 ' +stdout '^example.com/b v0.3.0 ' +stdout '^example.com/c v0.2.0 ' + +go get -d example.com/c@v0.1.0 example.com/b@v0.1.0 +go list -m all +stdout '^example.com/a v0.1.0 ' +stdout '^example.com/b v0.1.0 ' +stdout '^example.com/c v0.1.0 ' + +go mod tidy +go list -m all +stdout '^example.com/a v0.1.0 ' +stdout '^example.com/b v0.1.0 ' +! stdout '^example.com/c ' + +go get -d example.com/c@none +go list -m all +stdout '^example.com/a v0.1.0' +stdout '^example.com/b v0.1.0' +! stdout '^example.com/c ' + + -- go.mod -- module example.com/lazy @@ -91,7 +129,7 @@ import _ "example.com/a" -- a/go.mod -- module example.com/a -go 1.15 +go 1.17 require example.com/b v0.1.0 -- a/a.go -- @@ -104,7 +142,7 @@ import _ "example.com/b" -- b1/go.mod -- module example.com/b -go 1.15 +go 1.17 require example.com/c v0.1.0 -- b1/b.go -- @@ -116,7 +154,7 @@ import _ "example.com/c" -- b2/go.mod -- module example.com/b -go 1.15 +go 1.17 require example.com/c v0.1.0 -- b2/b.go -- @@ -128,7 +166,7 @@ import _ "example.com/c" -- b3/go.mod -- module example.com/b -go 1.15 +go 1.17 require example.com/c v0.2.0 -- b3/b.go -- @@ -140,6 +178,6 @@ import _ "example.com/c" -- c/go.mod -- module example.com/c -go 1.15 +go 1.17 -- c/c.go -- package c diff --git a/src/cmd/go/testdata/script/mod_lazy_import_allmod.txt b/src/cmd/go/testdata/script/mod_lazy_import_allmod.txt index 4ad8cbf8ee2de35a915c707f325708560f7241ff..97718c4513b55bffd6819544119f32b74b734173 100644 --- a/src/cmd/go/testdata/script/mod_lazy_import_allmod.txt +++ b/src/cmd/go/testdata/script/mod_lazy_import_allmod.txt @@ -53,8 +53,8 @@ stdout '^c v0.1.0 ' cp m.go.orig m.go cp go.mod.orig go.mod -go mod edit -go=1.16 -go mod edit -go=1.16 go.mod.new +go mod edit -go=1.17 +go mod edit -go=1.17 go.mod.new cp go.mod go.mod.orig go mod tidy @@ -63,14 +63,15 @@ cmp go.mod.orig go.mod go list -m all stdout '^a v0.1.0 ' stdout '^b v0.1.0 ' -stdout '^c v0.1.0 ' # TODO(#36460): This should be pruned out. +! stdout '^c ' -# After adding a new import of b/y, -# the import of c from b/y should again resolve to the version required by b. +# After adding a new direct import of b/y, +# the existing verison of b should be promoted to a root, +# bringing the version of c required by b into the build list. cp m.go.new m.go go mod tidy -cmp go.mod.new go.mod +cmp go.mod.lazy go.mod go list -m all stdout '^a v0.1.0 ' @@ -124,6 +125,24 @@ require ( b v0.1.0 ) +replace ( + a v0.1.0 => ./a1 + b v0.1.0 => ./b1 + c v0.1.0 => ./c1 + c v0.2.0 => ./c2 +) +-- go.mod.lazy -- +module m + +go 1.17 + +require ( + a v0.1.0 + b v0.1.0 +) + +require c v0.1.0 // indirect + replace ( a v0.1.0 => ./a1 b v0.1.0 => ./b1 @@ -133,7 +152,7 @@ replace ( -- a1/go.mod -- module a -go 1.16 +go 1.17 require b v0.1.0 -- a1/a.go -- @@ -145,7 +164,7 @@ import _ "b/x" -- b1/go.mod -- module b -go 1.16 +go 1.17 require c v0.1.0 -- b1/x/x.go -- @@ -161,7 +180,7 @@ func CVersion() string { -- c1/go.mod -- module c -go 1.16 +go 1.17 -- c1/c.go -- package c diff --git a/src/cmd/go/testdata/script/mod_lazy_new_import.txt b/src/cmd/go/testdata/script/mod_lazy_new_import.txt index 02935bf236528b4e56efaefef9dd850a3b9fe074..4272a52de1c2671db78a1c2708e199d6351c511e 100644 --- a/src/cmd/go/testdata/script/mod_lazy_new_import.txt +++ b/src/cmd/go/testdata/script/mod_lazy_new_import.txt @@ -5,7 +5,7 @@ # # lazy ---- a/x ---- b # \ -# ---- a/y ---- c +# ---- a/y (new) ---- c # # Where a/x and x/y are disjoint packages, but both contained in module a. # @@ -32,17 +32,32 @@ cmp go.mod go.mod.old cp lazy.go.new lazy.go go list all go list -m all -stdout '^example.com/c v0.1.0' # not v0.2.0 as would be be resolved by 'latest' +stdout '^example.com/c v0.1.0' # not v0.2.0 as would be resolved by 'latest' cmp go.mod go.mod.old -# TODO(#36460): +# Now, we repeat the test with a lazy main module. cp lazy.go.old lazy.go -cp go.mod.old go.mod -go mod edit -go=1.16 +cp go.mod.117 go.mod + +# Before adding a new import, the go.mod file should +# enumerate modules for all packages already imported. +go list all +cmp go.mod go.mod.117 # When a new import is found, we should perform a deepening scan of the existing # dependencies and add a requirement on the version required by those # dependencies — not re-resolve 'latest'. +cp lazy.go.new lazy.go + +! go list all +stderr '^go: updates to go.mod needed; to update it:\n\tgo mod tidy$' + +go mod tidy +go list all +go list -m all +stdout '^example.com/c v0.1.0' # not v0.2.0 as would be resolved by 'latest' + +cmp go.mod go.mod.new -- go.mod -- @@ -52,6 +67,39 @@ go 1.15 require example.com/a v0.1.0 +replace ( + example.com/a v0.1.0 => ./a + example.com/b v0.1.0 => ./b + example.com/c v0.1.0 => ./c1 + example.com/c v0.2.0 => ./c2 +) +-- go.mod.117 -- +module example.com/lazy + +go 1.17 + +require example.com/a v0.1.0 + +require example.com/b v0.1.0 // indirect + +replace ( + example.com/a v0.1.0 => ./a + example.com/b v0.1.0 => ./b + example.com/c v0.1.0 => ./c1 + example.com/c v0.2.0 => ./c2 +) +-- go.mod.new -- +module example.com/lazy + +go 1.17 + +require example.com/a v0.1.0 + +require ( + example.com/b v0.1.0 // indirect + example.com/c v0.1.0 // indirect +) + replace ( example.com/a v0.1.0 => ./a example.com/b v0.1.0 => ./b diff --git a/src/cmd/go/testdata/script/mod_lazy_test_horizon.txt b/src/cmd/go/testdata/script/mod_lazy_test_horizon.txt index 9cdfad79f6a926d0d743668e44dc5322fe758b29..7d07eb60aa66baade8024253d584e44e5ad8b524 100644 --- a/src/cmd/go/testdata/script/mod_lazy_test_horizon.txt +++ b/src/cmd/go/testdata/script/mod_lazy_test_horizon.txt @@ -32,12 +32,12 @@ stdout '^c v0.2.0 ' # but the irrelevant dependency on c v0.2.0 should be pruned out, # leaving only the relevant dependency on c v0.1.0. -go mod edit -go=1.16 +go mod edit -go=1.17 go list -m c -stdout '^c v0.2.0' # TODO(#36460): v0.1.0 +stdout '^c v0.1.0' [!short] go test -v x -[!short] stdout ' c v0.2.0$' # TODO(#36460): v0.1.0 +[!short] stdout ' c v0.1.0$' -- m.go -- package m @@ -66,7 +66,7 @@ replace ( -- a1/go.mod -- module a -go 1.16 +go 1.17 require b v0.1.0 -- a1/a.go -- @@ -78,7 +78,7 @@ import _ "b" -- b1/go.mod -- module b -go 1.16 +go 1.17 require c v0.2.0 -- b1/b.go -- @@ -97,7 +97,7 @@ func TestCVersion(t *testing.T) { -- c1/go.mod -- module c -go 1.16 +go 1.17 -- c1/c.go -- package c @@ -105,7 +105,7 @@ const Version = "v0.1.0" -- c2/go.mod -- module c -go 1.16 +go 1.17 -- c2/c.go -- package c @@ -113,7 +113,7 @@ const Version = "v0.2.0" -- x1/go.mod -- module x -go 1.16 +go 1.17 require c v0.1.0 -- x1/x.go -- diff --git a/src/cmd/go/testdata/script/mod_lazy_test_of_test_dep.txt b/src/cmd/go/testdata/script/mod_lazy_test_of_test_dep.txt index ca6c55040eb715bf9f13bc15cb632cad10777086..68a5b6dca2ad6091068df7795873d764096a9903 100644 --- a/src/cmd/go/testdata/script/mod_lazy_test_of_test_dep.txt +++ b/src/cmd/go/testdata/script/mod_lazy_test_of_test_dep.txt @@ -21,12 +21,13 @@ cp go.mod go.mod.old go mod tidy cmp go.mod go.mod.old + # In Go 1.15 mode, 'go list -m all' includes modules needed by the # transitive closure of tests of dependencies of tests of dependencies of …. go list -m all -stdout 'example.com/b v0.1.0' -stdout 'example.com/c v0.1.0' +stdout '^example.com/b v0.1.0 ' +stdout '^example.com/c v0.1.0 ' cmp go.mod go.mod.old # 'go test' (or equivalent) of any such dependency, no matter how remote, does @@ -36,18 +37,24 @@ go list -test -deps example.com/a stdout example.com/b ! stdout example.com/c -[!short] go test -c example.com/a +[!short] go test -c -o $devnull example.com/a [!short] cmp go.mod go.mod.old go list -test -deps example.com/b stdout example.com/c -[!short] go test -c example.com/b +[!short] go test -c -o $devnull example.com/b [!short] cmp go.mod go.mod.old -# TODO(#36460): +go mod edit -go=1.17 a/go.mod +go mod edit -go=1.17 b1/go.mod +go mod edit -go=1.17 b2/go.mod +go mod edit -go=1.17 c1/go.mod +go mod edit -go=1.17 c2/go.mod +go mod edit -go=1.17 + -# After changing to 'go 1.16` uniformly, 'go list -m all' should prune out +# After changing to 'go 1.17` uniformly, 'go list -m all' should prune out # example.com/c, because it is not imported by any package (or test of a package) # transitively imported by the main module. # @@ -62,10 +69,66 @@ stdout example.com/c # version of its module. # However, if we reach c by running successive tests starting from the main -# module, we should end up with exactly the version require by c, with an update +# module, we should end up with exactly the version required by b, with an update # to the go.mod file as soon as we test a test dependency that is not itself in # "all". +cp go.mod go.mod.117 +go mod tidy +cmp go.mod go.mod.117 + +go list -m all +stdout '^example.com/b v0.1.0 ' +! stdout '^example.com/c ' + +# 'go test' of a package (transitively) imported by the main module +# should work without changes to the go.mod file. + +go list -test -deps example.com/a +stdout example.com/b +! stdout example.com/c + +[!short] go test -c -o $devnull example.com/a + +# However, 'go test' of a package that is itself a dependency should require an +# update to the go.mod file. +! go list -test -deps example.com/b + + # TODO(#36460): The hint here is wrong. We should suggest + # 'go get -t example.com/b@v0.1.0' instead of 'go mod tidy'. +stderr '^go: updates to go\.mod needed; to update it:\n\tgo mod tidy$' + +[!short] ! go test -c -o $devnull example.com/b +[!short] stderr '^go: updates to go\.mod needed; to update it:\n\tgo mod tidy$' + +go get -t example.com/b@v0.1.0 +go list -test -deps example.com/b +stdout example.com/c + +[!short] go test -c -o $devnull example.com/b + +# The update should bring the version required by b, not the latest version of c. + +go list -m example.com/c +stdout '^example.com/c v0.1.0 ' + +cmp go.mod go.mod.b + + +# We should reach the same state if we arrive at it via `go test -mod=mod`. + +cp go.mod.117 go.mod + +[short] go list -mod=mod -test -deps example.com/a +[!short] go test -mod=mod -c -o $devnull example.com/a + +[short] go list -mod=mod -test -deps example.com/b +[!short] go test -mod=mod -c -o $devnull example.com/b + +cmp go.mod go.mod.b + + + -- go.mod -- module example.com/lazy @@ -73,6 +136,22 @@ go 1.15 require example.com/a v0.1.0 +replace ( + example.com/a v0.1.0 => ./a + example.com/b v0.1.0 => ./b1 + example.com/b v0.2.0 => ./b2 + example.com/c v0.1.0 => ./c1 + example.com/c v0.2.0 => ./c2 +) +-- go.mod.b -- +module example.com/lazy + +go 1.17 + +require example.com/a v0.1.0 + +require example.com/b v0.1.0 // indirect + replace ( example.com/a v0.1.0 => ./a example.com/b v0.1.0 => ./b1 diff --git a/src/cmd/go/testdata/script/mod_list.txt b/src/cmd/go/testdata/script/mod_list.txt index 1ba6d7c910e8c88bbbd45ce22d2e8f6612408386..239c7caa4a2246e87eaaef22b2ace9ce6ba800dc 100644 --- a/src/cmd/go/testdata/script/mod_list.txt +++ b/src/cmd/go/testdata/script/mod_list.txt @@ -29,7 +29,8 @@ stdout 'v1.3.0.*mod[\\/]rsc.io[\\/]sampler@v1.3.1 .*[\\/]v1.3.1.mod => v1.3.1.*s go list std stdout ^math/big -# rsc.io/quote/buggy should be listable as a package +# rsc.io/quote/buggy should be listable as a package, +# even though it is only a test. go list -mod=mod rsc.io/quote/buggy # rsc.io/quote/buggy should not be listable as a module diff --git a/src/cmd/go/testdata/script/mod_list_deprecated.txt b/src/cmd/go/testdata/script/mod_list_deprecated.txt new file mode 100644 index 0000000000000000000000000000000000000000..f0ecbba2cea13d8c65f512036e3d0b8c887a3703 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_list_deprecated.txt @@ -0,0 +1,52 @@ +# 'go list pkg' does not show deprecation. +go list example.com/deprecated/a +stdout '^example.com/deprecated/a$' + +# 'go list -m' does not show deprecation. +go list -m example.com/deprecated/a +stdout '^example.com/deprecated/a v1.9.0$' + +# 'go list -m -versions' does not show deprecation. +go list -m -versions example.com/deprecated/a +stdout '^example.com/deprecated/a v1.0.0 v1.9.0$' + +# 'go list -m -u' shows deprecation. +go list -m -u example.com/deprecated/a +stdout '^example.com/deprecated/a v1.9.0 \(deprecated\)$' + +# 'go list -m -u -f' exposes the deprecation message. +go list -m -u -f {{.Deprecated}} example.com/deprecated/a +stdout '^in example.com/deprecated/a@v1.9.0$' + +# This works even if we use an old version that does not have the deprecation +# message in its go.mod file. +go get -d example.com/deprecated/a@v1.0.0 +! grep Deprecated: $WORK/gopath/pkg/mod/cache/download/example.com/deprecated/a/@v/v1.0.0.mod +go list -m -u -f {{.Deprecated}} example.com/deprecated/a +stdout '^in example.com/deprecated/a@v1.9.0$' + +# 'go list -m -u' does not show deprecation for the main module. +go list -m -u +! stdout deprecated +go list -m -u -f '{{if not .Deprecated}}ok{{end}}' +stdout ok + +# 'go list -m -u' does not show a deprecation message for a module that is not +# deprecated at the latest version, even if it is deprecated at the current +# version. +go list -m -u example.com/undeprecated +stdout '^example.com/undeprecated v1.0.0 \[v1.0.1\]$' +-- go.mod -- +// Deprecated: main module is deprecated, too! +module example.com/use + +go 1.17 + +require ( + example.com/deprecated/a v1.9.0 + example.com/undeprecated v1.0.0 +) +-- go.sum -- +example.com/deprecated/a v1.9.0 h1:pRyvBIZheJpQVVnNW4Fdg8QuoqDgtkCreqZZbASV3BE= +example.com/deprecated/a v1.9.0/go.mod h1:Z1uUVshSY9kh6l/2hZ8oA9SBviX2yfaeEpcLDz6AZwY= +example.com/undeprecated v1.0.0/go.mod h1:1qiRbdA9VzJXDqlG26Y41O5Z7YyO+jAD9do8XCZQ+Gg= diff --git a/src/cmd/go/testdata/script/mod_list_deprecated_replace.txt b/src/cmd/go/testdata/script/mod_list_deprecated_replace.txt new file mode 100644 index 0000000000000000000000000000000000000000..48b991fc473f60aed647705d4fcc99b216d702f9 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_list_deprecated_replace.txt @@ -0,0 +1,68 @@ +# When all versions are replaced, we should not look up a deprecation message. +# We will still look up a deprecation message for the replacement. +cp go.mod.allreplaced go.mod +go list -m -u -f '{{.Path}}@{{.Version}} <{{.Deprecated}}>{{with .Replace}} => {{.Path}}@{{.Version}} <{{.Deprecated}}>{{end}}' all +stdout '^example.com/deprecated/a@v1.0.0 <> => example.com/deprecated/b@v1.0.0 $' + +# When one version is replaced, we should see a deprecation message. +cp go.mod.onereplaced go.mod +go list -m -u -f '{{.Path}}@{{.Version}} <{{.Deprecated}}>{{with .Replace}} => {{.Path}}@{{.Version}} <{{.Deprecated}}>{{end}}' all +stdout '^example.com/deprecated/a@v1.0.0 => example.com/deprecated/b@v1.0.0 $' + +# If the replacement is a directory, we won't look that up. +cp go.mod.dirreplacement go.mod +go list -m -u -f '{{.Path}}@{{.Version}} <{{.Deprecated}}>{{with .Replace}} => {{.Path}}@{{.Version}} <{{.Deprecated}}>{{end}}' all +stdout '^example.com/deprecated/a@v1.0.0 <> => ./a@ <>$' + +# If the latest version of the replacement is replaced, we'll use the content +# from that replacement. +cp go.mod.latestreplaced go.mod +go list -m -u -f '{{.Path}}@{{.Version}} <{{.Deprecated}}>{{with .Replace}} => {{.Path}}@{{.Version}} <{{.Deprecated}}>{{end}}' all +stdout '^example.com/deprecated/a@v1.0.0 <> => example.com/deprecated/b@v1.0.0 $' + +-- go.mod.allreplaced -- +module m + +go 1.17 + +require example.com/deprecated/a v1.0.0 + +replace example.com/deprecated/a => example.com/deprecated/b v1.0.0 +-- go.mod.onereplaced -- +module m + +go 1.17 + +require example.com/deprecated/a v1.0.0 + +replace example.com/deprecated/a v1.0.0 => example.com/deprecated/b v1.0.0 +-- go.mod.dirreplacement -- +module m + +go 1.17 + +require example.com/deprecated/a v1.0.0 + +replace example.com/deprecated/a => ./a +-- go.mod.latestreplaced -- +module m + +go 1.17 + +require example.com/deprecated/a v1.0.0 + +replace ( + example.com/deprecated/a => example.com/deprecated/b v1.0.0 + example.com/deprecated/b v1.9.0 => ./b +) +-- go.sum -- +example.com/deprecated/b v1.0.0/go.mod h1:b19J9ywRGviY7Nq4aJ1WBJ+A7qUlEY9ihp22yI4/F6M= +-- a/go.mod -- +module example.com/deprecated/a + +go 1.17 +-- b/go.mod -- +// Deprecated: in ./b +module example.com/deprecated/b + +go 1.17 diff --git a/src/cmd/go/testdata/script/mod_list_retract.txt b/src/cmd/go/testdata/script/mod_list_retract.txt index 3ba53bc59691c2a22fde558c62c2f5f278c92afd..4b133485152da0b6315c529dff65d77c28fe056e 100644 --- a/src/cmd/go/testdata/script/mod_list_retract.txt +++ b/src/cmd/go/testdata/script/mod_list_retract.txt @@ -29,12 +29,12 @@ go list -m -retracted -f '{{with .Retracted}}retracted{{end}}' example.com/retra go list -m -f '{{with .Retracted}}retracted{{end}}' example.com/retract@v1.0.0-unused ! stdout . -# 'go list -m -retracted mod@version' shows an error if the go.mod that should -# contain the retractions is not available. -! go list -m -retracted example.com/retract/missingmod@v1.0.0 -stderr '^go list -m: loading module retractions for example.com/retract/missingmod@v1.0.0: .*404 Not Found$' -go list -e -m -retracted -f '{{.Error.Err}}' example.com/retract/missingmod@v1.0.0 -stdout '^loading module retractions for example.com/retract/missingmod@v1.0.0: .*404 Not Found$' +# 'go list -m -retracted mod@version' does not show an error if the module +# that would contain the retraction is unavailable. See #45305. +go list -m -retracted -f '{{.Path}} {{.Version}} {{.Error}}' example.com/retract/missingmod@v1.0.0 +stdout '^example.com/retract/missingmod v1.0.0 $' +exists $GOPATH/pkg/mod/cache/download/example.com/retract/missingmod/@v/v1.9.0.info +! exists $GOPATH/pkg/mod/cache/download/example.com/retract/missingmod/@v/v1.9.0.mod # 'go list -m -retracted mod@version' shows retractions. go list -m -retracted example.com/retract@v1.0.0-unused diff --git a/src/cmd/go/testdata/script/mod_list_std.txt b/src/cmd/go/testdata/script/mod_list_std.txt index baf7908ab93d2a946c5a8b4a1cb8c00ad816fdd8..f4e0433d8a0e171f585673c3664306a20fcc49b6 100644 --- a/src/cmd/go/testdata/script/mod_list_std.txt +++ b/src/cmd/go/testdata/script/mod_list_std.txt @@ -48,18 +48,20 @@ stdout ^vendor/golang.org/x/crypto/internal/subtle ! stdout ^golang\.org/x # Within the std module, the dependencies of the non-vendored packages within -# std should appear to come from modules, but they should be loaded from the -# vendor directory (just like ordinary vendored module dependencies). +# std should appear to be packages beginning with 'vendor/', not 'golang.org/…' +# module dependencies. go list all -stdout ^golang.org/x/ +! stdout ^golang.org/x/ ! stdout ^std/ ! stdout ^cmd/ -! stdout ^vendor/ +stdout ^vendor/ go list -deps -f '{{if not .Standard}}{{.ImportPath}}{{end}}' std -! stdout ^vendor/golang.org/x/net/http2/hpack -stdout ^golang.org/x/net/http2/hpack +! stdout . + +# However, the 'golang.org/…' module dependencies should resolve to those same +# directories. go list -f '{{.Dir}}' golang.org/x/net/http2/hpack stdout $GOROOT[/\\]src[/\\]vendor diff --git a/src/cmd/go/testdata/script/mod_list_sums.txt b/src/cmd/go/testdata/script/mod_list_sums.txt new file mode 100644 index 0000000000000000000000000000000000000000..86c528f82907b0d2989b014bf49f154ba4b1353d --- /dev/null +++ b/src/cmd/go/testdata/script/mod_list_sums.txt @@ -0,0 +1,32 @@ +# https://golang.org/issue/41297: 'go list -m' should not require go.sum with +# -versions or when all args are version queries. + +go mod init m +go mod edit -require=rsc.io/quote@v1.5.1 + +go list -m -mod=readonly rsc.io/quote@latest +stdout '^rsc\.io/quote v1\.5\.2$' +! stderr . + +go list -m -mod=readonly -versions rsc.io/quote +stdout 'rsc\.io/quote v1\.0\.0 .* v1\.5\.3-pre1$' +! stderr . + +# Incidentally fetching the required version of a module records its checksum, +# just because it happens to be in the build list, and recording the checksum +# triggers an error under -mod=readonly. +# +# TODO(#41297): This should not be an error. +! go list -m -mod=readonly rsc.io/quote@' should fail if any of the mods lacks an explicit version. +! go list -m example.com/printversion +stderr 'go: cannot match "example.com/printversion" without -versions or an explicit version: go.mod file not found in current directory or any parent directory; see ''go help modules''$' +! stdout 'example.com/version' + # 'go list -m' with wildcards should fail. Wildcards match modules in the # build list, so they aren't meaningful outside a module. ! go list -m ... @@ -164,6 +169,8 @@ go build -n -o ignore ./stdonly/stdonly.go # 'go build' should succeed for standard-library packages. go build -n fmt +# 'go build' should use the latest version of the Go language. +go build ./newgo/newgo.go # 'go doc' without arguments implicitly operates on the current directory, and should fail. # TODO(golang.org/issue/32027): currently, it succeeds. @@ -200,10 +207,6 @@ stderr 'needmod[/\\]needmod.go:10:2: no required module provides package example go install cmd/addr2line ! stderr . -# 'go run' with a verison should fail due to syntax. -! go run example.com/printversion@v1.0.0 -stderr 'can only use path@version syntax with' - # 'go run' should fail if a package argument must be resolved to a module. ! go run example.com/printversion stderr '^no required module provides package example.com/printversion: go.mod file not found in current directory or any parent directory; see ''go help modules''$' @@ -326,3 +329,15 @@ func Test(t *testing.T) { fmt.Println("stdonly was tested") } +-- newgo/newgo.go -- +// Package newgo requires Go 1.14 or newer. +package newgo + +import "io" + +const C = 299_792_458 + +type ReadWriteCloser interface { + io.ReadCloser + io.WriteCloser +} diff --git a/src/cmd/go/testdata/script/mod_overlay.txt b/src/cmd/go/testdata/script/mod_overlay.txt index 92e79c725a3faf0d426a4e6773d4564536e8c9c3..86ab04bd3cfab4a8c4a594dce374bbff8a2e1f46 100644 --- a/src/cmd/go/testdata/script/mod_overlay.txt +++ b/src/cmd/go/testdata/script/mod_overlay.txt @@ -21,7 +21,7 @@ go list -deps -overlay overlay.json . cd $WORK/gopath/src/get-doesnt-add-dep cp $WORK/overlay/get_doesnt_add_dep_go_mod $WORK/want_go_mod ! go get -d -overlay overlay.json . -stderr 'overlaid files can''t be opened for write' +stderr '^go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay$' cmp $WORK/overlay/get_doesnt_add_dep_go_mod $WORK/want_go_mod # Content of overlaid go.sum is used. @@ -41,10 +41,10 @@ go mod verify -overlay overlay.json # attempting to update the file cp incomplete-sum-file $WORK/overlay/overlay-sum-used-correct-sums ! go get -d -overlay overlay.json . -stderr 'overlaid files can''t be opened for write' +stderr '^go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay$' cmp incomplete-sum-file $WORK/overlay/overlay-sum-used-correct-sums ! go mod tidy -overlay overlay.json -stderr 'overlaid files can''t be opened for write' +stderr '^go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay$' cmp incomplete-sum-file $WORK/overlay/overlay-sum-used-correct-sums # -overlay works with -modfile. @@ -56,7 +56,7 @@ go list -modfile=alternate.mod -overlay overlay.json . stdout 'found.the/module' # Even with -modfile, overlaid files can't be opened for write. ! go get -modfile=alternate.mod -overlay overlay.json -d rsc.io/quote -stderr 'overlaid files can''t be opened for write' +stderr '^go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay$' # Carving out a module by adding an overlaid go.mod file cd $WORK/gopath/src/carve @@ -78,7 +78,7 @@ go list -overlay overlay.json all stdout ^carve2/nomod$ # Editing go.mod file fails because overlay is read only ! go get -overlay overlay.json -d rsc.io/quote -stderr 'overlaid files can''t be opened for write' +stderr '^go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay$' ! grep rsc.io/quote $WORK/overlay/carve2-nomod-go.mod # Editing go.mod file succeeds because we use -modfile to redirect to same file go get -overlay overlay.json -modfile $WORK/overlay/carve2-nomod-go.mod -d rsc.io/quote diff --git a/src/cmd/go/testdata/script/mod_prefer_compatible.txt b/src/cmd/go/testdata/script/mod_prefer_compatible.txt index aa6260f63c82ee1b7a8d0c7e3c1c1dd3ccaa4acd..1b408c3e9e925d02cf6ea590bc721fb4880b2f7a 100644 --- a/src/cmd/go/testdata/script/mod_prefer_compatible.txt +++ b/src/cmd/go/testdata/script/mod_prefer_compatible.txt @@ -23,8 +23,8 @@ stdout '^github.com/russross/blackfriday v1\.' go list -m github.com/russross/blackfriday@upgrade stdout '^github.com/russross/blackfriday v1\.' -go list -m github.com/russross/blackfriday@patch -stdout '^github.com/russross/blackfriday v1\.' +! go list -m github.com/russross/blackfriday@patch +stderr '^go list -m: github.com/russross/blackfriday@patch: can''t query version "patch" of module github.com/russross/blackfriday: no existing version is required$' # If we're fetching directly from version control, ignored +incompatible # versions should also be omitted by 'go list'. diff --git a/src/cmd/go/testdata/script/mod_proxy_https.txt b/src/cmd/go/testdata/script/mod_proxy_https.txt index a23090cd0ad04ac1d294e87e6da7c00b71ec034e..a5e28dd0b9771b04b68c7d3dc5b82fdca9934960 100644 --- a/src/cmd/go/testdata/script/mod_proxy_https.txt +++ b/src/cmd/go/testdata/script/mod_proxy_https.txt @@ -10,6 +10,7 @@ stderr 'invalid proxy URL.*proxydir' # GOPROXY HTTPS paths may elide the "https://" prefix. # (See golang.org/issue/32191.) env GOPROXY=proxy.golang.org +env GOSUMDB= go list -versions -m golang.org/x/text -- go.mod -- diff --git a/src/cmd/go/testdata/script/mod_query.txt b/src/cmd/go/testdata/script/mod_query.txt index e10185709d95a6083121d9dda6efee196d44aa78..a75f86ed7c5729d3c4b55734ae48b33f2f68170b 100644 --- a/src/cmd/go/testdata/script/mod_query.txt +++ b/src/cmd/go/testdata/script/mod_query.txt @@ -1,9 +1,7 @@ env GO111MODULE=on -# Populate go.sum. # TODO(golang.org/issue/41297): we shouldn't need go.sum. None of the commands # below depend on the build list. -go mod download go list -m -versions rsc.io/quote stdout '^rsc.io/quote v1.0.0 v1.1.0 v1.2.0 v1.2.1 v1.3.0 v1.4.0 v1.5.0 v1.5.1 v1.5.2 v1.5.3-pre1$' @@ -36,6 +34,9 @@ stdout 'no matching versions for query ">v1.5.3"' module x require rsc.io/quote v1.0.0 +-- go.sum -- +rsc.io/quote v1.0.0 h1:kQ3IZQzPTiDJxSZI98YaWgxFEhlNdYASHvh+MplbViw= +rsc.io/quote v1.0.0/go.mod h1:v83Ri/njykPcgJltBc/gEkJTmjTsNgtO1Y7vyIK1CQA= -- use.go -- package use diff --git a/src/cmd/go/testdata/script/mod_readonly.txt b/src/cmd/go/testdata/script/mod_readonly.txt index 176be7296794a7ebf38b1be8c8f1188840ec2a80..d05ad2a3174f8ed5066299fb25516d131e654a08 100644 --- a/src/cmd/go/testdata/script/mod_readonly.txt +++ b/src/cmd/go/testdata/script/mod_readonly.txt @@ -89,7 +89,7 @@ stderr '^no required module provides package rsc.io/quote; to add it:\n\tgo get -- go.mod -- module m -go 1.20 +go 1.16 -- x.go -- package x @@ -104,7 +104,7 @@ require ( -- go.mod.redundant -- module m -go 1.20 +go 1.16 require ( rsc.io/quote v1.5.2 @@ -114,7 +114,7 @@ require ( -- go.mod.indirect -- module m -go 1.20 +go 1.16 require ( rsc.io/quote v1.5.2 // indirect @@ -124,7 +124,7 @@ require ( -- go.mod.untidy -- module m -go 1.20 +go 1.16 require ( rsc.io/sampler v1.3.0 // indirect diff --git a/src/cmd/go/testdata/script/mod_replace.txt b/src/cmd/go/testdata/script/mod_replace.txt index dc9667f1d0d84c7c37f4e29c6c003dc173fb4662..a0a367fb1db11c7765ae0f09945fe13bbe2fe622 100644 --- a/src/cmd/go/testdata/script/mod_replace.txt +++ b/src/cmd/go/testdata/script/mod_replace.txt @@ -48,7 +48,7 @@ stderr 'module rsc.io/quote/v3@upgrade found \(v3.0.0, replaced by ./local/rsc.i # The reported Dir and GoMod for a replaced module should be accurate. cp go.mod.orig go.mod go mod edit -replace=rsc.io/quote/v3=not-rsc.io/quote@v0.1.0-nomod -go mod download +go mod download rsc.io/quote/v3 go list -m -f '{{.Path}} {{.Version}} {{.Dir}} {{.GoMod}}{{with .Replace}} => {{.Path}} {{.Version}} {{.Dir}} {{.GoMod}}{{end}}' rsc.io/quote/v3 stdout '^rsc.io/quote/v3 v3.0.0 '$GOPATH'[/\\]pkg[/\\]mod[/\\]not-rsc.io[/\\]quote@v0.1.0-nomod '$GOPATH'[/\\]pkg[/\\]mod[/\\]cache[/\\]download[/\\]not-rsc.io[/\\]quote[/\\]@v[/\\]v0.1.0-nomod.mod => not-rsc.io/quote v0.1.0-nomod '$GOPATH'[/\\]pkg[/\\]mod[/\\]not-rsc.io[/\\]quote@v0.1.0-nomod '$GOPATH'[/\\]pkg[/\\]mod[/\\]cache[/\\]download[/\\]not-rsc.io[/\\]quote[/\\]@v[/\\]v0.1.0-nomod.mod$' diff --git a/src/cmd/go/testdata/script/mod_replace_gopkgin.txt b/src/cmd/go/testdata/script/mod_replace_gopkgin.txt index df752d9716e538b4bb89c73679d45df8e1b395ca..d24f37b7880920f9a3f7d949fdafabe09fd92103 100644 --- a/src/cmd/go/testdata/script/mod_replace_gopkgin.txt +++ b/src/cmd/go/testdata/script/mod_replace_gopkgin.txt @@ -35,7 +35,7 @@ go list -m gopkg.in/src-d/go-git.v4 # A mismatched gopkg.in path should not be able to replace a different major version. cd ../3-to-gomod-4 ! go list -m gopkg.in/src-d/go-git.v3 -stderr '^go: gopkg\.in/src-d/go-git\.v3@v3\.2\.0 \(replaced by gopkg\.in/src-d/go-git\.v3@v3\.0\.0-20190801152248-0d1a009cbb60\): version "v3\.0\.0-20190801152248-0d1a009cbb60" invalid: go\.mod has non-\.\.\.\.v3 module path "gopkg\.in/src-d/go-git\.v4" at revision 0d1a009cbb60$' +stderr '^go list -m: gopkg\.in/src-d/go-git\.v3@v3\.2\.0 \(replaced by gopkg\.in/src-d/go-git\.v3@v3\.0\.0-20190801152248-0d1a009cbb60\): version "v3\.0\.0-20190801152248-0d1a009cbb60" invalid: go\.mod has non-\.\.\.\.v3 module path "gopkg\.in/src-d/go-git\.v4" at revision 0d1a009cbb60$' -- 4-to-4/go.mod -- module golang.org/issue/34254 diff --git a/src/cmd/go/testdata/script/mod_require_exclude.txt b/src/cmd/go/testdata/script/mod_require_exclude.txt index 9156d4ce5d5fdfb47c93071a79ec09e8ef7dde35..0946dbf0bb39d0796b3f66c8d593c037c5ef51b9 100644 --- a/src/cmd/go/testdata/script/mod_require_exclude.txt +++ b/src/cmd/go/testdata/script/mod_require_exclude.txt @@ -7,16 +7,27 @@ cp go.mod go.mod.orig ! go list -mod=readonly -m all stderr '^go: ignoring requirement on excluded version rsc.io/sampler v1\.99\.99$' -stderr '^go: updates to go.mod needed, disabled by -mod=readonly$' +stderr '^go: updates to go.mod needed, disabled by -mod=readonly; to update it:\n\tgo mod tidy$' ! stdout '^rsc.io/sampler v1.99.99' cmp go.mod go.mod.orig ! go list -mod=vendor -m rsc.io/sampler stderr '^go: ignoring requirement on excluded version rsc.io/sampler v1\.99\.99$' -stderr '^go list -m: module rsc.io/sampler: can''t resolve module using the vendor directory\n\t\(Use -mod=mod or -mod=readonly to bypass\.\)$' +stderr '^go: updates to go.mod needed, disabled by -mod=vendor; to update it:\n\tgo mod tidy$' ! stdout '^rsc.io/sampler v1.99.99' cmp go.mod go.mod.orig +# The failure message should be clear when -mod=vendor is implicit. + +go mod edit -go=1.14 +! go list -m rsc.io/sampler +stderr '^go: ignoring requirement on excluded version rsc.io/sampler v1\.99\.99$' +stderr '^go: updates to go.mod needed, disabled by -mod=vendor\n\t\(Go version in go.mod is at least 1.14 and vendor directory exists\.\)\n\tto update it:\n\tgo mod tidy$' +! stdout '^rsc.io/sampler v1.99.99' +go mod edit -go=1.13 +cmp go.mod go.mod.orig + + # With the selected version excluded, commands that load only modules should # drop the excluded module. @@ -58,7 +69,11 @@ module x go 1.13 exclude rsc.io/sampler v1.99.99 + require rsc.io/sampler v1.99.99 +-- vendor/modules.txt -- +# rsc.io/sampler v1.99.99 +## explicit -- go.moddrop -- module x diff --git a/src/cmd/go/testdata/script/mod_retention.txt b/src/cmd/go/testdata/script/mod_retention.txt index a4441c4b3c7fba858b418d6d1d1571c0ec72009f..7a371b18068cd2e6619fc7ffdf14ddf03fa63898 100644 --- a/src/cmd/go/testdata/script/mod_retention.txt +++ b/src/cmd/go/testdata/script/mod_retention.txt @@ -62,9 +62,10 @@ cmp go.mod go.mod.tidy # A missing "go" version directive should be added. # However, that should not remove other redundant requirements. +# In fact, it may *add* redundant requirements due to activating lazy loading. cp go.mod.nogo go.mod go list -mod=mod all -cmpenv go.mod go.mod.currentgo +cmpenv go.mod go.mod.addedgo -- go.mod.tidy -- @@ -133,7 +134,7 @@ require ( rsc.io/sampler v1.3.0 // indirect rsc.io/testonly v1.0.0 // indirect ) --- go.mod.currentgo -- +-- go.mod.addedgo -- module m go $goversion @@ -143,3 +144,5 @@ require ( rsc.io/sampler v1.3.0 // indirect rsc.io/testonly v1.0.0 // indirect ) + +require golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect diff --git a/src/cmd/go/testdata/script/mod_retract.txt b/src/cmd/go/testdata/script/mod_retract.txt index a52e05bc72e6a8ab77250893f8663c0f05c0b051..4f95ece8d7aaaeb72016d12d273d9c61fe60befe 100644 --- a/src/cmd/go/testdata/script/mod_retract.txt +++ b/src/cmd/go/testdata/script/mod_retract.txt @@ -1,8 +1,5 @@ cp go.mod go.mod.orig -# Populate go.sum. -go mod download - # 'go list pkg' does not report an error when a retracted version is used. go list -e -f '{{if .Error}}{{.Error}}{{end}}' ./use ! stdout . @@ -32,6 +29,11 @@ go 1.15 require example.com/retract v1.0.0-bad +-- go.sum -- +example.com/retract v1.0.0-bad h1:liAW69rbtjY67x2CcNzat668L/w+YGgNX3lhJsWIJis= +example.com/retract v1.0.0-bad/go.mod h1:0DvGGofJ9hr1q63cBrOY/jSY52OwhRGA0K47NE80I5Y= +example.com/retract/self/prev v1.1.0 h1:0/8I/GTG+1eJTFeDQ/fUbgrMsVHHyKhh3Z8DSZp1fuA= +example.com/retract/self/prev v1.1.0/go.mod h1:xl2EcklWuZZHVtHWcpzfSJQmnzAGpKZYpA/Wto7SZN4= -- use/use.go -- package use diff --git a/src/cmd/go/testdata/script/mod_retract_rationale.txt b/src/cmd/go/testdata/script/mod_retract_rationale.txt index 4d3a3d67c65b5026a3f7724bef209414e71d55a0..823c384e488ff6d109d12fd2ae2e01bd419ffa70 100644 --- a/src/cmd/go/testdata/script/mod_retract_rationale.txt +++ b/src/cmd/go/testdata/script/mod_retract_rationale.txt @@ -29,7 +29,7 @@ cmp stdout multiline # 'go get' should omit long messages. go get -d example.com/retract/rationale@v1.0.0-long -stderr '^go: warning: example.com/retract/rationale@v1.0.0-long: retracted by module author: \(rationale omitted: too long\)' +stderr '^go: warning: example.com/retract/rationale@v1.0.0-long: retracted by module author: \(message omitted: too long\)' # 'go list' should show the full message. go list -m -retracted -f '{{.Retracted}}' example.com/retract/rationale @@ -38,7 +38,7 @@ stdout '^\[lo{500}ng\]$' # 'go get' should omit messages with unprintable characters. go get -d example.com/retract/rationale@v1.0.0-unprintable -stderr '^go: warning: example.com/retract/rationale@v1.0.0-unprintable: retracted by module author: \(rationale omitted: contains non-printable characters\)' +stderr '^go: warning: example.com/retract/rationale@v1.0.0-unprintable: retracted by module author: \(message omitted: contains non-printable characters\)' # 'go list' should show the full message. go list -m -retracted -f '{{.Retracted}}' example.com/retract/rationale diff --git a/src/cmd/go/testdata/script/mod_retract_replace.txt b/src/cmd/go/testdata/script/mod_retract_replace.txt index 770aea41a593beec60ed7d32f455047f2dfd5ec0..9cd714739abf556dc8818d3ef6f9c93f0a68e66e 100644 --- a/src/cmd/go/testdata/script/mod_retract_replace.txt +++ b/src/cmd/go/testdata/script/mod_retract_replace.txt @@ -5,8 +5,10 @@ go get -d # The latest version, v1.9.0, is not available on the proxy. -! go list -m -retracted example.com/retract/missingmod -stderr '^go list -m: loading module retractions for example.com/retract/missingmod@v1.0.0: .*404 Not Found$' +go list -m -retracted example.com/retract/missingmod +stdout '^example.com/retract/missingmod v1.0.0$' +exists $GOPATH/pkg/mod/cache/download/example.com/retract/missingmod/@v/v1.9.0.info +! exists $GOPATH/pkg/mod/cache/download/example.com/retract/missingmod/@v/v1.9.0.mod # If we replace that version, we should see retractions. go mod edit -replace=example.com/retract/missingmod@v1.9.0=./missingmod-v1.9.0 diff --git a/src/cmd/go/testdata/script/mod_retract_versions.txt b/src/cmd/go/testdata/script/mod_retract_versions.txt new file mode 100644 index 0000000000000000000000000000000000000000..012fa15f420c8678e74448e6ffa1919c8dcce8d9 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_retract_versions.txt @@ -0,0 +1,22 @@ +# https://golang.org/issue/44296: the --versions flag should not affect +# the version reported by 'go list' in case of retractions. + +env FMT='{{.Path}}{{with .Error}}: {{printf "%q" .Err}}{{end}} {{printf "%q" .Version}}{{with .Versions}} {{.}}{{end}}' + +go list -m -e -f $FMT example.com/retract/self/pseudo +stdout '^example.com/retract/self/pseudo: "module example.com/retract/self/pseudo: not a known dependency" ""$' + +go list -m -e -f $FMT example.com/retract/self/pseudo@latest +stdout '^example.com/retract/self/pseudo: "module example.com/retract/self/pseudo: no matching versions for query \\"latest\\"" "latest"$' + + +go list -m -e -f $FMT --versions example.com/retract/self/pseudo +stdout '^example.com/retract/self/pseudo ""$' + +go list -m -e -f $FMT --versions example.com/retract/self/pseudo@latest +stdout '^example.com/retract/self/pseudo: "module example.com/retract/self/pseudo: no matching versions for query \\"latest\\"" "latest"$' + +-- go.mod -- +module test + +go 1.17 diff --git a/src/cmd/go/testdata/script/mod_run_nonmain.txt b/src/cmd/go/testdata/script/mod_run_nonmain.txt new file mode 100644 index 0000000000000000000000000000000000000000..036755d2d1ac2781df0d92185f59975520d93328 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_run_nonmain.txt @@ -0,0 +1,18 @@ +! go run $PWD +! stderr 'no packages loaded' +stderr '^package example.net/nonmain is not a main package$' + +! go run . +stderr '^package example.net/nonmain is not a main package$' + +! go run ./... +stderr '^go: warning: "\./\.\.\." matched only non-main packages$' +stderr '^go run: no packages loaded from \./\.\.\.$' + +-- go.mod -- +module example.net/nonmain + +go 1.17 +-- nonmain.go -- +// Package nonmain is not a main package. +package nonmain diff --git a/src/cmd/go/testdata/script/mod_run_pkg_version.txt b/src/cmd/go/testdata/script/mod_run_pkg_version.txt new file mode 100644 index 0000000000000000000000000000000000000000..e921fab508540433d4c240af4bfb68346e53b03b --- /dev/null +++ b/src/cmd/go/testdata/script/mod_run_pkg_version.txt @@ -0,0 +1,103 @@ +# This test checks the behavior of 'go run' with a 'cmd@version' argument. +# Most of 'go run' is covered in other tests. +# mod_install_pkg_version covers most of the package loading functionality. +# This test focuses on 'go run' behavior specific to this mode. +[short] skip + +# 'go run pkg@version' works outside a module. +env GO111MODULE=auto +go run example.com/cmd/a@v1.0.0 +stdout '^a@v1.0.0$' + + +# 'go run pkg@version' reports an error if modules are disabled. +env GO111MODULE=off +! go run example.com/cmd/a@v1.0.0 +stderr '^go: modules disabled by GO111MODULE=off; see ''go help modules''$' +env GO111MODULE=on + + +# 'go run pkg@version' ignores go.mod in the current directory. +cd m +cp go.mod go.mod.orig +! go list -m all +stderr '^go list -m: example.com/cmd@v1.1.0-doesnotexist: missing go.sum entry; to add it:\n\tgo mod download example.com/cmd$' +go run example.com/cmd/a@v1.0.0 +stdout '^a@v1.0.0$' +cmp go.mod go.mod.orig +cd .. + + +# 'go install pkg@version' works on a module that doesn't have a go.mod file +# and with a module whose go.mod file has missing requirements. +# With a proxy, the two cases are indistinguishable. +go run rsc.io/fortune@v1.0.0 +stderr '^go: found rsc.io/quote in rsc.io/quote v1.5.2$' +stderr '^Hello, world.$' + + +# 'go run pkg@version' should report an error if pkg is not a main package. +! go run example.com/cmd/err@v1.0.0 +stderr '^package example.com/cmd/err is not a main package$' + + +# 'go run pkg@version' should report errors if the module contains +# replace or exclude directives. +go mod download example.com/cmd@v1.0.0-replace +! go run example.com/cmd/a@v1.0.0-replace +cmp stderr replace-err + +go mod download example.com/cmd@v1.0.0-exclude +! go run example.com/cmd/a@v1.0.0-exclude +cmp stderr exclude-err + + +# 'go run dir@version' works like a normal 'go run' command if +# dir is a relative or absolute path. +go mod download rsc.io/fortune@v1.0.0 +! go run $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0 +stderr '^go: go\.mod file not found in current directory or any parent directory; see ''go help modules''$' +! go run ../pkg/mod/rsc.io/fortune@v1.0.0 +stderr '^go: go\.mod file not found in current directory or any parent directory; see ''go help modules''$' +mkdir tmp +cd tmp +go mod init tmp +go mod edit -require=rsc.io/fortune@v1.0.0 +! go run -mod=readonly $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0 +stderr '^missing go\.sum entry for module providing package rsc\.io/fortune; to add:\n\tgo mod download rsc\.io/fortune$' +! go run -mod=readonly ../../pkg/mod/rsc.io/fortune@v1.0.0 +stderr '^missing go\.sum entry for module providing package rsc\.io/fortune; to add:\n\tgo mod download rsc\.io/fortune$' +cd .. +rm tmp + + +# 'go run' does not interpret @version arguments after the first. +go run example.com/cmd/a@v1.0.0 example.com/doesnotexist@v1.0.0 +stdout '^a@v1.0.0$' + + +# 'go run pkg@version' succeeds when -mod=readonly is set explicitly. +# Verifies #43278. +go run -mod=readonly example.com/cmd/a@v1.0.0 +stdout '^a@v1.0.0$' + +-- m/go.mod -- +module m + +go 1.16 + +require example.com/cmd v1.1.0-doesnotexist +-- x/x.go -- +package main + +func main() {} +-- replace-err -- +go run: example.com/cmd/a@v1.0.0-replace (in example.com/cmd@v1.0.0-replace): + The go.mod file for the module providing named packages contains one or + more replace directives. It must not contain directives that would cause + it to be interpreted differently than if it were the main module. +-- exclude-err -- +go run: example.com/cmd/a@v1.0.0-exclude (in example.com/cmd@v1.0.0-exclude): + The go.mod file for the module providing named packages contains one or + more exclude directives. It must not contain directives that would cause + it to be interpreted differently than if it were the main module. diff --git a/src/cmd/go/testdata/script/mod_run_pkgerror.txt b/src/cmd/go/testdata/script/mod_run_pkgerror.txt new file mode 100644 index 0000000000000000000000000000000000000000..48f900dd3466af59d632e5a713c129ca407051d4 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_run_pkgerror.txt @@ -0,0 +1,32 @@ +# https://golang.org/issue/39986: files reported as invalid by go/build should +# be listed in InvalidGoFiles. + +go list -e -f '{{.Incomplete}}{{"\n"}}{{.Error}}{{"\n"}}{{.InvalidGoFiles}}{{"\n"}}' . +stdout '^true\nfound packages m \(m\.go\) and main \(main\.go\) in '$PWD'\n\[main.go\]\n' + + +# https://golang.org/issue/45827: 'go run .' should report the same package +# errors as 'go build' and 'go list'. + +! go build +stderr '^found packages m \(m\.go\) and main \(main\.go\) in '$PWD'$' + +! go list . +stderr '^found packages m \(m\.go\) and main \(main\.go\) in '$PWD'$' + +! go run . +! stderr 'no packages loaded' +stderr '^found packages m \(m\.go\) and main \(main\.go\) in '$PWD'$' + +! go run ./... +! stderr 'no packages loaded' +stderr '^found packages m \(m\.go\) and main \(main\.go\) in '$PWD'$' + +-- go.mod -- +module m + +go 1.17 +-- m.go -- +package m +-- main.go -- +package main diff --git a/src/cmd/go/testdata/script/mod_std_vendor.txt b/src/cmd/go/testdata/script/mod_std_vendor.txt index fb954d74edbe9e76aa4f0a45e1eabf20a29d0adb..c3cde52953f86b023ecd1b8c854e3ec65dbf18ca 100644 --- a/src/cmd/go/testdata/script/mod_std_vendor.txt +++ b/src/cmd/go/testdata/script/mod_std_vendor.txt @@ -36,11 +36,11 @@ stderr 'use of vendored package' # When run within the 'std' module, 'go list -test' should report vendored -# transitive dependencies at their original module paths. +# transitive dependencies at their vendored paths. cd $GOROOT/src go list -test -f '{{range .Deps}}{{.}}{{"\n"}}{{end}}' net/http -stdout ^golang.org/x/net/http2/hpack -! stdout ^vendor/golang.org/x/net/http2/hpack +! stdout ^golang.org/x/net/http2/hpack +stdout ^vendor/golang.org/x/net/http2/hpack -- go.mod -- module m diff --git a/src/cmd/go/testdata/script/mod_sum_readonly.txt b/src/cmd/go/testdata/script/mod_sum_readonly.txt index 57c5bbeefdf0203c43f28b50d8294500b6dff7a7..113f13ea390852c5138afc1df78dd6794ca0fa1f 100644 --- a/src/cmd/go/testdata/script/mod_sum_readonly.txt +++ b/src/cmd/go/testdata/script/mod_sum_readonly.txt @@ -4,7 +4,7 @@ env GO111MODULE=on # When a sum is needed to load the build list, we get an error for the # specific module. The .mod file is not downloaded, and go.sum is not written. ! go list -m all -stderr '^go: rsc.io/quote@v1.5.2: missing go.sum entry; to add it:\n\tgo mod download rsc.io/quote$' +stderr '^go list -m: rsc.io/quote@v1.5.2: missing go.sum entry; to add it:\n\tgo mod download rsc.io/quote$' ! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.mod ! exists go.sum @@ -12,7 +12,7 @@ stderr '^go: rsc.io/quote@v1.5.2: missing go.sum entry; to add it:\n\tgo mod dow # we should see the same error. cp go.sum.h2only go.sum ! go list -m all -stderr '^go: rsc.io/quote@v1.5.2: missing go.sum entry; to add it:\n\tgo mod download rsc.io/quote$' +stderr '^go list -m: rsc.io/quote@v1.5.2: missing go.sum entry; to add it:\n\tgo mod download rsc.io/quote$' ! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.mod cmp go.sum go.sum.h2only rm go.sum @@ -21,7 +21,7 @@ rm go.sum cp go.mod go.mod.orig go mod edit -replace rsc.io/quote@v1.5.2=rsc.io/quote@v1.5.1 ! go list -m all -stderr '^go: rsc.io/quote@v1.5.2 \(replaced by rsc.io/quote@v1.5.1\): missing go.sum entry; to add it:\n\tgo mod download rsc.io/quote$' +stderr '^go list -m: rsc.io/quote@v1.5.2 \(replaced by rsc.io/quote@v1.5.1\): missing go.sum entry; to add it:\n\tgo mod download rsc.io/quote$' ! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.1.mod ! exists go.sum cp go.mod.orig go.mod diff --git a/src/cmd/go/testdata/script/mod_sumdb.txt b/src/cmd/go/testdata/script/mod_sumdb.txt index 9a688e1461ca94efdce410cafbc9d4f06146fd1d..fa3483c5cb1a510b9d5c19cbf16c4bdf116f155d 100644 --- a/src/cmd/go/testdata/script/mod_sumdb.txt +++ b/src/cmd/go/testdata/script/mod_sumdb.txt @@ -37,3 +37,9 @@ go get -d rsc.io/fortune -- go.mod.orig -- module m + +go 1.16 +-- m.go -- +package m + +import _ "rsc.io/quote" diff --git a/src/cmd/go/testdata/script/mod_sumdb_cache.txt b/src/cmd/go/testdata/script/mod_sumdb_cache.txt index 2937b2e4dcd66afce921342bcbf6ae7f13d80a0b..1b38475fb5e378c0e1d4fbb88ec266bba83fe90b 100644 --- a/src/cmd/go/testdata/script/mod_sumdb_cache.txt +++ b/src/cmd/go/testdata/script/mod_sumdb_cache.txt @@ -43,12 +43,5 @@ env GOPROXY=$proxy/sumdb-504 ! go get -d rsc.io/quote@v1.5.2 stderr 504 -# but -insecure bypasses the checksum lookup entirely -env GOINSECURE= -go get -d -insecure rsc.io/quote@v1.5.2 - -# and then it is in go.sum again -go get -d rsc.io/quote@v1.5.2 - -- go.mod.orig -- module m diff --git a/src/cmd/go/testdata/script/mod_sumdb_golang.txt b/src/cmd/go/testdata/script/mod_sumdb_golang.txt index cc0b0da474a2286521453d81ca91c1b2c9c263d5..becd88b52e7cffaa18b94b99a574b84c1ab7df2f 100644 --- a/src/cmd/go/testdata/script/mod_sumdb_golang.txt +++ b/src/cmd/go/testdata/script/mod_sumdb_golang.txt @@ -10,45 +10,73 @@ go env GOSUMDB stdout '^sum.golang.org$' # Download direct from github. + [!net] skip [!exec:git] skip env GOSUMDB=sum.golang.org env GOPROXY=direct + go get -d rsc.io/quote@v1.5.2 cp go.sum saved.sum + # Download from proxy.golang.org with go.sum entry already. # Use 'go list' instead of 'go get' since the latter may download extra go.mod # files not listed in go.sum. + go clean -modcache env GOSUMDB= env GOPROXY= -go list -x -deps rsc.io/quote + +go list -x -m all # Download go.mod files. ! stderr github stderr proxy.golang.org/rsc.io/quote ! stderr sum.golang.org/tile ! stderr sum.golang.org/lookup/rsc.io/quote + +go list -x -deps rsc.io/quote # Download module source. +! stderr github +stderr proxy.golang.org/rsc.io/quote +! stderr sum.golang.org/tile +! stderr sum.golang.org/lookup/rsc.io/quote + cmp go.sum saved.sum + # Download again. # Should use the checksum database to validate new go.sum lines, # but not need to fetch any new data from the proxy. + rm go.sum -go list -mod=mod -x rsc.io/quote + +go list -mod=mod -x -m all # Add checksums for go.mod files. +stderr sum.golang.org/tile ! stderr github ! stderr proxy.golang.org/rsc.io/quote -stderr sum.golang.org/tile stderr sum.golang.org/lookup/rsc.io/quote + +go list -mod=mod -x rsc.io/quote # Add checksums for module source. +! stderr . # Adds checksums, but for entities already in the module cache. + cmp go.sum saved.sum + # test fallback to direct + env TESTGOPROXY404=1 go clean -modcache rm go.sum -go list -mod=mod -x rsc.io/quote + +go list -mod=mod -x -m all # Download go.mod files +stderr 'proxy.golang.org.*404 testing' +stderr github.com/rsc + +go list -mod=mod -x rsc.io/quote # Download module source. stderr 'proxy.golang.org.*404 testing' stderr github.com/rsc + cmp go.sum saved.sum + -- go.mod -- module m diff --git a/src/cmd/go/testdata/script/mod_test.txt b/src/cmd/go/testdata/script/mod_test.txt index 50f00355c17e5bce4ee2621c6ee22661f35decee..76f1d7a9a4d994c20634de26305d0df2aa0ef1cb 100644 --- a/src/cmd/go/testdata/script/mod_test.txt +++ b/src/cmd/go/testdata/script/mod_test.txt @@ -60,6 +60,8 @@ go list -test -- a/go.mod.empty -- module example.com/user/a +go 1.11 + -- a/a.go -- package a diff --git a/src/cmd/go/testdata/script/mod_tidy_compat.txt b/src/cmd/go/testdata/script/mod_tidy_compat.txt new file mode 100644 index 0000000000000000000000000000000000000000..e6edef5ee3b78c83a59063f990bf4527f2493b99 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_compat.txt @@ -0,0 +1,95 @@ +# https://golang.org/issue/46141: 'go mod tidy' for a Go 1.17 module should by +# default preserve enough checksums for the module to be used by Go 1.16. +# +# We don't have a copy of Go 1.16 handy, but we can simulate it by editing the +# 'go' version in the go.mod file to 1.16, without actually updating the +# requirements to match. + +[short] skip + +env MODFMT='{{with .Module}}{{.Path}} {{.Version}}{{end}}' + + +# This module has the same module dependency graph in Go 1.16 as in Go 1.17, +# but in 1.16 requires (checksums for) additional (irrelevant) go.mod files. +# +# The module graph under both versions looks like: +# +# m ---- example.com/version v1.1.0 +# | +# + ---- example.net/lazy v0.1.0 ---- example.com/version v1.0.1 +# +# Go 1.17 avoids loading the go.mod file for example.com/version v1.0.1 +# (because it is lower than the verison explicitly required by m, +# and the module that requires it — m — specifies 'go 1.17'). +# +# That go.mod file happens not to affect the final 1.16 module graph anyway, +# so the pruned graph is equivalent to the unpruned one. + +cp go.mod go.mod.orig +go mod tidy +cmp go.mod go.mod.orig + +go list -m all +cmp stdout m_all.txt + +go mod edit -go=1.16 +go list -m all +cmp stdout m_all.txt + + +# If we explicitly drop compatibility with 1.16, we retain fewer checksums, +# which gives a cleaner go.sum file but causes 1.16 to fail in readonly mode. + +cp go.mod.orig go.mod +go mod tidy -compat=1.17 +cmp go.mod go.mod.orig + +go list -m all +cmp stdout m_all.txt + +go mod edit -go=1.16 +! go list -m all +stderr '^go list -m: example.net/lazy@v0.1.0 requires\n\texample.com/version@v1.0.1: missing go.sum entry; to add it:\n\tgo mod download example.com/version$' + + +-- go.mod -- +// Module m happens to have the exact same build list as what would be +// selected under Go 1.16, but computes that build list without looking at +// as many go.mod files. +module example.com/m + +go 1.17 + +replace example.net/lazy v0.1.0 => ./lazy + +require ( + example.com/version v1.1.0 + example.net/lazy v0.1.0 +) +-- m_all.txt -- +example.com/m +example.com/version v1.1.0 +example.net/lazy v0.1.0 => ./lazy +-- compatible.go -- +package compatible + +import ( + _ "example.com/version" + _ "example.net/lazy" +) +-- lazy/go.mod -- +// Module lazy requires example.com/version v1.0.1. +// +// However, since this module is lazy, its dependents +// should not need checksums for that version of the module +// unless they actually import packages from it. +module example.net/lazy + +go 1.17 + +require example.com/version v1.0.1 +-- lazy/lazy.go -- +package lazy + +import _ "example.com/version" diff --git a/src/cmd/go/testdata/script/mod_tidy_compat_added.txt b/src/cmd/go/testdata/script/mod_tidy_compat_added.txt new file mode 100644 index 0000000000000000000000000000000000000000..94fa79bc9f51629f471318f7fffde7dc1aaa28d7 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_compat_added.txt @@ -0,0 +1,105 @@ +# https://golang.org/issue/46141: 'go mod tidy' for a Go 1.17 module should by +# default preserve enough checksums for the module to be used by Go 1.16. +# +# We don't have a copy of Go 1.16 handy, but we can simulate it by editing the +# 'go' version in the go.mod file to 1.16, without actually updating the +# requirements to match. + +[short] skip + +env MODFMT='{{with .Module}}{{.Path}} {{.Version}}{{end}}' + + +# For this module, Go 1.17 produces an error for one module, and Go 1.16 +# produces a different error for a different module. + +cp go.mod go.mod.orig + +! go mod tidy + +stderr '^example\.com/m imports\n\texample\.net/added: module example\.net/added@latest found \(v0\.3\.0, replaced by \./a1\), but does not contain package example\.net/added$' + +cmp go.mod go.mod.orig + + +# When we run 'go mod tidy -e', we should proceed past the first error and follow +# it with a second error describing the version descrepancy. +# +# We should not provide advice on how to push past the version descrepancy, +# because the '-e' flag should already do that, writing out an otherwise-tidied +# go.mod file. + +go mod tidy -e + +stderr '^example\.com/m imports\n\texample\.net/added: module example\.net/added@latest found \(v0\.3\.0, replaced by \./a1\), but does not contain package example\.net/added\nexample\.net/added failed to load from any module,\n\tbut go 1\.16 would load it from example\.net/added@v0\.2\.0$' + +! stderr '\n\tgo mod tidy' + +cmp go.mod go.mod.tidy + + +-- go.mod -- +module example.com/m + +go 1.17 + +replace ( + example.net/added v0.1.0 => ./a1 + example.net/added v0.2.0 => ./a2 + example.net/added v0.3.0 => ./a1 + example.net/lazy v0.1.0 => ./lazy + example.net/pruned v0.1.0 => ./pruned +) + +require ( + example.net/added v0.1.0 + example.net/lazy v0.1.0 +) +-- go.mod.tidy -- +module example.com/m + +go 1.17 + +replace ( + example.net/added v0.1.0 => ./a1 + example.net/added v0.2.0 => ./a2 + example.net/added v0.3.0 => ./a1 + example.net/lazy v0.1.0 => ./lazy + example.net/pruned v0.1.0 => ./pruned +) + +require example.net/lazy v0.1.0 +-- m.go -- +package m + +import ( + _ "example.net/added" + _ "example.net/lazy" +) + +-- a1/go.mod -- +module example.net/added + +go 1.17 +-- a2/go.mod -- +module example.net/added + +go 1.17 +-- a2/added.go -- +package added + +-- lazy/go.mod -- +module example.net/lazy + +go 1.17 + +require example.net/pruned v0.1.0 +-- lazy/lazy.go -- +package lazy + +-- pruned/go.mod -- +module example.net/pruned + +go 1.17 + +require example.net/added v0.2.0 diff --git a/src/cmd/go/testdata/script/mod_tidy_compat_ambiguous.txt b/src/cmd/go/testdata/script/mod_tidy_compat_ambiguous.txt new file mode 100644 index 0000000000000000000000000000000000000000..c544cb7413fcdc6b4f791d98429fd7c8e3bc7feb --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_compat_ambiguous.txt @@ -0,0 +1,98 @@ +# https://golang.org/issue/46141: 'go mod tidy' for a Go 1.17 module should by +# default preserve enough checksums for the module to be used by Go 1.16. +# +# We don't have a copy of Go 1.16 handy, but we can simulate it by editing the +# 'go' version in the go.mod file to 1.16, without actually updating the +# requirements to match. + +[short] skip + +env MODFMT='{{with .Module}}{{.Path}} {{.Version}}{{end}}' + +# For this module, the dependency providing package +# example.net/ambiguous/nested/pkg is unambiguous in Go 1.17 (because only one +# root of the module graph contains the package), whereas it is ambiguous in +# Go 1.16 (because two different modules contain plausible packages and Go 1.16 +# does not privilege roots above other dependencies). +# +# However, the overall build list is identical for both versions. + +cp go.mod go.mod.orig + +! go mod tidy + +stderr '^example\.com/m imports\n\texample\.net/indirect imports\n\texample\.net/ambiguous/nested/pkg loaded from example\.net/ambiguous/nested@v0\.1\.0,\n\tbut go 1.16 would fail to locate it:\n\tambiguous import: found package example\.net/ambiguous/nested/pkg in multiple modules:\n\texample\.net/ambiguous v0.1.0 \(.*\)\n\texample\.net/ambiguous/nested v0.1.0 \(.*\)\n\n' + +stderr '\n\nTo proceed despite packages unresolved in go 1\.16:\n\tgo mod tidy -e\nIf reproducibility with go 1.16 is not needed:\n\tgo mod tidy -compat=1\.17\nFor other options, see:\n\thttps://golang\.org/doc/modules/pruning\n' + +cmp go.mod go.mod.orig + + +# If we run 'go mod tidy -e', we should still save enough checksums to run +# 'go list -m all' reproducibly with go 1.16, even though we can't list +# the specific package. + +go mod tidy -e +! stderr '\n\tgo mod tidy' +cmp go.mod go.mod.orig + +go list -m all +cmp stdout all-m.txt + +go list -f $MODFMT example.net/ambiguous/nested/pkg +stdout '^example.net/ambiguous/nested v0\.1\.0$' +! stderr . + +go mod edit -go=1.16 +go list -m all +cmp stdout all-m.txt + +! go list -f $MODFMT example.net/ambiguous/nested/pkg +stderr '^ambiguous import: found package example\.net/ambiguous/nested/pkg in multiple modules:\n\texample\.net/ambiguous v0\.1\.0 \(.*\)\n\texample\.net/ambiguous/nested v0\.1\.0 \(.*\)\n' + + +# On the other hand, if we use -compat=1.17, 1.16 can't even load +# the build list (due to missing checksums). + +cp go.mod.orig go.mod +go mod tidy -compat=1.17 +! stderr . +go list -m all +cmp stdout all-m.txt + +go mod edit -go=1.16 +! go list -m all +stderr '^go list -m: example\.net/indirect@v0\.1\.0 requires\n\texample\.net/ambiguous@v0\.1\.0: missing go\.sum entry; to add it:\n\tgo mod download example\.net/ambiguous\n' + + +-- go.mod -- +module example.com/m + +go 1.17 + +replace example.net/indirect v0.1.0 => ./indirect + +require ( + example.net/ambiguous/nested v0.1.0 // indirect + example.net/indirect v0.1.0 +) +-- all-m.txt -- +example.com/m +example.net/ambiguous v0.1.0 +example.net/ambiguous/nested v0.1.0 +example.net/indirect v0.1.0 => ./indirect +-- m.go -- +package m + +import _ "example.net/indirect" + +-- indirect/go.mod -- +module example.net/indirect + +go 1.17 + +require example.net/ambiguous v0.1.0 +-- indirect/indirect.go -- +package indirect + +import _ "example.net/ambiguous/nested/pkg" diff --git a/src/cmd/go/testdata/script/mod_tidy_compat_deleted.txt b/src/cmd/go/testdata/script/mod_tidy_compat_deleted.txt new file mode 100644 index 0000000000000000000000000000000000000000..dcf13e204902e1c4276532e8c754b5c164fb37a5 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_compat_deleted.txt @@ -0,0 +1,128 @@ +# https://golang.org/issue/46141: 'go mod tidy' for a Go 1.17 module should by +# default preserve enough checksums for the module to be used by Go 1.16. +# +# We don't have a copy of Go 1.16 handy, but we can simulate it by editing the +# 'go' version in the go.mod file to 1.16, without actually updating the +# requirements to match. + +[short] skip + +env MODFMT='{{with .Module}}{{.Path}} {{.Version}}{{end}}' + + +# For this module, the "deleted" dependency contains an imported package, but +# Go 1.16 selects a higher version (in which that package has been deleted). + +cp go.mod go.mod.orig + +! go mod tidy + +stderr '^example\.com/m imports\n\texample\.net/deleted loaded from example\.net/deleted@v0\.1\.0,\n\tbut go 1\.16 would fail to locate it in example\.net/deleted@v0\.2\.0\n\n' + +stderr '\n\nTo upgrade to the versions selected by go 1.16, leaving some packages unresolved:\n\tgo mod tidy -e -go=1\.16 && go mod tidy -e -go=1\.17\nIf reproducibility with go 1.16 is not needed:\n\tgo mod tidy -compat=1\.17\nFor other options, see:\n\thttps://golang\.org/doc/modules/pruning\n' + + +# The suggested 'go mod tidy -e' command should proceed anyway. + +go mod tidy -e +cmp go.mod go.mod.tidy + + +# In 'go 1.16' mode we should error out in the way we claimed. + +cd 116-outside +! go list -deps -f $MODFMT example.com/m +stderr '^\.\.[/\\]m\.go:4:2: no required module provides package example\.net/deleted; to add it:\n\tgo get example\.net/deleted$' +cd .. + +go mod edit -go=1.16 +! go list -deps -f $MODFMT example.com/m +stderr '^go: updates to go\.mod needed; to update it:\n\tgo mod tidy$' + +! go mod tidy +stderr '^example\.com/m imports\n\texample\.net/deleted: module example\.net/deleted@latest found \(v0\.2\.0, replaced by \./d2\), but does not contain package example\.net/deleted$' + + +-- go.mod -- +module example.com/m + +go 1.17 + +replace ( + example.net/deleted v0.1.0 => ./d1 + example.net/deleted v0.2.0 => ./d2 + example.net/lazy v0.1.0 => ./lazy + example.net/pruned v0.1.0 => ./pruned +) + +require ( + example.net/deleted v0.1.0 + example.net/deleted v0.1.0 // redundant + example.net/lazy v0.1.0 +) +-- go.mod.tidy -- +module example.com/m + +go 1.17 + +replace ( + example.net/deleted v0.1.0 => ./d1 + example.net/deleted v0.2.0 => ./d2 + example.net/lazy v0.1.0 => ./lazy + example.net/pruned v0.1.0 => ./pruned +) + +require ( + example.net/deleted v0.1.0 + example.net/lazy v0.1.0 +) +-- 116-outside/go.mod -- +module outside + +go 1.16 + +replace ( + example.com/m => ../ + example.net/deleted v0.1.0 => ../d1 + example.net/deleted v0.2.0 => ../d2 + example.net/lazy v0.1.0 => ../lazy + example.net/pruned v0.1.0 => ../pruned +) + +require example.com/m v0.1.0 +-- m.go -- +package m + +import ( + _ "example.net/deleted" + _ "example.net/lazy" +) + +-- d1/go.mod -- +module example.net/deleted + +go 1.17 +-- d1/deleted.go -- +package deleted +-- d2/go.mod -- +module example.net/deleted + +go 1.17 +-- d2/README -- +There is no longer a Go package here. + +-- lazy/go.mod -- +module example.net/lazy + +go 1.17 + +require example.net/pruned v0.1.0 +-- lazy/lazy.go -- +package lazy + +-- pruned/go.mod -- +module example.net/pruned + +go 1.17 + +require example.net/deleted v0.2.0 diff --git a/src/cmd/go/testdata/script/mod_tidy_compat_implicit.txt b/src/cmd/go/testdata/script/mod_tidy_compat_implicit.txt new file mode 100644 index 0000000000000000000000000000000000000000..186a3f8e6720f5654bc34cda9ec4e04ba23ccd1b --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_compat_implicit.txt @@ -0,0 +1,129 @@ +# https://golang.org/issue/46141: 'go mod tidy' for a Go 1.17 module should by +# default preserve enough checksums for the module to be used by Go 1.16. +# +# We don't have a copy of Go 1.16 handy, but we can simulate it by editing the +# 'go' version in the go.mod file to 1.16, without actually updating the +# requirements to match. + +[short] skip + +env MODFMT='{{with .Module}}{{.Path}} {{.Version}}{{end}}' + + +# For this module, Go 1.16 selects the same versions of all explicit dependencies +# as Go 1.17 does. However, Go 1.16 selects a higher version of an *implicit* +# dependency, imported by a test of one of the (external) imported packages. +# As a result, Go 1.16 also needs checksums for the module sources for that higher +# version. +# +# The Go 1.16 module graph looks like: +# +# m ---- lazy v0.1.0 ---- incompatible v1.0.0 +# | +# + ------------- requireincompatible v0.1.0 ---- incompatible v2.0.0+incompatible +# +# The Go 1.17 module graph is the same except that the dependencies of +# requireincompatible are pruned out (because the module that requires +# it — lazy v0.1.0 — specifies 'go 1.17', and it is not otherwise relevant to +# the main module). + +# 'go mod tidy' should by default diagnose the difference in dependencies as an +# error, with useful suggestions about how to resolve it. + +cp go.mod go.mod.orig +! go mod tidy +stderr '^example\.com/m imports\n\texample\.net/lazy tested by\n\texample\.net/lazy.test imports\n\texample\.com/retract/incompatible loaded from example\.com/retract/incompatible@v1\.0\.0,\n\tbut go 1\.16 would select v2\.0\.0\+incompatible\n\n' +stderr '\n\nTo upgrade to the versions selected by go 1\.16:\n\tgo mod tidy -go=1\.16 && go mod tidy -go=1\.17\nIf reproducibility with go 1.16 is not needed:\n\tgo mod tidy -compat=1.17\nFor other options, see:\n\thttps://golang\.org/doc/modules/pruning\n' + +cmp go.mod go.mod.orig + +# The suggested '-compat' flag to ignore differences should silence the error +# and leave go.mod unchanged, resulting in checksum errors when Go 1.16 tries +# to load a module pruned out by Go 1.17. + +go mod tidy -compat=1.17 +! stderr . +cmp go.mod go.mod.orig + +go list -deps -test -f $MODFMT all +stdout '^example\.com/retract/incompatible v1\.0\.0$' + +go mod edit -go=1.16 +! go list -deps -test -f $MODFMT all + + # TODO(#46160): -count=1 instead of -count=2. +stderr -count=2 '^go: example\.net/lazy@v0\.1\.0 requires\n\texample\.com/retract/incompatible@v1\.0\.0: missing go\.sum entry; to add it:\n\tgo mod download example\.com/retract/incompatible$' + + +# If we combine a Go 1.16 go.sum file... +go mod tidy -go=1.16 + +# ...with a Go 1.17 go.mod file... +cp go.mod.orig go.mod + +# ...then Go 1.17 no longer works. 😞 +! go list -deps -test -f $MODFMT all +stderr -count=1 '^can''t load test package: lazy[/\\]lazy_test.go:3:8: missing go\.sum entry for module providing package example\.com/retract/incompatible \(imported by example\.net/lazy\); to add:\n\tgo get -t example.net/lazy@v0\.1\.0$' + + +# However, if we take the union of the go.sum files... +go list -mod=mod -deps -test all +cmp go.mod go.mod.orig + +# ...then Go 1.17 continues to work... +go list -deps -test -f $MODFMT all +stdout '^example\.com/retract/incompatible v1\.0\.0$' + +# ...and 1.16 also works(‽), but selects a different version for the +# external-test dependency. +go mod edit -go=1.16 +go list -deps -test -f $MODFMT all +stdout '^example\.com/retract/incompatible v2\.0\.0\+incompatible$' + + +-- go.mod -- +// Module m imports packages from the same versions under Go 1.17 +// as under Go 1.16, but under 1.16 its (implicit) external test dependencies +// are higher. +module example.com/m + +go 1.17 + +replace ( + example.net/lazy v0.1.0 => ./lazy + example.net/requireincompatible v0.1.0 => ./requireincompatible +) + +require example.net/lazy v0.1.0 +-- implicit.go -- +package implicit + +import _ "example.net/lazy" +-- lazy/go.mod -- +// Module lazy requires example.com/retract/incompatible v1.0.0. +// +// When viewed from the outside it also has a transitive dependency +// on v2.0.0+incompatible, but in lazy mode that transitive dependency +// is pruned out. +module example.net/lazy + +go 1.17 + +exclude example.com/retract/incompatible v2.0.0+incompatible + +require ( + example.com/retract/incompatible v1.0.0 + example.net/requireincompatible v0.1.0 +) +-- lazy/lazy.go -- +package lazy +-- lazy/lazy_test.go -- +package lazy_test + +import _ "example.com/retract/incompatible" +-- requireincompatible/go.mod -- +module example.net/requireincompatible + +go 1.15 + +require example.com/retract/incompatible v2.0.0+incompatible diff --git a/src/cmd/go/testdata/script/mod_tidy_compat_incompatible.txt b/src/cmd/go/testdata/script/mod_tidy_compat_incompatible.txt new file mode 100644 index 0000000000000000000000000000000000000000..ea9e42e87e20fc57b7e14164f9f606954ccad269 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_compat_incompatible.txt @@ -0,0 +1,135 @@ +# https://golang.org/issue/46141: 'go mod tidy' for a Go 1.17 module should by +# default preserve enough checksums for the module to be used by Go 1.16. +# +# We don't have a copy of Go 1.16 handy, but we can simulate it by editing the +# 'go' version in the go.mod file to 1.16, without actually updating the +# requirements to match. + +[short] skip + +env MODFMT='{{with .Module}}{{.Path}} {{.Version}}{{end}}' + + +# For this module, Go 1.17 prunes out a (transitive and otherwise-irrelevant) +# requirement on a retracted higher version of a dependency. +# However, when Go 1.16 reads the same requirements from the go.mod file, +# it does not prune out that requirement, and selects the retracted version. +# +# The Go 1.16 module graph looks like: +# +# m ---- lazy v0.1.0 ---- requireincompatible v0.1.0 ---- incompatible v2.0.0+incompatible +# | | +# + -------+------------- incompatible v1.0.0 +# +# The Go 1.17 module graph is the same except that the dependencies of +# requireincompatible are pruned out (because the module that requires +# it — lazy v0.1.0 — specifies 'go 1.17', and it is not otherwise relevant to +# the main module). + + +# 'go mod tidy' should by default diagnose the difference in dependencies as an +# error, with useful suggestions about how to resolve it. + +cp go.mod go.mod.orig +! go mod tidy +stderr '^example\.com/m imports\n\texample\.net/lazy imports\n\texample\.com/retract/incompatible loaded from example\.com/retract/incompatible@v1\.0\.0,\n\tbut go 1\.16 would select v2\.0\.0\+incompatible\n\n' +stderr '\n\nTo upgrade to the versions selected by go 1\.16:\n\tgo mod tidy -go=1\.16 && go mod tidy -go=1\.17\nIf reproducibility with go 1\.16 is not needed:\n\tgo mod tidy -compat=1.17\nFor other options, see:\n\thttps://golang\.org/doc/modules/pruning\n' + +cmp go.mod go.mod.orig + + +# The suggested '-compat' flag to ignore differences should silence the error +# and leave go.mod unchanged, resulting in checksum errors when Go 1.16 tries +# to load a module pruned out by Go 1.17. + +go mod tidy -compat=1.17 +! stderr . +cmp go.mod go.mod.orig + +go mod edit -go=1.16 +! go list -f $MODFMT -deps ./... + # TODO(#46160): -count=1 instead of -count=2. +stderr -count=2 '^go: example\.net/lazy@v0\.1\.0 requires\n\texample\.net/requireincompatible@v0\.1\.0 requires\n\texample\.com/retract/incompatible@v2\.0\.0\+incompatible: missing go.sum entry; to add it:\n\tgo mod download example.com/retract/incompatible$' + + +# There are two ways for the module author to bring the two into alignment. +# One is to *explicitly* 'exclude' the version that is already *implicitly* +# pruned out under 1.17. + +go mod edit -exclude=example.com/retract/incompatible@v2.0.0+incompatible +go list -f $MODFMT -deps ./... +stdout '^example.com/retract/incompatible v1\.0\.0$' +! stdout 'v2\.0\.0' + + +# The other is to explicitly upgrade the version required under Go 1.17 +# to match the version selected by Go 1.16. The commands suggested by +# 'go mod tidy' should do exactly that. + +cp go.mod.orig go.mod + +go mod tidy -go=1.16 +go list -f $MODFMT -deps ./... +stdout '^example.com/retract/incompatible v2\.0\.0\+incompatible$' +! stdout 'v1\.0\.0' + +go mod tidy -go=1.17 +go list -f $MODFMT -deps ./... +stdout '^example.com/retract/incompatible v2\.0\.0\+incompatible$' +! stdout 'v1\.0\.0' + +go mod edit -go=1.16 +go list -f $MODFMT -deps ./... +stdout '^example.com/retract/incompatible v2\.0\.0\+incompatible$' +! stdout 'v1\.0\.0' + + +-- go.mod -- +// Module m indirectly imports a package from +// example.com/retract/incompatible. Its selected version of +// that module is lower under Go 1.17 semantics than under Go 1.16. +module example.com/m + +go 1.17 + +replace ( + example.net/lazy v0.1.0 => ./lazy + example.net/requireincompatible v0.1.0 => ./requireincompatible +) + +require ( + example.com/retract/incompatible v1.0.0 // indirect + example.net/lazy v0.1.0 +) +-- incompatible.go -- +package incompatible + +import _ "example.net/lazy" + +-- lazy/go.mod -- +// Module lazy requires example.com/retract/incompatible v1.0.0. +// +// When viewed from the outside it also has a transitive dependency +// on v2.0.0+incompatible, but in lazy mode that transitive dependency +// is pruned out. +module example.net/lazy + +go 1.17 + +exclude example.com/retract/incompatible v2.0.0+incompatible + +require ( + example.com/retract/incompatible v1.0.0 + example.net/requireincompatible v0.1.0 +) +-- lazy/lazy.go -- +package lazy + +import _ "example.com/retract/incompatible" + +-- requireincompatible/go.mod -- +module example.net/requireincompatible + +go 1.15 + +require example.com/retract/incompatible v2.0.0+incompatible diff --git a/src/cmd/go/testdata/script/mod_tidy_compat_irrelevant.txt b/src/cmd/go/testdata/script/mod_tidy_compat_irrelevant.txt new file mode 100644 index 0000000000000000000000000000000000000000..7c22fca6c0f4d15b0e27f1c0f11706efe7b11398 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_compat_irrelevant.txt @@ -0,0 +1,99 @@ +# https://golang.org/issue/46141: 'go mod tidy' for a Go 1.17 module should by +# default preserve enough checksums for the module to be used by Go 1.16. +# +# We don't have a copy of Go 1.16 handy, but we can simulate it by editing the +# 'go' version in the go.mod file to 1.16, without actually updating the +# requirements to match. + +[short] skip + +env MODFMT='{{with .Module}}{{.Path}} {{.Version}}{{end}}' + + +# This module selects the same versions in Go 1.16 and 1.17 for all modules +# that provide packages (or test dependencies of packages) imported by the +# main module. However, in Go 1.16 it selects a higher version of a +# transitive module dependency that is not otherwise relevant to the main module. +# As a result, Go 1.16 needs an additional checksum for the go.mod file of +# that irrelevant dependency. +# +# The Go 1.16 module graph looks like: +# +# m ---- lazy v0.1.0 ---- incompatible v1.0.0 +# | +# + ------------- requireincompatible v0.1.0 ---- incompatible v2.0.0+incompatible + +cp go.mod go.mod.orig +go mod tidy +cmp go.mod go.mod.orig + +go list -deps -test -f $MODFMT all +cp stdout out-117.txt + +go mod edit -go=1.16 +go list -deps -test -f $MODFMT all +cmp stdout out-117.txt + + +# If we explicitly drop compatibility with 1.16, we retain fewer checksums, +# which gives a cleaner go.sum file but causes 1.16 to fail in readonly mode. + +cp go.mod.orig go.mod +go mod tidy -compat=1.17 +cmp go.mod go.mod.orig + +go list -deps -test -f $MODFMT all +cmp stdout out-117.txt + +go mod edit -go=1.16 +! go list -deps -test -f $MODFMT all + # TODO(#46160): -count=1 instead of -count=2. +stderr -count=2 '^go: example.net/lazy@v0.1.0 requires\n\texample.com/retract/incompatible@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download example.com/retract/incompatible$' + + +-- go.mod -- +// Module m imports packages from the same versions under Go 1.17 +// as under Go 1.16, but under 1.16 its (implicit) external test dependencies +// are higher. +module example.com/m + +go 1.17 + +replace ( + example.net/lazy v0.1.0 => ./lazy + example.net/requireincompatible v0.1.0 => ./requireincompatible +) + +require example.net/lazy v0.1.0 +-- m.go -- +package m + +import _ "example.net/lazy" +-- lazy/go.mod -- +// Module lazy requires example.com/retract/incompatible v1.0.0. +// +// When viewed from the outside it also has a transitive dependency +// on v2.0.0+incompatible, but in lazy mode that transitive dependency +// is pruned out. +module example.net/lazy + +go 1.17 + +exclude example.com/retract/incompatible v2.0.0+incompatible + +require ( + example.com/retract/incompatible v1.0.0 + example.net/requireincompatible v0.1.0 +) +-- lazy/lazy.go -- +package lazy +-- lazy/unimported/unimported.go -- +package unimported + +import _ "example.com/retract/incompatible" +-- requireincompatible/go.mod -- +module example.net/requireincompatible + +go 1.15 + +require example.com/retract/incompatible v2.0.0+incompatible diff --git a/src/cmd/go/testdata/script/mod_tidy_convergence.txt b/src/cmd/go/testdata/script/mod_tidy_convergence.txt new file mode 100644 index 0000000000000000000000000000000000000000..09c46f764bf06fd434f80454fa15f6eb2f2f53af --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_convergence.txt @@ -0,0 +1,202 @@ +# This test demonstrates a simple case in which 'go mod tidy' may resolve a +# missing package, only to remove that package when resolving its dependencies. +# +# If we naively iterate 'go mod tidy' until the dependency graph converges, this +# scenario may fail to converge. + +# The import graph used in this test looks like: +# +# m --- x +# | +# x_test --- y +# +# The module dependency graph of m is initially empty. +# Modules x and y look like: +# +# x.1 (provides package x that imports y, but does not depend on module y) +# +# x.2-pre (no dependencies, but does not provide package x) +# +# y.1 (no dependencies, but provides package y) +# +# y.2 --- x.2-pre (provides package y) +# +# +# When we resolve the missing import of y in x_test, we add y@latest — which is +# y.2, not y.1 — as a new dependency. That upgrades to x to x.2-pre, which +# removes package x (and also the need for module y). We can then safely remove +# the dependency on module y, because nothing imports package y any more! +# +# We might be tempted to remove the dependency on module x for the same reason: +# it no longer provides any imported package. However, that would cause 'go mod +# tidy -e' to become unstable: with x.2-pre out of the way, we could once again +# resolve the missing import of package x by re-adding x.1. + +cp go.mod go.mod.orig + +# 'go mod tidy' without -e should fail without modifying go.mod, +# because it cannot resolve x and y simultaneously. +! go mod tidy + +cmp go.mod go.mod.orig + +stderr '^go: found example\.net/y in example\.net/y v0.2.0$' +stderr '^go: finding module for package example\.net/x$' + + # TODO: This error message should be clearer — it doesn't indicate why v0.2.0-pre is required. +stderr '^example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$' + + +# 'go mod tidy -e' should follow upgrades to try to resolve the modules that it +# can, and then stop. When we resolve example.net/y, we upgrade to example.net/x +# to v0.2.0-pre. At that version, package x no longer exists and no longer +# imports package y, so the import of x should be left unsatisfied and the +# existing dependency on example.net/x removed. +# +# TODO(bcmills): It would be ever better if we could keep the original +# dependency on example.net/x v0.1.0, but I don't see a way to do that without +# making the algorithm way too complicated. (We would have to detect that the +# new dependency on example.net/y interferes with the package that caused us to +# to add that dependency in the first place, and back out that part of the change +# without also backing out any other needed changes.) + +go mod tidy -e +cmp go.mod go.mod.tidye +stderr '^go: found example\.net/y in example\.net/y v0.2.0$' + + # TODO: This error message should be clearer — it doesn't indicate why v0.2.0-pre is required. +stderr '^example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$' + + +# Since we attempt to resolve the dependencies of package x whenever we add x itself, +# this end state is stable. + +go mod tidy -e +cmp go.mod go.mod.tidye + + +# An explicit 'go get' with the correct versions should allow 'go mod tidy' to +# succeed and remain stable. y.1 does not upgrade x, and can therefore be used +# with it. + +go get -d example.net/x@v0.1.0 example.net/y@v0.1.0 +go mod tidy +cmp go.mod go.mod.postget + + +# The 'tidy' logic for a lazy main module is somewhat different from that for an +# eager main module, but the overall behavior is the same. + +cp go.mod.orig go.mod +go mod edit -go=1.17 go.mod +go mod edit -go=1.17 go.mod.tidye + +go mod tidy -e +cmp go.mod go.mod.tidye +stderr '^go: found example\.net/y in example\.net/y v0.2.0$' +stderr '^example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$' + +go get -d example.net/x@v0.1.0 example.net/y@v0.1.0 +go mod tidy +cmp go.mod go.mod.postget-117 + + +-- go.mod -- +module example.net/m + +go 1.16 + +replace ( + example.net/x v0.1.0 => ./x1 + example.net/x v0.2.0-pre => ./x2-pre + example.net/y v0.1.0 => ./y1 + example.net/y v0.2.0 => ./y2 +) + +require ( + example.net/x v0.1.0 +) +-- go.mod.tidye -- +module example.net/m + +go 1.16 + +replace ( + example.net/x v0.1.0 => ./x1 + example.net/x v0.2.0-pre => ./x2-pre + example.net/y v0.1.0 => ./y1 + example.net/y v0.2.0 => ./y2 +) +-- go.mod.postget -- +module example.net/m + +go 1.16 + +replace ( + example.net/x v0.1.0 => ./x1 + example.net/x v0.2.0-pre => ./x2-pre + example.net/y v0.1.0 => ./y1 + example.net/y v0.2.0 => ./y2 +) + +require ( + example.net/x v0.1.0 + example.net/y v0.1.0 // indirect +) +-- go.mod.postget-117 -- +module example.net/m + +go 1.17 + +replace ( + example.net/x v0.1.0 => ./x1 + example.net/x v0.2.0-pre => ./x2-pre + example.net/y v0.1.0 => ./y1 + example.net/y v0.2.0 => ./y2 +) + +require example.net/x v0.1.0 + +require example.net/y v0.1.0 // indirect +-- m.go -- +package m + +import _ "example.net/x" + +-- x1/go.mod -- +module example.net/x + +go 1.16 +-- x1/x.go -- +package x +-- x1/x_test.go -- +package x + +import _ "example.net/y" + +-- x2-pre/go.mod -- +module example.net/x + +go 1.16 +-- x2-pre/README.txt -- +There is no package x here. Use example.com/x/subpkg instead. +-- x2-pre/subpkg/subpkg.go -- +package subpkg // import "example.net/x/subpkg" + +-- y1/go.mod -- +module example.net/y + +go 1.16 +-- y1/y.go -- +package y + +-- y2/go.mod -- +module example.net/y + +go 1.16 + +require example.net/x v0.2.0-pre +-- y2/y.go -- +package y + +import _ "example.net/x/subpkg" diff --git a/src/cmd/go/testdata/script/mod_tidy_convergence_loop.txt b/src/cmd/go/testdata/script/mod_tidy_convergence_loop.txt new file mode 100644 index 0000000000000000000000000000000000000000..3c4d3244d5daa4e5ba92d4df0ba512473a19d42e --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_convergence_loop.txt @@ -0,0 +1,329 @@ +# This test demonstrates a simple case in which 'go mod tidy' may resolve a +# missing package, only to remove that package when resolving its dependencies. +# +# If we naively iterate 'go mod tidy' until the dependency graph converges, this +# scenario may fail to converge. + +# The import graph used in this test looks like: +# +# m --- w +# | +# + --- x +# | +# + --- y +# | +# + --- z +# +# The module dependency graph of m initially contains w.1 (and, by extension, +# y.2-pre and z.2-pre). This is an arbitrary point in the cycle of possible +# configurations. +# +# w.1 requires y.2-pre and z.2-pre +# x.1 requires z.2-pre and w.2-pre +# y.1 requires w.2-pre and x.2-pre +# z.1 requires x.2-pre and y.2-pre +# +# At each point, exactly one missing package can be resolved by adding a +# dependency on the .1 release of the module that provides that package. +# However, adding that dependency causes the module providing another package to +# roll over from its .1 release to its .2-pre release, which removes the +# package. Once the package is removed, 'go mod tidy -e' no longer sees the +# module as relevant to the main module, and will happily remove the existing +# dependency on it. +# +# The cycle is of length 4 so that at every step only one package can be +# resolved. This is important because it prevents the iteration from ever +# reaching a state in which every package is simultaneously over-upgraded — such +# a state is stable and does not exhibit failure to converge. + +cp go.mod go.mod.orig + +# 'go mod tidy' without -e should fail without modifying go.mod, +# because it cannot resolve x, y, and z simultaneously. +! go mod tidy + +cmp go.mod go.mod.orig + +stderr '^go: finding module for package example\.net/w$' +stderr '^go: finding module for package example\.net/x$' +stderr -count=2 '^go: finding module for package example\.net/y$' +stderr -count=2 '^go: finding module for package example\.net/z$' +stderr '^go: found example\.net/x in example\.net/x v0.1.0$' + + # TODO: These error messages should be clearer — it doesn't indicate why v0.2.0-pre is required. +stderr '^example\.net/m imports\n\texample\.net/w: package example\.net/w provided by example\.net/w at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$' +stderr '^example\.net/m imports\n\texample\.net/y: package example\.net/y provided by example\.net/y at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$' +stderr '^example\.net/m imports\n\texample\.net/z: package example\.net/z provided by example\.net/z at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$' + + +# 'go mod tidy -e' should preserve all of the upgrades to modules that could +# provide the missing packages but don't. That would at least explain why they +# are missing, and why no individual module can be upgraded in order to satisfy +# a missing import. +# +# TODO(bcmills): Today, it doesn't preserve those upgrades, and instead advances +# the state by one through the cycle of semi-tidy states. + +go mod tidy -e + +cmp go.mod go.mod.tidye1 + +stderr '^go: finding module for package example\.net/w$' +stderr '^go: finding module for package example\.net/x$' +stderr -count=2 '^go: finding module for package example\.net/y$' +stderr -count=2 '^go: finding module for package example\.net/z$' +stderr '^go: found example\.net/x in example\.net/x v0.1.0$' + +stderr '^example\.net/m imports\n\texample\.net/w: package example\.net/w provided by example\.net/w at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$' +stderr '^example\.net/m imports\n\texample\.net/y: package example\.net/y provided by example\.net/y at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$' +stderr '^example\.net/m imports\n\texample\.net/z: package example\.net/z provided by example\.net/z at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$' + + +go mod tidy -e +cmp go.mod go.mod.tidye2 + +go mod tidy -e +cmp go.mod go.mod.tidye3 + +go mod tidy -e +cmp go.mod go.mod.orig + + +# If we upgrade away all of the packages simultaneously, the resulting tidy +# state converges at "no dependencies", because simultaneously adding all of the +# packages simultaneously over-upgrades all of the dependencies, and 'go mod +# tidy' treats "no package can be added" as a terminal state. + +go get -d example.net/w@v0.2.0-pre example.net/x@v0.2.0-pre example.net/y@v0.2.0-pre example.net/z@v0.2.0-pre +go mod tidy -e +cmp go.mod go.mod.postget +go mod tidy -e +cmp go.mod go.mod.postget + + +# The 'tidy' logic for a lazy main module requires more iterations to converge, +# because it is willing to drop dependencies on non-root modules that do not +# otherwise provide imported packages. +# +# On the first iteration, it adds x.1 as a root, which upgrades z and w, +# dropping w.1's requirement on y. w.1 was initially a root, so the upgraded +# w.2-pre is retained as a root. +# +# On the second iteration, it adds y.1 as a root, which upgrades w and x, +# dropping x.1's requirement on z. x.1 was added as a root in the previous step, +# so the upgraded x.2-pre is retained as a root. +# +# On the third iteration, it adds z.1 as a root, which upgrades x and y. +# x and y were already roots (from the previous steps), so their upgraded versions +# are retained (not dropped) and the iteration stops. +# +# At that point, we have z.1 as a root providing package z, +# and w, x, and y have all been upgraded to no longer provide any packages. +# So only z is retained as a new root. +# +# (From the above, we can see that in a lazy module we still cycle through the +# same possible root states, but in a different order from the eager case.) +# +# TODO(bcmills): if we retained the upgrades on w, x, and y (since they are +# lexical prefixes for unresolved packages w, x, and y, respectively), then 'go +# mod tidy -e' itself would become stable and no longer cycle through states. + +cp go.mod.orig go.mod +go mod edit -go=1.17 go.mod +cp go.mod go.mod.117 +go mod edit -go=1.17 go.mod.tidye1 +go mod edit -go=1.17 go.mod.tidye2 +go mod edit -go=1.17 go.mod.tidye3 +go mod edit -go=1.17 go.mod.postget + +go list -m all + +go mod tidy -e +cmp go.mod go.mod.tidye3 + +go mod tidy -e +cmp go.mod go.mod.tidye2 + +go mod tidy -e +cmp go.mod go.mod.tidye1 + +go mod tidy -e +cmp go.mod go.mod.117 + + +# As in the eager case, for the lazy module the fully-upgraded dependency graph +# becomes empty, and the empty graph is stable. + +go get -d example.net/w@v0.2.0-pre example.net/x@v0.2.0-pre example.net/y@v0.2.0-pre example.net/z@v0.2.0-pre +go mod tidy -e +cmp go.mod go.mod.postget +go mod tidy -e +cmp go.mod go.mod.postget + + +-- m.go -- +package m + +import ( + _ "example.net/w" + _ "example.net/x" + _ "example.net/y" + _ "example.net/z" +) + +-- go.mod -- +module example.net/m + +go 1.16 + +replace ( + example.net/w v0.1.0 => ./w1 + example.net/w v0.2.0-pre => ./w2-pre + example.net/x v0.1.0 => ./x1 + example.net/x v0.2.0-pre => ./x2-pre + example.net/y v0.1.0 => ./y1 + example.net/y v0.2.0-pre => ./y2-pre + example.net/z v0.1.0 => ./z1 + example.net/z v0.2.0-pre => ./z2-pre +) + +require example.net/w v0.1.0 +-- go.mod.tidye1 -- +module example.net/m + +go 1.16 + +replace ( + example.net/w v0.1.0 => ./w1 + example.net/w v0.2.0-pre => ./w2-pre + example.net/x v0.1.0 => ./x1 + example.net/x v0.2.0-pre => ./x2-pre + example.net/y v0.1.0 => ./y1 + example.net/y v0.2.0-pre => ./y2-pre + example.net/z v0.1.0 => ./z1 + example.net/z v0.2.0-pre => ./z2-pre +) + +require example.net/x v0.1.0 +-- go.mod.tidye2 -- +module example.net/m + +go 1.16 + +replace ( + example.net/w v0.1.0 => ./w1 + example.net/w v0.2.0-pre => ./w2-pre + example.net/x v0.1.0 => ./x1 + example.net/x v0.2.0-pre => ./x2-pre + example.net/y v0.1.0 => ./y1 + example.net/y v0.2.0-pre => ./y2-pre + example.net/z v0.1.0 => ./z1 + example.net/z v0.2.0-pre => ./z2-pre +) + +require example.net/y v0.1.0 +-- go.mod.tidye3 -- +module example.net/m + +go 1.16 + +replace ( + example.net/w v0.1.0 => ./w1 + example.net/w v0.2.0-pre => ./w2-pre + example.net/x v0.1.0 => ./x1 + example.net/x v0.2.0-pre => ./x2-pre + example.net/y v0.1.0 => ./y1 + example.net/y v0.2.0-pre => ./y2-pre + example.net/z v0.1.0 => ./z1 + example.net/z v0.2.0-pre => ./z2-pre +) + +require example.net/z v0.1.0 +-- go.mod.postget -- +module example.net/m + +go 1.16 + +replace ( + example.net/w v0.1.0 => ./w1 + example.net/w v0.2.0-pre => ./w2-pre + example.net/x v0.1.0 => ./x1 + example.net/x v0.2.0-pre => ./x2-pre + example.net/y v0.1.0 => ./y1 + example.net/y v0.2.0-pre => ./y2-pre + example.net/z v0.1.0 => ./z1 + example.net/z v0.2.0-pre => ./z2-pre +) +-- w1/go.mod -- +module example.net/w + +go 1.16 + +require ( + example.net/y v0.2.0-pre + example.net/z v0.2.0-pre +) +-- w1/w.go -- +package w +-- w2-pre/go.mod -- +module example.net/w + +go 1.16 +-- w2-pre/README.txt -- +Package w has been removed. + +-- x1/go.mod -- +module example.net/x + +go 1.16 + +require ( + example.net/z v0.2.0-pre + example.net/w v0.2.0-pre +) +-- x1/x.go -- +package x +-- x2-pre/go.mod -- +module example.net/x + +go 1.16 +-- x2-pre/README.txt -- +Package x has been removed. + +-- y1/go.mod -- +module example.net/y + +go 1.16 + +require ( + example.net/w v0.2.0-pre + example.net/x v0.2.0-pre +) +-- y1/y.go -- +package y + +-- y2-pre/go.mod -- +module example.net/y + +go 1.16 +-- y2-pre/README.txt -- +Package y has been removed. + +-- z1/go.mod -- +module example.net/z + +go 1.16 + +require ( + example.net/x v0.2.0-pre + example.net/y v0.2.0-pre +) +-- z1/z.go -- +package z + +-- z2-pre/go.mod -- +module example.net/z + +go 1.16 +-- z2-pre/README.txt -- +Package z has been removed. diff --git a/src/cmd/go/testdata/script/mod_tidy_indirect.txt b/src/cmd/go/testdata/script/mod_tidy_indirect.txt new file mode 100644 index 0000000000000000000000000000000000000000..1f092b223bd6357914de5e52b0eab24d094a14d0 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_indirect.txt @@ -0,0 +1,67 @@ +cp go.mod go.mod.orig +go mod tidy +cmp go.mod go.mod.orig + +-- go.mod -- +module example.com/tidy + +go 1.16 + +require ( + example.net/incomplete v0.1.0 + example.net/indirect v0.2.0 // indirect + example.net/toolow v0.1.0 +) + +replace ( + example.net/incomplete v0.1.0 => ./incomplete + example.net/indirect v0.1.0 => ./indirect.1 + example.net/indirect v0.2.0 => ./indirect.2 + example.net/toolow v0.1.0 => ./toolow +) +-- tidy.go -- +package tidy + +import ( + _ "example.net/incomplete" + _ "example.net/toolow" +) + +-- incomplete/go.mod -- +module example.net/incomplete + +go 1.16 + +// This module omits a needed requirement on example.net/indirect. +-- incomplete/incomplete.go -- +package incomplete + +import _ "example.net/indirect/newpkg" + +-- toolow/go.mod -- +module example.net/toolow + +go 1.16 + +require example.net/indirect v0.1.0 +-- toolow/toolow.go -- +package toolow + +import _ "example.net/indirect/oldpkg" + +-- indirect.1/go.mod -- +module example.net/indirect + +go 1.16 +-- indirect.1/oldpkg/oldpkg.go -- +package oldpkg + + +-- indirect.2/go.mod -- +module example.net/indirect + +go 1.16 +-- indirect.2/oldpkg/oldpkg.go -- +package oldpkg +-- indirect.2/newpkg/newpkg.go -- +package newpkg diff --git a/src/cmd/go/testdata/script/mod_tidy_lazy_self.txt b/src/cmd/go/testdata/script/mod_tidy_lazy_self.txt new file mode 100644 index 0000000000000000000000000000000000000000..9abbabd2ebe47345e3e1cbe9067a9e98fcf76b7f --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_lazy_self.txt @@ -0,0 +1,66 @@ +# Regression test for https://golang.org/issue/46078: +# 'go mod tidy' should not panic if the main module initially +# requires an older version of itself. + +# A module may require an older version of itself without error. This is +# inconsistent (the required version is never selected), but we still get +# a reproducible build list. +go list -m all +stdout '^golang.org/issue/46078$' + +# 'go mod tidy' should fix this (and not crash). +go mod tidy + + +# We prune out redundant roots very early on in module loading, and at that +# point the indirect requirement on example.net/x v0.1.0 appears to be +# irrelevant. It should be pruned out; when the import of "example.net/x" is +# later resolved, it should resolve at the latest version (v0.2.0), not the +# version implied by the (former) misleading requirement on the older version of +# the main module. + +cmp go.mod go.mod.tidy + + +-- go.mod -- +module golang.org/issue/46078 + +go 1.17 + +replace ( + example.net/x v0.1.0 => ./x + example.net/x v0.2.0 => ./x + golang.org/issue/46078 v0.1.0 => ./old +) + +require golang.org/issue/46078 v0.1.0 +-- go.mod.tidy -- +module golang.org/issue/46078 + +go 1.17 + +replace ( + example.net/x v0.1.0 => ./x + example.net/x v0.2.0 => ./x + golang.org/issue/46078 v0.1.0 => ./old +) + +require example.net/x v0.2.0 +-- issue46078/issue.go -- +package issue46078 + +import _ "example.net/x" + +-- old/go.mod -- +module golang.org/issue/46078 + +go 1.17 + +require example.net/x v0.1.0 + +-- x/go.mod -- +module example.net/x + +go 1.17 +-- x/x.go -- +package x diff --git a/src/cmd/go/testdata/script/mod_tidy_newroot.txt b/src/cmd/go/testdata/script/mod_tidy_newroot.txt new file mode 100644 index 0000000000000000000000000000000000000000..3abd5ef08a300858080657986c0e403e40d558fa --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_newroot.txt @@ -0,0 +1,82 @@ +# https://golang.org/issue/45952: 'go mod tidy' in an eager module failed due +# to an erroneous check on root completeness. +# +# Per the issue report: +# > It may have to do with: +# > +# > package A imports package B in go.mod, which imports package C in its own go.mod +# > package A drops direct dependency on package B … +# +# We infer from that that package C is still needed by some other indirect +# dependency, and must be at a higher version than what is required by that +# dependency (or else no new root would be needed). An additional package D +# in its own module satisfies that condition, reproducing the bug. + +go mod tidy +cmp go.mod go.mod.tidy + +-- go.mod -- +module example.net/a + +go 1.16 + +require ( + example.net/b v0.1.0 + example.net/d v0.1.0 +) + +replace ( + example.net/b v0.1.0 => ./b + example.net/c v0.1.0 => ./c + example.net/c v0.2.0 => ./c + example.net/d v0.1.0 => ./d +) +-- go.mod.tidy -- +module example.net/a + +go 1.16 + +require ( + example.net/c v0.2.0 // indirect + example.net/d v0.1.0 +) + +replace ( + example.net/b v0.1.0 => ./b + example.net/c v0.1.0 => ./c + example.net/c v0.2.0 => ./c + example.net/d v0.1.0 => ./d +) +-- a.go -- +package a + +import _ "example.net/d" + +-- b/go.mod -- +module example.net/b + +go 1.16 + +require example.net/c v0.2.0 +-- b/b.go -- +package b + +import _ "example.net/c" + +-- c/go.mod -- +module example.net/c + +go 1.16 +-- c/c.go -- +package c + +-- d/go.mod -- +module example.net/d + +go 1.16 + +require example.net/c v0.1.0 +-- d/d.go -- +package d + +import _ "example.net/c" diff --git a/src/cmd/go/testdata/script/mod_tidy_oldgo.txt b/src/cmd/go/testdata/script/mod_tidy_oldgo.txt new file mode 100644 index 0000000000000000000000000000000000000000..0e88b068a76bcfa815521221e8fec344c9a1ec18 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_oldgo.txt @@ -0,0 +1,21 @@ +# Modules were introduced in Go 1.11, but for various reasons users may +# decide to declare a (much!) older go version in their go.mod file. +# Modules with very old versions should not be rejected, and should have +# the same module-graph semantics as in Go 1.11. + +cp go.mod go.mod.orig +go mod tidy +cmp go.mod go.mod.orig + +-- go.mod -- +module example.com/legacy/go1 + +go 1.0 + +require golang.org/x/text v0.3.0 +-- main.go -- +package main + +import _ "golang.org/x/text/language" + +func main() {} diff --git a/src/cmd/go/testdata/script/mod_tidy_replace.txt b/src/cmd/go/testdata/script/mod_tidy_replace.txt index dd9943889106568addf3a503a22c478b0dfb8bde..297f6a6a45c872da732b127e8a4cb8d376b3ba68 100644 --- a/src/cmd/go/testdata/script/mod_tidy_replace.txt +++ b/src/cmd/go/testdata/script/mod_tidy_replace.txt @@ -136,3 +136,10 @@ require ( ) replace not-rsc.io/quote/v3 => rsc.io/quote/v3 v3.0.0 +-- multiple-paths/use.go -- +package quoter + +import ( + _ "not-rsc.io/quote/v3" + _ "rsc.io/quote/v3" +) diff --git a/src/cmd/go/testdata/script/mod_tidy_replace_old.txt b/src/cmd/go/testdata/script/mod_tidy_replace_old.txt new file mode 100644 index 0000000000000000000000000000000000000000..cfd3792600cd01e2cb8a537617c2860891c52dc8 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_replace_old.txt @@ -0,0 +1,34 @@ +# Regression test for https://golang.org/issue/46659. +# +# If a 'replace' directive specifies an older-than-selected version of a module, +# 'go mod tidy' shouldn't try to add that version to the build list to resolve a +# missing package: it won't be selected, and would cause the module loader to +# loop indefinitely trying to resolve the package. + +cp go.mod go.mod.orig + +! go mod tidy +! stderr panic +stderr '^golang\.org/issue46659 imports\n\texample\.com/missingpkg/deprecated: package example\.com/missingpkg/deprecated provided by example\.com/missingpkg at latest version v1\.0\.0 but not at required version v1\.0\.1-beta$' + +go mod tidy -e + +cmp go.mod go.mod.orig + +-- go.mod -- +module golang.org/issue46659 + +go 1.17 + +replace example.com/missingpkg v1.0.1-alpha => example.com/missingpkg v1.0.0 + +require example.com/usemissingpre v1.0.0 + +require example.com/missingpkg v1.0.1-beta // indirect +-- m.go -- +package m + +import ( + _ "example.com/missingpkg/deprecated" + _ "example.com/usemissingpre" +) diff --git a/src/cmd/go/testdata/script/mod_tidy_symlink_issue35941.txt b/src/cmd/go/testdata/script/mod_tidy_symlink_issue35941.txt new file mode 100644 index 0000000000000000000000000000000000000000..d4658c65d40c4e389242bc38e7ce8497dc6ec22e --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_symlink_issue35941.txt @@ -0,0 +1,36 @@ +env GO111MODULE=on +[!symlink] skip + +cd m +symlink symlink -> ../outside + +cp go.mod go.mod.orig + +# Issue 35941: suppress symlink warnings when running 'go mod tidy'. +# 'go mod tidy' should not scan packages in symlinked subdirectories. +go mod tidy +! stderr 'warning: ignoring symlink' +cmp go.mod go.mod.orig + +! go build ./symlink +stderr '^symlink[\\/]symlink.go:3:8: module example.net/unresolved provides package example.net/unresolved and is replaced but not required; to add it:\n\tgo get example.net/unresolved@v0.1.0$' + +-- m/go.mod -- +module example.net/m + +go 1.16 + +replace example.net/unresolved v0.1.0 => ../unresolved +-- m/a.go -- +package a +-- outside/symlink.go -- +package symlink + +import _ "example.net/unresolved" +-- unresolved/go.mod -- +module example.net/unresolved + +go 1.16 +-- unresolved/unresolved.go -- +// Package unresolved exists, but 'go mod tidy' won't add it. +package unresolved diff --git a/src/cmd/go/testdata/script/mod_tidy_too_new.txt b/src/cmd/go/testdata/script/mod_tidy_too_new.txt new file mode 100644 index 0000000000000000000000000000000000000000..b9c53b510daeb06c5d91e8fb2c720d5dd89b12ca --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_too_new.txt @@ -0,0 +1,57 @@ +# https://golang.org/issue/46142: 'go mod tidy' should error out if the version +# in the go.mod file is newer than the most recent supported version. + +cp go.mod go.mod.orig + + +# If the go.mod file specifies an unsupported Go version, 'go mod tidy' should +# refuse to edit it: we don't know what a tidy go.mod file for that version +# would look like. + +! go mod tidy +stderr 'go mod tidy: go.mod file indicates go 2000.0, but maximum supported version is '$goversion'$' +cmp go.mod go.mod.orig + + +# The -e flag should push past the error and edit the file anyway, +# but preserve the too-high version. + +cp go.mod.orig go.mod +go mod tidy -e +stderr 'go mod tidy: go.mod file indicates go 2000.0, but maximum supported version is '$goversion'$' +cmp go.mod go.mod.tidy + + +# Explicitly switching to a supported version should suppress the error completely. + +cp go.mod.orig go.mod +go mod tidy -go=1.17 +! stderr 'maximum supported version' +go mod edit -go=1.17 go.mod.tidy +cmp go.mod go.mod.tidy + + +-- go.mod -- +module example.net/from/the/future + +go 2000.0 + +replace example.net/m v0.0.0 => ./m +-- go.mod.tidy -- +module example.net/from/the/future + +go 2000.0 + +replace example.net/m v0.0.0 => ./m + +require example.net/m v0.0.0 +-- x.go -- +package x + +import "example.net/m" +-- m/go.mod -- +module example.net/m + +go 1.17 +-- m/m.go -- +package m diff --git a/src/cmd/go/testdata/script/mod_tidy_version.txt b/src/cmd/go/testdata/script/mod_tidy_version.txt new file mode 100644 index 0000000000000000000000000000000000000000..3bc97bcb1e702eeadc87210089bdfbc1f1def02c --- /dev/null +++ b/src/cmd/go/testdata/script/mod_tidy_version.txt @@ -0,0 +1,262 @@ +# https://golang.org/issue/45094: 'go mod tidy' now accepts a '-go' flag +# to change the language version in use. +# +# The package import graph used in this test looks like: +# +# m --- a --- b +# | +# b_test --- c +# | +# c_test --- d +# +# The module diagram looks like: +# +# m --- a --- b +# | +# + --- c +# | +# + --- d +# +# Module b omits its dependency on c, and module c omits its dependency on d. +# +# In go 1.15, the tidy main module must require a (because it is direct), +# c (because it is a missing test dependency of an imported package), +# and d (because it is a missing transitive test dependency). +# +# In go 1.16, the tidy main module can omit d because it is no longer +# included in "all". +# +# In go 1.17, the main module must explicitly require b +# (because it is transitively imported by the main module). + + +cp go.mod go.mod.orig + + +# An invalid argument should be rejected. + +! go mod tidy -go=bananas +stderr '^invalid value "bananas" for flag -go: expecting a Go version like "'$goversion'"$' +cmp go.mod go.mod.orig + +! go mod tidy -go=0.9 +stderr '^invalid value "0.9" for flag -go: expecting a Go version like "'$goversion'"$' + +! go mod tidy -go=2000.0 +stderr '^invalid value "2000.0" for flag -go: maximum supported Go version is '$goversion'$' + + +# Supported versions should change the go.mod file to be tidy according to the +# indicated version. + +go mod tidy -go=1.15 +cmp go.mod go.mod.115 + +go mod tidy +cmp go.mod go.mod.115 + + +go mod tidy -go=1.16 +cmp go.mod go.mod.116 + +go mod tidy +cmp go.mod go.mod.116 + + +go mod tidy -go=1.17 +cmp go.mod go.mod.117 + +go mod tidy +cmp go.mod go.mod.117 + + +# If we downgrade back to 1.15, we should re-resolve d to v0.2.0 instead +# of the original v0.1.0 (because the original requirement is lost). + +go mod tidy -go=1.15 +cmp go.mod go.mod.115-2 + + +# -go= (with an empty argument) maintains the existing version or adds the +# default version (just like omitting the flag). + +go mod tidy -go='' +cmp go.mod go.mod.115-2 + +cp go.mod.orig go.mod +go mod tidy -go='' +cmpenv go.mod go.mod.latest + + + +-- go.mod -- +module example.com/m + +require example.net/a v0.1.0 + +require ( + example.net/c v0.1.0 // indirect + example.net/d v0.1.0 // indirect +) + +replace ( + example.net/a v0.1.0 => ./a + example.net/a v0.2.0 => ./a + example.net/b v0.1.0 => ./b + example.net/b v0.2.0 => ./b + example.net/c v0.1.0 => ./c + example.net/c v0.2.0 => ./c + example.net/d v0.1.0 => ./d + example.net/d v0.2.0 => ./d +) +-- m.go -- +package m + +import _ "example.net/a" + +-- go.mod.115 -- +module example.com/m + +go 1.15 + +require example.net/a v0.1.0 + +require ( + example.net/c v0.1.0 // indirect + example.net/d v0.1.0 // indirect +) + +replace ( + example.net/a v0.1.0 => ./a + example.net/a v0.2.0 => ./a + example.net/b v0.1.0 => ./b + example.net/b v0.2.0 => ./b + example.net/c v0.1.0 => ./c + example.net/c v0.2.0 => ./c + example.net/d v0.1.0 => ./d + example.net/d v0.2.0 => ./d +) +-- go.mod.115-2 -- +module example.com/m + +go 1.15 + +require example.net/a v0.1.0 + +require ( + example.net/c v0.1.0 // indirect + example.net/d v0.2.0 // indirect +) + +replace ( + example.net/a v0.1.0 => ./a + example.net/a v0.2.0 => ./a + example.net/b v0.1.0 => ./b + example.net/b v0.2.0 => ./b + example.net/c v0.1.0 => ./c + example.net/c v0.2.0 => ./c + example.net/d v0.1.0 => ./d + example.net/d v0.2.0 => ./d +) +-- go.mod.116 -- +module example.com/m + +go 1.16 + +require example.net/a v0.1.0 + +require example.net/c v0.1.0 // indirect + +replace ( + example.net/a v0.1.0 => ./a + example.net/a v0.2.0 => ./a + example.net/b v0.1.0 => ./b + example.net/b v0.2.0 => ./b + example.net/c v0.1.0 => ./c + example.net/c v0.2.0 => ./c + example.net/d v0.1.0 => ./d + example.net/d v0.2.0 => ./d +) +-- go.mod.117 -- +module example.com/m + +go 1.17 + +require example.net/a v0.1.0 + +require ( + example.net/b v0.1.0 // indirect + example.net/c v0.1.0 // indirect +) + +replace ( + example.net/a v0.1.0 => ./a + example.net/a v0.2.0 => ./a + example.net/b v0.1.0 => ./b + example.net/b v0.2.0 => ./b + example.net/c v0.1.0 => ./c + example.net/c v0.2.0 => ./c + example.net/d v0.1.0 => ./d + example.net/d v0.2.0 => ./d +) +-- go.mod.latest -- +module example.com/m + +go $goversion + +require example.net/a v0.1.0 + +require ( + example.net/b v0.1.0 // indirect + example.net/c v0.1.0 // indirect +) + +replace ( + example.net/a v0.1.0 => ./a + example.net/a v0.2.0 => ./a + example.net/b v0.1.0 => ./b + example.net/b v0.2.0 => ./b + example.net/c v0.1.0 => ./c + example.net/c v0.2.0 => ./c + example.net/d v0.1.0 => ./d + example.net/d v0.2.0 => ./d +) +-- a/go.mod -- +module example.net/a + +go 1.15 + +require example.net/b v0.1.0 +-- a/a.go -- +package a + +import _ "example.net/b" + +-- b/go.mod -- +module example.net/b + +go 1.15 +-- b/b.go -- +package b +-- b/b_test.go -- +package b_test + +import _ "example.net/c" + +-- c/go.mod -- +module example.net/c + +go 1.15 +-- c/c.go -- +package c +-- c/c_test.go -- +package c_test + +import _ "example.net/d" + +-- d/go.mod -- +module example.net/d + +go 1.15 +-- d/d.go -- +package d diff --git a/src/cmd/go/testdata/script/mod_update_sum_readonly.txt b/src/cmd/go/testdata/script/mod_update_sum_readonly.txt new file mode 100644 index 0000000000000000000000000000000000000000..41f12e4084d1f5d8dfe423ce24e116ed5a266021 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_update_sum_readonly.txt @@ -0,0 +1,34 @@ +# When finding the latest version of a module, we should not download version +# contents. Previously, we downloaded .zip files to determine whether a real +# .mod file was present in order to decide whether +incompatible versions +# could be "latest". +# +# Verifies #47377. + +# rsc.io/breaker has two versions, neither of which has a .mod file. +go list -m -versions rsc.io/breaker +stdout '^rsc.io/breaker v1.0.0 v2.0.0\+incompatible$' +go mod download rsc.io/breaker@v1.0.0 +! grep '^go' $GOPATH/pkg/mod/cache/download/rsc.io/breaker/@v/v1.0.0.mod +go mod download rsc.io/breaker@v2.0.0+incompatible +! grep '^go' $GOPATH/pkg/mod/cache/download/rsc.io/breaker/@v/v2.0.0+incompatible.mod + +# Delete downloaded .zip files. +go clean -modcache + +# Check for updates. +go list -m -u rsc.io/breaker +stdout '^rsc.io/breaker v1.0.0 \[v2.0.0\+incompatible\]$' + +# We should not have downloaded zips. +! exists $GOPATH/pkg/mod/cache/download/rsc.io/breaker/@v/v1.0.0.zip +! exists $GOPATH/pkg/mod/cache/download/rsc.io/breaker/@v/v2.0.0+incompatible.zip + +-- go.mod -- +module m + +go 1.16 + +require rsc.io/breaker v1.0.0 +-- go.sum -- +rsc.io/breaker v1.0.0/go.mod h1:s5yxDXvD88U1/ESC23I2FK3Lkv4YIKaB1ij/Hbm805g= diff --git a/src/cmd/go/testdata/script/mod_vendor_gomod.txt b/src/cmd/go/testdata/script/mod_vendor_gomod.txt new file mode 100644 index 0000000000000000000000000000000000000000..3f6ea3561a44b12461057023ee1e75506ed74def --- /dev/null +++ b/src/cmd/go/testdata/script/mod_vendor_gomod.txt @@ -0,0 +1,38 @@ +# https://golang.org/issue/42970: As of Go 1.17, go.mod and go.sum files should +# be stripped from vendored dependencies. + +go mod vendor +cd vendor/example.net/x +go list all +! stdout '^example.net/m' +stdout '^example.net/x$' +exists ./go.sum + +cd ../../.. +go mod edit -go=1.17 +go mod vendor +cd vendor/example.net/x +go list all +stdout '^example.net/m$' +stdout '^example.net/x$' +! exists ./go.sum + +-- go.mod -- +module example.net/m + +go 1.16 + +require example.net/x v0.1.0 + +replace example.net/x v0.1.0 => ./x +-- m.go -- +package m + +import _ "example.net/x" +-- x/go.mod -- +module example.net/x + +go 1.16 +-- x/go.sum -- +-- x/x.go -- +package x diff --git a/src/cmd/go/testdata/script/mod_vendor_goversion.txt b/src/cmd/go/testdata/script/mod_vendor_goversion.txt new file mode 100644 index 0000000000000000000000000000000000000000..aa4cb41171a5199aaf94fd8566d050798ec31fff --- /dev/null +++ b/src/cmd/go/testdata/script/mod_vendor_goversion.txt @@ -0,0 +1,102 @@ +# https://golang.org/issue/36876: As of Go 1.17, vendor/modules.txt should +# indicate the language version used by each dependency. + +[short] skip + + +# Control case: without a vendor directory, need117 builds and bad114 doesn't. + +go build example.net/need117 +! go build example.net/bad114 +stderr '^bad114[/\\]bad114.go:15:2: duplicate method Y$' + + +# With a vendor/modules.txt lacking language versions, the world is topsy-turvy, +# because we have to guess a uniform version for everything. +# +# We always guess Go 1.16, because that was the last version for which +# 'go mod vendor' failed to record dependency versions, and it has most of +# the language features added since modules were introduced in Go 1.11. +# +# Even so, modules that declare 'go 1.17' and use 1.17 features spuriously fail +# to build, and modules that declare an older version and use features from a +# newer one spuriously build (instead of failing as they ought to). + +go mod vendor + +! grep 1.17 vendor/modules.txt +! go build example.net/need117 +stderr '^vendor[/\\]example\.net[/\\]need117[/\\]need117.go:5:18: .*\n\tconversion of slices to array pointers only supported as of -lang=go1\.17' + +! grep 1.13 vendor/modules.txt +go build example.net/bad114 + + +# Upgrading the main module to 1.17 adds version annotations. +# Then everything is once again consistent with the non-vendored world. + +go mod edit -go=1.17 +go mod vendor + +grep '^## explicit; go 1.17$' vendor/modules.txt +go build example.net/need117 + +grep '^## explicit; go 1.13$' vendor/modules.txt +! go build example.net/bad114 +stderr '^vendor[/\\]example\.net[/\\]bad114[/\\]bad114.go:15:2: duplicate method Y$' + +-- go.mod -- +module example.net/m + +go 1.16 + +require ( + example.net/bad114 v0.1.0 + example.net/need117 v0.1.0 +) + +replace ( + example.net/bad114 v0.1.0 => ./bad114 + example.net/need117 v0.1.0 => ./need117 +) +-- m.go -- +package m + +import _ "example.net/bad114" +import _ "example.net/need117" + +-- bad114/go.mod -- +// Module bad114 requires Go 1.14 or higher, but declares Go 1.13. +module example.net/bad114 + +go 1.13 +-- bad114/bad114.go -- +package bad114 + +type XY interface { + X() + Y() +} + +type YZ interface { + Y() + Z() +} + +type XYZ interface { + XY + YZ +} + +-- need117/go.mod -- +// Module need117 requires Go 1.17 or higher. +module example.net/need117 + +go 1.17 +-- need117/need117.go -- +package need117 + +func init() { + s := make([]byte, 4) + _ = (*[4]byte)(s) +} diff --git a/src/cmd/go/testdata/script/mod_vendor_issue46867.txt b/src/cmd/go/testdata/script/mod_vendor_issue46867.txt new file mode 100644 index 0000000000000000000000000000000000000000..38ae87b44a981e145b1321b7a9a3a13a560312df --- /dev/null +++ b/src/cmd/go/testdata/script/mod_vendor_issue46867.txt @@ -0,0 +1,31 @@ +# Regression test for golang.org/issue/46867: +# 'go mod vendor' on Windows attempted to open and copy +# files from directories outside of the module. + +cd subdir +go mod vendor +! exists vendor/example.net/NOTICE +exists vendor/example.net/m/NOTICE + +-- subdir/go.mod -- +module golang.org/issue46867 + +go 1.17 + +replace example.net/m v0.1.0 => ./m + +require example.net/m v0.1.0 +-- subdir/issue.go -- +package issue + +import _ "example.net/m/n" +-- subdir/m/go.mod -- +module example.net/m + +go 1.17 +-- subdir/m/n/n.go -- +package n +-- subdir/m/NOTICE -- +This notice is in module m and SHOULD be vendored. +-- subdir/NOTICE -- +This notice is outside of module m and SHOULD NOT be vendored. diff --git a/src/cmd/go/testdata/script/mod_vendor_redundant_requirement.txt b/src/cmd/go/testdata/script/mod_vendor_redundant_requirement.txt new file mode 100644 index 0000000000000000000000000000000000000000..3f6f5c5276b9752f9445840181aa9689801731f5 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_vendor_redundant_requirement.txt @@ -0,0 +1,29 @@ +# 'go list -mod=vendor' should succeed even when go.mod contains redundant +# requirements. Verifies #47565. +go list -mod=vendor + +-- go.mod -- +module m + +go 1.17 + +require example.com/m v0.0.0 +require example.com/m v0.0.0 + +replace example.com/m v0.0.0 => ./m +-- m/go.mod -- +module example.com/m + +go 1.17 +-- m/m.go -- +package m +-- use.go -- +package use + +import _ "example.com/m" +-- vendor/example.com/m/m.go -- +package m +-- vendor/modules.txt -- +# example.com/m v0.0.0 => ./m +## explicit; go 1.17 +example.com/m diff --git a/src/cmd/go/testdata/script/test_cache_inputs.txt b/src/cmd/go/testdata/script/test_cache_inputs.txt index 50486e19090dd304c0fe22a9241e5c8ca1b8a5ff..d694a30994710e70145469998b3fd086b5f2964f 100644 --- a/src/cmd/go/testdata/script/test_cache_inputs.txt +++ b/src/cmd/go/testdata/script/test_cache_inputs.txt @@ -99,6 +99,15 @@ rm $WORK/external.txt go test testcache -run=ExternalFile stdout '\(cached\)' +# The -benchtime flag without -bench should not affect caching. +go test testcache -run=Benchtime -benchtime=1x +go test testcache -run=Benchtime -benchtime=1x +stdout '\(cached\)' + +go test testcache -run=Benchtime -bench=Benchtime -benchtime=1x +go test testcache -run=Benchtime -bench=Benchtime -benchtime=1x +! stdout '\(cached\)' + # Executables within GOROOT and GOPATH should affect caching, # even if the test does not stat them explicitly. @@ -228,6 +237,10 @@ func TestExternalFile(t *testing.T) { func TestOSArgs(t *testing.T) { t.Log(os.Args) } + +func TestBenchtime(t *testing.T) { +} + -- mkold.go -- package main diff --git a/src/cmd/go/testdata/script/test_chatty_parallel_fail.txt b/src/cmd/go/testdata/script/test_chatty_parallel_fail.txt index 3f7360b6592be4b2fe2adfe2ece716e14028406a..f8faa93663d73ede0defebac1baea68ac9e5c1cf 100644 --- a/src/cmd/go/testdata/script/test_chatty_parallel_fail.txt +++ b/src/cmd/go/testdata/script/test_chatty_parallel_fail.txt @@ -14,7 +14,7 @@ stdout -count=1 '{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"comma stdout -count=1 '{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-2","Output":"=== CONT TestChattyParallel/sub-2\\n"}\n{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-2","Output":" chatty_parallel_test.go:38: error from sub-2\\n"}' -- chatty_parallel_test.go -- -package chatty_paralell_test +package chatty_parallel_test import ( "testing" @@ -22,7 +22,7 @@ import ( "flag" ) -// This test ensures the the order of CONT lines in parallel chatty tests. +// This test ensures the order of CONT lines in parallel chatty tests. func TestChattyParallel(t *testing.T) { t.Parallel() diff --git a/src/cmd/go/testdata/script/test_chatty_parallel_success.txt b/src/cmd/go/testdata/script/test_chatty_parallel_success.txt index 4a86d74f19691175c13f3b5caa6c507c1543ec38..63034fa3b5f2996649a0728e6609b983a75dc485 100644 --- a/src/cmd/go/testdata/script/test_chatty_parallel_success.txt +++ b/src/cmd/go/testdata/script/test_chatty_parallel_success.txt @@ -13,7 +13,7 @@ stdout -count=2 '{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"comma stdout -count=2 '{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-2","Output":"=== CONT TestChattyParallel/sub-2\\n"}\n{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-2","Output":" chatty_parallel_test.go:32: this is sub-2\\n"}' -- chatty_parallel_test.go -- -package chatty_paralell_test +package chatty_parallel_test import ( "testing" @@ -21,7 +21,7 @@ import ( "flag" ) -// This test ensures the the order of CONT lines in parallel chatty tests. +// This test ensures the order of CONT lines in parallel chatty tests. func TestChattyParallel(t *testing.T) { t.Parallel() diff --git a/src/cmd/go/testdata/script/test_chatty_parallel_success_sleepy.txt b/src/cmd/go/testdata/script/test_chatty_parallel_success_sleepy.txt index 5952a87beaba88f2fd87c01b37bf857e31090f05..e651a7ed243f6bb0ac48754a013c4d0abd28db78 100644 --- a/src/cmd/go/testdata/script/test_chatty_parallel_success_sleepy.txt +++ b/src/cmd/go/testdata/script/test_chatty_parallel_success_sleepy.txt @@ -5,7 +5,7 @@ go test -parallel 3 chatty_parallel_test.go -v stdout '--- PASS: TestFast \([0-9.]{4}s\)\n=== CONT TestSlow\n chatty_parallel_test.go:31: this is the second TestSlow log\n--- PASS: TestSlow \([0-9.]{4}s\)' -- chatty_parallel_test.go -- -package chatty_paralell_test +package chatty_parallel_test import ( "testing" diff --git a/src/cmd/go/testdata/script/test_finished_subtest_goroutines.txt b/src/cmd/go/testdata/script/test_finished_subtest_goroutines.txt new file mode 100644 index 0000000000000000000000000000000000000000..8db821eb77f2ecee3f1daad4144570f2c50f68f7 --- /dev/null +++ b/src/cmd/go/testdata/script/test_finished_subtest_goroutines.txt @@ -0,0 +1,52 @@ +# Regression test for https://golang.org/issue/45127: +# Goroutines for completed parallel subtests should exit immediately, +# not block until earlier subtests have finished. + +[short] skip + +! go test . +stdout 'panic: slow failure' +! stdout '\[chan send' + +-- go.mod -- +module golang.org/issue45127 + +go 1.16 +-- issue45127_test.go -- +package main + +import ( + "fmt" + "runtime" + "runtime/debug" + "sync" + "testing" +) + +func TestTestingGoroutineLeak(t *testing.T) { + debug.SetTraceback("all") + + var wg sync.WaitGroup + const nFast = 10 + + t.Run("slow", func(t *testing.T) { + t.Parallel() + wg.Wait() + for i := 0; i < nFast; i++ { + // If the subtest goroutines are going to park on the channel + // send, allow them to park now. If they're not going to park, + // make sure they have had a chance to run to completion so + // that they aren't spuriously parked when we panic. + runtime.Gosched() + } + panic("slow failure") + }) + + wg.Add(nFast) + for i := 0; i < nFast; i++ { + t.Run(fmt.Sprintf("leaky%d", i), func(t *testing.T) { + t.Parallel() + wg.Done() + }) + } +} diff --git a/src/cmd/go/testdata/script/test_overlay.txt b/src/cmd/go/testdata/script/test_overlay.txt new file mode 100644 index 0000000000000000000000000000000000000000..b6bdc116e62315b09e0640bf45d6621b14d5da97 --- /dev/null +++ b/src/cmd/go/testdata/script/test_overlay.txt @@ -0,0 +1,24 @@ +[short] skip + +cd $WORK/gopath/src/foo +go test -list=. -overlay=overlay.json . +stdout 'TestBar' + +-- go.mod -- +module test.pkg +-- foo/foo_test.go -- +package foo + +import "testing" + +func TestFoo(t *testing.T) { } +-- tmp/bar_test.go -- +package foo + +import "testing" + +func TestBar(t *testing.T) { + t.Fatal("dummy failure") +} +-- foo/overlay.json -- +{"Replace": {"foo_test.go": "../tmp/bar_test.go"}} diff --git a/src/cmd/go/testdata/script/test_race_install_cgo.txt b/src/cmd/go/testdata/script/test_race_install_cgo.txt index 3f4eb90e3f66e72b48cc52106ea8a9868706e8c9..e1fe4f2acea96f161198dec4f45558d341749f6f 100644 --- a/src/cmd/go/testdata/script/test_race_install_cgo.txt +++ b/src/cmd/go/testdata/script/test_race_install_cgo.txt @@ -2,8 +2,6 @@ [!race] skip -[!darwin] ! stale cmd/cgo # The darwin builders are spuriously stale; see #33598. - env GOBIN=$WORK/bin go install m/mtime m/sametime diff --git a/src/cmd/go/testdata/script/test_script_cmdcd.txt b/src/cmd/go/testdata/script/test_script_cmdcd.txt new file mode 100644 index 0000000000000000000000000000000000000000..6e6f67e13d089326dea85f0f9080bb1ead6cfd82 --- /dev/null +++ b/src/cmd/go/testdata/script/test_script_cmdcd.txt @@ -0,0 +1,13 @@ +# Tests that after a cd command, where usually the UNIX path separator is used, +# a match against $PWD does not fail on Windows. + +cd $WORK/a/b/c/pkg + +go list -find -f {{.Root}} +stdout $PWD + +-- $WORK/a/b/c/pkg/go.mod -- +module pkg + +-- $WORK/a/b/c/pkg/pkg.go -- +package pkg diff --git a/src/cmd/go/testdata/script/test_shuffle.txt b/src/cmd/go/testdata/script/test_shuffle.txt new file mode 100644 index 0000000000000000000000000000000000000000..3a50605c3444923b425a7a208bbe81e4975da250 --- /dev/null +++ b/src/cmd/go/testdata/script/test_shuffle.txt @@ -0,0 +1,148 @@ +# Shuffle order of tests and benchmarks + +# Run tests +go test -v foo_test.go +! stdout '-test.shuffle ' +stdout '(?s)TestOne(.*)TestTwo(.*)TestThree' + +go test -v -shuffle=off foo_test.go +! stdout '-test.shuffle ' +stdout '(?s)TestOne(.*)TestTwo(.*)TestThree' + +go test -v -shuffle=42 foo_test.go +stdout '^-test.shuffle 42' +stdout '(?s)TestThree(.*)TestOne(.*)TestTwo' + +go test -v -shuffle=43 foo_test.go +stdout '^-test.shuffle 43' +stdout '(?s)TestThree(.*)TestTwo(.*)TestOne' + +go test -v -shuffle=44 foo_test.go +stdout '^-test.shuffle 44' +stdout '(?s)TestOne(.*)TestThree(.*)TestTwo' + +go test -v -shuffle=0 foo_test.go +stdout '^-test.shuffle 0' +stdout '(?s)TestTwo(.*)TestOne(.*)TestThree' + +go test -v -shuffle -1 foo_test.go +stdout '^-test.shuffle -1' +stdout '(?s)TestThree(.*)TestOne(.*)TestTwo' + +go test -v -shuffle=on foo_test.go +stdout '^-test.shuffle ' +stdout '(?s)=== RUN TestOne(.*)--- PASS: TestOne' +stdout '(?s)=== RUN TestTwo(.*)--- PASS: TestTwo' +stdout '(?s)=== RUN TestThree(.*)--- PASS: TestThree' + + +# Run tests and benchmarks +go test -v -bench=. foo_test.go +! stdout '-test.shuffle ' +stdout '(?s)TestOne(.*)TestTwo(.*)TestThree(.*)BenchmarkOne(.*)BenchmarkTwo(.*)BenchmarkThree' + +go test -v -bench=. -shuffle=off foo_test.go +! stdout '-test.shuffle ' +stdout '(?s)TestOne(.*)TestTwo(.*)TestThree(.*)BenchmarkOne(.*)BenchmarkTwo(.*)BenchmarkThree' + +go test -v -bench=. -shuffle=42 foo_test.go +stdout '^-test.shuffle 42' +stdout '(?s)TestThree(.*)TestOne(.*)TestTwo(.*)BenchmarkThree(.*)BenchmarkOne(.*)BenchmarkTwo' + +go test -v -bench=. -shuffle=43 foo_test.go +stdout '^-test.shuffle 43' +stdout '(?s)TestThree(.*)TestTwo(.*)TestOne(.*)BenchmarkThree(.*)BenchmarkOne(.*)BenchmarkTwo' + +go test -v -bench=. -shuffle=44 foo_test.go +stdout '^-test.shuffle 44' +stdout '(?s)TestOne(.*)TestThree(.*)TestTwo(.*)BenchmarkTwo(.*)BenchmarkOne(.*)BenchmarkThree' + +go test -v -bench=. -shuffle=0 foo_test.go +stdout '^-test.shuffle 0' +stdout '(?s)TestTwo(.*)TestOne(.*)TestThree(.*)BenchmarkThree(.*)BenchmarkOne(.*)BenchmarkTwo' + +go test -v -bench=. -shuffle -1 foo_test.go +stdout '^-test.shuffle -1' +stdout '(?s)TestThree(.*)TestOne(.*)TestTwo(.*)BenchmarkOne(.*)BenchmarkThree(.*)BenchmarkTwo' + +go test -v -bench=. -shuffle=on foo_test.go +stdout '^-test.shuffle ' +stdout '(?s)=== RUN TestOne(.*)--- PASS: TestOne' +stdout '(?s)=== RUN TestTwo(.*)--- PASS: TestTwo' +stdout '(?s)=== RUN TestThree(.*)--- PASS: TestThree' +stdout -count=2 'BenchmarkOne' +stdout -count=2 'BenchmarkTwo' +stdout -count=2 'BenchmarkThree' + + +# When running go test -count=N, each of the N runs distinct runs should maintain the same +# shuffled order of these tests. +go test -v -shuffle=43 -count=4 foo_test.go +stdout '^-test.shuffle 43' +stdout '(?s)TestThree(.*)TestTwo(.*)TestOne(.*)TestThree(.*)TestTwo(.*)TestOne(.*)TestThree(.*)TestTwo(.*)TestOne(.*)TestThree(.*)TestTwo(.*)TestOne' + +go test -v -bench=. -shuffle=44 -count=2 foo_test.go +stdout '^-test.shuffle 44' +stdout '(?s)TestOne(.*)TestThree(.*)TestTwo(.*)TestOne(.*)TestThree(.*)TestTwo(.*)BenchmarkTwo(.*)BenchmarkOne(.*)BenchmarkThree(.*)' + + +# The feature should work with test binaries as well +go test -c +exec ./m.test -test.shuffle=off +! stdout '^-test.shuffle ' + +exec ./m.test -test.shuffle=on +stdout '^-test.shuffle ' + +exec ./m.test -test.v -test.bench=. -test.shuffle=0 foo_test.go +stdout '^-test.shuffle 0' +stdout '(?s)TestTwo(.*)TestOne(.*)TestThree(.*)BenchmarkThree(.*)BenchmarkOne(.*)BenchmarkTwo' + +exec ./m.test -test.v -test.bench=. -test.shuffle=123 foo_test.go +stdout '^-test.shuffle 123' +stdout '(?s)TestThree(.*)TestOne(.*)TestTwo(.*)BenchmarkThree(.*)BenchmarkTwo(.*)BenchmarkOne' + +exec ./m.test -test.v -test.bench=. -test.shuffle=-1 foo_test.go +stdout '^-test.shuffle -1' +stdout '(?s)TestThree(.*)TestOne(.*)TestTwo(.*)BenchmarkOne(.*)BenchmarkThree(.*)BenchmarkTwo' + +exec ./m.test -test.v -test.bench=. -test.shuffle=44 -test.count=2 foo_test.go +stdout '^-test.shuffle 44' +stdout '(?s)TestOne(.*)TestThree(.*)TestTwo(.*)TestOne(.*)TestThree(.*)TestTwo(.*)BenchmarkTwo(.*)BenchmarkOne(.*)BenchmarkThree(.*)' + + +# Negative testcases for invalid input +! go test -shuffle -count=2 +stderr 'invalid value "-count=2" for flag -shuffle: -shuffle argument must be "on", "off", or an int64: strconv.ParseInt: parsing "-count=2": invalid syntax' + +! go test -shuffle= +stderr '(?s)invalid value "" for flag -shuffle: -shuffle argument must be "on", "off", or an int64: strconv.ParseInt: parsing "": invalid syntax' + +! go test -shuffle=' ' +stderr '(?s)invalid value " " for flag -shuffle: -shuffle argument must be "on", "off", or an int64: strconv.ParseInt: parsing " ": invalid syntax' + +! go test -shuffle=true +stderr 'invalid value "true" for flag -shuffle: -shuffle argument must be "on", "off", or an int64: strconv.ParseInt: parsing "true": invalid syntax' + +! go test -shuffle='abc' +stderr 'invalid value "abc" for flag -shuffle: -shuffle argument must be "on", "off", or an int64: strconv.ParseInt: parsing "abc": invalid syntax' + +-- go.mod -- +module m + +go 1.16 +-- foo_test.go -- +package foo + +import "testing" + +func TestOne(t *testing.T) {} +func TestTwo(t *testing.T) {} +func TestThree(t *testing.T) {} + +func BenchmarkOne(b *testing.B) {} +func BenchmarkTwo(b *testing.B) {} +func BenchmarkThree(b *testing.B) {} + +-- foo.go -- +package foo diff --git a/src/cmd/go/testdata/script/test_trimpath.txt b/src/cmd/go/testdata/script/test_trimpath.txt new file mode 100644 index 0000000000000000000000000000000000000000..065f9ce4d17852c87a360bba019132ad8df27bf8 --- /dev/null +++ b/src/cmd/go/testdata/script/test_trimpath.txt @@ -0,0 +1,51 @@ +[short] skip + +go test -trimpath -v . +! stdout '[/\\]pkg_test[/\\]' +stdout -count=3 '[/\\]pkg[/\\]' + +-- go.mod -- +module example.com/pkg + +go 1.17 + +-- pkg.go -- +package pkg + +import "runtime" + +func PrintFile() { + _, file, _, _ := runtime.Caller(0) + println(file) +} + +-- pkg_test.go -- +package pkg + +import "runtime" + +func PrintFileForTest() { + _, file, _, _ := runtime.Caller(0) + println(file) +} + +-- pkg_x_test.go -- +package pkg_test + +import ( + "runtime" + "testing" + + "example.com/pkg" +) + +func TestMain(m *testing.M) { + pkg.PrintFile() + pkg.PrintFileForTest() + PrintFileInXTest() +} + +func PrintFileInXTest() { + _, file, _, _ := runtime.Caller(0) + println(file) +} diff --git a/src/cmd/go/testdata/script/test_trimpath_main.txt b/src/cmd/go/testdata/script/test_trimpath_main.txt new file mode 100644 index 0000000000000000000000000000000000000000..c07621245fbdfcdda6057277ec43ef27cb48730e --- /dev/null +++ b/src/cmd/go/testdata/script/test_trimpath_main.txt @@ -0,0 +1,38 @@ +[short] skip + +go test -trimpath -v . +! stdout '[/\\]pkg_test[/\\]' +stdout -count=2 '[/\\]pkg[/\\]' + +-- go.mod -- +module example.com/pkg + +go 1.17 + +-- main.go -- +package main + +import "runtime" + +func PrintFile() { + _, file, _, _ := runtime.Caller(0) + println(file) +} + +-- main_test.go -- +package main + +import ( + "runtime" + "testing" +) + +func PrintFileForTest() { + _, file, _, _ := runtime.Caller(0) + println(file) +} + +func TestMain(m *testing.M) { + PrintFile() + PrintFileForTest() +} diff --git a/src/cmd/go/testdata/script/test_trimpath_test_suffix.txt b/src/cmd/go/testdata/script/test_trimpath_test_suffix.txt new file mode 100644 index 0000000000000000000000000000000000000000..6cbad83bc78051199936e32cc4aec35a80e2a50f --- /dev/null +++ b/src/cmd/go/testdata/script/test_trimpath_test_suffix.txt @@ -0,0 +1,40 @@ +[short] skip + +go test -trimpath -v . +! stdout '[/\\]pkg_test_test[/\\]' +stdout -count=2 '[/\\]pkg_test[/\\]' + +-- go.mod -- +module example.com/pkg_test + +go 1.17 + +-- pkg.go -- +package pkg_test + +import "runtime" + +func PrintFile() { + _, file, _, _ := runtime.Caller(0) + println(file) +} + +-- pkg_x_test.go -- +package pkg_test_test + +import ( + "runtime" + "testing" + + "example.com/pkg_test" +) + +func PrintFileForTest() { + _, file, _, _ := runtime.Caller(0) + println(file) +} + +func TestMain(m *testing.M) { + pkg_test.PrintFile() + PrintFileForTest() +} diff --git a/src/cmd/go/testdata/script/test_write_profiles_on_timeout.txt b/src/cmd/go/testdata/script/test_write_profiles_on_timeout.txt index 08e67a429e0a899abc7c1db925d2dcbc3dc27bc4..0db183f8f04f33aae08a9c4573f5472e099adef7 100644 --- a/src/cmd/go/testdata/script/test_write_profiles_on_timeout.txt +++ b/src/cmd/go/testdata/script/test_write_profiles_on_timeout.txt @@ -3,6 +3,7 @@ [short] skip ! go test -cpuprofile cpu.pprof -memprofile mem.pprof -timeout 1ms +stdout '^panic: test timed out' grep . cpu.pprof grep . mem.pprof @@ -12,6 +13,14 @@ module profiling go 1.16 -- timeout_test.go -- package timeouttest_test -import "testing" -import "time" -func TestSleep(t *testing.T) { time.Sleep(time.Second) } + +import ( + "testing" + "time" +) + +func TestSleep(t *testing.T) { + for { + time.Sleep(1 * time.Second) + } +} diff --git a/src/cmd/go/testdata/script/toolexec.txt b/src/cmd/go/testdata/script/toolexec.txt index 526234196b599262cb64bf632319744a63db83f1..bb86467942b9fd240c3f0579818564672d607e1f 100644 --- a/src/cmd/go/testdata/script/toolexec.txt +++ b/src/cmd/go/testdata/script/toolexec.txt @@ -3,6 +3,12 @@ # Build our simple toolexec program. go build ./cmd/mytool +# Use an ephemeral build cache so that our toolexec output is not cached +# for any stale standard-library dependencies. +# +# TODO(#27628): This should not be necessary. +env GOCACHE=$WORK/gocache + # Build the main package with our toolexec program. For each action, it will # print the tool's name and the TOOLEXEC_IMPORTPATH value. We expect to compile # each package once, and link the main package once. @@ -11,12 +17,37 @@ go build ./cmd/mytool # Finally, note that asm and cgo are run twice. go build -toolexec=$PWD/mytool -[amd64] stderr -count=2 '^asm'${GOEXE}' TOOLEXEC_IMPORTPATH=test/main/withasm$' -stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH=test/main/withasm$' -[cgo] stderr -count=2 '^cgo'${GOEXE}' TOOLEXEC_IMPORTPATH=test/main/withcgo$' -[cgo] stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH=test/main/withcgo$' -stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH=test/main$' -stderr -count=1 '^link'${GOEXE}' TOOLEXEC_IMPORTPATH=test/main$' +[amd64] stderr -count=2 '^asm'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main/withasm"$' +stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main/withasm"$' +[cgo] stderr -count=2 '^cgo'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main/withcgo"$' +[cgo] stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main/withcgo"$' +stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main"$' +stderr -count=1 '^link'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main"$' + +# Test packages are a little bit trickier. +# We have four variants of test/main, as reported by 'go list -test': +# +# test/main - the regular non-test package +# test/main.test - the generated test program +# test/main [test/main.test] - the test package for foo_test.go +# test/main_test [test/main.test] - the test package for foo_separate_test.go +# +# As such, TOOLEXEC_IMPORTPATH must see the same strings, to be able to uniquely +# identify each package being built as reported by 'go list -f {{.ImportPath}}'. +# Note that these are not really "import paths" anymore, but that naming is +# consistent with 'go list -json' at least. + +go test -toolexec=$PWD/mytool + +stderr -count=2 '^# test/main\.test$' +stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main\.test"$' +stderr -count=1 '^link'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main\.test"$' + +stderr -count=1 '^# test/main \[test/main\.test\]$' +stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main \[test/main\.test\]"$' + +stderr -count=1 '^# test/main_test \[test/main\.test\]$' +stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main_test \[test/main\.test\]"$' -- go.mod -- module test/main @@ -32,6 +63,18 @@ import ( ) func main() {} +-- foo_test.go -- +package main + +import "testing" + +func TestFoo(t *testing.T) {} +-- foo_separate_test.go -- +package main_test + +import "testing" + +func TestSeparateFoo(t *testing.T) {} -- withcgo/withcgo.go -- package withcgo @@ -71,7 +114,7 @@ func main() { // We can't alter the version output. } else { // Print which tool we're running, and on what package. - fmt.Fprintf(os.Stdout, "%s TOOLEXEC_IMPORTPATH=%s\n", toolName, os.Getenv("TOOLEXEC_IMPORTPATH")) + fmt.Fprintf(os.Stdout, "%s TOOLEXEC_IMPORTPATH=%q\n", toolName, os.Getenv("TOOLEXEC_IMPORTPATH")) } // Simply run the tool. diff --git a/src/cmd/go/testdata/script/vendor_test_issue14613.txt b/src/cmd/go/testdata/script/vendor_test_issue14613.txt index 7801e6944d22f2221dd857cdd32803616a313b80..cfd7e58f4f77903e10220bf89b7a65682a6606af 100644 --- a/src/cmd/go/testdata/script/vendor_test_issue14613.txt +++ b/src/cmd/go/testdata/script/vendor_test_issue14613.txt @@ -19,4 +19,4 @@ go test github.com/clsung/go-vendor-issue-14613/vendor_test.go # test with imported and not used go test -i github.com/clsung/go-vendor-issue-14613/vendor/mylibtesttest/myapp/myapp_test.go ! go test github.com/clsung/go-vendor-issue-14613/vendor/mylibtesttest/myapp/myapp_test.go -stderr 'imported and not used:' +stderr 'imported and not used' diff --git a/src/cmd/go/testdata/script/version_goexperiment.txt b/src/cmd/go/testdata/script/version_goexperiment.txt new file mode 100644 index 0000000000000000000000000000000000000000..4b165eb6055447fe01a54b9e24c5c4c00fd24052 --- /dev/null +++ b/src/cmd/go/testdata/script/version_goexperiment.txt @@ -0,0 +1,16 @@ +# Test that experiments appear in "go version " + +# This test requires rebuilding the runtime, which takes a while. +[short] skip + +env GOEXPERIMENT=fieldtrack +go build -o main$GOEXE version.go +go version main$GOEXE +stdout 'X:fieldtrack$' +exec ./main$GOEXE +stderr 'X:fieldtrack$' + +-- version.go -- +package main +import "runtime" +func main() { println(runtime.Version()) } diff --git a/src/cmd/gofmt/gofmt.go b/src/cmd/gofmt/gofmt.go index 2793c2c2a43f0891f9d91c0cdb94515c72572e50..b3c120daab42c41c824c91f6bd901b3eeda25a0c 100644 --- a/src/cmd/gofmt/gofmt.go +++ b/src/cmd/gofmt/gofmt.go @@ -151,7 +151,7 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error if err != nil { return fmt.Errorf("computing diff: %s", err) } - fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename)) + fmt.Fprintf(out, "diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename)) out.Write(data) } } @@ -164,21 +164,15 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error } func visitFile(path string, f fs.DirEntry, err error) error { - if err == nil && isGoFile(f) { - err = processFile(path, nil, os.Stdout, false) + if err != nil || !isGoFile(f) { + return err } - // Don't complain if a file was deleted in the meantime (i.e. - // the directory changed concurrently while running gofmt). - if err != nil && !os.IsNotExist(err) { + if err := processFile(path, nil, os.Stdout, false); err != nil { report(err) } return nil } -func walkDir(path string) { - filepath.WalkDir(path, visitFile) -} - func main() { // call gofmtMain in a separate function // so that it can use defer and have them @@ -206,7 +200,8 @@ func gofmtMain() { initParserMode() initRewrite() - if flag.NArg() == 0 { + args := flag.Args() + if len(args) == 0 { if *write { fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input") exitCode = 2 @@ -218,15 +213,18 @@ func gofmtMain() { return } - for i := 0; i < flag.NArg(); i++ { - path := flag.Arg(i) - switch dir, err := os.Stat(path); { + for _, arg := range args { + switch info, err := os.Stat(arg); { case err != nil: report(err) - case dir.IsDir(): - walkDir(path) + case !info.IsDir(): + // Non-directory arguments are always formatted. + if err := processFile(arg, nil, os.Stdout, false); err != nil { + report(err) + } default: - if err := processFile(path, nil, os.Stdout, false); err != nil { + // Directories are walked, ignoring non-Go files. + if err := filepath.WalkDir(arg, visitFile); err != nil { report(err) } } diff --git a/src/cmd/gofmt/gofmt_test.go b/src/cmd/gofmt/gofmt_test.go index bf2adfe64c52b1ef9362cd803cb04473b8ce0dae..f0d3f8780f40e135f2e86f8d21e1feb61dbfecbd 100644 --- a/src/cmd/gofmt/gofmt_test.go +++ b/src/cmd/gofmt/gofmt_test.go @@ -49,12 +49,13 @@ func gofmtFlags(filename string, maxLines int) string { case scanner.EOF: return "" } - } return "" } +var typeParamsEnabled = false + func runTest(t *testing.T, in, out string) { // process flags *simplifyAST = false @@ -77,6 +78,11 @@ func runTest(t *testing.T, in, out string) { case "-stdin": // fake flag - pretend input is from stdin stdin = true + case "-G": + // fake flag - test is for generic code + if !typeParamsEnabled { + return + } default: t.Errorf("unrecognized flag name: %s", name) } diff --git a/src/cmd/gofmt/gofmt_typeparams_test.go b/src/cmd/gofmt/gofmt_typeparams_test.go new file mode 100644 index 0000000000000000000000000000000000000000..10641a77cb2f8762dfd10c2dfbb25313adab225a --- /dev/null +++ b/src/cmd/gofmt/gofmt_typeparams_test.go @@ -0,0 +1,12 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build typeparams +// +build typeparams + +package main + +func init() { + typeParamsEnabled = true +} diff --git a/src/cmd/gofmt/testdata/typeparams.golden b/src/cmd/gofmt/testdata/typeparams.golden new file mode 100644 index 0000000000000000000000000000000000000000..35f08d13792c70e458b0d14fe2c63b9c8c441d74 --- /dev/null +++ b/src/cmd/gofmt/testdata/typeparams.golden @@ -0,0 +1,35 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//gofmt -G + +package typeparams + +type T[P any] struct{} +type T[P1, P2, P3 any] struct{} + +type T[P C] struct{} +type T[P1, P2, P3 C] struct{} + +type T[P C[P]] struct{} +type T[P1, P2, P3 C[P1, P2, P3]] struct{} + +func f[P any](x P) +func f[P1, P2, P3 any](x1 P1, x2 P2, x3 P3) struct{} + +func f[P interface{}](x P) +func f[P1, P2, P3 interface { + m1(P1) + type P2, P3 +}](x1 P1, x2 P2, x3 P3) struct{} +func f[P any](T1[P], T2[P]) T3[P] + +func (x T[P]) m() +func (T[P]) m(x T[P]) P + +func _() { + type _ []T[P] + var _ []T[P] + _ = []T[P]{} +} diff --git a/src/cmd/gofmt/testdata/typeparams.input b/src/cmd/gofmt/testdata/typeparams.input new file mode 100644 index 0000000000000000000000000000000000000000..7f3212c8e4a8d833045b8e066dd6d515f9ac1f42 --- /dev/null +++ b/src/cmd/gofmt/testdata/typeparams.input @@ -0,0 +1,32 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//gofmt -G + +package typeparams + +type T[ P any] struct{} +type T[P1, P2, P3 any] struct{} + +type T[P C] struct{} +type T[P1,P2, P3 C] struct{} + +type T[P C[P]] struct{} +type T[P1, P2, P3 C[P1,P2,P3]] struct{} + +func f[P any](x P) +func f[P1, P2, P3 any](x1 P1, x2 P2, x3 P3) struct{} + +func f[P interface{}](x P) +func f[P1, P2, P3 interface{ m1(P1); type P2, P3 }](x1 P1, x2 P2, x3 P3) struct{} +func f[P any](T1[P], T2[P]) T3[P] + +func (x T[P]) m() +func ((T[P])) m(x T[P]) P + +func _() { + type _ []T[P] + var _ []T[P] + _ = []T[P]{} +} diff --git a/src/cmd/internal/archive/archive.go b/src/cmd/internal/archive/archive.go index c1661d7711ac901ef1ae73539e38cd2f64c83785..da1f293243573e82e207cf8ec65390eb310f5ce6 100644 --- a/src/cmd/internal/archive/archive.go +++ b/src/cmd/internal/archive/archive.go @@ -106,6 +106,12 @@ var ( errNotObject = errors.New("unrecognized object file format") ) +type ErrGoObjOtherVersion struct{ magic []byte } + +func (e ErrGoObjOtherVersion) Error() string { + return fmt.Sprintf("go object of a different version: %q", e.magic) +} + // An objReader is an object file reader. type objReader struct { a *Archive @@ -118,9 +124,9 @@ type objReader struct { func (r *objReader) init(f *os.File) { r.a = &Archive{f, nil} - r.offset, _ = f.Seek(0, io.SeekCurrent) - r.limit, _ = f.Seek(0, io.SeekEnd) - f.Seek(r.offset, io.SeekStart) + r.offset, _ = f.Seek(0, os.SEEK_CUR) + r.limit, _ = f.Seek(0, os.SEEK_END) + f.Seek(r.offset, os.SEEK_SET) r.b = bio.NewReader(f) } @@ -221,7 +227,7 @@ func (r *objReader) skip(n int64) { r.readFull(r.tmp[:n]) } else { // Seek, giving up buffered data. - r.b.MustSeek(r.offset+n, io.SeekStart) + r.b.MustSeek(r.offset+n, os.SEEK_SET) r.offset += n } } @@ -389,7 +395,7 @@ func (r *objReader) parseArchive(verbose bool) error { // The object file consists of a textual header ending in "\n!\n" // and then the part we want to parse begins. // The format of that part is defined in a comment at the top -// of src/liblink/objfile.c. +// of cmd/internal/goobj/objfile.go. func (r *objReader) parseObject(o *GoObj, size int64) error { h := make([]byte, 0, 256) var c1, c2, c3 byte @@ -418,6 +424,9 @@ func (r *objReader) parseObject(o *GoObj, size int64) error { return err } if !bytes.Equal(p, []byte(goobj.Magic)) { + if bytes.HasPrefix(p, []byte("\x00go1")) && bytes.HasSuffix(p, []byte("ld")) { + return r.error(ErrGoObjOtherVersion{p[1:]}) // strip the \x00 byte + } return r.error(errCorruptObject) } r.skip(o.Size) @@ -426,7 +435,7 @@ func (r *objReader) parseObject(o *GoObj, size int64) error { // AddEntry adds an entry to the end of a, with the content from r. func (a *Archive) AddEntry(typ EntryType, name string, mtime int64, uid, gid int, mode os.FileMode, size int64, r io.Reader) { - off, err := a.f.Seek(0, io.SeekEnd) + off, err := a.f.Seek(0, os.SEEK_END) if err != nil { log.Fatal(err) } @@ -464,3 +473,24 @@ func exactly16Bytes(s string) string { s += sixteenSpaces[:16-len(s)] return s } + +// architecture-independent object file output +const HeaderSize = 60 + +func ReadHeader(b *bufio.Reader, name string) int { + var buf [HeaderSize]byte + if _, err := io.ReadFull(b, buf[:]); err != nil { + return -1 + } + aname := strings.Trim(string(buf[0:16]), " ") + if !strings.HasPrefix(aname, name) { + return -1 + } + asize := strings.Trim(string(buf[48:58]), " ") + i, _ := strconv.Atoi(asize) + return i +} + +func FormatHeader(arhdr []byte, name string, size int64) { + copy(arhdr[:], fmt.Sprintf("%-16s%-12d%-6d%-6d%-8o%-10d`\n", name, 0, 0, 0, 0644, size)) +} diff --git a/src/cmd/internal/archive/archive_test.go b/src/cmd/internal/archive/archive_test.go index cb4eb842b452572102d7ff99c8c07d802be518b0..c284a9cf0dcc5331d13b9cc325972bd53ef8f068 100644 --- a/src/cmd/internal/archive/archive_test.go +++ b/src/cmd/internal/archive/archive_test.go @@ -173,7 +173,7 @@ func TestParseGoobj(t *testing.T) { continue } if e.Type != EntryGoObj { - t.Errorf("wrong type of object: wnat EntryGoObj, got %v", e.Type) + t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type) } if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) { t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader) @@ -204,7 +204,7 @@ func TestParseArchive(t *testing.T) { continue } if e.Type != EntryGoObj { - t.Errorf("wrong type of object: wnat EntryGoObj, got %v", e.Type) + t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type) } if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) { t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader) diff --git a/src/cmd/internal/bio/buf_mmap.go b/src/cmd/internal/bio/buf_mmap.go index 4b43d74f26951cf069ce8943f0209246e782289a..b9755c7e50e8b4ac56a1d035ba59fb851088cf30 100644 --- a/src/cmd/internal/bio/buf_mmap.go +++ b/src/cmd/internal/bio/buf_mmap.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd // +build darwin dragonfly freebsd linux netbsd openbsd package bio diff --git a/src/cmd/internal/bio/buf_nommap.go b/src/cmd/internal/bio/buf_nommap.go index f43c67ac2d8d629ba9b028f858676ab0d0cbcc8e..533a93180cdbf9e966df3b607e067d667a1dd2dc 100644 --- a/src/cmd/internal/bio/buf_nommap.go +++ b/src/cmd/internal/bio/buf_nommap.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd // +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd package bio diff --git a/src/cmd/internal/diff/diff.go b/src/cmd/internal/diff/diff.go index c0ca2f310630cd7266a770d6b42748ae034cb050..0ec2d7f8f95dceb7d5872b842e650c040d24fbc4 100644 --- a/src/cmd/internal/diff/diff.go +++ b/src/cmd/internal/diff/diff.go @@ -7,6 +7,7 @@ package diff import ( + "bytes" exec "internal/execabs" "io/ioutil" "os" @@ -38,6 +39,25 @@ func Diff(prefix string, b1, b2 []byte) ([]byte, error) { // Ignore that failure as long as we get output. err = nil } + + // If we are on Windows and the diff is Cygwin diff, + // machines can get into a state where every Cygwin + // command works fine but prints a useless message like: + // + // Cygwin WARNING: + // Couldn't compute FAST_CWD pointer. This typically occurs if you're using + // an older Cygwin version on a newer Windows. Please update to the latest + // available Cygwin version from https://cygwin.com/. If the problem persists, + // please see https://cygwin.com/problems.html + // + // Skip over that message and just return the actual diff. + if len(data) > 0 && !bytes.HasPrefix(data, []byte("--- ")) { + i := bytes.Index(data, []byte("\n--- ")) + if i >= 0 && i < 80*10 && bytes.Contains(data[:i], []byte("://cygwin.com/")) { + data = data[i+1:] + } + } + return data, err } diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go index 8de4096f068ea6fa60b655f5458686f11d765579..ec441c2bcb687d898ff75dd0646ffc5ab5230b1b 100644 --- a/src/cmd/internal/dwarf/dwarf.go +++ b/src/cmd/internal/dwarf/dwarf.go @@ -9,13 +9,15 @@ package dwarf import ( "bytes" - "cmd/internal/objabi" "errors" "fmt" + "internal/buildcfg" exec "internal/execabs" "sort" "strconv" "strings" + + "cmd/internal/objabi" ) // InfoPrefix is the prefix for all the symbols containing DWARF info entries. @@ -318,8 +320,6 @@ const ( ) // Index into the abbrevs table below. -// Keep in sync with ispubname() and ispubtype() in ld/dwarf.go. -// ispubtype considers >= NULLTYPE public const ( DW_ABRV_NULL = iota DW_ABRV_COMPUNIT @@ -383,7 +383,7 @@ func expandPseudoForm(form uint8) uint8 { return form } expandedForm := DW_FORM_udata - if objabi.GOOS == "darwin" || objabi.GOOS == "ios" { + if buildcfg.GOOS == "darwin" || buildcfg.GOOS == "ios" { expandedForm = DW_FORM_data4 } return uint8(expandedForm) @@ -1043,6 +1043,15 @@ func PutIntConst(ctxt Context, info, typ Sym, name string, val int64) { putattr(ctxt, info, DW_ABRV_INT_CONSTANT, DW_FORM_sdata, DW_CLS_CONSTANT, val, nil) } +// PutGlobal writes a DIE for a global variable. +func PutGlobal(ctxt Context, info, typ, gvar Sym, name string) { + Uleb128put(ctxt, info, DW_ABRV_VARIABLE) + putattr(ctxt, info, DW_ABRV_VARIABLE, DW_FORM_string, DW_CLS_STRING, int64(len(name)), name) + putattr(ctxt, info, DW_ABRV_VARIABLE, DW_FORM_block1, DW_CLS_ADDRESS, 0, gvar) + putattr(ctxt, info, DW_ABRV_VARIABLE, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, typ) + putattr(ctxt, info, DW_ABRV_VARIABLE, DW_FORM_flag, DW_CLS_FLAG, 1, nil) +} + // PutBasedRanges writes a range table to sym. All addresses in ranges are // relative to some base address, which must be arranged by the caller // (e.g., with a DW_AT_low_pc attribute, or in a BASE-prefixed range). @@ -1257,7 +1266,7 @@ func PutAbstractFunc(ctxt Context, s *FnState) error { // its corresponding 'abstract' DIE (containing location-independent // attributes such as name, type, etc). Inlined subroutine DIEs can // have other inlined subroutine DIEs as children. -func PutInlinedFunc(ctxt Context, s *FnState, callersym Sym, callIdx int) error { +func putInlinedFunc(ctxt Context, s *FnState, callersym Sym, callIdx int) error { ic := s.InlCalls.Calls[callIdx] callee := ic.AbsFunSym @@ -1268,7 +1277,7 @@ func PutInlinedFunc(ctxt Context, s *FnState, callersym Sym, callIdx int) error Uleb128put(ctxt, s.Info, int64(abbrev)) if logDwarf { - ctxt.Logf("PutInlinedFunc(caller=%v,callee=%v,abbrev=%d)\n", callersym, callee, abbrev) + ctxt.Logf("putInlinedFunc(caller=%v,callee=%v,abbrev=%d)\n", callersym, callee, abbrev) } // Abstract origin. @@ -1304,7 +1313,7 @@ func PutInlinedFunc(ctxt Context, s *FnState, callersym Sym, callIdx int) error // Children of this inline. for _, sib := range inlChildren(callIdx, &s.InlCalls) { absfn := s.InlCalls.Calls[sib].AbsFunSym - err := PutInlinedFunc(ctxt, s, absfn, sib) + err := putInlinedFunc(ctxt, s, absfn, sib) if err != nil { return err } @@ -1346,7 +1355,7 @@ func PutConcreteFunc(ctxt Context, s *FnState) error { // Inlined subroutines. for _, sib := range inlChildren(-1, &s.InlCalls) { absfn := s.InlCalls.Calls[sib].AbsFunSym - err := PutInlinedFunc(ctxt, s, absfn, sib) + err := putInlinedFunc(ctxt, s, absfn, sib) if err != nil { return err } @@ -1394,7 +1403,7 @@ func PutDefaultFunc(ctxt Context, s *FnState) error { // Inlined subroutines. for _, sib := range inlChildren(-1, &s.InlCalls) { absfn := s.InlCalls.Calls[sib].AbsFunSym - err := PutInlinedFunc(ctxt, s, absfn, sib) + err := putInlinedFunc(ctxt, s, absfn, sib) if err != nil { return err } @@ -1600,14 +1609,6 @@ func putvar(ctxt Context, s *FnState, v *Var, absfn Sym, fnabbrev, inlIndex int, // Var has no children => no terminator } -// VarsByOffset attaches the methods of sort.Interface to []*Var, -// sorting in increasing StackOffset. -type VarsByOffset []*Var - -func (s VarsByOffset) Len() int { return len(s) } -func (s VarsByOffset) Less(i, j int) bool { return s[i].StackOffset < s[j].StackOffset } -func (s VarsByOffset) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - // byChildIndex implements sort.Interface for []*dwarf.Var by child index. type byChildIndex []*Var diff --git a/src/cmd/internal/goobj/builtinlist.go b/src/cmd/internal/goobj/builtinlist.go index 0cca752332958bca44ab205a96502c02f4877edb..9f248137daabce0eb9b07e93cecb8b73c274f1dd 100644 --- a/src/cmd/internal/goobj/builtinlist.go +++ b/src/cmd/internal/goobj/builtinlist.go @@ -41,6 +41,7 @@ var builtins = [...]struct { {"runtime.printcomplex", 1}, {"runtime.printstring", 1}, {"runtime.printpointer", 1}, + {"runtime.printuintptr", 1}, {"runtime.printiface", 1}, {"runtime.printeface", 1}, {"runtime.printslice", 1}, @@ -61,7 +62,6 @@ var builtins = [...]struct { {"runtime.stringtoslicebyte", 1}, {"runtime.stringtoslicerune", 1}, {"runtime.slicecopy", 1}, - {"runtime.slicestringcopy", 1}, {"runtime.decoderune", 1}, {"runtime.countrunes", 1}, {"runtime.convI2I", 1}, @@ -122,7 +122,6 @@ var builtins = [...]struct { {"runtime.typedslicecopy", 1}, {"runtime.selectnbsend", 1}, {"runtime.selectnbrecv", 1}, - {"runtime.selectnbrecv2", 1}, {"runtime.selectsetpc", 1}, {"runtime.selectgo", 1}, {"runtime.block", 1}, @@ -172,8 +171,9 @@ var builtins = [...]struct { {"runtime.uint64tofloat64", 1}, {"runtime.uint32tofloat64", 1}, {"runtime.complex128div", 1}, + {"runtime.getcallerpc", 1}, + {"runtime.getcallersp", 1}, {"runtime.racefuncenter", 1}, - {"runtime.racefuncenterfp", 1}, {"runtime.racefuncexit", 1}, {"runtime.raceread", 1}, {"runtime.racewrite", 1}, @@ -181,6 +181,7 @@ var builtins = [...]struct { {"runtime.racewriterange", 1}, {"runtime.msanread", 1}, {"runtime.msanwrite", 1}, + {"runtime.msanmove", 1}, {"runtime.checkptrAlignment", 1}, {"runtime.checkptrArithmetic", 1}, {"runtime.libfuzzerTraceCmp1", 1}, diff --git a/src/cmd/internal/goobj/funcinfo.go b/src/cmd/internal/goobj/funcinfo.go index 2cca8f6c4ef9c05f831cf8597c7190c4f2defabd..6d33a10a51cda4cde69a36aaf281f6c61a520a1c 100644 --- a/src/cmd/internal/goobj/funcinfo.go +++ b/src/cmd/internal/goobj/funcinfo.go @@ -19,9 +19,10 @@ type CUFileIndex uint32 // // TODO: make each pcdata a separate symbol? type FuncInfo struct { - Args uint32 - Locals uint32 - FuncID objabi.FuncID + Args uint32 + Locals uint32 + FuncID objabi.FuncID + FuncFlag objabi.FuncFlag Pcsp SymRef Pcfile SymRef @@ -35,6 +36,9 @@ type FuncInfo struct { } func (a *FuncInfo) Write(w *bytes.Buffer) { + writeUint8 := func(x uint8) { + w.WriteByte(x) + } var b [4]byte writeUint32 := func(x uint32) { binary.LittleEndian.PutUint32(b[:], x) @@ -47,8 +51,10 @@ func (a *FuncInfo) Write(w *bytes.Buffer) { writeUint32(a.Args) writeUint32(a.Locals) - writeUint32(uint32(a.FuncID)) - + writeUint8(uint8(a.FuncID)) + writeUint8(uint8(a.FuncFlag)) + writeUint8(0) // pad to uint32 boundary + writeUint8(0) writeSymRef(a.Pcsp) writeSymRef(a.Pcfile) writeSymRef(a.Pcline) @@ -72,46 +78,6 @@ func (a *FuncInfo) Write(w *bytes.Buffer) { } } -func (a *FuncInfo) Read(b []byte) { - readUint32 := func() uint32 { - x := binary.LittleEndian.Uint32(b) - b = b[4:] - return x - } - readSymIdx := func() SymRef { - return SymRef{readUint32(), readUint32()} - } - - a.Args = readUint32() - a.Locals = readUint32() - a.FuncID = objabi.FuncID(readUint32()) - - a.Pcsp = readSymIdx() - a.Pcfile = readSymIdx() - a.Pcline = readSymIdx() - a.Pcinline = readSymIdx() - a.Pcdata = make([]SymRef, readUint32()) - for i := range a.Pcdata { - a.Pcdata[i] = readSymIdx() - } - - funcdataofflen := readUint32() - a.Funcdataoff = make([]uint32, funcdataofflen) - for i := range a.Funcdataoff { - a.Funcdataoff[i] = readUint32() - } - filelen := readUint32() - a.File = make([]CUFileIndex, filelen) - for i := range a.File { - a.File[i] = CUFileIndex(readUint32()) - } - inltreelen := readUint32() - a.InlTree = make([]InlTreeNode, inltreelen) - for i := range a.InlTree { - b = a.InlTree[i].Read(b) - } -} - // FuncInfoLengths is a cache containing a roadmap of offsets and // lengths for things within a serialized FuncInfo. Each length field // stores the number of items (e.g. files, inltree nodes, etc), and the @@ -159,7 +125,9 @@ func (*FuncInfo) ReadArgs(b []byte) uint32 { return binary.LittleEndian.Uint32(b func (*FuncInfo) ReadLocals(b []byte) uint32 { return binary.LittleEndian.Uint32(b[4:]) } -func (*FuncInfo) ReadFuncID(b []byte) uint32 { return binary.LittleEndian.Uint32(b[8:]) } +func (*FuncInfo) ReadFuncID(b []byte) objabi.FuncID { return objabi.FuncID(b[8]) } + +func (*FuncInfo) ReadFuncFlag(b []byte) objabi.FuncFlag { return objabi.FuncFlag(b[9]) } func (*FuncInfo) ReadPcsp(b []byte) SymRef { return SymRef{binary.LittleEndian.Uint32(b[12:]), binary.LittleEndian.Uint32(b[16:])} diff --git a/src/cmd/internal/goobj/mkbuiltin.go b/src/cmd/internal/goobj/mkbuiltin.go index 07c340668138d1503e36959be1ee974961062fb1..18b969586cceda54f55d97cb010b610032a33888 100644 --- a/src/cmd/internal/goobj/mkbuiltin.go +++ b/src/cmd/internal/goobj/mkbuiltin.go @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore -// Generate builtinlist.go from cmd/compile/internal/gc/builtin/runtime.go. +// Generate builtinlist.go from cmd/compile/internal/typecheck/builtin/runtime.go. package main @@ -53,7 +54,7 @@ func main() { func mkbuiltin(w io.Writer) { pkg := "runtime" fset := token.NewFileSet() - path := filepath.Join("..", "..", "compile", "internal", "gc", "builtin", "runtime.go") + path := filepath.Join("..", "..", "compile", "internal", "typecheck", "builtin", "runtime.go") f, err := parser.ParseFile(fset, path, nil, 0) if err != nil { log.Fatal(err) @@ -118,8 +119,8 @@ func mkbuiltin(w io.Writer) { // addBasicTypes returns the symbol names for basic types that are // defined in the runtime and referenced in other packages. -// Needs to be kept in sync with reflect.go:dumpbasictypes() and -// reflect.go:dtypesym() in the compiler. +// Needs to be kept in sync with reflect.go:WriteBasicTypes() and +// reflect.go:writeType() in the compiler. func enumerateBasicTypes() []extra { names := [...]string{ "int8", "uint8", "int16", "uint16", diff --git a/src/cmd/internal/goobj/objfile.go b/src/cmd/internal/goobj/objfile.go index e6447e455dca6f8acfa4a6786b356187c491a092..e2858bd57da0ca42abe5eafc7f8df2f7c8a36882 100644 --- a/src/cmd/internal/goobj/objfile.go +++ b/src/cmd/internal/goobj/objfile.go @@ -33,7 +33,7 @@ import ( // New object file format. // // Header struct { -// Magic [...]byte // "\x00go116ld" +// Magic [...]byte // "\x00go117ld" // Fingerprint [8]byte // Flags uint32 // Offsets [...]uint32 // byte offset of each block below @@ -89,7 +89,7 @@ import ( // Relocs [...]struct { // Off int32 // Size uint8 -// Type uint8 +// Type uint16 // Add int64 // Sym symRef // } @@ -219,7 +219,7 @@ type Header struct { Offsets [NBlk]uint32 } -const Magic = "\x00go116ld" +const Magic = "\x00go117ld" func (h *Header) Write(w *Writer) { w.RawString(h.Magic) @@ -298,7 +298,6 @@ const ( SymFlagNoSplit SymFlagReflectMethod SymFlagGoType - SymFlagTopFrame ) // Sym.Flag2 @@ -332,7 +331,6 @@ func (s *Sym) Leaf() bool { return s.Flag()&SymFlagLeaf != 0 } func (s *Sym) NoSplit() bool { return s.Flag()&SymFlagNoSplit != 0 } func (s *Sym) ReflectMethod() bool { return s.Flag()&SymFlagReflectMethod != 0 } func (s *Sym) IsGoType() bool { return s.Flag()&SymFlagGoType != 0 } -func (s *Sym) TopFrame() bool { return s.Flag()&SymFlagTopFrame != 0 } func (s *Sym) UsedInIface() bool { return s.Flag2()&SymFlagUsedInIface != 0 } func (s *Sym) IsItab() bool { return s.Flag2()&SymFlagItab != 0 } @@ -375,32 +373,32 @@ const HashSize = sha1.Size // Reloc struct { // Off int32 // Siz uint8 -// Type uint8 +// Type uint16 // Add int64 // Sym SymRef // } type Reloc [RelocSize]byte -const RelocSize = 4 + 1 + 1 + 8 + 8 +const RelocSize = 4 + 1 + 2 + 8 + 8 -func (r *Reloc) Off() int32 { return int32(binary.LittleEndian.Uint32(r[:])) } -func (r *Reloc) Siz() uint8 { return r[4] } -func (r *Reloc) Type() uint8 { return r[5] } -func (r *Reloc) Add() int64 { return int64(binary.LittleEndian.Uint64(r[6:])) } +func (r *Reloc) Off() int32 { return int32(binary.LittleEndian.Uint32(r[:])) } +func (r *Reloc) Siz() uint8 { return r[4] } +func (r *Reloc) Type() uint16 { return binary.LittleEndian.Uint16(r[5:]) } +func (r *Reloc) Add() int64 { return int64(binary.LittleEndian.Uint64(r[7:])) } func (r *Reloc) Sym() SymRef { - return SymRef{binary.LittleEndian.Uint32(r[14:]), binary.LittleEndian.Uint32(r[18:])} + return SymRef{binary.LittleEndian.Uint32(r[15:]), binary.LittleEndian.Uint32(r[19:])} } -func (r *Reloc) SetOff(x int32) { binary.LittleEndian.PutUint32(r[:], uint32(x)) } -func (r *Reloc) SetSiz(x uint8) { r[4] = x } -func (r *Reloc) SetType(x uint8) { r[5] = x } -func (r *Reloc) SetAdd(x int64) { binary.LittleEndian.PutUint64(r[6:], uint64(x)) } +func (r *Reloc) SetOff(x int32) { binary.LittleEndian.PutUint32(r[:], uint32(x)) } +func (r *Reloc) SetSiz(x uint8) { r[4] = x } +func (r *Reloc) SetType(x uint16) { binary.LittleEndian.PutUint16(r[5:], x) } +func (r *Reloc) SetAdd(x int64) { binary.LittleEndian.PutUint64(r[7:], uint64(x)) } func (r *Reloc) SetSym(x SymRef) { - binary.LittleEndian.PutUint32(r[14:], x.PkgIdx) - binary.LittleEndian.PutUint32(r[18:], x.SymIdx) + binary.LittleEndian.PutUint32(r[15:], x.PkgIdx) + binary.LittleEndian.PutUint32(r[19:], x.SymIdx) } -func (r *Reloc) Set(off int32, size uint8, typ uint8, add int64, sym SymRef) { +func (r *Reloc) Set(off int32, size uint8, typ uint16, add int64, sym SymRef) { r.SetOff(off) r.SetSiz(size) r.SetType(typ) @@ -483,7 +481,7 @@ func (r *RefFlags) SetFlag2(x uint8) { r[9] = x } func (r *RefFlags) Write(w *Writer) { w.Bytes(r[:]) } -// Used to construct an artifically large array type when reading an +// Used to construct an artificially large array type when reading an // item from the object file relocs section or aux sym section (needs // to work on 32-bit as well as 64-bit). See issue 41621. const huge = (1<<31 - 1) / RelocSize diff --git a/src/cmd/internal/goobj/objfile_test.go b/src/cmd/internal/goobj/objfile_test.go index c6fd427c150dec5ef251dd0125160bcd9a570279..ed942aa93448bd1e36c57309039beadf5efaf090 100644 --- a/src/cmd/internal/goobj/objfile_test.go +++ b/src/cmd/internal/goobj/objfile_test.go @@ -9,6 +9,12 @@ import ( "bytes" "cmd/internal/bio" "cmd/internal/objabi" + "fmt" + "internal/buildcfg" + "internal/testenv" + "io/ioutil" + "os" + "os/exec" "testing" ) @@ -35,7 +41,7 @@ func TestReadWrite(t *testing.T) { var r Reloc r.SetOff(12) r.SetSiz(4) - r.SetType(uint8(objabi.R_ADDR)) + r.SetType(uint16(objabi.R_ADDR)) r.SetAdd(54321) r.SetSym(SymRef{11, 22}) r.Write(w) @@ -58,7 +64,7 @@ func TestReadWrite(t *testing.T) { b = b[SymSize:] var r2 Reloc r2.fromBytes(b) - if r2.Off() != 12 || r2.Siz() != 4 || r2.Type() != uint8(objabi.R_ADDR) || r2.Add() != 54321 || r2.Sym() != (SymRef{11, 22}) { + if r2.Off() != 12 || r2.Siz() != 4 || r2.Type() != uint16(objabi.R_ADDR) || r2.Add() != 54321 || r2.Sym() != (SymRef{11, 22}) { t.Errorf("read Reloc2 mismatch: got %v %v %v %v %v", r2.Off(), r2.Siz(), r2.Type(), r2.Add(), r2.Sym()) } @@ -69,3 +75,60 @@ func TestReadWrite(t *testing.T) { t.Errorf("read Aux2 mismatch: got %v %v", a2.Type(), a2.Sym()) } } + +var issue41621prolog = ` +package main +var lines = []string{ +` + +var issue41621epilog = ` +} +func getLines() []string { + return lines +} +func main() { + println(getLines()) +} +` + +func TestIssue41621LargeNumberOfRelocations(t *testing.T) { + if testing.Short() || (buildcfg.GOARCH != "amd64") { + t.Skipf("Skipping large number of relocations test in short mode or on %s", buildcfg.GOARCH) + } + testenv.MustHaveGoBuild(t) + + tmpdir, err := ioutil.TempDir("", "lotsofrelocs") + if err != nil { + t.Fatalf("can't create temp directory: %v\n", err) + } + defer os.RemoveAll(tmpdir) + + // Emit testcase. + var w bytes.Buffer + fmt.Fprintf(&w, issue41621prolog) + for i := 0; i < 1048576+13; i++ { + fmt.Fprintf(&w, "\t\"%d\",\n", i) + } + fmt.Fprintf(&w, issue41621epilog) + err = ioutil.WriteFile(tmpdir+"/large.go", w.Bytes(), 0666) + if err != nil { + t.Fatalf("can't write output: %v\n", err) + } + + // Emit go.mod + w.Reset() + fmt.Fprintf(&w, "module issue41621\n\ngo 1.12\n") + err = ioutil.WriteFile(tmpdir+"/go.mod", w.Bytes(), 0666) + if err != nil { + t.Fatalf("can't write output: %v\n", err) + } + w.Reset() + + // Build. + cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "large") + cmd.Dir = tmpdir + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Build failed: %v, output: %s", err, out) + } +} diff --git a/src/cmd/internal/moddeps/moddeps_test.go b/src/cmd/internal/moddeps/moddeps_test.go index cba401c896aad2a6e0cc811a7425a5731216c6a4..56c3b2585c7fdc211a0c4e17827bf182eb938661 100644 --- a/src/cmd/internal/moddeps/moddeps_test.go +++ b/src/cmd/internal/moddeps/moddeps_test.go @@ -5,6 +5,7 @@ package moddeps_test import ( + "bytes" "encoding/json" "fmt" "internal/testenv" @@ -61,14 +62,14 @@ func TestAllDependencies(t *testing.T) { _, err := cmd.Output() if err != nil { t.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr) - t.Logf("(Run 'go mod vendor' in %s to ensure that dependecies have been vendored.)", m.Dir) + t.Logf("(Run 'go mod vendor' in %s to ensure that dependencies have been vendored.)", m.Dir) } return } // There is no vendor directory, so the module must have no dependencies. // Check that the list of active modules contains only the main module. - cmd := exec.Command(goBin, "list", "-mod=mod", "-m", "all") + cmd := exec.Command(goBin, "list", "-mod=readonly", "-m", "all") cmd.Env = append(os.Environ(), "GO111MODULE=on") cmd.Dir = m.Dir cmd.Stderr = new(strings.Builder) @@ -123,10 +124,38 @@ func TestAllDependencies(t *testing.T) { t.Skip("skipping because a diff command with support for --recursive and --unified flags is unavailable") } + // We're going to check the standard modules for tidiness, so we need a usable + // GOMODCACHE. If the default directory doesn't exist, use a temporary + // directory instead. (That can occur, for example, when running under + // run.bash with GO_TEST_SHORT=0: run.bash sets GOPATH=/nonexist-gopath, and + // GO_TEST_SHORT=0 causes it to run this portion of the test.) + var modcacheEnv []string + { + out, err := exec.Command(goBin, "env", "GOMODCACHE").Output() + if err != nil { + t.Fatalf("%s env GOMODCACHE: %v", goBin, err) + } + modcacheOk := false + if gomodcache := string(bytes.TrimSpace(out)); gomodcache != "" { + if _, err := os.Stat(gomodcache); err == nil { + modcacheOk = true + } + } + if !modcacheOk { + modcacheEnv = []string{ + "GOMODCACHE=" + t.TempDir(), + "GOFLAGS=" + os.Getenv("GOFLAGS") + " -modcacherw", // Allow t.TempDir() to clean up subdirectories. + } + } + } + // Build the bundle binary at the golang.org/x/tools // module version specified in GOROOT/src/cmd/go.mod. bundleDir := t.TempDir() - r := runner{Dir: filepath.Join(runtime.GOROOT(), "src/cmd")} + r := runner{ + Dir: filepath.Join(runtime.GOROOT(), "src/cmd"), + Env: append(os.Environ(), modcacheEnv...), + } r.run(t, goBin, "build", "-mod=readonly", "-o", bundleDir, "golang.org/x/tools/cmd/bundle") var gorootCopyDir string @@ -160,11 +189,11 @@ func TestAllDependencies(t *testing.T) { } r := runner{ Dir: filepath.Join(gorootCopyDir, rel), - Env: append(os.Environ(), + Env: append(append(os.Environ(), modcacheEnv...), // Set GOROOT. "GOROOT="+gorootCopyDir, - // Explicitly clear PWD and GOROOT_FINAL so that GOROOT=gorootCopyDir is definitely used. - "PWD=", + // Explicitly override PWD and clear GOROOT_FINAL so that GOROOT=gorootCopyDir is definitely used. + "PWD="+filepath.Join(gorootCopyDir, rel), "GOROOT_FINAL=", // Add GOROOTcopy/bin and bundleDir to front of PATH. "PATH="+filepath.Join(gorootCopyDir, "bin")+string(filepath.ListSeparator)+ @@ -179,7 +208,7 @@ func TestAllDependencies(t *testing.T) { r.run(t, goBinCopy, "generate", `-run=^//go:generate bundle `, pkgs) // See issue 41409. advice := "$ cd " + m.Dir + "\n" + "$ go mod tidy # to remove extraneous dependencies\n" + - "$ go mod vendor # to vendor dependecies\n" + + "$ go mod vendor # to vendor dependencies\n" + "$ go generate -run=bundle " + pkgs + " # to regenerate bundled packages\n" if m.Path == "std" { r.run(t, goBinCopy, "generate", "syscall", "internal/syscall/...") // See issue 43440. @@ -227,7 +256,7 @@ func makeGOROOTCopy(t *testing.T) string { if err != nil { return err } - if src == filepath.Join(runtime.GOROOT(), ".git") { + if info.IsDir() && src == filepath.Join(runtime.GOROOT(), ".git") { return filepath.SkipDir } @@ -237,9 +266,8 @@ func makeGOROOTCopy(t *testing.T) string { } dst := filepath.Join(gorootCopyDir, rel) - switch src { - case filepath.Join(runtime.GOROOT(), "bin"), - filepath.Join(runtime.GOROOT(), "pkg"): + if info.IsDir() && (src == filepath.Join(runtime.GOROOT(), "bin") || + src == filepath.Join(runtime.GOROOT(), "pkg")) { // If the OS supports symlinks, use them instead // of copying the bin and pkg directories. if err := os.Symlink(src, dst); err == nil { @@ -414,7 +442,7 @@ func findGorootModules(t *testing.T) []gorootModule { if info.IsDir() && (info.Name() == "vendor" || info.Name() == "testdata") { return filepath.SkipDir } - if path == filepath.Join(runtime.GOROOT(), "pkg") { + if info.IsDir() && path == filepath.Join(runtime.GOROOT(), "pkg") { // GOROOT/pkg contains generated artifacts, not source code. // // In https://golang.org/issue/37929 it was observed to somehow contain @@ -422,6 +450,12 @@ func findGorootModules(t *testing.T) []gorootModule { // running time of this test anyway.) return filepath.SkipDir } + if info.IsDir() && (strings.HasPrefix(info.Name(), "_") || strings.HasPrefix(info.Name(), ".")) { + // _ and . prefixed directories can be used for internal modules + // without a vendor directory that don't contribute to the build + // but might be used for example as code generators. + return filepath.SkipDir + } if info.IsDir() || info.Name() != "go.mod" { return nil } @@ -451,8 +485,31 @@ func findGorootModules(t *testing.T) []gorootModule { goroot.modules = append(goroot.modules, m) return nil }) - }) + if goroot.err != nil { + return + } + // knownGOROOTModules is a hard-coded list of modules that are known to exist in GOROOT. + // If findGorootModules doesn't find a module, it won't be covered by tests at all, + // so make sure at least these modules are found. See issue 46254. If this list + // becomes a nuisance to update, can be replaced with len(goroot.modules) check. + knownGOROOTModules := [...]string{ + "std", + "cmd", + "misc", + "test/bench/go1", + } + var seen = make(map[string]bool) // Key is module path. + for _, m := range goroot.modules { + seen[m.Path] = true + } + for _, m := range knownGOROOTModules { + if !seen[m] { + goroot.err = fmt.Errorf("findGorootModules didn't find the well-known module %q", m) + break + } + } + }) if goroot.err != nil { t.Fatal(goroot.err) } diff --git a/src/cmd/internal/obj/arm/a.out.go b/src/cmd/internal/obj/arm/a.out.go index a1d9e28b96045ebe5f04910a6d27769a57a5da34..fd695ad0c98d1bbe8c4d984f1b7895a7b464ab35 100644 --- a/src/cmd/internal/obj/arm/a.out.go +++ b/src/cmd/internal/obj/arm/a.out.go @@ -163,8 +163,8 @@ const ( C_SFCON C_LFCON - C_RACON - C_LACON + C_RACON /* <=0xff rotated constant offset from auto */ + C_LACON /* Large Auto CONstant, i.e. large offset from SP */ C_SBRA C_LBRA diff --git a/src/cmd/internal/obj/arm/asm5.go b/src/cmd/internal/obj/arm/asm5.go index ebb98b4859d1a5b16a3a47af17fb33a497683020..ccf5f9e7f8d959da412e76835290e8f694b85599 100644 --- a/src/cmd/internal/obj/arm/asm5.go +++ b/src/cmd/internal/obj/arm/asm5.go @@ -34,6 +34,7 @@ import ( "cmd/internal/obj" "cmd/internal/objabi" "fmt" + "internal/buildcfg" "log" "math" "sort" @@ -976,7 +977,7 @@ func (c *ctxt5) aclass(a *obj.Addr) int { if immrot(^uint32(c.instoffset)) != 0 { return C_NCON } - if uint32(c.instoffset) <= 0xffff && objabi.GOARM == 7 { + if uint32(c.instoffset) <= 0xffff && buildcfg.GOARM == 7 { return C_SCON } if x, y := immrot2a(uint32(c.instoffset)); x != 0 && y != 0 { @@ -3044,7 +3045,7 @@ func (c *ctxt5) omvl(p *obj.Prog, a *obj.Addr, dr int) uint32 { func (c *ctxt5) chipzero5(e float64) int { // We use GOARM=7 to gate the use of VFPv3 vmov (imm) instructions. - if objabi.GOARM < 7 || math.Float64bits(e) != 0 { + if buildcfg.GOARM < 7 || math.Float64bits(e) != 0 { return -1 } return 0 @@ -3052,7 +3053,7 @@ func (c *ctxt5) chipzero5(e float64) int { func (c *ctxt5) chipfloat5(e float64) int { // We use GOARM=7 to gate the use of VFPv3 vmov (imm) instructions. - if objabi.GOARM < 7 { + if buildcfg.GOARM < 7 { return -1 } diff --git a/src/cmd/internal/obj/arm/obj5.go b/src/cmd/internal/obj/arm/obj5.go index 29d3a5867d5245e75f77f842427dd0a8d6228bb0..1454d8a7c92045e0f9b6a9af7a282ecfc577d080 100644 --- a/src/cmd/internal/obj/arm/obj5.go +++ b/src/cmd/internal/obj/arm/obj5.go @@ -34,6 +34,8 @@ import ( "cmd/internal/obj" "cmd/internal/objabi" "cmd/internal/sys" + "internal/buildcfg" + "log" ) var progedit_tlsfallback *obj.LSym @@ -63,7 +65,7 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { ctxt.Diag("%v: TLS MRC instruction must write to R0 as it might get translated into a BL instruction", p.Line()) } - if objabi.GOARM < 7 { + if buildcfg.GOARM < 7 { // Replace it with BL runtime.read_tls_fallback(SB) for ARM CPUs that lack the tls extension. if progedit_tlsfallback == nil { progedit_tlsfallback = ctxt.Lookup("runtime.read_tls_fallback") @@ -613,6 +615,21 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { p.From.Reg = REGSP } } + + if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.Spadj == 0 { + f := c.cursym.Func() + if f.FuncFlag&objabi.FuncFlag_SPWRITE == 0 { + c.cursym.Func().FuncFlag |= objabi.FuncFlag_SPWRITE + if ctxt.Debugvlog || !ctxt.IsAsm { + ctxt.Logf("auto-SPWRITE: %s %v\n", c.cursym.Name, p) + if !ctxt.IsAsm { + ctxt.Diag("invalid auto-SPWRITE in non-assembly") + ctxt.DiagFlush() + log.Fatalf("bad SPWRITE") + } + } + } + } } } @@ -664,57 +681,37 @@ func (c *ctxt5) stacksplit(p *obj.Prog, framesize int32) *obj.Prog { p.From.Reg = REG_R1 p.Reg = REG_R2 } else { - // Such a large stack we need to protect against wraparound - // if SP is close to zero. - // SP-stackguard+StackGuard < framesize + (StackGuard-StackSmall) - // The +StackGuard on both sides is required to keep the left side positive: - // SP is allowed to be slightly below stackguard. See stack.h. - // CMP $StackPreempt, R1 - // MOVW.NE $StackGuard(SP), R2 - // SUB.NE R1, R2 - // MOVW.NE $(framesize+(StackGuard-StackSmall)), R3 - // CMP.NE R3, R2 - p = obj.Appendp(p, c.newprog) - - p.As = ACMP - p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(uint32(objabi.StackPreempt & (1<<32 - 1))) - p.Reg = REG_R1 - - p = obj.Appendp(p, c.newprog) - p.As = AMOVW - p.From.Type = obj.TYPE_ADDR - p.From.Reg = REGSP - p.From.Offset = int64(objabi.StackGuard) - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R2 - p.Scond = C_SCOND_NE + // Such a large stack we need to protect against underflow. + // The runtime guarantees SP > objabi.StackBig, but + // framesize is large enough that SP-framesize may + // underflow, causing a direct comparison with the + // stack guard to incorrectly succeed. We explicitly + // guard against underflow. + // + // // Try subtracting from SP and check for underflow. + // // If this underflows, it sets C to 0. + // SUB.S $(framesize-StackSmall), SP, R2 + // // If C is 1 (unsigned >=), compare with guard. + // CMP.HS stackguard, R2 p = obj.Appendp(p, c.newprog) p.As = ASUB - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R1 + p.Scond = C_SBIT + p.From.Type = obj.TYPE_CONST + p.From.Offset = int64(framesize) - objabi.StackSmall + p.Reg = REGSP p.To.Type = obj.TYPE_REG p.To.Reg = REG_R2 - p.Scond = C_SCOND_NE - - p = obj.Appendp(p, c.newprog) - p.As = AMOVW - p.From.Type = obj.TYPE_ADDR - p.From.Offset = int64(framesize) + (int64(objabi.StackGuard) - objabi.StackSmall) - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R3 - p.Scond = C_SCOND_NE p = obj.Appendp(p, c.newprog) p.As = ACMP + p.Scond = C_SCOND_HS p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R3 + p.From.Reg = REG_R1 p.Reg = REG_R2 - p.Scond = C_SCOND_NE } - // BLS call-to-morestack + // BLS call-to-morestack (C is 0 or Z is 1) bls := obj.Appendp(p, c.newprog) bls.As = ABLS bls.To.Type = obj.TYPE_BRANCH diff --git a/src/cmd/internal/obj/arm64/a.out.go b/src/cmd/internal/obj/arm64/a.out.go index 1d1bea505cd17f9d0406f41911b38cd60452530b..bf75bb4a89156cede1626c676a11c58702c647b0 100644 --- a/src/cmd/internal/obj/arm64/a.out.go +++ b/src/cmd/internal/obj/arm64/a.out.go @@ -239,7 +239,7 @@ const ( REGCTXT = REG_R26 // environment for closures REGTMP = REG_R27 // reserved for liblink REGG = REG_R28 // G - REGFP = REG_R29 // frame pointer, unused in the Go toolchain + REGFP = REG_R29 // frame pointer REGLINK = REG_R30 // ARM64 uses R31 as both stack pointer and zero register, @@ -420,16 +420,21 @@ const ( C_LBRA C_ZAUTO // 0(RSP) + C_NSAUTO_16 // -256 <= x < 0, 0 mod 16 C_NSAUTO_8 // -256 <= x < 0, 0 mod 8 C_NSAUTO_4 // -256 <= x < 0, 0 mod 4 C_NSAUTO // -256 <= x < 0 + C_NPAUTO_16 // -512 <= x < 0, 0 mod 16 C_NPAUTO // -512 <= x < 0, 0 mod 8 + C_NQAUTO_16 // -1024 <= x < 0, 0 mod 16 C_NAUTO4K // -4095 <= x < 0 + C_PSAUTO_16 // 0 to 255, 0 mod 16 C_PSAUTO_8 // 0 to 255, 0 mod 8 C_PSAUTO_4 // 0 to 255, 0 mod 4 C_PSAUTO // 0 to 255 C_PPAUTO_16 // 0 to 504, 0 mod 16 C_PPAUTO // 0 to 504, 0 mod 8 + C_PQAUTO_16 // 0 to 1008, 0 mod 16 C_UAUTO4K_16 // 0 to 4095, 0 mod 16 C_UAUTO4K_8 // 0 to 4095, 0 mod 8 C_UAUTO4K_4 // 0 to 4095, 0 mod 4 @@ -454,17 +459,22 @@ const ( C_SEXT16 // 0 to 65520 C_LEXT - C_ZOREG // 0(R) - C_NSOREG_8 // must mirror C_NSAUTO_8, etc + C_ZOREG // 0(R) + C_NSOREG_16 // must mirror C_NSAUTO_16, etc + C_NSOREG_8 C_NSOREG_4 C_NSOREG + C_NPOREG_16 C_NPOREG + C_NQOREG_16 C_NOREG4K + C_PSOREG_16 C_PSOREG_8 C_PSOREG_4 C_PSOREG C_PPOREG_16 C_PPOREG + C_PQOREG_16 C_UOREG4K_16 C_UOREG4K_8 C_UOREG4K_4 @@ -898,6 +908,7 @@ const ( AFDIVD AFDIVS AFLDPD + AFLDPQ AFLDPS AFMOVQ AFMOVD @@ -912,6 +923,7 @@ const ( AFSQRTD AFSQRTS AFSTPD + AFSTPQ AFSTPS AFSUBD AFSUBS @@ -1019,6 +1031,8 @@ const ( AVEXT AVRBIT AVRAX1 + AVUMAX + AVUMIN AVUSHR AVUSHLL AVUSHLL2 diff --git a/src/cmd/internal/obj/arm64/anames.go b/src/cmd/internal/obj/arm64/anames.go index a98f8c7ed546ea6b75ab78a8735aeacff4700ad0..9cc58716488ee0cbec4e22f87a5d3abb7963fe21 100644 --- a/src/cmd/internal/obj/arm64/anames.go +++ b/src/cmd/internal/obj/arm64/anames.go @@ -392,6 +392,7 @@ var Anames = []string{ "FDIVD", "FDIVS", "FLDPD", + "FLDPQ", "FLDPS", "FMOVQ", "FMOVD", @@ -406,6 +407,7 @@ var Anames = []string{ "FSQRTD", "FSQRTS", "FSTPD", + "FSTPQ", "FSTPS", "FSUBD", "FSUBS", @@ -513,6 +515,8 @@ var Anames = []string{ "VEXT", "VRBIT", "VRAX1", + "VUMAX", + "VUMIN", "VUSHR", "VUSHLL", "VUSHLL2", diff --git a/src/cmd/internal/obj/arm64/anames7.go b/src/cmd/internal/obj/arm64/anames7.go index f7e99517ceeb595b3723ecb36e6420ec17e7b2d1..2ecd8164b6b2cc7e4b9886f35052fc4470297b57 100644 --- a/src/cmd/internal/obj/arm64/anames7.go +++ b/src/cmd/internal/obj/arm64/anames7.go @@ -42,15 +42,21 @@ var cnames7 = []string{ "SBRA", "LBRA", "ZAUTO", + "NSAUTO_16", "NSAUTO_8", "NSAUTO_4", "NSAUTO", + "NPAUTO_16", "NPAUTO", + "NQAUTO_16", "NAUTO4K", + "PSAUTO_16", "PSAUTO_8", "PSAUTO_4", "PSAUTO", + "PPAUTO_16", "PPAUTO", + "PQAUTO_16", "UAUTO4K_16", "UAUTO4K_8", "UAUTO4K_4", @@ -74,15 +80,21 @@ var cnames7 = []string{ "SEXT16", "LEXT", "ZOREG", + "NSOREG_16", "NSOREG_8", "NSOREG_4", "NSOREG", + "NPOREG_16", "NPOREG", + "NQOREG_16", "NOREG4K", + "PSOREG_16", "PSOREG_8", "PSOREG_4", "PSOREG", + "PPOREG_16", "PPOREG", + "PQOREG_16", "UOREG4K_16", "UOREG4K_8", "UOREG4K_4", diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go index 70072cfba41943a85987dc752d0c61d3e4672d09..d99afa3d27606f362798eeeed403e8e1420dc4c2 100644 --- a/src/cmd/internal/obj/arm64/asm7.go +++ b/src/cmd/internal/obj/arm64/asm7.go @@ -321,15 +321,17 @@ var optab = []Optab{ {ACMP, C_VCON, C_REG, C_NONE, C_NONE, 13, 20, 0, 0, 0}, {AADD, C_SHIFT, C_REG, C_NONE, C_REG, 3, 4, 0, 0, 0}, {AADD, C_SHIFT, C_NONE, C_NONE, C_REG, 3, 4, 0, 0, 0}, + {AADD, C_SHIFT, C_RSP, C_NONE, C_RSP, 26, 4, 0, 0, 0}, + {AADD, C_SHIFT, C_NONE, C_NONE, C_RSP, 26, 4, 0, 0, 0}, {AMVN, C_SHIFT, C_NONE, C_NONE, C_REG, 3, 4, 0, 0, 0}, {ACMP, C_SHIFT, C_REG, C_NONE, C_NONE, 3, 4, 0, 0, 0}, - {ANEG, C_SHIFT, C_NONE, C_NONE, C_REG, 26, 4, 0, 0, 0}, + {ACMP, C_SHIFT, C_RSP, C_NONE, C_NONE, 26, 4, 0, 0, 0}, + {ANEG, C_SHIFT, C_NONE, C_NONE, C_REG, 3, 4, 0, 0, 0}, {AADD, C_REG, C_RSP, C_NONE, C_RSP, 27, 4, 0, 0, 0}, {AADD, C_REG, C_NONE, C_NONE, C_RSP, 27, 4, 0, 0, 0}, {ACMP, C_REG, C_RSP, C_NONE, C_NONE, 27, 4, 0, 0, 0}, {AADD, C_EXTREG, C_RSP, C_NONE, C_RSP, 27, 4, 0, 0, 0}, {AADD, C_EXTREG, C_NONE, C_NONE, C_RSP, 27, 4, 0, 0, 0}, - {AMVN, C_EXTREG, C_NONE, C_NONE, C_RSP, 27, 4, 0, 0, 0}, {ACMP, C_EXTREG, C_RSP, C_NONE, C_NONE, 27, 4, 0, 0, 0}, {AADD, C_REG, C_REG, C_NONE, C_REG, 1, 4, 0, 0, 0}, {AADD, C_REG, C_NONE, C_NONE, C_REG, 1, 4, 0, 0, 0}, @@ -404,8 +406,8 @@ var optab = []Optab{ /* MOVs that become MOVK/MOVN/MOVZ/ADD/SUB/OR */ {AMOVW, C_MOVCON, C_NONE, C_NONE, C_REG, 32, 4, 0, 0, 0}, {AMOVD, C_MOVCON, C_NONE, C_NONE, C_REG, 32, 4, 0, 0, 0}, - {AMOVW, C_BITCON, C_NONE, C_NONE, C_REG, 32, 4, 0, 0, 0}, - {AMOVD, C_BITCON, C_NONE, C_NONE, C_REG, 32, 4, 0, 0, 0}, + {AMOVW, C_BITCON, C_NONE, C_NONE, C_RSP, 32, 4, 0, 0, 0}, + {AMOVD, C_BITCON, C_NONE, C_NONE, C_RSP, 32, 4, 0, 0, 0}, {AMOVW, C_MOVCON2, C_NONE, C_NONE, C_REG, 12, 8, 0, NOTUSETMP, 0}, {AMOVD, C_MOVCON2, C_NONE, C_NONE, C_REG, 12, 8, 0, NOTUSETMP, 0}, {AMOVD, C_MOVCON3, C_NONE, C_NONE, C_REG, 12, 12, 0, NOTUSETMP, 0}, @@ -499,6 +501,8 @@ var optab = []Optab{ {AVMOV, C_REG, C_NONE, C_NONE, C_ELEM, 78, 4, 0, 0, 0}, {AVMOV, C_ARNG, C_NONE, C_NONE, C_ARNG, 83, 4, 0, 0, 0}, {AVDUP, C_ELEM, C_NONE, C_NONE, C_ARNG, 79, 4, 0, 0, 0}, + {AVDUP, C_ELEM, C_NONE, C_NONE, C_VREG, 80, 4, 0, 0, 0}, + {AVDUP, C_REG, C_NONE, C_NONE, C_ARNG, 82, 4, 0, 0, 0}, {AVMOVI, C_ADDCON, C_NONE, C_NONE, C_ARNG, 86, 4, 0, 0, 0}, {AVFMLA, C_ARNG, C_ARNG, C_NONE, C_ARNG, 72, 4, 0, 0, 0}, {AVEXT, C_VCON, C_ARNG, C_ARNG, C_ARNG, 94, 4, 0, 0, 0}, @@ -689,6 +693,46 @@ var optab = []Optab{ /* pre/post-indexed/signed-offset load/store register pair (unscaled, signed 10-bit quad-aligned and long offset) */ + {AFLDPQ, C_NQAUTO_16, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, 0}, + {AFLDPQ, C_NQAUTO_16, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, C_XPRE}, + {AFLDPQ, C_NQAUTO_16, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, C_XPOST}, + {AFLDPQ, C_PQAUTO_16, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, 0}, + {AFLDPQ, C_PQAUTO_16, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, C_XPRE}, + {AFLDPQ, C_PQAUTO_16, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, C_XPOST}, + {AFLDPQ, C_UAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, 0}, + {AFLDPQ, C_NAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, 0}, + {AFLDPQ, C_LAUTO, C_NONE, C_NONE, C_PAIR, 75, 12, REGSP, LFROM, 0}, + {AFLDPQ, C_NQOREG_16, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, 0}, + {AFLDPQ, C_NQOREG_16, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPRE}, + {AFLDPQ, C_NQOREG_16, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPOST}, + {AFLDPQ, C_PQOREG_16, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, 0}, + {AFLDPQ, C_PQOREG_16, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPRE}, + {AFLDPQ, C_PQOREG_16, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPOST}, + {AFLDPQ, C_UOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, 0}, + {AFLDPQ, C_NOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, 0}, + {AFLDPQ, C_LOREG, C_NONE, C_NONE, C_PAIR, 75, 12, 0, LFROM, 0}, + {AFLDPQ, C_ADDR, C_NONE, C_NONE, C_PAIR, 88, 12, 0, 0, 0}, + + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_NQAUTO_16, 67, 4, REGSP, 0, 0}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_NQAUTO_16, 67, 4, REGSP, 0, C_XPRE}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_NQAUTO_16, 67, 4, REGSP, 0, C_XPOST}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_PQAUTO_16, 67, 4, REGSP, 0, 0}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_PQAUTO_16, 67, 4, REGSP, 0, C_XPRE}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_PQAUTO_16, 67, 4, REGSP, 0, C_XPOST}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_UAUTO4K, 76, 8, REGSP, 0, 0}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_NAUTO4K, 76, 8, REGSP, 0, 0}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_LAUTO, 77, 12, REGSP, LTO, 0}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_NQOREG_16, 67, 4, 0, 0, 0}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_NQOREG_16, 67, 4, 0, 0, C_XPRE}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_NQOREG_16, 67, 4, 0, 0, C_XPOST}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_PQOREG_16, 67, 4, 0, 0, 0}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_PQOREG_16, 67, 4, 0, 0, C_XPRE}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_PQOREG_16, 67, 4, 0, 0, C_XPOST}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_UOREG4K, 76, 8, 0, 0, 0}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_NOREG4K, 76, 8, 0, 0, 0}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_LOREG, 77, 12, 0, LTO, 0}, + {AFSTPQ, C_PAIR, C_NONE, C_NONE, C_ADDR, 87, 12, 0, 0, 0}, + {ALDP, C_NPAUTO, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, 0}, {ALDP, C_NPAUTO, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, C_XPRE}, {ALDP, C_NPAUTO, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, C_XPOST}, @@ -696,14 +740,8 @@ var optab = []Optab{ {ALDP, C_PPAUTO, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, C_XPRE}, {ALDP, C_PPAUTO, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, C_XPOST}, {ALDP, C_UAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, 0}, - {ALDP, C_UAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, C_XPRE}, - {ALDP, C_UAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, C_XPOST}, {ALDP, C_NAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, 0}, - {ALDP, C_NAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, C_XPRE}, - {ALDP, C_NAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, C_XPOST}, {ALDP, C_LAUTO, C_NONE, C_NONE, C_PAIR, 75, 12, REGSP, LFROM, 0}, - {ALDP, C_LAUTO, C_NONE, C_NONE, C_PAIR, 75, 12, REGSP, LFROM, C_XPRE}, - {ALDP, C_LAUTO, C_NONE, C_NONE, C_PAIR, 75, 12, REGSP, LFROM, C_XPOST}, {ALDP, C_NPOREG, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, 0}, {ALDP, C_NPOREG, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPRE}, {ALDP, C_NPOREG, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPOST}, @@ -711,14 +749,8 @@ var optab = []Optab{ {ALDP, C_PPOREG, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPRE}, {ALDP, C_PPOREG, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPOST}, {ALDP, C_UOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, 0}, - {ALDP, C_UOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, C_XPRE}, - {ALDP, C_UOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, C_XPOST}, {ALDP, C_NOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, 0}, - {ALDP, C_NOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, C_XPRE}, - {ALDP, C_NOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, C_XPOST}, {ALDP, C_LOREG, C_NONE, C_NONE, C_PAIR, 75, 12, 0, LFROM, 0}, - {ALDP, C_LOREG, C_NONE, C_NONE, C_PAIR, 75, 12, 0, LFROM, C_XPRE}, - {ALDP, C_LOREG, C_NONE, C_NONE, C_PAIR, 75, 12, 0, LFROM, C_XPOST}, {ALDP, C_ADDR, C_NONE, C_NONE, C_PAIR, 88, 12, 0, 0, 0}, {ASTP, C_PAIR, C_NONE, C_NONE, C_NPAUTO, 67, 4, REGSP, 0, 0}, @@ -728,14 +760,8 @@ var optab = []Optab{ {ASTP, C_PAIR, C_NONE, C_NONE, C_PPAUTO, 67, 4, REGSP, 0, C_XPRE}, {ASTP, C_PAIR, C_NONE, C_NONE, C_PPAUTO, 67, 4, REGSP, 0, C_XPOST}, {ASTP, C_PAIR, C_NONE, C_NONE, C_UAUTO4K, 76, 8, REGSP, 0, 0}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_UAUTO4K, 76, 8, REGSP, 0, C_XPRE}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_UAUTO4K, 76, 8, REGSP, 0, C_XPOST}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_NAUTO4K, 76, 12, REGSP, 0, 0}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_NAUTO4K, 76, 12, REGSP, 0, C_XPRE}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_NAUTO4K, 76, 12, REGSP, 0, C_XPOST}, + {ASTP, C_PAIR, C_NONE, C_NONE, C_NAUTO4K, 76, 8, REGSP, 0, 0}, {ASTP, C_PAIR, C_NONE, C_NONE, C_LAUTO, 77, 12, REGSP, LTO, 0}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_LAUTO, 77, 12, REGSP, LTO, C_XPRE}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_LAUTO, 77, 12, REGSP, LTO, C_XPOST}, {ASTP, C_PAIR, C_NONE, C_NONE, C_NPOREG, 67, 4, 0, 0, 0}, {ASTP, C_PAIR, C_NONE, C_NONE, C_NPOREG, 67, 4, 0, 0, C_XPRE}, {ASTP, C_PAIR, C_NONE, C_NONE, C_NPOREG, 67, 4, 0, 0, C_XPOST}, @@ -743,14 +769,8 @@ var optab = []Optab{ {ASTP, C_PAIR, C_NONE, C_NONE, C_PPOREG, 67, 4, 0, 0, C_XPRE}, {ASTP, C_PAIR, C_NONE, C_NONE, C_PPOREG, 67, 4, 0, 0, C_XPOST}, {ASTP, C_PAIR, C_NONE, C_NONE, C_UOREG4K, 76, 8, 0, 0, 0}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_UOREG4K, 76, 8, 0, 0, C_XPRE}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_UOREG4K, 76, 8, 0, 0, C_XPOST}, {ASTP, C_PAIR, C_NONE, C_NONE, C_NOREG4K, 76, 8, 0, 0, 0}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_NOREG4K, 76, 8, 0, 0, C_XPRE}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_NOREG4K, 76, 8, 0, 0, C_XPOST}, {ASTP, C_PAIR, C_NONE, C_NONE, C_LOREG, 77, 12, 0, LTO, 0}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_LOREG, 77, 12, 0, LTO, C_XPRE}, - {ASTP, C_PAIR, C_NONE, C_NONE, C_LOREG, 77, 12, 0, LTO, C_XPOST}, {ASTP, C_PAIR, C_NONE, C_NONE, C_ADDR, 87, 12, 0, 0, 0}, // differ from LDP/STP for C_NSAUTO_4/C_PSAUTO_4/C_NSOREG_4/C_PSOREG_4 @@ -761,14 +781,8 @@ var optab = []Optab{ {ALDPW, C_PSAUTO_4, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, C_XPRE}, {ALDPW, C_PSAUTO_4, C_NONE, C_NONE, C_PAIR, 66, 4, REGSP, 0, C_XPOST}, {ALDPW, C_UAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, 0}, - {ALDPW, C_UAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, C_XPRE}, - {ALDPW, C_UAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, C_XPOST}, {ALDPW, C_NAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, 0}, - {ALDPW, C_NAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, C_XPRE}, - {ALDPW, C_NAUTO4K, C_NONE, C_NONE, C_PAIR, 74, 8, REGSP, 0, C_XPOST}, {ALDPW, C_LAUTO, C_NONE, C_NONE, C_PAIR, 75, 12, REGSP, LFROM, 0}, - {ALDPW, C_LAUTO, C_NONE, C_NONE, C_PAIR, 75, 12, REGSP, LFROM, C_XPRE}, - {ALDPW, C_LAUTO, C_NONE, C_NONE, C_PAIR, 75, 12, REGSP, LFROM, C_XPOST}, {ALDPW, C_NSOREG_4, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, 0}, {ALDPW, C_NSOREG_4, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPRE}, {ALDPW, C_NSOREG_4, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPOST}, @@ -776,14 +790,8 @@ var optab = []Optab{ {ALDPW, C_PSOREG_4, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPRE}, {ALDPW, C_PSOREG_4, C_NONE, C_NONE, C_PAIR, 66, 4, 0, 0, C_XPOST}, {ALDPW, C_UOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, 0}, - {ALDPW, C_UOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, C_XPRE}, - {ALDPW, C_UOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, C_XPOST}, {ALDPW, C_NOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, 0}, - {ALDPW, C_NOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, C_XPRE}, - {ALDPW, C_NOREG4K, C_NONE, C_NONE, C_PAIR, 74, 8, 0, 0, C_XPOST}, {ALDPW, C_LOREG, C_NONE, C_NONE, C_PAIR, 75, 12, 0, LFROM, 0}, - {ALDPW, C_LOREG, C_NONE, C_NONE, C_PAIR, 75, 12, 0, LFROM, C_XPRE}, - {ALDPW, C_LOREG, C_NONE, C_NONE, C_PAIR, 75, 12, 0, LFROM, C_XPOST}, {ALDPW, C_ADDR, C_NONE, C_NONE, C_PAIR, 88, 12, 0, 0, 0}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_NSAUTO_4, 67, 4, REGSP, 0, 0}, @@ -793,14 +801,8 @@ var optab = []Optab{ {ASTPW, C_PAIR, C_NONE, C_NONE, C_PSAUTO_4, 67, 4, REGSP, 0, C_XPRE}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_PSAUTO_4, 67, 4, REGSP, 0, C_XPOST}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_UAUTO4K, 76, 8, REGSP, 0, 0}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_UAUTO4K, 76, 8, REGSP, 0, C_XPRE}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_UAUTO4K, 76, 8, REGSP, 0, C_XPOST}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_NAUTO4K, 76, 12, REGSP, 0, 0}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_NAUTO4K, 76, 12, REGSP, 0, C_XPRE}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_NAUTO4K, 76, 12, REGSP, 0, C_XPOST}, + {ASTPW, C_PAIR, C_NONE, C_NONE, C_NAUTO4K, 76, 8, REGSP, 0, 0}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_LAUTO, 77, 12, REGSP, LTO, 0}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_LAUTO, 77, 12, REGSP, LTO, C_XPRE}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_LAUTO, 77, 12, REGSP, LTO, C_XPOST}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_NSOREG_4, 67, 4, 0, 0, 0}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_NSOREG_4, 67, 4, 0, 0, C_XPRE}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_NSOREG_4, 67, 4, 0, 0, C_XPOST}, @@ -808,14 +810,8 @@ var optab = []Optab{ {ASTPW, C_PAIR, C_NONE, C_NONE, C_PSOREG_4, 67, 4, 0, 0, C_XPRE}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_PSOREG_4, 67, 4, 0, 0, C_XPOST}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_UOREG4K, 76, 8, 0, 0, 0}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_UOREG4K, 76, 8, 0, 0, C_XPRE}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_UOREG4K, 76, 8, 0, 0, C_XPOST}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_NOREG4K, 76, 8, 0, 0, 0}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_NOREG4K, 76, 8, 0, 0, C_XPRE}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_NOREG4K, 76, 8, 0, 0, C_XPOST}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_LOREG, 77, 12, 0, LTO, 0}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_LOREG, 77, 12, 0, LTO, C_XPRE}, - {ASTPW, C_PAIR, C_NONE, C_NONE, C_LOREG, 77, 12, 0, LTO, C_XPOST}, {ASTPW, C_PAIR, C_NONE, C_NONE, C_ADDR, 87, 12, 0, 0, 0}, {ASWPD, C_REG, C_NONE, C_NONE, C_ZOREG, 47, 4, 0, 0, 0}, // RegTo2=C_REG @@ -1204,19 +1200,16 @@ func (c *ctxt7) flushpool(p *obj.Prog, skip int) { // addpool128 adds a 128-bit constant to literal pool by two consecutive DWORD // instructions, the 128-bit constant is formed by ah.Offset<<64+al.Offset. func (c *ctxt7) addpool128(p *obj.Prog, al, ah *obj.Addr) { - lit := al.Offset q := c.newprog() q.As = ADWORD q.To.Type = obj.TYPE_CONST - q.To.Offset = lit - q.Pc = int64(c.pool.size) + q.To.Offset = al.Offset - lit = ah.Offset t := c.newprog() t.As = ADWORD t.To.Type = obj.TYPE_CONST - t.To.Offset = lit - t.Pc = int64(c.pool.size + 8) + t.To.Offset = ah.Offset + q.Link = t if c.blitrl == nil { @@ -1227,6 +1220,7 @@ func (c *ctxt7) addpool128(p *obj.Prog, al, ah *obj.Addr) { } c.elitrl = t + c.pool.size = roundUp(c.pool.size, 16) c.pool.size += 16 p.Pool = q } @@ -1259,88 +1253,8 @@ func (c *ctxt7) addpool(p *obj.Prog, a *obj.Addr) { sz = 8 } - switch cls { - // TODO(aram): remove. - default: - if a.Name != obj.NAME_EXTERN { - fmt.Printf("addpool: %v in %v shouldn't go to default case\n", DRconv(cls), p) - } - - t.To.Offset = a.Offset - t.To.Sym = a.Sym - t.To.Type = a.Type - t.To.Name = a.Name - - /* This is here because MOV uint12<<12, R is disabled in optab. - Because of this, we need to load the constant from memory. */ - case C_ADDCON: - fallthrough - - case C_ZAUTO, - C_PSAUTO, - C_PSAUTO_8, - C_PSAUTO_4, - C_PPAUTO_16, - C_PPAUTO, - C_UAUTO4K_16, - C_UAUTO4K_8, - C_UAUTO4K_4, - C_UAUTO4K_2, - C_UAUTO4K, - C_UAUTO8K_16, - C_UAUTO8K_8, - C_UAUTO8K_4, - C_UAUTO8K, - C_UAUTO16K_16, - C_UAUTO16K_8, - C_UAUTO16K, - C_UAUTO32K_16, - C_UAUTO32K, - C_UAUTO64K, - C_NSAUTO_8, - C_NSAUTO_4, - C_NSAUTO, - C_NPAUTO, - C_NAUTO4K, - C_LAUTO, - C_PSOREG, - C_PSOREG_8, - C_PSOREG_4, - C_PPOREG_16, - C_PPOREG, - C_UOREG4K_16, - C_UOREG4K_8, - C_UOREG4K_4, - C_UOREG4K_2, - C_UOREG4K, - C_UOREG8K_16, - C_UOREG8K_8, - C_UOREG8K_4, - C_UOREG8K, - C_UOREG16K_16, - C_UOREG16K_8, - C_UOREG16K, - C_UOREG32K_16, - C_UOREG32K, - C_UOREG64K, - C_NSOREG_8, - C_NSOREG_4, - C_NSOREG, - C_NPOREG, - C_NOREG4K, - C_LOREG, - C_LACON, - C_ADDCON2, - C_LCON, - C_VCON: - if a.Name == obj.NAME_EXTERN { - fmt.Printf("addpool: %v in %v needs reloc\n", DRconv(cls), p) - } - - t.To.Type = obj.TYPE_CONST - t.To.Offset = lit - break - } + t.To.Type = obj.TYPE_CONST + t.To.Offset = lit for q := c.blitrl; q != nil; q = q.Link { /* could hash on t.t0.offset */ if q.To == t.To { @@ -1351,7 +1265,6 @@ func (c *ctxt7) addpool(p *obj.Prog, a *obj.Addr) { q := c.newprog() *q = *t - q.Pc = int64(c.pool.size) if c.blitrl == nil { c.blitrl = q c.pool.start = uint32(p.Pc) @@ -1359,11 +1272,24 @@ func (c *ctxt7) addpool(p *obj.Prog, a *obj.Addr) { c.elitrl.Link = q } c.elitrl = q - c.pool.size = -c.pool.size & (funcAlign - 1) + if q.As == ADWORD { + // make DWORD 8-byte aligned, this is not required by ISA, + // just to avoid performance penalties when loading from + // the constant pool across a cache line. + c.pool.size = roundUp(c.pool.size, 8) + } c.pool.size += uint32(sz) p.Pool = q } +// roundUp rounds up x to "to". +func roundUp(x, to uint32) uint32 { + if to == 0 || to&(to-1) != 0 { + log.Fatalf("rounded up to a value that is not a power of 2: %d\n", to) + } + return (x + to - 1) &^ (to - 1) +} + func (c *ctxt7) regoff(a *obj.Addr) uint32 { c.instoffset = 0 c.aclass(a) @@ -1421,6 +1347,22 @@ func isADDWop(op obj.As) bool { return false } +func isADDSop(op obj.As) bool { + switch op { + case AADDS, AADDSW, ASUBS, ASUBSW: + return true + } + return false +} + +func isNEGop(op obj.As) bool { + switch op { + case ANEG, ANEGW, ANEGS, ANEGSW: + return true + } + return false +} + func isRegShiftOrExt(a *obj.Addr) bool { return (a.Index-obj.RBaseARM64)®_EXT != 0 || (a.Index-obj.RBaseARM64)®_LSL != 0 } @@ -1590,6 +1532,9 @@ func autoclass(l int64) int { } if l < 0 { + if l >= -256 && (l&15) == 0 { + return C_NSAUTO_16 + } if l >= -256 && (l&7) == 0 { return C_NSAUTO_8 } @@ -1599,9 +1544,15 @@ func autoclass(l int64) int { if l >= -256 { return C_NSAUTO } + if l >= -512 && (l&15) == 0 { + return C_NPAUTO_16 + } if l >= -512 && (l&7) == 0 { return C_NPAUTO } + if l >= -1024 && (l&15) == 0 { + return C_NQAUTO_16 + } if l >= -4095 { return C_NAUTO4K } @@ -1609,6 +1560,9 @@ func autoclass(l int64) int { } if l <= 255 { + if (l & 15) == 0 { + return C_PSAUTO_16 + } if (l & 7) == 0 { return C_PSAUTO_8 } @@ -1625,6 +1579,11 @@ func autoclass(l int64) int { return C_PPAUTO } } + if l <= 1008 { + if l&15 == 0 { + return C_PQAUTO_16 + } + } if l <= 4095 { if l&15 == 0 { return C_UAUTO4K_16 @@ -2038,9 +1997,10 @@ func (c *ctxt7) oplook(p *obj.Prog) *Optab { } a1 = a0 + 1 p.From.Class = int8(a1) - // more specific classification of 32-bit integers if p.From.Type == obj.TYPE_CONST && p.From.Name == obj.NAME_NONE { - if p.As == AMOVW || isADDWop(p.As) { + if p.As == AMOVW || isADDWop(p.As) || isANDWop(p.As) { + // For 32-bit instruction with constant, we need to + // treat its offset value as 32 bits to classify it. ra0 := c.con32class(&p.From) // do not break C_ADDCON2 when S bit is set if (p.As == AADDSW || p.As == ASUBSW) && ra0 == C_ADDCON2 { @@ -2049,16 +2009,8 @@ func (c *ctxt7) oplook(p *obj.Prog) *Optab { a1 = ra0 + 1 p.From.Class = int8(a1) } - if isANDWop(p.As) && a0 != C_BITCON { - // For 32-bit logical instruction with constant, - // the BITCON test is special in that it looks at - // the 64-bit which has the high 32-bit as a copy - // of the low 32-bit. We have handled that and - // don't pass it to con32class. - a1 = c.con32class(&p.From) + 1 - p.From.Class = int8(a1) - } if ((p.As == AMOVD) || isANDop(p.As) || isADDop(p.As)) && (a0 == C_LCON || a0 == C_VCON) { + // more specific classification of 64-bit integers a1 = c.con64class(&p.From) + 1 p.From.Class = int8(a1) } @@ -2193,64 +2145,99 @@ func cmp(a int, b int) bool { return true } + case C_NSAUTO_8: + if b == C_NSAUTO_16 { + return true + } + case C_NSAUTO_4: - if b == C_NSAUTO_8 { + if b == C_NSAUTO_16 || b == C_NSAUTO_8 { return true } case C_NSAUTO: switch b { - case C_NSAUTO_4, C_NSAUTO_8: + case C_NSAUTO_4, C_NSAUTO_8, C_NSAUTO_16: + return true + } + + case C_NPAUTO_16: + switch b { + case C_NSAUTO_16: return true } case C_NPAUTO: switch b { - case C_NSAUTO_8: + case C_NSAUTO_16, C_NSAUTO_8, C_NPAUTO_16: + return true + } + + case C_NQAUTO_16: + switch b { + case C_NSAUTO_16, C_NPAUTO_16: return true } case C_NAUTO4K: switch b { - case C_NSAUTO_8, C_NSAUTO_4, C_NSAUTO, C_NPAUTO: + case C_NSAUTO_16, C_NSAUTO_8, C_NSAUTO_4, C_NSAUTO, C_NPAUTO_16, + C_NPAUTO, C_NQAUTO_16: return true } - case C_PSAUTO_8: + case C_PSAUTO_16: if b == C_ZAUTO { return true } + case C_PSAUTO_8: + if b == C_ZAUTO || b == C_PSAUTO_16 { + return true + } + case C_PSAUTO_4: switch b { - case C_ZAUTO, C_PSAUTO_8: + case C_ZAUTO, C_PSAUTO_16, C_PSAUTO_8: return true } case C_PSAUTO: switch b { - case C_ZAUTO, C_PSAUTO_8, C_PSAUTO_4: + case C_ZAUTO, C_PSAUTO_16, C_PSAUTO_8, C_PSAUTO_4: + return true + } + + case C_PPAUTO_16: + switch b { + case C_ZAUTO, C_PSAUTO_16: return true } case C_PPAUTO: switch b { - case C_ZAUTO, C_PSAUTO_8, C_PPAUTO_16: + case C_ZAUTO, C_PSAUTO_16, C_PSAUTO_8, C_PPAUTO_16: + return true + } + + case C_PQAUTO_16: + switch b { + case C_ZAUTO, C_PSAUTO_16, C_PPAUTO_16: return true } case C_UAUTO4K: switch b { - case C_ZAUTO, C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, - C_PPAUTO, C_PPAUTO_16, + case C_ZAUTO, C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, C_PSAUTO_16, + C_PPAUTO, C_PPAUTO_16, C_PQAUTO_16, C_UAUTO4K_2, C_UAUTO4K_4, C_UAUTO4K_8, C_UAUTO4K_16: return true } case C_UAUTO8K: switch b { - case C_ZAUTO, C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, - C_PPAUTO, C_PPAUTO_16, + case C_ZAUTO, C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, C_PSAUTO_16, + C_PPAUTO, C_PPAUTO_16, C_PQAUTO_16, C_UAUTO4K_2, C_UAUTO4K_4, C_UAUTO4K_8, C_UAUTO4K_16, C_UAUTO8K_4, C_UAUTO8K_8, C_UAUTO8K_16: return true @@ -2258,8 +2245,8 @@ func cmp(a int, b int) bool { case C_UAUTO16K: switch b { - case C_ZAUTO, C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, - C_PPAUTO, C_PPAUTO_16, + case C_ZAUTO, C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, C_PSAUTO_16, + C_PPAUTO, C_PPAUTO_16, C_PQAUTO_16, C_UAUTO4K_4, C_UAUTO4K_8, C_UAUTO4K_16, C_UAUTO8K_4, C_UAUTO8K_8, C_UAUTO8K_16, C_UAUTO16K_8, C_UAUTO16K_16: @@ -2268,8 +2255,8 @@ func cmp(a int, b int) bool { case C_UAUTO32K: switch b { - case C_ZAUTO, C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, - C_PPAUTO, C_PPAUTO_16, + case C_ZAUTO, C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, C_PSAUTO_16, + C_PPAUTO, C_PPAUTO_16, C_PQAUTO_16, C_UAUTO4K_8, C_UAUTO4K_16, C_UAUTO8K_8, C_UAUTO8K_16, C_UAUTO16K_8, C_UAUTO16K_16, @@ -2279,17 +2266,17 @@ func cmp(a int, b int) bool { case C_UAUTO64K: switch b { - case C_ZAUTO, C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, - C_PPAUTO_16, C_UAUTO4K_16, C_UAUTO8K_16, C_UAUTO16K_16, + case C_ZAUTO, C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, C_PSAUTO_16, + C_PPAUTO_16, C_PQAUTO_16, C_UAUTO4K_16, C_UAUTO8K_16, C_UAUTO16K_16, C_UAUTO32K_16: return true } case C_LAUTO: switch b { - case C_ZAUTO, C_NSAUTO, C_NSAUTO_4, C_NSAUTO_8, C_NPAUTO, C_NAUTO4K, - C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, - C_PPAUTO, C_PPAUTO_16, + case C_ZAUTO, C_NSAUTO, C_NSAUTO_4, C_NSAUTO_8, C_NSAUTO_16, C_NPAUTO_16, C_NPAUTO, C_NQAUTO_16, C_NAUTO4K, + C_PSAUTO, C_PSAUTO_4, C_PSAUTO_8, C_PSAUTO_16, + C_PPAUTO, C_PPAUTO_16, C_PQAUTO_16, C_UAUTO4K, C_UAUTO4K_2, C_UAUTO4K_4, C_UAUTO4K_8, C_UAUTO4K_16, C_UAUTO8K, C_UAUTO8K_4, C_UAUTO8K_8, C_UAUTO8K_16, C_UAUTO16K, C_UAUTO16K_8, C_UAUTO16K_16, @@ -2298,64 +2285,98 @@ func cmp(a int, b int) bool { return true } + case C_NSOREG_8: + if b == C_NSOREG_16 { + return true + } + case C_NSOREG_4: - if b == C_NSOREG_8 { + if b == C_NSOREG_8 || b == C_NSOREG_16 { return true } case C_NSOREG: switch b { - case C_NSOREG_4, C_NSOREG_8: + case C_NSOREG_4, C_NSOREG_8, C_NSOREG_16: + return true + } + + case C_NPOREG_16: + switch b { + case C_NSOREG_16: return true } case C_NPOREG: switch b { - case C_NSOREG_8: + case C_NSOREG_16, C_NSOREG_8, C_NPOREG_16: + return true + } + + case C_NQOREG_16: + switch b { + case C_NSOREG_16, C_NPOREG_16: return true } case C_NOREG4K: switch b { - case C_NSOREG_8, C_NSOREG_4, C_NSOREG, C_NPOREG: + case C_NSOREG_16, C_NSOREG_8, C_NSOREG_4, C_NSOREG, C_NPOREG_16, C_NPOREG, C_NQOREG_16: return true } - case C_PSOREG_8: + case C_PSOREG_16: if b == C_ZOREG { return true } + case C_PSOREG_8: + if b == C_ZOREG || b == C_PSOREG_16 { + return true + } + case C_PSOREG_4: switch b { - case C_ZOREG, C_PSOREG_8: + case C_ZOREG, C_PSOREG_16, C_PSOREG_8: return true } case C_PSOREG: switch b { - case C_ZOREG, C_PSOREG_8, C_PSOREG_4: + case C_ZOREG, C_PSOREG_16, C_PSOREG_8, C_PSOREG_4: + return true + } + + case C_PPOREG_16: + switch b { + case C_ZOREG, C_PSOREG_16: return true } case C_PPOREG: switch b { - case C_ZOREG, C_PSOREG_8, C_PPOREG_16: + case C_ZOREG, C_PSOREG_16, C_PSOREG_8, C_PPOREG_16: + return true + } + + case C_PQOREG_16: + switch b { + case C_ZOREG, C_PSOREG_16, C_PPOREG_16: return true } case C_UOREG4K: switch b { - case C_ZOREG, C_PSOREG, C_PSOREG_4, C_PSOREG_8, - C_PPOREG, C_PPOREG_16, + case C_ZOREG, C_PSOREG, C_PSOREG_4, C_PSOREG_8, C_PSOREG_16, + C_PPOREG, C_PPOREG_16, C_PQOREG_16, C_UOREG4K_2, C_UOREG4K_4, C_UOREG4K_8, C_UOREG4K_16: return true } case C_UOREG8K: switch b { - case C_ZOREG, C_PSOREG, C_PSOREG_4, C_PSOREG_8, - C_PPOREG, C_PPOREG_16, + case C_ZOREG, C_PSOREG, C_PSOREG_4, C_PSOREG_8, C_PSOREG_16, + C_PPOREG, C_PPOREG_16, C_PQOREG_16, C_UOREG4K_2, C_UOREG4K_4, C_UOREG4K_8, C_UOREG4K_16, C_UOREG8K_4, C_UOREG8K_8, C_UOREG8K_16: return true @@ -2363,8 +2384,8 @@ func cmp(a int, b int) bool { case C_UOREG16K: switch b { - case C_ZOREG, C_PSOREG, C_PSOREG_4, C_PSOREG_8, - C_PPOREG, C_PPOREG_16, + case C_ZOREG, C_PSOREG, C_PSOREG_4, C_PSOREG_8, C_PSOREG_16, + C_PPOREG, C_PPOREG_16, C_PQOREG_16, C_UOREG4K_4, C_UOREG4K_8, C_UOREG4K_16, C_UOREG8K_4, C_UOREG8K_8, C_UOREG8K_16, C_UOREG16K_8, C_UOREG16K_16: @@ -2373,8 +2394,8 @@ func cmp(a int, b int) bool { case C_UOREG32K: switch b { - case C_ZOREG, C_PSOREG, C_PSOREG_4, C_PSOREG_8, - C_PPOREG, C_PPOREG_16, + case C_ZOREG, C_PSOREG, C_PSOREG_4, C_PSOREG_8, C_PSOREG_16, + C_PPOREG, C_PPOREG_16, C_PQOREG_16, C_UOREG4K_8, C_UOREG4K_16, C_UOREG8K_8, C_UOREG8K_16, C_UOREG16K_8, C_UOREG16K_16, @@ -2384,17 +2405,17 @@ func cmp(a int, b int) bool { case C_UOREG64K: switch b { - case C_ZOREG, C_PSOREG, C_PSOREG_4, C_PSOREG_8, - C_PPOREG_16, C_UOREG4K_16, C_UOREG8K_16, C_UOREG16K_16, + case C_ZOREG, C_PSOREG, C_PSOREG_4, C_PSOREG_8, C_PSOREG_16, + C_PPOREG_16, C_PQOREG_16, C_UOREG4K_16, C_UOREG8K_16, C_UOREG16K_16, C_UOREG32K_16: return true } case C_LOREG: switch b { - case C_ZOREG, C_NSOREG, C_NSOREG_4, C_NSOREG_8, C_NPOREG, C_NOREG4K, - C_PSOREG, C_PSOREG_4, C_PSOREG_8, - C_PPOREG, C_PPOREG_16, + case C_ZOREG, C_NSOREG, C_NSOREG_4, C_NSOREG_8, C_NSOREG_16, C_NPOREG, C_NPOREG_16, C_NQOREG_16, C_NOREG4K, + C_PSOREG, C_PSOREG_4, C_PSOREG_8, C_PSOREG_16, + C_PPOREG, C_PPOREG_16, C_PQOREG_16, C_UOREG4K, C_UOREG4K_2, C_UOREG4K_4, C_UOREG4K_8, C_UOREG4K_16, C_UOREG8K, C_UOREG8K_4, C_UOREG8K_8, C_UOREG8K_16, C_UOREG16K, C_UOREG16K_8, C_UOREG16K_16, @@ -2722,6 +2743,10 @@ func buildop(ctxt *obj.Link) { obj.ATEXT: break + case AFLDPQ: + break + case AFSTPQ: + break case ALDP: oprangeset(AFLDPD, t) @@ -2922,6 +2947,8 @@ func buildop(ctxt *obj.Link) { oprangeset(AVBSL, t) oprangeset(AVBIT, t) oprangeset(AVCMTST, t) + oprangeset(AVUMAX, t) + oprangeset(AVUMIN, t) oprangeset(AVUZP1, t) oprangeset(AVUZP2, t) oprangeset(AVBIF, t) @@ -3204,6 +3231,9 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { o1 |= (uint32(rf&31) << 16) | (uint32(r&31) << 5) | uint32(rt&31) case 2: /* add/sub $(uimm12|uimm24)[,R],R; cmp $(uimm12|uimm24),R */ + if p.To.Reg == REG_RSP && isADDSop(p.As) { + c.ctxt.Diag("illegal destination register: %v\n", p) + } o1 = c.opirr(p, p.As) rt := int(p.To.Reg) @@ -3229,13 +3259,17 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { if is64bit == 0 && amount >= 32 { c.ctxt.Diag("shift amount out of range 0 to 31: %v", p) } + shift := (p.From.Offset >> 22) & 3 + if (shift > 2 || shift < 0) && (isADDop(p.As) || isADDWop(p.As) || isNEGop(p.As)) { + c.ctxt.Diag("unsupported shift operator: %v", p) + } o1 |= uint32(p.From.Offset) /* includes reg, op, etc */ rt := int(p.To.Reg) if p.To.Type == obj.TYPE_NONE { rt = REGZERO } r := int(p.Reg) - if p.As == AMVN || p.As == AMVNW { + if p.As == AMVN || p.As == AMVNW || isNEGop(p.As) { r = REGZERO } else if r == 0 { r = rt @@ -3385,6 +3419,9 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { o4 = os[3] case 13: /* addop $vcon, [R], R (64 bit literal); cmp $lcon,R -> addop $lcon,R, ZR */ + if p.To.Reg == REG_RSP && isADDSop(p.As) { + c.ctxt.Diag("illegal destination register: %v\n", p) + } o := uint32(0) num := uint8(0) cls := oclass(&p.From) @@ -3499,27 +3536,25 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { o1 = c.oprrr(p, p.As) cond := int(p.From.Reg) - if cond < COND_EQ || cond > COND_NV { + // AL and NV are not allowed for CINC/CINV/CNEG/CSET/CSETM instructions + if cond < COND_EQ || cond > COND_NV || (cond == COND_AL || cond == COND_NV) && p.From3Type() == obj.TYPE_NONE { c.ctxt.Diag("invalid condition: %v", p) } else { cond -= COND_EQ } r := int(p.Reg) - var rf int - if r != 0 { - if p.From3Type() == obj.TYPE_NONE { - /* CINC/CINV/CNEG */ - rf = r - cond ^= 1 - } else { - rf = int(p.GetFrom3().Reg) /* CSEL */ + var rf int = r + if p.From3Type() == obj.TYPE_NONE { + /* CINC/CINV/CNEG or CSET/CSETM*/ + if r == 0 { + /* CSET/CSETM */ + rf = REGZERO + r = rf } - } else { - /* CSET */ - rf = REGZERO - r = rf cond ^= 1 + } else { + rf = int(p.GetFrom3().Reg) /* CSEL */ } rt := int(p.To.Reg) @@ -3584,7 +3619,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { v := int32(p.From.Offset) if v < -256 || v > 255 { - c.ctxt.Diag("offset out of range [-255,254]: %v", p) + c.ctxt.Diag("offset out of range [-256,255]: %v", p) } o1 = c.opldr(p, p.As) if o.scond == C_XPOST { @@ -3602,7 +3637,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { v := int32(p.To.Offset) if v < -256 || v > 255 { - c.ctxt.Diag("offset out of range [-255,254]: %v", p) + c.ctxt.Diag("offset out of range [-256,255]: %v", p) } o1 = c.opstr(p, p.As) if o.scond == C_XPOST { @@ -3640,14 +3675,45 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { rt := int(p.To.Reg) o1 |= (uint32(rf&31) << 16) | (REGZERO & 31 << 5) | uint32(rt&31) - case 26: /* negX Rm< subX Rm<> 10) & 63 + shift := (p.From.Offset >> 22) & 3 + if shift != 0 { + c.ctxt.Diag("illegal combination: %v", p) + break + } - o1 |= uint32(p.From.Offset) /* includes reg, op, etc */ + if amount > 4 { + c.ctxt.Diag("the left shift amount out of range 0 to 4: %v", p) + break + } + rf := (p.From.Offset >> 16) & 31 rt := int(p.To.Reg) - o1 |= (REGZERO & 31 << 5) | uint32(rt&31) + r := int(p.Reg) + if p.To.Type == obj.TYPE_NONE { + rt = REGZERO + } + if r == 0 { + r = rt + } + + o1 = c.opxrrr(p, p.As, false) + o1 |= uint32(rf)<<16 | uint32(amount&7)<<10 | (uint32(r&31) << 5) | uint32(rt&31) case 27: /* op Rm<> 5) & 7 if amount > 4 { @@ -4264,8 +4330,13 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { if p.Reg == REGTMP { c.ctxt.Diag("cannot use REGTMP as source: %v\n", p) } + if p.To.Reg == REG_RSP && isADDSop(p.As) { + c.ctxt.Diag("illegal destination register: %v\n", p) + } + lsl0 := LSL0_64 if isADDWop(p.As) || isANDWop(p.As) { o1 = c.omovconst(AMOVW, p, &p.From, REGTMP) + lsl0 = LSL0_32 } else { o1 = c.omovconst(AMOVD, p, &p.From, REGTMP) } @@ -4281,7 +4352,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { if p.To.Reg == REGSP || r == REGSP { o2 = c.opxrrr(p, p.As, false) o2 |= REGTMP & 31 << 16 - o2 |= LSL0_64 + o2 |= uint32(lsl0) } else { o2 = c.oprrr(p, p.As) o2 |= REGTMP & 31 << 16 /* shift is 0 */ @@ -4434,6 +4505,10 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { if af != ARNG_2D && af != ARNG_2S && af != ARNG_4S { c.ctxt.Diag("invalid arrangement: %v", p) } + case AVUMAX, AVUMIN: + if af == ARNG_2D { + c.ctxt.Diag("invalid arrangement: %v", p) + } } switch p.As { case AVAND, AVEOR: @@ -4650,13 +4725,13 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { o1 |= (uint32(Q&1) << 30) | (uint32(imm5&0x1f) << 16) o1 |= (uint32(rf&31) << 5) | uint32(rt&31) - case 80: /* vmov V.[index], Vn */ + case 80: /* vmov/vdup V.[index], Vn */ rf := int(p.From.Reg) rt := int(p.To.Reg) imm5 := 0 index := int(p.From.Index) switch p.As { - case AVMOV: + case AVMOV, AVDUP: o1 = 1<<30 | 15<<25 | 1<<10 switch (p.From.Reg >> 5) & 15 { case ARNG_B: @@ -4706,7 +4781,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { o1 = c.maskOpvldvst(p, o1) o1 |= uint32(r&31) << 5 - case 82: /* vmov Rn, Vd. */ + case 82: /* vmov/vdup Rn, Vd. */ rf := int(p.From.Reg) rt := int(p.To.Reg) o1 = 7<<25 | 3<<10 @@ -4734,7 +4809,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { Q = 1 imm5 = 2 default: - c.ctxt.Diag("invalid arrangement on VMOV Rn, Vd.: %v\n", p) + c.ctxt.Diag("invalid arrangement: %v\n", p) } o1 |= (Q & 1 << 30) | (imm5 & 0x1f << 16) o1 |= (uint32(rf&31) << 5) | uint32(rt&31) @@ -6110,6 +6185,12 @@ func (c *ctxt7) oprrr(p *obj.Prog, a obj.As) uint32 { case AVCMTST: return 0xE<<24 | 1<<21 | 0x23<<10 + case AVUMAX: + return 1<<29 | 7<<25 | 1<<21 | 0x19<<10 + + case AVUMIN: + return 1<<29 | 7<<25 | 1<<21 | 0x1b<<10 + case AVUZP1: return 7<<25 | 3<<11 @@ -6387,11 +6468,10 @@ func (c *ctxt7) opbit(p *obj.Prog, a obj.As) uint32 { func (c *ctxt7) opxrrr(p *obj.Prog, a obj.As, extend bool) uint32 { extension := uint32(0) if !extend { - switch a { - case AADD, ACMN, AADDS, ASUB, ACMP, ASUBS: + if isADDop(a) { extension = LSL0_64 - - case AADDW, ACMNW, AADDSW, ASUBW, ACMPW, ASUBSW: + } + if isADDWop(a) { extension = LSL0_32 } } @@ -7192,7 +7272,7 @@ func (c *ctxt7) opextr(p *obj.Prog, a obj.As, v int32, rn int, rm int, rt int) u return o } -/* genrate instruction encoding for LDP/LDPW/LDPSW/STP/STPW */ +/* genrate instruction encoding for ldp and stp series */ func (c *ctxt7) opldpstp(p *obj.Prog, o *Optab, vo int32, rbase, rl, rh, ldp uint32) uint32 { wback := false if o.scond == C_XPOST || o.scond == C_XPRE { @@ -7205,30 +7285,36 @@ func (c *ctxt7) opldpstp(p *obj.Prog, o *Optab, vo int32, rbase, rl, rh, ldp uin if wback == true { c.checkUnpredictable(p, false, true, p.To.Reg, p.From.Reg, int16(p.From.Offset)) } - case AFLDPD, AFLDPS: + case AFLDPD, AFLDPQ, AFLDPS: c.checkUnpredictable(p, true, false, p.From.Reg, p.To.Reg, int16(p.To.Offset)) } var ret uint32 // check offset switch p.As { - case AFLDPD, AFSTPD: - if vo < -512 || vo > 504 || vo%8 != 0 { + case AFLDPQ, AFSTPQ: + if vo < -1024 || vo > 1008 || vo%16 != 0 { c.ctxt.Diag("invalid offset %v\n", p) } - vo /= 8 - ret = 1<<30 | 1<<26 - case ALDP, ASTP: + vo /= 16 + ret = 2<<30 | 1<<26 + case AFLDPD, AFSTPD: if vo < -512 || vo > 504 || vo%8 != 0 { c.ctxt.Diag("invalid offset %v\n", p) } vo /= 8 - ret = 2 << 30 + ret = 1<<30 | 1<<26 case AFLDPS, AFSTPS: if vo < -256 || vo > 252 || vo%4 != 0 { c.ctxt.Diag("invalid offset %v\n", p) } vo /= 4 ret = 1 << 26 + case ALDP, ASTP: + if vo < -512 || vo > 504 || vo%8 != 0 { + c.ctxt.Diag("invalid offset %v\n", p) + } + vo /= 8 + ret = 2 << 30 case ALDPW, ASTPW: if vo < -256 || vo > 252 || vo%4 != 0 { c.ctxt.Diag("invalid offset %v\n", p) @@ -7246,7 +7332,7 @@ func (c *ctxt7) opldpstp(p *obj.Prog, o *Optab, vo int32, rbase, rl, rh, ldp uin } // check register pair switch p.As { - case AFLDPD, AFLDPS, AFSTPD, AFSTPS: + case AFLDPQ, AFLDPD, AFLDPS, AFSTPQ, AFSTPD, AFSTPS: if rl < REG_F0 || REG_F31 < rl || rh < REG_F0 || REG_F31 < rh { c.ctxt.Diag("invalid register pair %v\n", p) } diff --git a/src/cmd/internal/obj/arm64/obj7.go b/src/cmd/internal/obj/arm64/obj7.go index 0baf51973ade229dbd5ae975a25233304bcd9e59..e41fb3bb7531cb01e7c8319343c2c2cb5730d3ea 100644 --- a/src/cmd/internal/obj/arm64/obj7.go +++ b/src/cmd/internal/obj/arm64/obj7.go @@ -35,6 +35,8 @@ import ( "cmd/internal/objabi" "cmd/internal/src" "cmd/internal/sys" + "internal/buildcfg" + "log" "math" ) @@ -106,55 +108,35 @@ func (c *ctxt7) stacksplit(p *obj.Prog, framesize int32) *obj.Prog { p.From.Reg = REG_R1 p.Reg = REG_R2 } else { - // Such a large stack we need to protect against wraparound - // if SP is close to zero. - // SP-stackguard+StackGuard < framesize + (StackGuard-StackSmall) - // The +StackGuard on both sides is required to keep the left side positive: - // SP is allowed to be slightly below stackguard. See stack.h. - // CMP $StackPreempt, R1 - // BEQ label_of_call_to_morestack - // ADD $StackGuard, SP, R2 - // SUB R1, R2 - // MOV $(framesize+(StackGuard-StackSmall)), R3 - // CMP R3, R2 - p = obj.Appendp(p, c.newprog) - - p.As = ACMP - p.From.Type = obj.TYPE_CONST - p.From.Offset = objabi.StackPreempt - p.Reg = REG_R1 - - p = obj.Appendp(p, c.newprog) - q = p - p.As = ABEQ - p.To.Type = obj.TYPE_BRANCH + // Such a large stack we need to protect against underflow. + // The runtime guarantees SP > objabi.StackBig, but + // framesize is large enough that SP-framesize may + // underflow, causing a direct comparison with the + // stack guard to incorrectly succeed. We explicitly + // guard against underflow. + // + // SUBS $(framesize-StackSmall), SP, R2 + // // On underflow, jump to morestack + // BLO label_of_call_to_morestack + // CMP stackguard, R2 p = obj.Appendp(p, c.newprog) - p.As = AADD + p.As = ASUBS p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(objabi.StackGuard) + p.From.Offset = int64(framesize) - objabi.StackSmall p.Reg = REGSP p.To.Type = obj.TYPE_REG p.To.Reg = REG_R2 p = obj.Appendp(p, c.newprog) - p.As = ASUB - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R1 - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R2 - - p = obj.Appendp(p, c.newprog) - p.As = AMOVD - p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(framesize) + (int64(objabi.StackGuard) - objabi.StackSmall) - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R3 + q = p + p.As = ABLO + p.To.Type = obj.TYPE_BRANCH p = obj.Appendp(p, c.newprog) p.As = ACMP p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R3 + p.From.Reg = REG_R1 p.Reg = REG_R2 } @@ -313,13 +295,13 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { } } - // For 32-bit logical instruction with constant, - // rewrite the high 32-bit to be a repetition of - // the low 32-bit, so that the BITCON test can be - // shared for both 32-bit and 64-bit. 32-bit ops - // will zero the high 32-bit of the destination - // register anyway. - if isANDWop(p.As) && p.From.Type == obj.TYPE_CONST { + // For 32-bit instruction with constant, rewrite + // the high 32-bit to be a repetition of the low + // 32-bit, so that the BITCON test can be shared + // for both 32-bit and 64-bit. 32-bit ops will + // zero the high 32-bit of the destination register + // anyway. + if (isANDWop(p.As) || isADDWop(p.As) || p.As == AMOVW) && p.From.Type == obj.TYPE_CONST { v := p.From.Offset & 0xffffffff p.From.Offset = v | v<<32 } @@ -538,6 +520,12 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { } } + if p.Mark&LEAF != 0 && c.autosize < objabi.StackSmall { + // A leaf function with a small stack can be marked + // NOSPLIT, avoiding a stack check. + p.From.Sym.Set(obj.AttrNoSplit, true) + } + if !p.From.Sym.NoSplit() { p = c.stacksplit(p, c.autosize) // emit split check } @@ -589,7 +577,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { q1.To.Reg = REGSP q1.Spadj = c.autosize - if objabi.GOOS == "ios" { + if buildcfg.GOOS == "ios" { // iOS does not support SA_ONSTACK. We will run the signal handler // on the G stack. If we write below SP, it may be clobbered by // the signal handler. So we save LR after decrementing SP. @@ -621,25 +609,24 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { prologueEnd.Pos = prologueEnd.Pos.WithXlogue(src.PosPrologueEnd) - if objabi.Framepointer_enabled { - q1 = obj.Appendp(q1, c.newprog) - q1.Pos = p.Pos - q1.As = AMOVD - q1.From.Type = obj.TYPE_REG - q1.From.Reg = REGFP - q1.To.Type = obj.TYPE_MEM - q1.To.Reg = REGSP - q1.To.Offset = -8 - - q1 = obj.Appendp(q1, c.newprog) - q1.Pos = p.Pos - q1.As = ASUB - q1.From.Type = obj.TYPE_CONST - q1.From.Offset = 8 - q1.Reg = REGSP - q1.To.Type = obj.TYPE_REG - q1.To.Reg = REGFP - } + // Frame pointer. + q1 = obj.Appendp(q1, c.newprog) + q1.Pos = p.Pos + q1.As = AMOVD + q1.From.Type = obj.TYPE_REG + q1.From.Reg = REGFP + q1.To.Type = obj.TYPE_MEM + q1.To.Reg = REGSP + q1.To.Offset = -8 + + q1 = obj.Appendp(q1, c.newprog) + q1.Pos = p.Pos + q1.As = ASUB + q1.From.Type = obj.TYPE_CONST + q1.From.Offset = 8 + q1.Reg = REGSP + q1.To.Type = obj.TYPE_REG + q1.To.Reg = REGFP if c.cursym.Func().Text.From.Sym.Wrapper() { // if(g->panic != nil && g->panic->argp == FP) g->panic->argp = bottom-of-frame @@ -764,28 +751,26 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { p.To.Reg = REGSP p.Spadj = -c.autosize - if objabi.Framepointer_enabled { - p = obj.Appendp(p, c.newprog) - p.As = ASUB - p.From.Type = obj.TYPE_CONST - p.From.Offset = 8 - p.Reg = REGSP - p.To.Type = obj.TYPE_REG - p.To.Reg = REGFP - } + // Frame pointer. + p = obj.Appendp(p, c.newprog) + p.As = ASUB + p.From.Type = obj.TYPE_CONST + p.From.Offset = 8 + p.Reg = REGSP + p.To.Type = obj.TYPE_REG + p.To.Reg = REGFP } } else { /* want write-back pre-indexed SP+autosize -> SP, loading REGLINK*/ - if objabi.Framepointer_enabled { - p.As = AMOVD - p.From.Type = obj.TYPE_MEM - p.From.Reg = REGSP - p.From.Offset = -8 - p.To.Type = obj.TYPE_REG - p.To.Reg = REGFP - p = obj.Appendp(p, c.newprog) - } + // Frame pointer. + p.As = AMOVD + p.From.Type = obj.TYPE_MEM + p.From.Reg = REGSP + p.From.Offset = -8 + p.To.Type = obj.TYPE_REG + p.To.Reg = REGFP + p = obj.Appendp(p, c.newprog) aoffset := c.autosize @@ -820,6 +805,28 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { } } + // If enabled, this code emits 'MOV PC, R27' before every 'MOV LR, PC', + // so that if you are debugging a low-level crash where PC and LR are zero, + // you can look at R27 to see what jumped to the zero. + // This is useful when bringing up Go on a new system. + // (There is similar code in ../ppc64/obj9.go:/if.false.) + const debugRETZERO = false + if debugRETZERO { + if p.As != obj.ARET { + q = newprog() + q.Pos = p.Pos + q.Link = p.Link + p.Link = q + p = q + } + p.As = AADR + p.From.Type = obj.TYPE_BRANCH + p.From.Offset = 0 + p.To.Type = obj.TYPE_REG + p.To.Reg = REGTMP + + } + if p.As != obj.ARET { q = newprog() q.Pos = p.Pos @@ -865,109 +872,120 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { } case obj.ADUFFCOPY: - if objabi.Framepointer_enabled { - // ADR ret_addr, R27 - // STP (FP, R27), -24(SP) - // SUB 24, SP, FP - // DUFFCOPY - // ret_addr: - // SUB 8, SP, FP - - q1 := p - // copy DUFFCOPY from q1 to q4 - q4 := obj.Appendp(p, c.newprog) - q4.Pos = p.Pos - q4.As = obj.ADUFFCOPY - q4.To = p.To - - q1.As = AADR - q1.From.Type = obj.TYPE_BRANCH - q1.To.Type = obj.TYPE_REG - q1.To.Reg = REG_R27 - - q2 := obj.Appendp(q1, c.newprog) - q2.Pos = p.Pos - q2.As = ASTP - q2.From.Type = obj.TYPE_REGREG - q2.From.Reg = REGFP - q2.From.Offset = int64(REG_R27) - q2.To.Type = obj.TYPE_MEM - q2.To.Reg = REGSP - q2.To.Offset = -24 - - // maintaine FP for DUFFCOPY - q3 := obj.Appendp(q2, c.newprog) - q3.Pos = p.Pos - q3.As = ASUB - q3.From.Type = obj.TYPE_CONST - q3.From.Offset = 24 - q3.Reg = REGSP - q3.To.Type = obj.TYPE_REG - q3.To.Reg = REGFP - - q5 := obj.Appendp(q4, c.newprog) - q5.Pos = p.Pos - q5.As = ASUB - q5.From.Type = obj.TYPE_CONST - q5.From.Offset = 8 - q5.Reg = REGSP - q5.To.Type = obj.TYPE_REG - q5.To.Reg = REGFP - q1.From.SetTarget(q5) - p = q5 - } + // ADR ret_addr, R27 + // STP (FP, R27), -24(SP) + // SUB 24, SP, FP + // DUFFCOPY + // ret_addr: + // SUB 8, SP, FP + + q1 := p + // copy DUFFCOPY from q1 to q4 + q4 := obj.Appendp(p, c.newprog) + q4.Pos = p.Pos + q4.As = obj.ADUFFCOPY + q4.To = p.To + + q1.As = AADR + q1.From.Type = obj.TYPE_BRANCH + q1.To.Type = obj.TYPE_REG + q1.To.Reg = REG_R27 + + q2 := obj.Appendp(q1, c.newprog) + q2.Pos = p.Pos + q2.As = ASTP + q2.From.Type = obj.TYPE_REGREG + q2.From.Reg = REGFP + q2.From.Offset = int64(REG_R27) + q2.To.Type = obj.TYPE_MEM + q2.To.Reg = REGSP + q2.To.Offset = -24 + + // maintain FP for DUFFCOPY + q3 := obj.Appendp(q2, c.newprog) + q3.Pos = p.Pos + q3.As = ASUB + q3.From.Type = obj.TYPE_CONST + q3.From.Offset = 24 + q3.Reg = REGSP + q3.To.Type = obj.TYPE_REG + q3.To.Reg = REGFP + + q5 := obj.Appendp(q4, c.newprog) + q5.Pos = p.Pos + q5.As = ASUB + q5.From.Type = obj.TYPE_CONST + q5.From.Offset = 8 + q5.Reg = REGSP + q5.To.Type = obj.TYPE_REG + q5.To.Reg = REGFP + q1.From.SetTarget(q5) + p = q5 case obj.ADUFFZERO: - if objabi.Framepointer_enabled { - // ADR ret_addr, R27 - // STP (FP, R27), -24(SP) - // SUB 24, SP, FP - // DUFFZERO - // ret_addr: - // SUB 8, SP, FP - - q1 := p - // copy DUFFZERO from q1 to q4 - q4 := obj.Appendp(p, c.newprog) - q4.Pos = p.Pos - q4.As = obj.ADUFFZERO - q4.To = p.To - - q1.As = AADR - q1.From.Type = obj.TYPE_BRANCH - q1.To.Type = obj.TYPE_REG - q1.To.Reg = REG_R27 - - q2 := obj.Appendp(q1, c.newprog) - q2.Pos = p.Pos - q2.As = ASTP - q2.From.Type = obj.TYPE_REGREG - q2.From.Reg = REGFP - q2.From.Offset = int64(REG_R27) - q2.To.Type = obj.TYPE_MEM - q2.To.Reg = REGSP - q2.To.Offset = -24 - - // maintaine FP for DUFFZERO - q3 := obj.Appendp(q2, c.newprog) - q3.Pos = p.Pos - q3.As = ASUB - q3.From.Type = obj.TYPE_CONST - q3.From.Offset = 24 - q3.Reg = REGSP - q3.To.Type = obj.TYPE_REG - q3.To.Reg = REGFP - - q5 := obj.Appendp(q4, c.newprog) - q5.Pos = p.Pos - q5.As = ASUB - q5.From.Type = obj.TYPE_CONST - q5.From.Offset = 8 - q5.Reg = REGSP - q5.To.Type = obj.TYPE_REG - q5.To.Reg = REGFP - q1.From.SetTarget(q5) - p = q5 + // ADR ret_addr, R27 + // STP (FP, R27), -24(SP) + // SUB 24, SP, FP + // DUFFZERO + // ret_addr: + // SUB 8, SP, FP + + q1 := p + // copy DUFFZERO from q1 to q4 + q4 := obj.Appendp(p, c.newprog) + q4.Pos = p.Pos + q4.As = obj.ADUFFZERO + q4.To = p.To + + q1.As = AADR + q1.From.Type = obj.TYPE_BRANCH + q1.To.Type = obj.TYPE_REG + q1.To.Reg = REG_R27 + + q2 := obj.Appendp(q1, c.newprog) + q2.Pos = p.Pos + q2.As = ASTP + q2.From.Type = obj.TYPE_REGREG + q2.From.Reg = REGFP + q2.From.Offset = int64(REG_R27) + q2.To.Type = obj.TYPE_MEM + q2.To.Reg = REGSP + q2.To.Offset = -24 + + // maintain FP for DUFFZERO + q3 := obj.Appendp(q2, c.newprog) + q3.Pos = p.Pos + q3.As = ASUB + q3.From.Type = obj.TYPE_CONST + q3.From.Offset = 24 + q3.Reg = REGSP + q3.To.Type = obj.TYPE_REG + q3.To.Reg = REGFP + + q5 := obj.Appendp(q4, c.newprog) + q5.Pos = p.Pos + q5.As = ASUB + q5.From.Type = obj.TYPE_CONST + q5.From.Offset = 8 + q5.Reg = REGSP + q5.To.Type = obj.TYPE_REG + q5.To.Reg = REGFP + q1.From.SetTarget(q5) + p = q5 + } + + if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.Spadj == 0 { + f := c.cursym.Func() + if f.FuncFlag&objabi.FuncFlag_SPWRITE == 0 { + c.cursym.Func().FuncFlag |= objabi.FuncFlag_SPWRITE + if ctxt.Debugvlog || !ctxt.IsAsm { + ctxt.Logf("auto-SPWRITE: %s %v\n", c.cursym.Name, p) + if !ctxt.IsAsm { + ctxt.Diag("invalid auto-SPWRITE in non-assembly") + ctxt.DiagFlush() + log.Fatalf("bad SPWRITE") + } + } } } } diff --git a/src/cmd/internal/obj/data.go b/src/cmd/internal/obj/data.go index f32e07acfed1640173e6ac1314592ef689bae2ad..bcba53c3a4b415482afddcb9d7205ce9def30649 100644 --- a/src/cmd/internal/obj/data.go +++ b/src/cmd/internal/obj/data.go @@ -135,6 +135,13 @@ func (s *LSym) WriteAddr(ctxt *Link, off int64, siz int, rsym *LSym, roff int64) s.writeAddr(ctxt, off, siz, rsym, roff, objabi.R_ADDR) } +// WriteWeakAddr writes an address of size siz into s at offset off. +// rsym and roff specify the relocation for the address. +// This is a weak reference. +func (s *LSym) WriteWeakAddr(ctxt *Link, off int64, siz int, rsym *LSym, roff int64) { + s.writeAddr(ctxt, off, siz, rsym, roff, objabi.R_WEAKADDR) +} + // WriteCURelativeAddr writes a pointer-sized address into s at offset off. // rsym and roff specify the relocation for the address which will be // resolved by the linker to an offset from the DW_AT_low_pc attribute of diff --git a/src/cmd/internal/obj/dwarf.go b/src/cmd/internal/obj/dwarf.go index 87c62e298165cb9ff640237013d1e929bdffb545..6dd53ffd1215e839b91d8657101e4a44ee9648c7 100644 --- a/src/cmd/internal/obj/dwarf.go +++ b/src/cmd/internal/obj/dwarf.go @@ -402,6 +402,31 @@ func (ctxt *Link) DwarfIntConst(myimportpath, name, typename string, val int64) dwarf.PutIntConst(dwCtxt{ctxt}, s, ctxt.Lookup(dwarf.InfoPrefix+typename), myimportpath+"."+name, val) } +// DwarfGlobal creates a link symbol containing a DWARF entry for +// a global variable. +func (ctxt *Link) DwarfGlobal(myimportpath, typename string, varSym *LSym) { + if myimportpath == "" || varSym.Local() { + return + } + var varname string + if varSym.Pkg == "_" { + // The frontend uses package "_" to mark symbols that should not + // be referenced by index, e.g. linkname'd symbols. + varname = varSym.Name + } else { + // Convert "". into a fully qualified package.sym name. + varname = objabi.PathToPrefix(myimportpath) + varSym.Name[len(`""`):] + } + dieSymName := dwarf.InfoPrefix + varname + dieSym := ctxt.LookupInit(dieSymName, func(s *LSym) { + s.Type = objabi.SDWARFVAR + s.Set(AttrDuplicateOK, true) // needed for shared linkage + ctxt.Data = append(ctxt.Data, s) + }) + typeSym := ctxt.Lookup(dwarf.InfoPrefix + typename) + dwarf.PutGlobal(dwCtxt{ctxt}, dieSym, typeSym, varSym, varname) +} + func (ctxt *Link) DwarfAbstractFunc(curfn interface{}, s *LSym, myimportpath string) { absfn := ctxt.DwFixups.AbsFuncDwarfSym(s) if absfn.Size != 0 { diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index 8c8ff587ffce73a3ac6c33dc51400452ebccf9db..28626e6e037a5c33f131aec8e2ba81aec5cf086c 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -39,6 +39,7 @@ import ( "cmd/internal/sys" "fmt" "sync" + "sync/atomic" ) // An Addr is an argument to an instruction. @@ -250,6 +251,12 @@ func (a *Addr) SetTarget(t *Prog) { a.Val = t } +func (a *Addr) SetConst(v int64) { + a.Sym = nil + a.Type = TYPE_CONST + a.Offset = v +} + // Prog describes a single machine instruction. // // The general instruction form is: @@ -353,7 +360,21 @@ func (p *Prog) SetFrom3(a Addr) { p.RestArgs = []AddrPos{{a, Source}} } -// SetTo2 assings []Args{{a, 1}} to p.RestArgs when the second destination +// SetFrom3Reg calls p.SetFrom3 with a register Addr containing reg. +// +// Deprecated: for the same reasons as Prog.GetFrom3. +func (p *Prog) SetFrom3Reg(reg int16) { + p.SetFrom3(Addr{Type: TYPE_REG, Reg: reg}) +} + +// SetFrom3Const calls p.SetFrom3 with a const Addr containing x. +// +// Deprecated: for the same reasons as Prog.GetFrom3. +func (p *Prog) SetFrom3Const(off int64) { + p.SetFrom3(Addr{Type: TYPE_CONST, Offset: off}) +} + +// SetTo2 assigns []Args{{a, 1}} to p.RestArgs when the second destination // operand does not fit into prog.RegTo2. func (p *Prog) SetTo2(a Addr) { p.RestArgs = []AddrPos{{a, Destination}} @@ -447,10 +468,12 @@ type FuncInfo struct { Locals int32 Align int32 FuncID objabi.FuncID + FuncFlag objabi.FuncFlag Text *Prog Autot map[*LSym]struct{} Pcln Pcln InlMarks []InlMark + spills []RegSpill dwarfInfoSym *LSym dwarfLocSym *LSym @@ -462,6 +485,7 @@ type FuncInfo struct { GCLocals *LSym StackObjects *LSym OpenCodedDeferInfo *LSym + ArgInfo *LSym // argument info for traceback FuncInfoSym *LSym } @@ -530,6 +554,11 @@ func (fi *FuncInfo) AddInlMark(p *Prog, id int32) { fi.InlMarks = append(fi.InlMarks, InlMark{p: p, id: id}) } +// AddSpill appends a spill record to the list for FuncInfo fi +func (fi *FuncInfo) AddSpill(s RegSpill) { + fi.spills = append(fi.spills, s) +} + // Record the type symbol for an auto variable so that the linker // an emit DWARF type information for the type. func (fi *FuncInfo) RecordAutoType(gotype *LSym) { @@ -575,6 +604,48 @@ func ParseABI(abistr string) (ABI, bool) { } } +// ABISet is a bit set of ABI values. +type ABISet uint8 + +const ( + // ABISetCallable is the set of all ABIs any function could + // potentially be called using. + ABISetCallable ABISet = (1 << ABI0) | (1 << ABIInternal) +) + +// Ensure ABISet is big enough to hold all ABIs. +var _ ABISet = 1 << (ABICount - 1) + +func ABISetOf(abi ABI) ABISet { + return 1 << abi +} + +func (a *ABISet) Set(abi ABI, value bool) { + if value { + *a |= 1 << abi + } else { + *a &^= 1 << abi + } +} + +func (a *ABISet) Get(abi ABI) bool { + return (*a>>abi)&1 != 0 +} + +func (a ABISet) String() string { + s := "{" + for i := ABI(0); a != 0; i++ { + if a&(1< objabi.StackBig { + // Such a large stack we need to protect against underflow. + // The runtime guarantees SP > objabi.StackBig, but + // framesize is large enough that SP-framesize may + // underflow, causing a direct comparison with the + // stack guard to incorrectly succeed. We explicitly + // guard against underflow. + // + // SGTU $(framesize-StackSmall), SP, R2 + // BNE R2, label-of-call-to-morestack + + p = obj.Appendp(p, c.newprog) + p.As = ASGTU + p.From.Type = obj.TYPE_CONST + p.From.Offset = offset + p.Reg = REGSP + p.To.Type = obj.TYPE_REG + p.To.Reg = REG_R2 - p.As = mov - p.From.Type = obj.TYPE_CONST - p.From.Offset = objabi.StackPreempt - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R2 + p = obj.Appendp(p, c.newprog) + q = p + p.As = ABNE + p.From.Type = obj.TYPE_REG + p.From.Reg = REG_R2 + p.To.Type = obj.TYPE_BRANCH + p.Mark |= BRANCH + } + // Check against the stack guard. We've ensured this won't underflow. + // ADD $-(framesize-StackSmall), SP, R2 + // SGTU R2, stackguard, R1 p = obj.Appendp(p, c.newprog) - q = p - p.As = ABEQ - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R1 - p.Reg = REG_R2 - p.To.Type = obj.TYPE_BRANCH - p.Mark |= BRANCH - p = obj.Appendp(p, c.newprog) p.As = add p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(objabi.StackGuard) + p.From.Offset = -offset p.Reg = REGSP p.To.Type = obj.TYPE_REG p.To.Reg = REG_R2 - p = obj.Appendp(p, c.newprog) - p.As = sub - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R1 - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R2 - - p = obj.Appendp(p, c.newprog) - p.As = mov - p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(framesize) + int64(objabi.StackGuard) - objabi.StackSmall - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R1 - p = obj.Appendp(p, c.newprog) p.As = ASGTU p.From.Type = obj.TYPE_REG diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index bb58b4f0c2b62d4103b99118f3aa458737850c73..24fb5a19dec5b1dd48f7da55a4dd79be40ac2fe7 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -330,9 +330,6 @@ func (w *writer) Sym(s *LSym) { if s.ReflectMethod() { flag |= goobj.SymFlagReflectMethod } - if s.TopFrame() { - flag |= goobj.SymFlagTopFrame - } if strings.HasPrefix(s.Name, "type.") && s.Name[5] != '.' && s.Type == objabi.SRODATA { flag |= goobj.SymFlagGoType } @@ -386,7 +383,7 @@ func (w *writer) Sym(s *LSym) { func (w *writer) Hash64(s *LSym) { if !s.ContentAddressable() || len(s.R) != 0 { - panic("Hash of non-content-addresable symbol") + panic("Hash of non-content-addressable symbol") } b := contentHash64(s) w.Bytes(b[:]) @@ -394,7 +391,7 @@ func (w *writer) Hash64(s *LSym) { func (w *writer) Hash(s *LSym) { if !s.ContentAddressable() { - panic("Hash of non-content-addresable symbol") + panic("Hash of non-content-addressable symbol") } b := w.contentHash(s) w.Bytes(b[:]) @@ -501,7 +498,7 @@ func (w *writer) Reloc(r *Reloc) { var o goobj.Reloc o.SetOff(r.Off) o.SetSiz(r.Siz) - o.SetType(uint8(r.Type)) + o.SetType(uint16(r.Type)) o.SetAdd(r.Add) o.SetSym(makeSymRef(r.Sym)) o.Write(w.Writer) @@ -673,9 +670,10 @@ func genFuncInfoSyms(ctxt *Link) { continue } o := goobj.FuncInfo{ - Args: uint32(fn.Args), - Locals: uint32(fn.Locals), - FuncID: objabi.FuncID(fn.FuncID), + Args: uint32(fn.Args), + Locals: uint32(fn.Locals), + FuncID: fn.FuncID, + FuncFlag: fn.FuncFlag, } pc := &fn.Pcln o.Pcsp = makeSymRef(preparePcSym(pc.Pcsp)) @@ -788,7 +786,7 @@ func (ctxt *Link) writeSymDebugNamed(s *LSym, name string) { if s.NoSplit() { fmt.Fprintf(ctxt.Bso, "nosplit ") } - if s.TopFrame() { + if s.Func() != nil && s.Func().FuncFlag&objabi.FuncFlag_TOPFRAME != 0 { fmt.Fprintf(ctxt.Bso, "topframe ") } fmt.Fprintf(ctxt.Bso, "size=%d", s.Size) diff --git a/src/cmd/internal/obj/pcln.go b/src/cmd/internal/obj/pcln.go index 67c4f9a62bdf9ac383aff0802515539ebaae6725..7af81335fb10f61be799c90f4b50d5ca4331cbae 100644 --- a/src/cmd/internal/obj/pcln.go +++ b/src/cmd/internal/obj/pcln.go @@ -37,7 +37,7 @@ func funcpctab(ctxt *Link, func_ *LSym, desc string, valfunc func(*Link, *LSym, oldval := val fn := func_.Func() if fn.Text == nil { - // Return the emtpy symbol we've built so far. + // Return the empty symbol we've built so far. return sym } diff --git a/src/cmd/internal/obj/plist.go b/src/cmd/internal/obj/plist.go index 2b096996f7a1bc10f0a2e87690168aebb2bede68..6beb4dd94cfdc61a9a3716fa6ab7b831fed2ea51 100644 --- a/src/cmd/internal/obj/plist.go +++ b/src/cmd/internal/obj/plist.go @@ -75,33 +75,60 @@ func Flushplist(ctxt *Link, plist *Plist, newprog ProgAlloc, myimportpath string newprog = ctxt.NewProg } - // Add reference to Go arguments for C or assembly functions without them. - for _, s := range text { - if !strings.HasPrefix(s.Name, "\"\".") { - continue - } - found := false - for p := s.Func().Text; p != nil; p = p.Link { - if p.As == AFUNCDATA && p.From.Type == TYPE_CONST && p.From.Offset == objabi.FUNCDATA_ArgsPointerMaps { - found = true - break + // Add reference to Go arguments for assembly functions without them. + if ctxt.IsAsm { + for _, s := range text { + if !strings.HasPrefix(s.Name, "\"\".") { + continue + } + // The current args_stackmap generation in the compiler assumes + // that the function in question is ABI0, so avoid introducing + // an args_stackmap reference if the func is not ABI0 (better to + // have no stackmap than an incorrect/lying stackmap). + if s.ABI() != ABI0 { + continue + } + foundArgMap, foundArgInfo := false, false + for p := s.Func().Text; p != nil; p = p.Link { + if p.As == AFUNCDATA && p.From.Type == TYPE_CONST { + if p.From.Offset == objabi.FUNCDATA_ArgsPointerMaps { + foundArgMap = true + } + if p.From.Offset == objabi.FUNCDATA_ArgInfo { + foundArgInfo = true + } + if foundArgMap && foundArgInfo { + break + } + } + } + if !foundArgMap { + p := Appendp(s.Func().Text, newprog) + p.As = AFUNCDATA + p.From.Type = TYPE_CONST + p.From.Offset = objabi.FUNCDATA_ArgsPointerMaps + p.To.Type = TYPE_MEM + p.To.Name = NAME_EXTERN + p.To.Sym = ctxt.LookupDerived(s, s.Name+".args_stackmap") + } + if !foundArgInfo { + p := Appendp(s.Func().Text, newprog) + p.As = AFUNCDATA + p.From.Type = TYPE_CONST + p.From.Offset = objabi.FUNCDATA_ArgInfo + p.To.Type = TYPE_MEM + p.To.Name = NAME_EXTERN + p.To.Sym = ctxt.LookupDerived(s, fmt.Sprintf("%s.arginfo%d", s.Name, s.ABI())) } - } - - if !found { - p := Appendp(s.Func().Text, newprog) - p.As = AFUNCDATA - p.From.Type = TYPE_CONST - p.From.Offset = objabi.FUNCDATA_ArgsPointerMaps - p.To.Type = TYPE_MEM - p.To.Name = NAME_EXTERN - p.To.Sym = ctxt.LookupDerived(s, s.Name+".args_stackmap") } } // Turn functions into machine code images. for _, s := range text { mkfwd(s) + if ctxt.Arch.ErrorCheck != nil { + ctxt.Arch.ErrorCheck(ctxt, s) + } linkpatch(ctxt, s, newprog) ctxt.Arch.Preprocess(ctxt, s, newprog) ctxt.Arch.Assemble(ctxt, s, newprog) @@ -128,15 +155,16 @@ func (ctxt *Link) InitTextSym(s *LSym, flag int) { ctxt.Diag("symbol %s listed multiple times", s.Name) } name := strings.Replace(s.Name, "\"\"", ctxt.Pkgpath, -1) - s.Func().FuncID = objabi.GetFuncID(name, flag&WRAPPER != 0) + s.Func().FuncID = objabi.GetFuncID(name, flag&WRAPPER != 0 || flag&ABIWRAPPER != 0) + s.Func().FuncFlag = toFuncFlag(flag) s.Set(AttrOnList, true) s.Set(AttrDuplicateOK, flag&DUPOK != 0) s.Set(AttrNoSplit, flag&NOSPLIT != 0) s.Set(AttrReflectMethod, flag&REFLECTMETHOD != 0) s.Set(AttrWrapper, flag&WRAPPER != 0) + s.Set(AttrABIWrapper, flag&ABIWRAPPER != 0) s.Set(AttrNeedCtxt, flag&NEEDCTXT != 0) s.Set(AttrNoFrame, flag&NOFRAME != 0) - s.Set(AttrTopFrame, flag&TOPFRAME != 0) s.Type = objabi.STEXT ctxt.Text = append(ctxt.Text, s) @@ -144,6 +172,14 @@ func (ctxt *Link) InitTextSym(s *LSym, flag int) { ctxt.dwarfSym(s) } +func toFuncFlag(flag int) objabi.FuncFlag { + var out objabi.FuncFlag + if flag&TOPFRAME != 0 { + out |= objabi.FuncFlag_TOPFRAME + } + return out +} + func (ctxt *Link) Globl(s *LSym, size int64, flag int) { if s.OnList() { ctxt.Diag("symbol %s listed multiple times", s.Name) diff --git a/src/cmd/internal/obj/ppc64/a.out.go b/src/cmd/internal/obj/ppc64/a.out.go index 4c97302f8376a95047711ff9b8a69bdd4280afa1..428cac528ac3b63748691c80a501e7ea86b99bf1 100644 --- a/src/cmd/internal/obj/ppc64/a.out.go +++ b/src/cmd/internal/obj/ppc64/a.out.go @@ -368,30 +368,21 @@ const ( C_LCON /* other 32 */ C_DCON /* other 64 (could subdivide further) */ C_SACON /* $n(REG) where n <= int16 */ - C_SECON - C_LACON /* $n(REG) where int16 < n <= int32 */ - C_LECON - C_DACON /* $n(REG) where int32 < n */ + C_LACON /* $n(REG) where int16 < n <= int32 */ + C_DACON /* $n(REG) where int32 < n */ C_SBRA C_LBRA C_LBRAPIC - C_SAUTO - C_LAUTO - C_SEXT - C_LEXT C_ZOREG // conjecture: either (1) register + zeroed offset, or (2) "R0" implies zero or C_REG - C_SOREG // register + signed offset - C_LOREG + C_SOREG // D/DS form memory operation + C_LOREG // 32 bit addis + D/DS-form memory operation C_FPSCR - C_MSR C_XER C_LR C_CTR C_ANY C_GOK C_ADDR - C_GOTADDR - C_TOCADDR C_TLS_LE C_TLS_IE C_TEXTSIZE diff --git a/src/cmd/internal/obj/ppc64/anames9.go b/src/cmd/internal/obj/ppc64/anames9.go index 4699a15d3bec9963077365265cf1212daddfc24a..b2632aa9ed00918155f408e0576935d3581b2eb6 100644 --- a/src/cmd/internal/obj/ppc64/anames9.go +++ b/src/cmd/internal/obj/ppc64/anames9.go @@ -20,30 +20,21 @@ var cnames9 = []string{ "LCON", "DCON", "SACON", - "SECON", "LACON", - "LECON", "DACON", "SBRA", "LBRA", "LBRAPIC", - "SAUTO", - "LAUTO", - "SEXT", - "LEXT", "ZOREG", "SOREG", "LOREG", "FPSCR", - "MSR", "XER", "LR", "CTR", "ANY", "GOK", "ADDR", - "GOTADDR", - "TOCADDR", "TLS_LE", "TLS_IE", "TEXTSIZE", diff --git a/src/cmd/internal/obj/ppc64/asm9.go b/src/cmd/internal/obj/ppc64/asm9.go index 41e263b2c073669ba007f4f3426569a1c4dc70a7..316959f62d792763b16382e13b8ba6123ee6be06 100644 --- a/src/cmd/internal/obj/ppc64/asm9.go +++ b/src/cmd/internal/obj/ppc64/asm9.go @@ -64,567 +64,464 @@ const ( type Optab struct { as obj.As // Opcode - a1 uint8 - a2 uint8 - a3 uint8 - a4 uint8 - type_ int8 // cases in asmout below. E.g., 44 = st r,(ra+rb); 45 = ld (ra+rb), r - size int8 - param int16 + a1 uint8 // p.From argument (obj.Addr). p is of type obj.Prog. + a2 uint8 // p.Reg argument (int16 Register) + a3 uint8 // p.RestArgs[0] (obj.AddrPos) + a4 uint8 // p.RestArgs[1] + a5 uint8 // p.RestARgs[2] + a6 uint8 // p.To (obj.Addr) + type_ int8 // cases in asmout below. E.g., 44 = st r,(ra+rb); 45 = ld (ra+rb), r + size int8 // Text space in bytes to lay operation } -// This optab contains a list of opcodes with the operand -// combinations that are implemented. Not all opcodes are in this -// table, but are added later in buildop by calling opset for those -// opcodes which allow the same operand combinations as an opcode -// already in the table. +// optab contains an array to be sliced of accepted operand combinations for an +// instruction. Unused arguments and fields are not explicitly enumerated, and +// should not be listed for clarity. Unused arguments and values should always +// assume the default value for the given type. // -// The type field in the Optabl identifies the case in asmout where -// the instruction word is assembled. +// optab does not list every valid ppc64 opcode, it enumerates representative +// operand combinations for a class of instruction. The variable oprange indexes +// all valid ppc64 opcodes. +// +// oprange is initialized to point a slice within optab which contains the valid +// operand combinations for a given instruction. This is initialized from buildop. +// +// Likewise, each slice of optab is dynamically sorted using the ocmp Sort interface +// to arrange entries to minimize text size of each opcode. var optab = []Optab{ - {obj.ATEXT, C_LEXT, C_NONE, C_NONE, C_TEXTSIZE, 0, 0, 0}, - {obj.ATEXT, C_LEXT, C_NONE, C_LCON, C_TEXTSIZE, 0, 0, 0}, - {obj.ATEXT, C_ADDR, C_NONE, C_NONE, C_TEXTSIZE, 0, 0, 0}, - {obj.ATEXT, C_ADDR, C_NONE, C_LCON, C_TEXTSIZE, 0, 0, 0}, + {as: obj.ATEXT, a1: C_LOREG, a6: C_TEXTSIZE, type_: 0, size: 0}, + {as: obj.ATEXT, a1: C_LOREG, a3: C_LCON, a6: C_TEXTSIZE, type_: 0, size: 0}, + {as: obj.ATEXT, a1: C_ADDR, a6: C_TEXTSIZE, type_: 0, size: 0}, + {as: obj.ATEXT, a1: C_ADDR, a3: C_LCON, a6: C_TEXTSIZE, type_: 0, size: 0}, /* move register */ - {AMOVD, C_REG, C_NONE, C_NONE, C_REG, 1, 4, 0}, - {AMOVB, C_REG, C_NONE, C_NONE, C_REG, 12, 4, 0}, - {AMOVBZ, C_REG, C_NONE, C_NONE, C_REG, 13, 4, 0}, - {AMOVW, C_REG, C_NONE, C_NONE, C_REG, 12, 4, 0}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_REG, 13, 4, 0}, - {AADD, C_REG, C_REG, C_NONE, C_REG, 2, 4, 0}, - {AADD, C_REG, C_NONE, C_NONE, C_REG, 2, 4, 0}, - {AADD, C_SCON, C_REG, C_NONE, C_REG, 4, 4, 0}, - {AADD, C_SCON, C_NONE, C_NONE, C_REG, 4, 4, 0}, - {AADD, C_ADDCON, C_REG, C_NONE, C_REG, 4, 4, 0}, - {AADD, C_ADDCON, C_NONE, C_NONE, C_REG, 4, 4, 0}, - {AADD, C_UCON, C_REG, C_NONE, C_REG, 20, 4, 0}, - {AADD, C_UCON, C_NONE, C_NONE, C_REG, 20, 4, 0}, - {AADD, C_ANDCON, C_REG, C_NONE, C_REG, 22, 8, 0}, - {AADD, C_ANDCON, C_NONE, C_NONE, C_REG, 22, 8, 0}, - {AADD, C_LCON, C_REG, C_NONE, C_REG, 22, 12, 0}, - {AADD, C_LCON, C_NONE, C_NONE, C_REG, 22, 12, 0}, - {AADDIS, C_ADDCON, C_REG, C_NONE, C_REG, 20, 4, 0}, - {AADDIS, C_ADDCON, C_NONE, C_NONE, C_REG, 20, 4, 0}, - {AADDC, C_REG, C_REG, C_NONE, C_REG, 2, 4, 0}, - {AADDC, C_REG, C_NONE, C_NONE, C_REG, 2, 4, 0}, - {AADDC, C_ADDCON, C_REG, C_NONE, C_REG, 4, 4, 0}, - {AADDC, C_ADDCON, C_NONE, C_NONE, C_REG, 4, 4, 0}, - {AADDC, C_LCON, C_REG, C_NONE, C_REG, 22, 12, 0}, - {AADDC, C_LCON, C_NONE, C_NONE, C_REG, 22, 12, 0}, - {AAND, C_REG, C_REG, C_NONE, C_REG, 6, 4, 0}, /* logical, no literal */ - {AAND, C_REG, C_NONE, C_NONE, C_REG, 6, 4, 0}, - {AANDCC, C_REG, C_REG, C_NONE, C_REG, 6, 4, 0}, - {AANDCC, C_REG, C_NONE, C_NONE, C_REG, 6, 4, 0}, - {AANDCC, C_ANDCON, C_NONE, C_NONE, C_REG, 58, 4, 0}, - {AANDCC, C_ANDCON, C_REG, C_NONE, C_REG, 58, 4, 0}, - {AANDCC, C_UCON, C_NONE, C_NONE, C_REG, 59, 4, 0}, - {AANDCC, C_UCON, C_REG, C_NONE, C_REG, 59, 4, 0}, - {AANDCC, C_ADDCON, C_NONE, C_NONE, C_REG, 23, 8, 0}, - {AANDCC, C_ADDCON, C_REG, C_NONE, C_REG, 23, 8, 0}, - {AANDCC, C_LCON, C_NONE, C_NONE, C_REG, 23, 12, 0}, - {AANDCC, C_LCON, C_REG, C_NONE, C_REG, 23, 12, 0}, - {AANDISCC, C_ANDCON, C_NONE, C_NONE, C_REG, 59, 4, 0}, - {AANDISCC, C_ANDCON, C_REG, C_NONE, C_REG, 59, 4, 0}, - {AMULLW, C_REG, C_REG, C_NONE, C_REG, 2, 4, 0}, - {AMULLW, C_REG, C_NONE, C_NONE, C_REG, 2, 4, 0}, - {AMULLW, C_ADDCON, C_REG, C_NONE, C_REG, 4, 4, 0}, - {AMULLW, C_ADDCON, C_NONE, C_NONE, C_REG, 4, 4, 0}, - {AMULLW, C_ANDCON, C_REG, C_NONE, C_REG, 4, 4, 0}, - {AMULLW, C_ANDCON, C_NONE, C_NONE, C_REG, 4, 4, 0}, - {AMULLW, C_LCON, C_REG, C_NONE, C_REG, 22, 12, 0}, - {AMULLW, C_LCON, C_NONE, C_NONE, C_REG, 22, 12, 0}, - {ASUBC, C_REG, C_REG, C_NONE, C_REG, 10, 4, 0}, - {ASUBC, C_REG, C_NONE, C_NONE, C_REG, 10, 4, 0}, - {ASUBC, C_REG, C_NONE, C_ADDCON, C_REG, 27, 4, 0}, - {ASUBC, C_REG, C_NONE, C_LCON, C_REG, 28, 12, 0}, - {AOR, C_REG, C_REG, C_NONE, C_REG, 6, 4, 0}, /* logical, literal not cc (or/xor) */ - {AOR, C_REG, C_NONE, C_NONE, C_REG, 6, 4, 0}, - {AOR, C_ANDCON, C_NONE, C_NONE, C_REG, 58, 4, 0}, - {AOR, C_ANDCON, C_REG, C_NONE, C_REG, 58, 4, 0}, - {AOR, C_UCON, C_NONE, C_NONE, C_REG, 59, 4, 0}, - {AOR, C_UCON, C_REG, C_NONE, C_REG, 59, 4, 0}, - {AOR, C_ADDCON, C_NONE, C_NONE, C_REG, 23, 8, 0}, - {AOR, C_ADDCON, C_REG, C_NONE, C_REG, 23, 8, 0}, - {AOR, C_LCON, C_NONE, C_NONE, C_REG, 23, 12, 0}, - {AOR, C_LCON, C_REG, C_NONE, C_REG, 23, 12, 0}, - {AORIS, C_ANDCON, C_NONE, C_NONE, C_REG, 59, 4, 0}, - {AORIS, C_ANDCON, C_REG, C_NONE, C_REG, 59, 4, 0}, - {ADIVW, C_REG, C_REG, C_NONE, C_REG, 2, 4, 0}, /* op r1[,r2],r3 */ - {ADIVW, C_REG, C_NONE, C_NONE, C_REG, 2, 4, 0}, - {ASUB, C_REG, C_REG, C_NONE, C_REG, 10, 4, 0}, /* op r2[,r1],r3 */ - {ASUB, C_REG, C_NONE, C_NONE, C_REG, 10, 4, 0}, - {ASLW, C_REG, C_NONE, C_NONE, C_REG, 6, 4, 0}, - {ASLW, C_REG, C_REG, C_NONE, C_REG, 6, 4, 0}, - {ASLD, C_REG, C_NONE, C_NONE, C_REG, 6, 4, 0}, - {ASLD, C_REG, C_REG, C_NONE, C_REG, 6, 4, 0}, - {ASLD, C_SCON, C_REG, C_NONE, C_REG, 25, 4, 0}, - {ASLD, C_SCON, C_NONE, C_NONE, C_REG, 25, 4, 0}, - {AEXTSWSLI, C_SCON, C_NONE, C_NONE, C_REG, 25, 4, 0}, - {AEXTSWSLI, C_SCON, C_REG, C_NONE, C_REG, 25, 4, 0}, - {ASLW, C_SCON, C_REG, C_NONE, C_REG, 57, 4, 0}, - {ASLW, C_SCON, C_NONE, C_NONE, C_REG, 57, 4, 0}, - {ASRAW, C_REG, C_NONE, C_NONE, C_REG, 6, 4, 0}, - {ASRAW, C_REG, C_REG, C_NONE, C_REG, 6, 4, 0}, - {ASRAW, C_SCON, C_REG, C_NONE, C_REG, 56, 4, 0}, - {ASRAW, C_SCON, C_NONE, C_NONE, C_REG, 56, 4, 0}, - {ASRAD, C_REG, C_NONE, C_NONE, C_REG, 6, 4, 0}, - {ASRAD, C_REG, C_REG, C_NONE, C_REG, 6, 4, 0}, - {ASRAD, C_SCON, C_REG, C_NONE, C_REG, 56, 4, 0}, - {ASRAD, C_SCON, C_NONE, C_NONE, C_REG, 56, 4, 0}, - {ARLWMI, C_SCON, C_REG, C_LCON, C_REG, 62, 4, 0}, - {ARLWMI, C_REG, C_REG, C_LCON, C_REG, 63, 4, 0}, - {ACLRLSLWI, C_SCON, C_REG, C_LCON, C_REG, 62, 4, 0}, - {ARLDMI, C_SCON, C_REG, C_LCON, C_REG, 30, 4, 0}, - {ARLDC, C_SCON, C_REG, C_LCON, C_REG, 29, 4, 0}, - {ARLDCL, C_SCON, C_REG, C_LCON, C_REG, 29, 4, 0}, - {ARLDCL, C_REG, C_REG, C_LCON, C_REG, 14, 4, 0}, - {ARLDICL, C_REG, C_REG, C_LCON, C_REG, 14, 4, 0}, - {ARLDICL, C_SCON, C_REG, C_LCON, C_REG, 14, 4, 0}, - {ARLDCL, C_REG, C_NONE, C_LCON, C_REG, 14, 4, 0}, - {AFADD, C_FREG, C_NONE, C_NONE, C_FREG, 2, 4, 0}, - {AFADD, C_FREG, C_FREG, C_NONE, C_FREG, 2, 4, 0}, - {AFABS, C_FREG, C_NONE, C_NONE, C_FREG, 33, 4, 0}, - {AFABS, C_NONE, C_NONE, C_NONE, C_FREG, 33, 4, 0}, - {AFMOVD, C_FREG, C_NONE, C_NONE, C_FREG, 33, 4, 0}, - {AFMADD, C_FREG, C_FREG, C_FREG, C_FREG, 34, 4, 0}, - {AFMUL, C_FREG, C_NONE, C_NONE, C_FREG, 32, 4, 0}, - {AFMUL, C_FREG, C_FREG, C_NONE, C_FREG, 32, 4, 0}, - - /* store, short offset */ - {AMOVD, C_REG, C_REG, C_NONE, C_ZOREG, 7, 4, REGZERO}, - {AMOVW, C_REG, C_REG, C_NONE, C_ZOREG, 7, 4, REGZERO}, - {AMOVWZ, C_REG, C_REG, C_NONE, C_ZOREG, 7, 4, REGZERO}, - {AMOVBZ, C_REG, C_REG, C_NONE, C_ZOREG, 7, 4, REGZERO}, - {AMOVBZU, C_REG, C_REG, C_NONE, C_ZOREG, 7, 4, REGZERO}, - {AMOVB, C_REG, C_REG, C_NONE, C_ZOREG, 7, 4, REGZERO}, - {AMOVBU, C_REG, C_REG, C_NONE, C_ZOREG, 7, 4, REGZERO}, - {AMOVD, C_REG, C_NONE, C_NONE, C_SEXT, 7, 4, REGSB}, - {AMOVW, C_REG, C_NONE, C_NONE, C_SEXT, 7, 4, REGSB}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_SEXT, 7, 4, REGSB}, - {AMOVBZ, C_REG, C_NONE, C_NONE, C_SEXT, 7, 4, REGSB}, - {AMOVB, C_REG, C_NONE, C_NONE, C_SEXT, 7, 4, REGSB}, - {AMOVD, C_REG, C_NONE, C_NONE, C_SAUTO, 7, 4, REGSP}, - {AMOVW, C_REG, C_NONE, C_NONE, C_SAUTO, 7, 4, REGSP}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_SAUTO, 7, 4, REGSP}, - {AMOVBZ, C_REG, C_NONE, C_NONE, C_SAUTO, 7, 4, REGSP}, - {AMOVB, C_REG, C_NONE, C_NONE, C_SAUTO, 7, 4, REGSP}, - {AMOVD, C_REG, C_NONE, C_NONE, C_SOREG, 7, 4, REGZERO}, - {AMOVW, C_REG, C_NONE, C_NONE, C_SOREG, 7, 4, REGZERO}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_SOREG, 7, 4, REGZERO}, - {AMOVBZ, C_REG, C_NONE, C_NONE, C_SOREG, 7, 4, REGZERO}, - {AMOVBZU, C_REG, C_NONE, C_NONE, C_SOREG, 7, 4, REGZERO}, - {AMOVB, C_REG, C_NONE, C_NONE, C_SOREG, 7, 4, REGZERO}, - {AMOVBU, C_REG, C_NONE, C_NONE, C_SOREG, 7, 4, REGZERO}, - - /* load, short offset */ - {AMOVD, C_ZOREG, C_REG, C_NONE, C_REG, 8, 4, REGZERO}, - {AMOVW, C_ZOREG, C_REG, C_NONE, C_REG, 8, 4, REGZERO}, - {AMOVWZ, C_ZOREG, C_REG, C_NONE, C_REG, 8, 4, REGZERO}, - {AMOVBZ, C_ZOREG, C_REG, C_NONE, C_REG, 8, 4, REGZERO}, - {AMOVBZU, C_ZOREG, C_REG, C_NONE, C_REG, 8, 4, REGZERO}, - {AMOVB, C_ZOREG, C_REG, C_NONE, C_REG, 9, 8, REGZERO}, - {AMOVBU, C_ZOREG, C_REG, C_NONE, C_REG, 9, 8, REGZERO}, - {AMOVD, C_SEXT, C_NONE, C_NONE, C_REG, 8, 4, REGSB}, - {AMOVW, C_SEXT, C_NONE, C_NONE, C_REG, 8, 4, REGSB}, - {AMOVWZ, C_SEXT, C_NONE, C_NONE, C_REG, 8, 4, REGSB}, - {AMOVBZ, C_SEXT, C_NONE, C_NONE, C_REG, 8, 4, REGSB}, - {AMOVB, C_SEXT, C_NONE, C_NONE, C_REG, 9, 8, REGSB}, - {AMOVD, C_SAUTO, C_NONE, C_NONE, C_REG, 8, 4, REGSP}, - {AMOVW, C_SAUTO, C_NONE, C_NONE, C_REG, 8, 4, REGSP}, - {AMOVWZ, C_SAUTO, C_NONE, C_NONE, C_REG, 8, 4, REGSP}, - {AMOVBZ, C_SAUTO, C_NONE, C_NONE, C_REG, 8, 4, REGSP}, - {AMOVB, C_SAUTO, C_NONE, C_NONE, C_REG, 9, 8, REGSP}, - {AMOVD, C_SOREG, C_NONE, C_NONE, C_REG, 8, 4, REGZERO}, - {AMOVW, C_SOREG, C_NONE, C_NONE, C_REG, 8, 4, REGZERO}, - {AMOVWZ, C_SOREG, C_NONE, C_NONE, C_REG, 8, 4, REGZERO}, - {AMOVBZ, C_SOREG, C_NONE, C_NONE, C_REG, 8, 4, REGZERO}, - {AMOVBZU, C_SOREG, C_NONE, C_NONE, C_REG, 8, 4, REGZERO}, - {AMOVB, C_SOREG, C_NONE, C_NONE, C_REG, 9, 8, REGZERO}, - {AMOVBU, C_SOREG, C_NONE, C_NONE, C_REG, 9, 8, REGZERO}, - - /* store, long offset */ - {AMOVD, C_REG, C_NONE, C_NONE, C_LEXT, 35, 8, REGSB}, - {AMOVW, C_REG, C_NONE, C_NONE, C_LEXT, 35, 8, REGSB}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_LEXT, 35, 8, REGSB}, - {AMOVBZ, C_REG, C_NONE, C_NONE, C_LEXT, 35, 8, REGSB}, - {AMOVB, C_REG, C_NONE, C_NONE, C_LEXT, 35, 8, REGSB}, - {AMOVD, C_REG, C_NONE, C_NONE, C_LAUTO, 35, 8, REGSP}, - {AMOVW, C_REG, C_NONE, C_NONE, C_LAUTO, 35, 8, REGSP}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_LAUTO, 35, 8, REGSP}, - {AMOVBZ, C_REG, C_NONE, C_NONE, C_LAUTO, 35, 8, REGSP}, - {AMOVB, C_REG, C_NONE, C_NONE, C_LAUTO, 35, 8, REGSP}, - {AMOVD, C_REG, C_NONE, C_NONE, C_LOREG, 35, 8, REGZERO}, - {AMOVW, C_REG, C_NONE, C_NONE, C_LOREG, 35, 8, REGZERO}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_LOREG, 35, 8, REGZERO}, - {AMOVBZ, C_REG, C_NONE, C_NONE, C_LOREG, 35, 8, REGZERO}, - {AMOVB, C_REG, C_NONE, C_NONE, C_LOREG, 35, 8, REGZERO}, - {AMOVD, C_REG, C_NONE, C_NONE, C_ADDR, 74, 8, 0}, - {AMOVW, C_REG, C_NONE, C_NONE, C_ADDR, 74, 8, 0}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_ADDR, 74, 8, 0}, - {AMOVBZ, C_REG, C_NONE, C_NONE, C_ADDR, 74, 8, 0}, - {AMOVB, C_REG, C_NONE, C_NONE, C_ADDR, 74, 8, 0}, - - /* load, long offset */ - {AMOVD, C_LEXT, C_NONE, C_NONE, C_REG, 36, 8, REGSB}, - {AMOVW, C_LEXT, C_NONE, C_NONE, C_REG, 36, 8, REGSB}, - {AMOVWZ, C_LEXT, C_NONE, C_NONE, C_REG, 36, 8, REGSB}, - {AMOVBZ, C_LEXT, C_NONE, C_NONE, C_REG, 36, 8, REGSB}, - {AMOVB, C_LEXT, C_NONE, C_NONE, C_REG, 37, 12, REGSB}, - {AMOVD, C_LAUTO, C_NONE, C_NONE, C_REG, 36, 8, REGSP}, - {AMOVW, C_LAUTO, C_NONE, C_NONE, C_REG, 36, 8, REGSP}, - {AMOVWZ, C_LAUTO, C_NONE, C_NONE, C_REG, 36, 8, REGSP}, - {AMOVBZ, C_LAUTO, C_NONE, C_NONE, C_REG, 36, 8, REGSP}, - {AMOVB, C_LAUTO, C_NONE, C_NONE, C_REG, 37, 12, REGSP}, - {AMOVD, C_LOREG, C_NONE, C_NONE, C_REG, 36, 8, REGZERO}, - {AMOVW, C_LOREG, C_NONE, C_NONE, C_REG, 36, 8, REGZERO}, - {AMOVWZ, C_LOREG, C_NONE, C_NONE, C_REG, 36, 8, REGZERO}, - {AMOVBZ, C_LOREG, C_NONE, C_NONE, C_REG, 36, 8, REGZERO}, - {AMOVB, C_LOREG, C_NONE, C_NONE, C_REG, 37, 12, REGZERO}, - {AMOVD, C_ADDR, C_NONE, C_NONE, C_REG, 75, 8, 0}, - {AMOVW, C_ADDR, C_NONE, C_NONE, C_REG, 75, 8, 0}, - {AMOVWZ, C_ADDR, C_NONE, C_NONE, C_REG, 75, 8, 0}, - {AMOVBZ, C_ADDR, C_NONE, C_NONE, C_REG, 75, 8, 0}, - {AMOVB, C_ADDR, C_NONE, C_NONE, C_REG, 76, 12, 0}, - - {AMOVD, C_TLS_LE, C_NONE, C_NONE, C_REG, 79, 4, 0}, - {AMOVD, C_TLS_IE, C_NONE, C_NONE, C_REG, 80, 8, 0}, - - {AMOVD, C_GOTADDR, C_NONE, C_NONE, C_REG, 81, 8, 0}, - {AMOVD, C_TOCADDR, C_NONE, C_NONE, C_REG, 95, 8, 0}, - - /* load constant */ - {AMOVD, C_SECON, C_NONE, C_NONE, C_REG, 3, 4, REGSB}, - {AMOVD, C_SACON, C_NONE, C_NONE, C_REG, 3, 4, REGSP}, - {AMOVD, C_LECON, C_NONE, C_NONE, C_REG, 26, 8, REGSB}, - {AMOVD, C_LACON, C_NONE, C_NONE, C_REG, 26, 8, REGSP}, - {AMOVD, C_ADDCON, C_NONE, C_NONE, C_REG, 3, 4, REGZERO}, - {AMOVD, C_ANDCON, C_NONE, C_NONE, C_REG, 3, 4, REGZERO}, - {AMOVW, C_SECON, C_NONE, C_NONE, C_REG, 3, 4, REGSB}, /* TO DO: check */ - {AMOVW, C_SACON, C_NONE, C_NONE, C_REG, 3, 4, REGSP}, - {AMOVW, C_LECON, C_NONE, C_NONE, C_REG, 26, 8, REGSB}, - {AMOVW, C_LACON, C_NONE, C_NONE, C_REG, 26, 8, REGSP}, - {AMOVW, C_ADDCON, C_NONE, C_NONE, C_REG, 3, 4, REGZERO}, - {AMOVW, C_ANDCON, C_NONE, C_NONE, C_REG, 3, 4, REGZERO}, - {AMOVWZ, C_SECON, C_NONE, C_NONE, C_REG, 3, 4, REGSB}, /* TO DO: check */ - {AMOVWZ, C_SACON, C_NONE, C_NONE, C_REG, 3, 4, REGSP}, - {AMOVWZ, C_LECON, C_NONE, C_NONE, C_REG, 26, 8, REGSB}, - {AMOVWZ, C_LACON, C_NONE, C_NONE, C_REG, 26, 8, REGSP}, - {AMOVWZ, C_ADDCON, C_NONE, C_NONE, C_REG, 3, 4, REGZERO}, - {AMOVWZ, C_ANDCON, C_NONE, C_NONE, C_REG, 3, 4, REGZERO}, - - /* load unsigned/long constants (TO DO: check) */ - {AMOVD, C_UCON, C_NONE, C_NONE, C_REG, 3, 4, REGZERO}, - {AMOVD, C_LCON, C_NONE, C_NONE, C_REG, 19, 8, 0}, - {AMOVW, C_UCON, C_NONE, C_NONE, C_REG, 3, 4, REGZERO}, - {AMOVW, C_LCON, C_NONE, C_NONE, C_REG, 19, 8, 0}, - {AMOVWZ, C_UCON, C_NONE, C_NONE, C_REG, 3, 4, REGZERO}, - {AMOVWZ, C_LCON, C_NONE, C_NONE, C_REG, 19, 8, 0}, - {AMOVHBR, C_ZOREG, C_REG, C_NONE, C_REG, 45, 4, 0}, - {AMOVHBR, C_ZOREG, C_NONE, C_NONE, C_REG, 45, 4, 0}, - {AMOVHBR, C_REG, C_REG, C_NONE, C_ZOREG, 44, 4, 0}, - {AMOVHBR, C_REG, C_NONE, C_NONE, C_ZOREG, 44, 4, 0}, - {ASYSCALL, C_NONE, C_NONE, C_NONE, C_NONE, 5, 4, 0}, - {ASYSCALL, C_REG, C_NONE, C_NONE, C_NONE, 77, 12, 0}, - {ASYSCALL, C_SCON, C_NONE, C_NONE, C_NONE, 77, 12, 0}, - {ABEQ, C_NONE, C_NONE, C_NONE, C_SBRA, 16, 4, 0}, - {ABEQ, C_CREG, C_NONE, C_NONE, C_SBRA, 16, 4, 0}, - {ABR, C_NONE, C_NONE, C_NONE, C_LBRA, 11, 4, 0}, - {ABR, C_NONE, C_NONE, C_NONE, C_LBRAPIC, 11, 8, 0}, - {ABC, C_SCON, C_REG, C_NONE, C_SBRA, 16, 4, 0}, - {ABC, C_SCON, C_REG, C_NONE, C_LBRA, 17, 4, 0}, - {ABR, C_NONE, C_NONE, C_NONE, C_LR, 18, 4, 0}, - {ABR, C_NONE, C_NONE, C_SCON, C_LR, 18, 4, 0}, - {ABR, C_NONE, C_NONE, C_NONE, C_CTR, 18, 4, 0}, - {ABR, C_REG, C_NONE, C_NONE, C_CTR, 18, 4, 0}, - {ABR, C_NONE, C_NONE, C_NONE, C_ZOREG, 15, 8, 0}, - {ABC, C_NONE, C_REG, C_NONE, C_LR, 18, 4, 0}, - {ABC, C_NONE, C_REG, C_NONE, C_CTR, 18, 4, 0}, - {ABC, C_SCON, C_REG, C_NONE, C_LR, 18, 4, 0}, - {ABC, C_SCON, C_REG, C_NONE, C_CTR, 18, 4, 0}, - {ABC, C_NONE, C_NONE, C_NONE, C_ZOREG, 15, 8, 0}, - {AFMOVD, C_SEXT, C_NONE, C_NONE, C_FREG, 8, 4, REGSB}, - {AFMOVD, C_SAUTO, C_NONE, C_NONE, C_FREG, 8, 4, REGSP}, - {AFMOVD, C_SOREG, C_NONE, C_NONE, C_FREG, 8, 4, REGZERO}, - {AFMOVD, C_LEXT, C_NONE, C_NONE, C_FREG, 36, 8, REGSB}, - {AFMOVD, C_LAUTO, C_NONE, C_NONE, C_FREG, 36, 8, REGSP}, - {AFMOVD, C_LOREG, C_NONE, C_NONE, C_FREG, 36, 8, REGZERO}, - {AFMOVD, C_ZCON, C_NONE, C_NONE, C_FREG, 24, 4, 0}, - {AFMOVD, C_ADDCON, C_NONE, C_NONE, C_FREG, 24, 8, 0}, - {AFMOVD, C_ADDR, C_NONE, C_NONE, C_FREG, 75, 8, 0}, - {AFMOVD, C_FREG, C_NONE, C_NONE, C_SEXT, 7, 4, REGSB}, - {AFMOVD, C_FREG, C_NONE, C_NONE, C_SAUTO, 7, 4, REGSP}, - {AFMOVD, C_FREG, C_NONE, C_NONE, C_SOREG, 7, 4, REGZERO}, - {AFMOVD, C_FREG, C_NONE, C_NONE, C_LEXT, 35, 8, REGSB}, - {AFMOVD, C_FREG, C_NONE, C_NONE, C_LAUTO, 35, 8, REGSP}, - {AFMOVD, C_FREG, C_NONE, C_NONE, C_LOREG, 35, 8, REGZERO}, - {AFMOVD, C_FREG, C_NONE, C_NONE, C_ADDR, 74, 8, 0}, - {AFMOVSX, C_ZOREG, C_REG, C_NONE, C_FREG, 45, 4, 0}, - {AFMOVSX, C_ZOREG, C_NONE, C_NONE, C_FREG, 45, 4, 0}, - {AFMOVSX, C_FREG, C_REG, C_NONE, C_ZOREG, 44, 4, 0}, - {AFMOVSX, C_FREG, C_NONE, C_NONE, C_ZOREG, 44, 4, 0}, - {AFMOVSZ, C_ZOREG, C_REG, C_NONE, C_FREG, 45, 4, 0}, - {AFMOVSZ, C_ZOREG, C_NONE, C_NONE, C_FREG, 45, 4, 0}, - {ASYNC, C_NONE, C_NONE, C_NONE, C_NONE, 46, 4, 0}, - {AWORD, C_LCON, C_NONE, C_NONE, C_NONE, 40, 4, 0}, - {ADWORD, C_LCON, C_NONE, C_NONE, C_NONE, 31, 8, 0}, - {ADWORD, C_DCON, C_NONE, C_NONE, C_NONE, 31, 8, 0}, - {AADDME, C_REG, C_NONE, C_NONE, C_REG, 47, 4, 0}, - {AEXTSB, C_REG, C_NONE, C_NONE, C_REG, 48, 4, 0}, - {AEXTSB, C_NONE, C_NONE, C_NONE, C_REG, 48, 4, 0}, - {AISEL, C_LCON, C_REG, C_REG, C_REG, 84, 4, 0}, - {AISEL, C_ZCON, C_REG, C_REG, C_REG, 84, 4, 0}, - {ANEG, C_REG, C_NONE, C_NONE, C_REG, 47, 4, 0}, - {ANEG, C_NONE, C_NONE, C_NONE, C_REG, 47, 4, 0}, - {AREM, C_REG, C_NONE, C_NONE, C_REG, 50, 12, 0}, - {AREM, C_REG, C_REG, C_NONE, C_REG, 50, 12, 0}, - {AREMU, C_REG, C_NONE, C_NONE, C_REG, 50, 16, 0}, - {AREMU, C_REG, C_REG, C_NONE, C_REG, 50, 16, 0}, - {AREMD, C_REG, C_NONE, C_NONE, C_REG, 51, 12, 0}, - {AREMD, C_REG, C_REG, C_NONE, C_REG, 51, 12, 0}, - {AMTFSB0, C_SCON, C_NONE, C_NONE, C_NONE, 52, 4, 0}, - {AMOVFL, C_FPSCR, C_NONE, C_NONE, C_FREG, 53, 4, 0}, - {AMOVFL, C_FREG, C_NONE, C_NONE, C_FPSCR, 64, 4, 0}, - {AMOVFL, C_FREG, C_NONE, C_LCON, C_FPSCR, 64, 4, 0}, - {AMOVFL, C_LCON, C_NONE, C_NONE, C_FPSCR, 65, 4, 0}, - {AMOVD, C_MSR, C_NONE, C_NONE, C_REG, 54, 4, 0}, /* mfmsr */ - {AMOVD, C_REG, C_NONE, C_NONE, C_MSR, 54, 4, 0}, /* mtmsrd */ - {AMOVWZ, C_REG, C_NONE, C_NONE, C_MSR, 54, 4, 0}, /* mtmsr */ - + {as: AADD, a1: C_REG, a2: C_REG, a6: C_REG, type_: 2, size: 4}, + {as: AADD, a1: C_REG, a6: C_REG, type_: 2, size: 4}, + {as: AADD, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 4, size: 4}, + {as: AADD, a1: C_SCON, a6: C_REG, type_: 4, size: 4}, + {as: AADD, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 4, size: 4}, + {as: AADD, a1: C_ADDCON, a6: C_REG, type_: 4, size: 4}, + {as: AADD, a1: C_UCON, a2: C_REG, a6: C_REG, type_: 20, size: 4}, + {as: AADD, a1: C_UCON, a6: C_REG, type_: 20, size: 4}, + {as: AADD, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 22, size: 8}, + {as: AADD, a1: C_ANDCON, a6: C_REG, type_: 22, size: 8}, + {as: AADD, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 22, size: 12}, + {as: AADD, a1: C_LCON, a6: C_REG, type_: 22, size: 12}, + {as: AADDIS, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 20, size: 4}, + {as: AADDIS, a1: C_ADDCON, a6: C_REG, type_: 20, size: 4}, + {as: AADDC, a1: C_REG, a2: C_REG, a6: C_REG, type_: 2, size: 4}, + {as: AADDC, a1: C_REG, a6: C_REG, type_: 2, size: 4}, + {as: AADDC, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 4, size: 4}, + {as: AADDC, a1: C_ADDCON, a6: C_REG, type_: 4, size: 4}, + {as: AADDC, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 22, size: 12}, + {as: AADDC, a1: C_LCON, a6: C_REG, type_: 22, size: 12}, + {as: AAND, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4}, /* logical, no literal */ + {as: AAND, a1: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: AANDCC, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: AANDCC, a1: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: AANDCC, a1: C_ANDCON, a6: C_REG, type_: 58, size: 4}, + {as: AANDCC, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 58, size: 4}, + {as: AANDCC, a1: C_UCON, a6: C_REG, type_: 59, size: 4}, + {as: AANDCC, a1: C_UCON, a2: C_REG, a6: C_REG, type_: 59, size: 4}, + {as: AANDCC, a1: C_ADDCON, a6: C_REG, type_: 23, size: 8}, + {as: AANDCC, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 23, size: 8}, + {as: AANDCC, a1: C_LCON, a6: C_REG, type_: 23, size: 12}, + {as: AANDCC, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 23, size: 12}, + {as: AANDISCC, a1: C_ANDCON, a6: C_REG, type_: 59, size: 4}, + {as: AANDISCC, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 59, size: 4}, + {as: AMULLW, a1: C_REG, a2: C_REG, a6: C_REG, type_: 2, size: 4}, + {as: AMULLW, a1: C_REG, a6: C_REG, type_: 2, size: 4}, + {as: AMULLW, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 4, size: 4}, + {as: AMULLW, a1: C_ADDCON, a6: C_REG, type_: 4, size: 4}, + {as: AMULLW, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 4, size: 4}, + {as: AMULLW, a1: C_ANDCON, a6: C_REG, type_: 4, size: 4}, + {as: AMULLW, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 22, size: 12}, + {as: AMULLW, a1: C_LCON, a6: C_REG, type_: 22, size: 12}, + {as: ASUBC, a1: C_REG, a2: C_REG, a6: C_REG, type_: 10, size: 4}, + {as: ASUBC, a1: C_REG, a6: C_REG, type_: 10, size: 4}, + {as: ASUBC, a1: C_REG, a3: C_ADDCON, a6: C_REG, type_: 27, size: 4}, + {as: ASUBC, a1: C_REG, a3: C_LCON, a6: C_REG, type_: 28, size: 12}, + {as: AOR, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4}, /* logical, literal not cc (or/xor) */ + {as: AOR, a1: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: AOR, a1: C_ANDCON, a6: C_REG, type_: 58, size: 4}, + {as: AOR, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 58, size: 4}, + {as: AOR, a1: C_UCON, a6: C_REG, type_: 59, size: 4}, + {as: AOR, a1: C_UCON, a2: C_REG, a6: C_REG, type_: 59, size: 4}, + {as: AOR, a1: C_ADDCON, a6: C_REG, type_: 23, size: 8}, + {as: AOR, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 23, size: 8}, + {as: AOR, a1: C_LCON, a6: C_REG, type_: 23, size: 12}, + {as: AOR, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 23, size: 12}, + {as: AORIS, a1: C_ANDCON, a6: C_REG, type_: 59, size: 4}, + {as: AORIS, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 59, size: 4}, + {as: ADIVW, a1: C_REG, a2: C_REG, a6: C_REG, type_: 2, size: 4}, /* op r1[,r2],r3 */ + {as: ADIVW, a1: C_REG, a6: C_REG, type_: 2, size: 4}, + {as: ASUB, a1: C_REG, a2: C_REG, a6: C_REG, type_: 10, size: 4}, /* op r2[,r1],r3 */ + {as: ASUB, a1: C_REG, a6: C_REG, type_: 10, size: 4}, + {as: ASLW, a1: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: ASLW, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: ASLD, a1: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: ASLD, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: ASLD, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 25, size: 4}, + {as: ASLD, a1: C_SCON, a6: C_REG, type_: 25, size: 4}, + {as: AEXTSWSLI, a1: C_SCON, a6: C_REG, type_: 25, size: 4}, + {as: AEXTSWSLI, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 25, size: 4}, + {as: ASLW, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 57, size: 4}, + {as: ASLW, a1: C_SCON, a6: C_REG, type_: 57, size: 4}, + {as: ASRAW, a1: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: ASRAW, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: ASRAW, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 56, size: 4}, + {as: ASRAW, a1: C_SCON, a6: C_REG, type_: 56, size: 4}, + {as: ASRAD, a1: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: ASRAD, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4}, + {as: ASRAD, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 56, size: 4}, + {as: ASRAD, a1: C_SCON, a6: C_REG, type_: 56, size: 4}, + {as: ARLWMI, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 62, size: 4}, + {as: ARLWMI, a1: C_SCON, a2: C_REG, a3: C_SCON, a4: C_SCON, a6: C_REG, type_: 102, size: 4}, + {as: ARLWMI, a1: C_REG, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 63, size: 4}, + {as: ARLWMI, a1: C_REG, a2: C_REG, a3: C_SCON, a4: C_SCON, a6: C_REG, type_: 103, size: 4}, + {as: ACLRLSLWI, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 62, size: 4}, + {as: ARLDMI, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 30, size: 4}, + {as: ARLDC, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 29, size: 4}, + {as: ARLDCL, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 29, size: 4}, + {as: ARLDCL, a1: C_REG, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 14, size: 4}, + {as: ARLDICL, a1: C_REG, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 14, size: 4}, + {as: ARLDICL, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 14, size: 4}, + {as: ARLDCL, a1: C_REG, a3: C_LCON, a6: C_REG, type_: 14, size: 4}, + {as: AFADD, a1: C_FREG, a6: C_FREG, type_: 2, size: 4}, + {as: AFADD, a1: C_FREG, a2: C_FREG, a6: C_FREG, type_: 2, size: 4}, + {as: AFABS, a1: C_FREG, a6: C_FREG, type_: 33, size: 4}, + {as: AFABS, a6: C_FREG, type_: 33, size: 4}, + {as: AFMADD, a1: C_FREG, a2: C_FREG, a3: C_FREG, a6: C_FREG, type_: 34, size: 4}, + {as: AFMUL, a1: C_FREG, a6: C_FREG, type_: 32, size: 4}, + {as: AFMUL, a1: C_FREG, a2: C_FREG, a6: C_FREG, type_: 32, size: 4}, + + {as: AMOVBU, a1: C_REG, a6: C_SOREG, type_: 7, size: 4}, + {as: AMOVBU, a1: C_SOREG, a6: C_REG, type_: 8, size: 8}, + + {as: AMOVBZU, a1: C_REG, a6: C_SOREG, type_: 7, size: 4}, + {as: AMOVBZU, a1: C_SOREG, a6: C_REG, type_: 8, size: 4}, + + {as: AMOVHBR, a1: C_REG, a6: C_ZOREG, type_: 44, size: 4}, + {as: AMOVHBR, a1: C_ZOREG, a6: C_REG, type_: 45, size: 4}, + + {as: AMOVB, a1: C_ADDR, a6: C_REG, type_: 75, size: 12}, + {as: AMOVB, a1: C_LOREG, a6: C_REG, type_: 36, size: 12}, + {as: AMOVB, a1: C_SOREG, a6: C_REG, type_: 8, size: 8}, + {as: AMOVB, a1: C_REG, a6: C_ADDR, type_: 74, size: 8}, + {as: AMOVB, a1: C_REG, a6: C_SOREG, type_: 7, size: 4}, + {as: AMOVB, a1: C_REG, a6: C_LOREG, type_: 35, size: 8}, + {as: AMOVB, a1: C_REG, a6: C_REG, type_: 13, size: 4}, + + {as: AMOVBZ, a1: C_ADDR, a6: C_REG, type_: 75, size: 8}, + {as: AMOVBZ, a1: C_LOREG, a6: C_REG, type_: 36, size: 8}, + {as: AMOVBZ, a1: C_SOREG, a6: C_REG, type_: 8, size: 4}, + {as: AMOVBZ, a1: C_REG, a6: C_ADDR, type_: 74, size: 8}, + {as: AMOVBZ, a1: C_REG, a6: C_SOREG, type_: 7, size: 4}, + {as: AMOVBZ, a1: C_REG, a6: C_LOREG, type_: 35, size: 8}, + {as: AMOVBZ, a1: C_REG, a6: C_REG, type_: 13, size: 4}, + + {as: AMOVD, a1: C_ADDCON, a6: C_REG, type_: 3, size: 4}, + {as: AMOVD, a1: C_ANDCON, a6: C_REG, type_: 3, size: 4}, + {as: AMOVD, a1: C_UCON, a6: C_REG, type_: 3, size: 4}, + {as: AMOVD, a1: C_LCON, a6: C_REG, type_: 19, size: 8}, + {as: AMOVD, a1: C_SACON, a6: C_REG, type_: 3, size: 4}, + {as: AMOVD, a1: C_LACON, a6: C_REG, type_: 26, size: 8}, + {as: AMOVD, a1: C_ADDR, a6: C_REG, type_: 75, size: 8}, + {as: AMOVD, a1: C_SOREG, a6: C_REG, type_: 8, size: 4}, + {as: AMOVD, a1: C_LOREG, a6: C_REG, type_: 36, size: 8}, + {as: AMOVD, a1: C_TLS_LE, a6: C_REG, type_: 79, size: 8}, + {as: AMOVD, a1: C_TLS_IE, a6: C_REG, type_: 80, size: 12}, + {as: AMOVD, a1: C_SPR, a6: C_REG, type_: 66, size: 4}, + {as: AMOVD, a1: C_REG, a6: C_ADDR, type_: 74, size: 8}, + {as: AMOVD, a1: C_REG, a6: C_SOREG, type_: 7, size: 4}, + {as: AMOVD, a1: C_REG, a6: C_LOREG, type_: 35, size: 8}, + {as: AMOVD, a1: C_REG, a6: C_SPR, type_: 66, size: 4}, + {as: AMOVD, a1: C_REG, a6: C_REG, type_: 13, size: 4}, + + {as: AMOVW, a1: C_ADDCON, a6: C_REG, type_: 3, size: 4}, + {as: AMOVW, a1: C_ANDCON, a6: C_REG, type_: 3, size: 4}, + {as: AMOVW, a1: C_UCON, a6: C_REG, type_: 3, size: 4}, + {as: AMOVW, a1: C_LCON, a6: C_REG, type_: 19, size: 8}, + {as: AMOVW, a1: C_SACON, a6: C_REG, type_: 3, size: 4}, + {as: AMOVW, a1: C_LACON, a6: C_REG, type_: 26, size: 8}, + {as: AMOVW, a1: C_ADDR, a6: C_REG, type_: 75, size: 8}, + {as: AMOVW, a1: C_CREG, a6: C_REG, type_: 68, size: 4}, + {as: AMOVW, a1: C_SOREG, a6: C_REG, type_: 8, size: 4}, + {as: AMOVW, a1: C_LOREG, a6: C_REG, type_: 36, size: 8}, + {as: AMOVW, a1: C_SPR, a6: C_REG, type_: 66, size: 4}, + {as: AMOVW, a1: C_REG, a6: C_ADDR, type_: 74, size: 8}, + {as: AMOVW, a1: C_REG, a6: C_CREG, type_: 69, size: 4}, + {as: AMOVW, a1: C_REG, a6: C_SOREG, type_: 7, size: 4}, + {as: AMOVW, a1: C_REG, a6: C_LOREG, type_: 35, size: 8}, + {as: AMOVW, a1: C_REG, a6: C_SPR, type_: 66, size: 4}, + {as: AMOVW, a1: C_REG, a6: C_REG, type_: 13, size: 4}, + + {as: AFMOVD, a1: C_ADDCON, a6: C_FREG, type_: 24, size: 8}, + {as: AFMOVD, a1: C_SOREG, a6: C_FREG, type_: 8, size: 4}, + {as: AFMOVD, a1: C_LOREG, a6: C_FREG, type_: 36, size: 8}, + {as: AFMOVD, a1: C_ZCON, a6: C_FREG, type_: 24, size: 4}, + {as: AFMOVD, a1: C_ADDR, a6: C_FREG, type_: 75, size: 8}, + {as: AFMOVD, a1: C_FREG, a6: C_FREG, type_: 33, size: 4}, + {as: AFMOVD, a1: C_FREG, a6: C_SOREG, type_: 7, size: 4}, + {as: AFMOVD, a1: C_FREG, a6: C_LOREG, type_: 35, size: 8}, + {as: AFMOVD, a1: C_FREG, a6: C_ADDR, type_: 74, size: 8}, + + {as: AFMOVSX, a1: C_ZOREG, a6: C_FREG, type_: 45, size: 4}, + {as: AFMOVSX, a1: C_FREG, a6: C_ZOREG, type_: 44, size: 4}, + + {as: AFMOVSZ, a1: C_ZOREG, a6: C_FREG, type_: 45, size: 4}, + + {as: AMOVFL, a1: C_CREG, a6: C_CREG, type_: 67, size: 4}, + {as: AMOVFL, a1: C_FPSCR, a6: C_CREG, type_: 73, size: 4}, + {as: AMOVFL, a1: C_FPSCR, a6: C_FREG, type_: 53, size: 4}, + {as: AMOVFL, a1: C_FREG, a3: C_LCON, a6: C_FPSCR, type_: 64, size: 4}, + {as: AMOVFL, a1: C_FREG, a6: C_FPSCR, type_: 64, size: 4}, + {as: AMOVFL, a1: C_LCON, a6: C_FPSCR, type_: 65, size: 4}, + {as: AMOVFL, a1: C_REG, a6: C_CREG, type_: 69, size: 4}, + {as: AMOVFL, a1: C_REG, a6: C_LCON, type_: 69, size: 4}, + + {as: ASYSCALL, type_: 5, size: 4}, + {as: ASYSCALL, a1: C_REG, type_: 77, size: 12}, + {as: ASYSCALL, a1: C_SCON, type_: 77, size: 12}, + {as: ABEQ, a6: C_SBRA, type_: 16, size: 4}, + {as: ABEQ, a1: C_CREG, a6: C_SBRA, type_: 16, size: 4}, + {as: ABR, a6: C_LBRA, type_: 11, size: 4}, + {as: ABR, a6: C_LBRAPIC, type_: 11, size: 8}, + {as: ABC, a1: C_SCON, a2: C_REG, a6: C_SBRA, type_: 16, size: 4}, + {as: ABC, a1: C_SCON, a2: C_REG, a6: C_LBRA, type_: 17, size: 4}, + {as: ABR, a6: C_LR, type_: 18, size: 4}, + {as: ABR, a3: C_SCON, a6: C_LR, type_: 18, size: 4}, + {as: ABR, a6: C_CTR, type_: 18, size: 4}, + {as: ABR, a1: C_REG, a6: C_CTR, type_: 18, size: 4}, + {as: ABR, a6: C_ZOREG, type_: 15, size: 8}, + {as: ABC, a2: C_REG, a6: C_LR, type_: 18, size: 4}, + {as: ABC, a2: C_REG, a6: C_CTR, type_: 18, size: 4}, + {as: ABC, a1: C_SCON, a2: C_REG, a6: C_LR, type_: 18, size: 4}, + {as: ABC, a1: C_SCON, a2: C_REG, a6: C_CTR, type_: 18, size: 4}, + {as: ABC, a6: C_ZOREG, type_: 15, size: 8}, + {as: ASYNC, type_: 46, size: 4}, + {as: AWORD, a1: C_LCON, type_: 40, size: 4}, + {as: ADWORD, a1: C_LCON, type_: 31, size: 8}, + {as: ADWORD, a1: C_DCON, type_: 31, size: 8}, + {as: ADWORD, a1: C_LACON, type_: 31, size: 8}, + {as: AADDME, a1: C_REG, a6: C_REG, type_: 47, size: 4}, + {as: AEXTSB, a1: C_REG, a6: C_REG, type_: 48, size: 4}, + {as: AEXTSB, a6: C_REG, type_: 48, size: 4}, + {as: AISEL, a1: C_LCON, a2: C_REG, a3: C_REG, a6: C_REG, type_: 84, size: 4}, + {as: AISEL, a1: C_ZCON, a2: C_REG, a3: C_REG, a6: C_REG, type_: 84, size: 4}, + {as: ANEG, a1: C_REG, a6: C_REG, type_: 47, size: 4}, + {as: ANEG, a6: C_REG, type_: 47, size: 4}, + {as: AREM, a1: C_REG, a6: C_REG, type_: 50, size: 12}, + {as: AREM, a1: C_REG, a2: C_REG, a6: C_REG, type_: 50, size: 12}, + {as: AREMU, a1: C_REG, a6: C_REG, type_: 50, size: 16}, + {as: AREMU, a1: C_REG, a2: C_REG, a6: C_REG, type_: 50, size: 16}, + {as: AREMD, a1: C_REG, a6: C_REG, type_: 51, size: 12}, + {as: AREMD, a1: C_REG, a2: C_REG, a6: C_REG, type_: 51, size: 12}, + {as: AMTFSB0, a1: C_SCON, type_: 52, size: 4}, /* Other ISA 2.05+ instructions */ - {APOPCNTD, C_REG, C_NONE, C_NONE, C_REG, 93, 4, 0}, /* population count, x-form */ - {ACMPB, C_REG, C_REG, C_NONE, C_REG, 92, 4, 0}, /* compare byte, x-form */ - {ACMPEQB, C_REG, C_REG, C_NONE, C_CREG, 92, 4, 0}, /* compare equal byte, x-form, ISA 3.0 */ - {ACMPEQB, C_REG, C_NONE, C_NONE, C_REG, 70, 4, 0}, - {AFTDIV, C_FREG, C_FREG, C_NONE, C_SCON, 92, 4, 0}, /* floating test for sw divide, x-form */ - {AFTSQRT, C_FREG, C_NONE, C_NONE, C_SCON, 93, 4, 0}, /* floating test for sw square root, x-form */ - {ACOPY, C_REG, C_NONE, C_NONE, C_REG, 92, 4, 0}, /* copy/paste facility, x-form */ - {ADARN, C_SCON, C_NONE, C_NONE, C_REG, 92, 4, 0}, /* deliver random number, x-form */ - {ALDMX, C_SOREG, C_NONE, C_NONE, C_REG, 45, 4, 0}, /* load doubleword monitored, x-form */ - {AMADDHD, C_REG, C_REG, C_REG, C_REG, 83, 4, 0}, /* multiply-add high/low doubleword, va-form */ - {AADDEX, C_REG, C_REG, C_SCON, C_REG, 94, 4, 0}, /* add extended using alternate carry, z23-form */ - {ACRAND, C_CREG, C_NONE, C_NONE, C_CREG, 2, 4, 0}, /* logical ops for condition registers xl-form */ + {as: APOPCNTD, a1: C_REG, a6: C_REG, type_: 93, size: 4}, /* population count, x-form */ + {as: ACMPB, a1: C_REG, a2: C_REG, a6: C_REG, type_: 92, size: 4}, /* compare byte, x-form */ + {as: ACMPEQB, a1: C_REG, a2: C_REG, a6: C_CREG, type_: 92, size: 4}, /* compare equal byte, x-form, ISA 3.0 */ + {as: ACMPEQB, a1: C_REG, a6: C_REG, type_: 70, size: 4}, + {as: AFTDIV, a1: C_FREG, a2: C_FREG, a6: C_SCON, type_: 92, size: 4}, /* floating test for sw divide, x-form */ + {as: AFTSQRT, a1: C_FREG, a6: C_SCON, type_: 93, size: 4}, /* floating test for sw square root, x-form */ + {as: ACOPY, a1: C_REG, a6: C_REG, type_: 92, size: 4}, /* copy/paste facility, x-form */ + {as: ADARN, a1: C_SCON, a6: C_REG, type_: 92, size: 4}, /* deliver random number, x-form */ + {as: ALDMX, a1: C_SOREG, a6: C_REG, type_: 45, size: 4}, /* load doubleword monitored, x-form */ + {as: AMADDHD, a1: C_REG, a2: C_REG, a3: C_REG, a6: C_REG, type_: 83, size: 4}, /* multiply-add high/low doubleword, va-form */ + {as: AADDEX, a1: C_REG, a2: C_REG, a3: C_SCON, a6: C_REG, type_: 94, size: 4}, /* add extended using alternate carry, z23-form */ + {as: ACRAND, a1: C_CREG, a6: C_CREG, type_: 2, size: 4}, /* logical ops for condition registers xl-form */ /* Vector instructions */ /* Vector load */ - {ALV, C_SOREG, C_NONE, C_NONE, C_VREG, 45, 4, 0}, /* vector load, x-form */ + {as: ALV, a1: C_SOREG, a6: C_VREG, type_: 45, size: 4}, /* vector load, x-form */ /* Vector store */ - {ASTV, C_VREG, C_NONE, C_NONE, C_SOREG, 44, 4, 0}, /* vector store, x-form */ + {as: ASTV, a1: C_VREG, a6: C_SOREG, type_: 44, size: 4}, /* vector store, x-form */ /* Vector logical */ - {AVAND, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector and, vx-form */ - {AVOR, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector or, vx-form */ + {as: AVAND, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector and, vx-form */ + {as: AVOR, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector or, vx-form */ /* Vector add */ - {AVADDUM, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector add unsigned modulo, vx-form */ - {AVADDCU, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector add & write carry unsigned, vx-form */ - {AVADDUS, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector add unsigned saturate, vx-form */ - {AVADDSS, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector add signed saturate, vx-form */ - {AVADDE, C_VREG, C_VREG, C_VREG, C_VREG, 83, 4, 0}, /* vector add extended, va-form */ + {as: AVADDUM, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector add unsigned modulo, vx-form */ + {as: AVADDCU, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector add & write carry unsigned, vx-form */ + {as: AVADDUS, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector add unsigned saturate, vx-form */ + {as: AVADDSS, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector add signed saturate, vx-form */ + {as: AVADDE, a1: C_VREG, a2: C_VREG, a3: C_VREG, a6: C_VREG, type_: 83, size: 4}, /* vector add extended, va-form */ /* Vector subtract */ - {AVSUBUM, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector subtract unsigned modulo, vx-form */ - {AVSUBCU, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector subtract & write carry unsigned, vx-form */ - {AVSUBUS, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector subtract unsigned saturate, vx-form */ - {AVSUBSS, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector subtract signed saturate, vx-form */ - {AVSUBE, C_VREG, C_VREG, C_VREG, C_VREG, 83, 4, 0}, /* vector subtract extended, va-form */ + {as: AVSUBUM, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector subtract unsigned modulo, vx-form */ + {as: AVSUBCU, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector subtract & write carry unsigned, vx-form */ + {as: AVSUBUS, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector subtract unsigned saturate, vx-form */ + {as: AVSUBSS, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector subtract signed saturate, vx-form */ + {as: AVSUBE, a1: C_VREG, a2: C_VREG, a3: C_VREG, a6: C_VREG, type_: 83, size: 4}, /* vector subtract extended, va-form */ /* Vector multiply */ - {AVMULESB, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 9}, /* vector multiply, vx-form */ - {AVPMSUM, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector polynomial multiply & sum, vx-form */ - {AVMSUMUDM, C_VREG, C_VREG, C_VREG, C_VREG, 83, 4, 0}, /* vector multiply-sum, va-form */ + {as: AVMULESB, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector multiply, vx-form */ + {as: AVPMSUM, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector polynomial multiply & sum, vx-form */ + {as: AVMSUMUDM, a1: C_VREG, a2: C_VREG, a3: C_VREG, a6: C_VREG, type_: 83, size: 4}, /* vector multiply-sum, va-form */ /* Vector rotate */ - {AVR, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector rotate, vx-form */ + {as: AVR, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector rotate, vx-form */ /* Vector shift */ - {AVS, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector shift, vx-form */ - {AVSA, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector shift algebraic, vx-form */ - {AVSOI, C_ANDCON, C_VREG, C_VREG, C_VREG, 83, 4, 0}, /* vector shift by octet immediate, va-form */ + {as: AVS, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector shift, vx-form */ + {as: AVSA, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector shift algebraic, vx-form */ + {as: AVSOI, a1: C_ANDCON, a2: C_VREG, a3: C_VREG, a6: C_VREG, type_: 83, size: 4}, /* vector shift by octet immediate, va-form */ /* Vector count */ - {AVCLZ, C_VREG, C_NONE, C_NONE, C_VREG, 85, 4, 0}, /* vector count leading zeros, vx-form */ - {AVPOPCNT, C_VREG, C_NONE, C_NONE, C_VREG, 85, 4, 0}, /* vector population count, vx-form */ + {as: AVCLZ, a1: C_VREG, a6: C_VREG, type_: 85, size: 4}, /* vector count leading zeros, vx-form */ + {as: AVPOPCNT, a1: C_VREG, a6: C_VREG, type_: 85, size: 4}, /* vector population count, vx-form */ /* Vector compare */ - {AVCMPEQ, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector compare equal, vc-form */ - {AVCMPGT, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector compare greater than, vc-form */ - {AVCMPNEZB, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector compare not equal, vx-form */ + {as: AVCMPEQ, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector compare equal, vc-form */ + {as: AVCMPGT, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector compare greater than, vc-form */ + {as: AVCMPNEZB, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector compare not equal, vx-form */ /* Vector merge */ - {AVMRGOW, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector merge odd word, vx-form */ + {as: AVMRGOW, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector merge odd word, vx-form */ /* Vector permute */ - {AVPERM, C_VREG, C_VREG, C_VREG, C_VREG, 83, 4, 0}, /* vector permute, va-form */ + {as: AVPERM, a1: C_VREG, a2: C_VREG, a3: C_VREG, a6: C_VREG, type_: 83, size: 4}, /* vector permute, va-form */ /* Vector bit permute */ - {AVBPERMQ, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector bit permute, vx-form */ + {as: AVBPERMQ, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector bit permute, vx-form */ /* Vector select */ - {AVSEL, C_VREG, C_VREG, C_VREG, C_VREG, 83, 4, 0}, /* vector select, va-form */ + {as: AVSEL, a1: C_VREG, a2: C_VREG, a3: C_VREG, a6: C_VREG, type_: 83, size: 4}, /* vector select, va-form */ /* Vector splat */ - {AVSPLTB, C_SCON, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector splat, vx-form */ - {AVSPLTB, C_ADDCON, C_VREG, C_NONE, C_VREG, 82, 4, 0}, - {AVSPLTISB, C_SCON, C_NONE, C_NONE, C_VREG, 82, 4, 0}, /* vector splat immediate, vx-form */ - {AVSPLTISB, C_ADDCON, C_NONE, C_NONE, C_VREG, 82, 4, 0}, + {as: AVSPLTB, a1: C_SCON, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector splat, vx-form */ + {as: AVSPLTB, a1: C_ADDCON, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, + {as: AVSPLTISB, a1: C_SCON, a6: C_VREG, type_: 82, size: 4}, /* vector splat immediate, vx-form */ + {as: AVSPLTISB, a1: C_ADDCON, a6: C_VREG, type_: 82, size: 4}, /* Vector AES */ - {AVCIPH, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector AES cipher, vx-form */ - {AVNCIPH, C_VREG, C_VREG, C_NONE, C_VREG, 82, 4, 0}, /* vector AES inverse cipher, vx-form */ - {AVSBOX, C_VREG, C_NONE, C_NONE, C_VREG, 82, 4, 0}, /* vector AES subbytes, vx-form */ + {as: AVCIPH, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector AES cipher, vx-form */ + {as: AVNCIPH, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector AES inverse cipher, vx-form */ + {as: AVSBOX, a1: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector AES subbytes, vx-form */ /* Vector SHA */ - {AVSHASIGMA, C_ANDCON, C_VREG, C_ANDCON, C_VREG, 82, 4, 0}, /* vector SHA sigma, vx-form */ + {as: AVSHASIGMA, a1: C_ANDCON, a2: C_VREG, a3: C_ANDCON, a6: C_VREG, type_: 82, size: 4}, /* vector SHA sigma, vx-form */ /* VSX vector load */ - {ALXVD2X, C_SOREG, C_NONE, C_NONE, C_VSREG, 87, 4, 0}, /* vsx vector load, xx1-form */ - {ALXV, C_SOREG, C_NONE, C_NONE, C_VSREG, 96, 4, 0}, /* vsx vector load, dq-form */ - {ALXVL, C_REG, C_REG, C_NONE, C_VSREG, 98, 4, 0}, /* vsx vector load length */ + {as: ALXVD2X, a1: C_SOREG, a6: C_VSREG, type_: 87, size: 4}, /* vsx vector load, xx1-form */ + {as: ALXV, a1: C_SOREG, a6: C_VSREG, type_: 96, size: 4}, /* vsx vector load, dq-form */ + {as: ALXVL, a1: C_REG, a2: C_REG, a6: C_VSREG, type_: 98, size: 4}, /* vsx vector load length */ /* VSX vector store */ - {ASTXVD2X, C_VSREG, C_NONE, C_NONE, C_SOREG, 86, 4, 0}, /* vsx vector store, xx1-form */ - {ASTXV, C_VSREG, C_NONE, C_NONE, C_SOREG, 97, 4, 0}, /* vsx vector store, dq-form */ - {ASTXVL, C_VSREG, C_REG, C_NONE, C_REG, 99, 4, 0}, /* vsx vector store with length x-form */ + {as: ASTXVD2X, a1: C_VSREG, a6: C_SOREG, type_: 86, size: 4}, /* vsx vector store, xx1-form */ + {as: ASTXV, a1: C_VSREG, a6: C_SOREG, type_: 97, size: 4}, /* vsx vector store, dq-form */ + {as: ASTXVL, a1: C_VSREG, a2: C_REG, a6: C_REG, type_: 99, size: 4}, /* vsx vector store with length x-form */ /* VSX scalar load */ - {ALXSDX, C_SOREG, C_NONE, C_NONE, C_VSREG, 87, 4, 0}, /* vsx scalar load, xx1-form */ + {as: ALXSDX, a1: C_SOREG, a6: C_VSREG, type_: 87, size: 4}, /* vsx scalar load, xx1-form */ /* VSX scalar store */ - {ASTXSDX, C_VSREG, C_NONE, C_NONE, C_SOREG, 86, 4, 0}, /* vsx scalar store, xx1-form */ + {as: ASTXSDX, a1: C_VSREG, a6: C_SOREG, type_: 86, size: 4}, /* vsx scalar store, xx1-form */ /* VSX scalar as integer load */ - {ALXSIWAX, C_SOREG, C_NONE, C_NONE, C_VSREG, 87, 4, 0}, /* vsx scalar as integer load, xx1-form */ + {as: ALXSIWAX, a1: C_SOREG, a6: C_VSREG, type_: 87, size: 4}, /* vsx scalar as integer load, xx1-form */ /* VSX scalar store as integer */ - {ASTXSIWX, C_VSREG, C_NONE, C_NONE, C_SOREG, 86, 4, 0}, /* vsx scalar as integer store, xx1-form */ + {as: ASTXSIWX, a1: C_VSREG, a6: C_SOREG, type_: 86, size: 4}, /* vsx scalar as integer store, xx1-form */ /* VSX move from VSR */ - {AMFVSRD, C_VSREG, C_NONE, C_NONE, C_REG, 88, 4, 0}, /* vsx move from vsr, xx1-form */ - {AMFVSRD, C_FREG, C_NONE, C_NONE, C_REG, 88, 4, 0}, - {AMFVSRD, C_VREG, C_NONE, C_NONE, C_REG, 88, 4, 0}, + {as: AMFVSRD, a1: C_VSREG, a6: C_REG, type_: 88, size: 4}, /* vsx move from vsr, xx1-form */ + {as: AMFVSRD, a1: C_FREG, a6: C_REG, type_: 88, size: 4}, + {as: AMFVSRD, a1: C_VREG, a6: C_REG, type_: 88, size: 4}, /* VSX move to VSR */ - {AMTVSRD, C_REG, C_NONE, C_NONE, C_VSREG, 88, 4, 0}, /* vsx move to vsr, xx1-form */ - {AMTVSRD, C_REG, C_REG, C_NONE, C_VSREG, 88, 4, 0}, - {AMTVSRD, C_REG, C_NONE, C_NONE, C_FREG, 88, 4, 0}, - {AMTVSRD, C_REG, C_NONE, C_NONE, C_VREG, 88, 4, 0}, + {as: AMTVSRD, a1: C_REG, a6: C_VSREG, type_: 88, size: 4}, /* vsx move to vsr, xx1-form */ + {as: AMTVSRD, a1: C_REG, a2: C_REG, a6: C_VSREG, type_: 88, size: 4}, + {as: AMTVSRD, a1: C_REG, a6: C_FREG, type_: 88, size: 4}, + {as: AMTVSRD, a1: C_REG, a6: C_VREG, type_: 88, size: 4}, /* VSX logical */ - {AXXLAND, C_VSREG, C_VSREG, C_NONE, C_VSREG, 90, 4, 0}, /* vsx and, xx3-form */ - {AXXLOR, C_VSREG, C_VSREG, C_NONE, C_VSREG, 90, 4, 0}, /* vsx or, xx3-form */ + {as: AXXLAND, a1: C_VSREG, a2: C_VSREG, a6: C_VSREG, type_: 90, size: 4}, /* vsx and, xx3-form */ + {as: AXXLOR, a1: C_VSREG, a2: C_VSREG, a6: C_VSREG, type_: 90, size: 4}, /* vsx or, xx3-form */ /* VSX select */ - {AXXSEL, C_VSREG, C_VSREG, C_VSREG, C_VSREG, 91, 4, 0}, /* vsx select, xx4-form */ + {as: AXXSEL, a1: C_VSREG, a2: C_VSREG, a3: C_VSREG, a6: C_VSREG, type_: 91, size: 4}, /* vsx select, xx4-form */ /* VSX merge */ - {AXXMRGHW, C_VSREG, C_VSREG, C_NONE, C_VSREG, 90, 4, 0}, /* vsx merge, xx3-form */ + {as: AXXMRGHW, a1: C_VSREG, a2: C_VSREG, a6: C_VSREG, type_: 90, size: 4}, /* vsx merge, xx3-form */ /* VSX splat */ - {AXXSPLTW, C_VSREG, C_NONE, C_SCON, C_VSREG, 89, 4, 0}, /* vsx splat, xx2-form */ - {AXXSPLTIB, C_SCON, C_NONE, C_NONE, C_VSREG, 100, 4, 0}, /* vsx splat, xx2-form */ + {as: AXXSPLTW, a1: C_VSREG, a3: C_SCON, a6: C_VSREG, type_: 89, size: 4}, /* vsx splat, xx2-form */ + {as: AXXSPLTIB, a1: C_SCON, a6: C_VSREG, type_: 100, size: 4}, /* vsx splat, xx2-form */ /* VSX permute */ - {AXXPERM, C_VSREG, C_VSREG, C_NONE, C_VSREG, 90, 4, 0}, /* vsx permute, xx3-form */ + {as: AXXPERM, a1: C_VSREG, a2: C_VSREG, a6: C_VSREG, type_: 90, size: 4}, /* vsx permute, xx3-form */ /* VSX shift */ - {AXXSLDWI, C_VSREG, C_VSREG, C_SCON, C_VSREG, 90, 4, 0}, /* vsx shift immediate, xx3-form */ + {as: AXXSLDWI, a1: C_VSREG, a2: C_VSREG, a3: C_SCON, a6: C_VSREG, type_: 90, size: 4}, /* vsx shift immediate, xx3-form */ /* VSX reverse bytes */ - {AXXBRQ, C_VSREG, C_NONE, C_NONE, C_VSREG, 101, 4, 0}, /* vsx reverse bytes */ + {as: AXXBRQ, a1: C_VSREG, a6: C_VSREG, type_: 101, size: 4}, /* vsx reverse bytes */ /* VSX scalar FP-FP conversion */ - {AXSCVDPSP, C_VSREG, C_NONE, C_NONE, C_VSREG, 89, 4, 0}, /* vsx scalar fp-fp conversion, xx2-form */ + {as: AXSCVDPSP, a1: C_VSREG, a6: C_VSREG, type_: 89, size: 4}, /* vsx scalar fp-fp conversion, xx2-form */ /* VSX vector FP-FP conversion */ - {AXVCVDPSP, C_VSREG, C_NONE, C_NONE, C_VSREG, 89, 4, 0}, /* vsx vector fp-fp conversion, xx2-form */ + {as: AXVCVDPSP, a1: C_VSREG, a6: C_VSREG, type_: 89, size: 4}, /* vsx vector fp-fp conversion, xx2-form */ /* VSX scalar FP-integer conversion */ - {AXSCVDPSXDS, C_VSREG, C_NONE, C_NONE, C_VSREG, 89, 4, 0}, /* vsx scalar fp-integer conversion, xx2-form */ + {as: AXSCVDPSXDS, a1: C_VSREG, a6: C_VSREG, type_: 89, size: 4}, /* vsx scalar fp-integer conversion, xx2-form */ /* VSX scalar integer-FP conversion */ - {AXSCVSXDDP, C_VSREG, C_NONE, C_NONE, C_VSREG, 89, 4, 0}, /* vsx scalar integer-fp conversion, xx2-form */ + {as: AXSCVSXDDP, a1: C_VSREG, a6: C_VSREG, type_: 89, size: 4}, /* vsx scalar integer-fp conversion, xx2-form */ /* VSX vector FP-integer conversion */ - {AXVCVDPSXDS, C_VSREG, C_NONE, C_NONE, C_VSREG, 89, 4, 0}, /* vsx vector fp-integer conversion, xx2-form */ + {as: AXVCVDPSXDS, a1: C_VSREG, a6: C_VSREG, type_: 89, size: 4}, /* vsx vector fp-integer conversion, xx2-form */ /* VSX vector integer-FP conversion */ - {AXVCVSXDDP, C_VSREG, C_NONE, C_NONE, C_VSREG, 89, 4, 0}, /* vsx vector integer-fp conversion, xx2-form */ - - /* 64-bit special registers */ - {AMOVD, C_REG, C_NONE, C_NONE, C_SPR, 66, 4, 0}, - {AMOVD, C_REG, C_NONE, C_NONE, C_LR, 66, 4, 0}, - {AMOVD, C_REG, C_NONE, C_NONE, C_CTR, 66, 4, 0}, - {AMOVD, C_REG, C_NONE, C_NONE, C_XER, 66, 4, 0}, - {AMOVD, C_SPR, C_NONE, C_NONE, C_REG, 66, 4, 0}, - {AMOVD, C_LR, C_NONE, C_NONE, C_REG, 66, 4, 0}, - {AMOVD, C_CTR, C_NONE, C_NONE, C_REG, 66, 4, 0}, - {AMOVD, C_XER, C_NONE, C_NONE, C_REG, 66, 4, 0}, - - /* 32-bit special registers (gloss over sign-extension or not?) */ - {AMOVW, C_REG, C_NONE, C_NONE, C_SPR, 66, 4, 0}, - {AMOVW, C_REG, C_NONE, C_NONE, C_CTR, 66, 4, 0}, - {AMOVW, C_REG, C_NONE, C_NONE, C_XER, 66, 4, 0}, - {AMOVW, C_SPR, C_NONE, C_NONE, C_REG, 66, 4, 0}, - {AMOVW, C_XER, C_NONE, C_NONE, C_REG, 66, 4, 0}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_SPR, 66, 4, 0}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_CTR, 66, 4, 0}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_XER, 66, 4, 0}, - {AMOVWZ, C_SPR, C_NONE, C_NONE, C_REG, 66, 4, 0}, - {AMOVWZ, C_XER, C_NONE, C_NONE, C_REG, 66, 4, 0}, - {AMOVFL, C_FPSCR, C_NONE, C_NONE, C_CREG, 73, 4, 0}, - {AMOVFL, C_CREG, C_NONE, C_NONE, C_CREG, 67, 4, 0}, - {AMOVW, C_CREG, C_NONE, C_NONE, C_REG, 68, 4, 0}, - {AMOVWZ, C_CREG, C_NONE, C_NONE, C_REG, 68, 4, 0}, - {AMOVFL, C_REG, C_NONE, C_NONE, C_LCON, 69, 4, 0}, - {AMOVFL, C_REG, C_NONE, C_NONE, C_CREG, 69, 4, 0}, - {AMOVW, C_REG, C_NONE, C_NONE, C_CREG, 69, 4, 0}, - {AMOVWZ, C_REG, C_NONE, C_NONE, C_CREG, 69, 4, 0}, - {ACMP, C_REG, C_NONE, C_NONE, C_REG, 70, 4, 0}, - {ACMP, C_REG, C_REG, C_NONE, C_REG, 70, 4, 0}, - {ACMP, C_REG, C_NONE, C_NONE, C_ADDCON, 71, 4, 0}, - {ACMP, C_REG, C_REG, C_NONE, C_ADDCON, 71, 4, 0}, - {ACMPU, C_REG, C_NONE, C_NONE, C_REG, 70, 4, 0}, - {ACMPU, C_REG, C_REG, C_NONE, C_REG, 70, 4, 0}, - {ACMPU, C_REG, C_NONE, C_NONE, C_ANDCON, 71, 4, 0}, - {ACMPU, C_REG, C_REG, C_NONE, C_ANDCON, 71, 4, 0}, - {AFCMPO, C_FREG, C_NONE, C_NONE, C_FREG, 70, 4, 0}, - {AFCMPO, C_FREG, C_REG, C_NONE, C_FREG, 70, 4, 0}, - {ATW, C_LCON, C_REG, C_NONE, C_REG, 60, 4, 0}, - {ATW, C_LCON, C_REG, C_NONE, C_ADDCON, 61, 4, 0}, - {ADCBF, C_ZOREG, C_NONE, C_NONE, C_NONE, 43, 4, 0}, - {ADCBF, C_SOREG, C_NONE, C_NONE, C_NONE, 43, 4, 0}, - {ADCBF, C_ZOREG, C_REG, C_NONE, C_SCON, 43, 4, 0}, - {ADCBF, C_SOREG, C_NONE, C_NONE, C_SCON, 43, 4, 0}, - {AECOWX, C_REG, C_REG, C_NONE, C_ZOREG, 44, 4, 0}, - {AECIWX, C_ZOREG, C_REG, C_NONE, C_REG, 45, 4, 0}, - {AECOWX, C_REG, C_NONE, C_NONE, C_ZOREG, 44, 4, 0}, - {AECIWX, C_ZOREG, C_NONE, C_NONE, C_REG, 45, 4, 0}, - {ALDAR, C_ZOREG, C_NONE, C_NONE, C_REG, 45, 4, 0}, - {ALDAR, C_ZOREG, C_NONE, C_ANDCON, C_REG, 45, 4, 0}, - {AEIEIO, C_NONE, C_NONE, C_NONE, C_NONE, 46, 4, 0}, - {ATLBIE, C_REG, C_NONE, C_NONE, C_NONE, 49, 4, 0}, - {ATLBIE, C_SCON, C_NONE, C_NONE, C_REG, 49, 4, 0}, - {ASLBMFEE, C_REG, C_NONE, C_NONE, C_REG, 55, 4, 0}, - {ASLBMTE, C_REG, C_NONE, C_NONE, C_REG, 55, 4, 0}, - {ASTSW, C_REG, C_NONE, C_NONE, C_ZOREG, 44, 4, 0}, - {ASTSW, C_REG, C_NONE, C_LCON, C_ZOREG, 41, 4, 0}, - {ALSW, C_ZOREG, C_NONE, C_NONE, C_REG, 45, 4, 0}, - {ALSW, C_ZOREG, C_NONE, C_LCON, C_REG, 42, 4, 0}, - {obj.AUNDEF, C_NONE, C_NONE, C_NONE, C_NONE, 78, 4, 0}, - {obj.APCDATA, C_LCON, C_NONE, C_NONE, C_LCON, 0, 0, 0}, - {obj.AFUNCDATA, C_SCON, C_NONE, C_NONE, C_ADDR, 0, 0, 0}, - {obj.ANOP, C_NONE, C_NONE, C_NONE, C_NONE, 0, 0, 0}, - {obj.ANOP, C_LCON, C_NONE, C_NONE, C_NONE, 0, 0, 0}, // NOP operand variations added for #40689 - {obj.ANOP, C_REG, C_NONE, C_NONE, C_NONE, 0, 0, 0}, // to preserve previous behavior - {obj.ANOP, C_FREG, C_NONE, C_NONE, C_NONE, 0, 0, 0}, - {obj.ADUFFZERO, C_NONE, C_NONE, C_NONE, C_LBRA, 11, 4, 0}, // same as ABR/ABL - {obj.ADUFFCOPY, C_NONE, C_NONE, C_NONE, C_LBRA, 11, 4, 0}, // same as ABR/ABL - {obj.APCALIGN, C_LCON, C_NONE, C_NONE, C_NONE, 0, 0, 0}, // align code - - {obj.AXXX, C_NONE, C_NONE, C_NONE, C_NONE, 0, 4, 0}, + {as: AXVCVSXDDP, a1: C_VSREG, a6: C_VSREG, type_: 89, size: 4}, /* vsx vector integer-fp conversion, xx2-form */ + + {as: ACMP, a1: C_REG, a6: C_REG, type_: 70, size: 4}, + {as: ACMP, a1: C_REG, a2: C_REG, a6: C_REG, type_: 70, size: 4}, + {as: ACMP, a1: C_REG, a6: C_ADDCON, type_: 71, size: 4}, + {as: ACMP, a1: C_REG, a2: C_REG, a6: C_ADDCON, type_: 71, size: 4}, + {as: ACMPU, a1: C_REG, a6: C_REG, type_: 70, size: 4}, + {as: ACMPU, a1: C_REG, a2: C_REG, a6: C_REG, type_: 70, size: 4}, + {as: ACMPU, a1: C_REG, a6: C_ANDCON, type_: 71, size: 4}, + {as: ACMPU, a1: C_REG, a2: C_REG, a6: C_ANDCON, type_: 71, size: 4}, + {as: AFCMPO, a1: C_FREG, a6: C_FREG, type_: 70, size: 4}, + {as: AFCMPO, a1: C_FREG, a2: C_REG, a6: C_FREG, type_: 70, size: 4}, + {as: ATW, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 60, size: 4}, + {as: ATW, a1: C_LCON, a2: C_REG, a6: C_ADDCON, type_: 61, size: 4}, + {as: ADCBF, a1: C_ZOREG, type_: 43, size: 4}, + {as: ADCBF, a1: C_SOREG, type_: 43, size: 4}, + {as: ADCBF, a1: C_ZOREG, a2: C_REG, a6: C_SCON, type_: 43, size: 4}, + {as: ADCBF, a1: C_SOREG, a6: C_SCON, type_: 43, size: 4}, + {as: AECOWX, a1: C_REG, a2: C_REG, a6: C_ZOREG, type_: 44, size: 4}, + {as: AECIWX, a1: C_ZOREG, a2: C_REG, a6: C_REG, type_: 45, size: 4}, + {as: AECOWX, a1: C_REG, a6: C_ZOREG, type_: 44, size: 4}, + {as: AECIWX, a1: C_ZOREG, a6: C_REG, type_: 45, size: 4}, + {as: ALDAR, a1: C_ZOREG, a6: C_REG, type_: 45, size: 4}, + {as: ALDAR, a1: C_ZOREG, a3: C_ANDCON, a6: C_REG, type_: 45, size: 4}, + {as: AEIEIO, type_: 46, size: 4}, + {as: ATLBIE, a1: C_REG, type_: 49, size: 4}, + {as: ATLBIE, a1: C_SCON, a6: C_REG, type_: 49, size: 4}, + {as: ASLBMFEE, a1: C_REG, a6: C_REG, type_: 55, size: 4}, + {as: ASLBMTE, a1: C_REG, a6: C_REG, type_: 55, size: 4}, + {as: ASTSW, a1: C_REG, a6: C_ZOREG, type_: 44, size: 4}, + {as: ASTSW, a1: C_REG, a3: C_LCON, a6: C_ZOREG, type_: 41, size: 4}, + {as: ALSW, a1: C_ZOREG, a6: C_REG, type_: 45, size: 4}, + {as: ALSW, a1: C_ZOREG, a3: C_LCON, a6: C_REG, type_: 42, size: 4}, + {as: obj.AUNDEF, type_: 78, size: 4}, + {as: obj.APCDATA, a1: C_LCON, a6: C_LCON, type_: 0, size: 0}, + {as: obj.AFUNCDATA, a1: C_SCON, a6: C_ADDR, type_: 0, size: 0}, + {as: obj.ANOP, type_: 0, size: 0}, + {as: obj.ANOP, a1: C_LCON, type_: 0, size: 0}, // NOP operand variations added for #40689 + {as: obj.ANOP, a1: C_REG, type_: 0, size: 0}, // to preserve previous behavior + {as: obj.ANOP, a1: C_FREG, type_: 0, size: 0}, + {as: obj.ADUFFZERO, a6: C_LBRA, type_: 11, size: 4}, // same as ABR/ABL + {as: obj.ADUFFCOPY, a6: C_LBRA, type_: 11, size: 4}, // same as ABR/ABL + {as: obj.APCALIGN, a1: C_LCON, type_: 0, size: 0}, // align code + + {as: obj.AXXX, type_: 0, size: 4}, } var oprange [ALAST & obj.AMask][]Optab @@ -674,6 +571,30 @@ func addpad(pc, a int64, ctxt *obj.Link, cursym *obj.LSym) int { return 0 } +// Get the implied register of a operand which doesn't specify one. These show up +// in handwritten asm like "MOVD R5, foosymbol" where a base register is not supplied, +// or "MOVD R5, foo+10(SP) or pseudo-register is used. The other common case is when +// generating constants in register like "MOVD $constant, Rx". +func (c *ctxt9) getimpliedreg(a *obj.Addr, p *obj.Prog) int { + switch oclass(a) { + case C_ADDCON, C_ANDCON, C_UCON, C_LCON, C_SCON, C_ZCON: + return REGZERO + case C_SACON, C_LACON: + return REGSP + case C_LOREG, C_SOREG, C_ZOREG: + switch a.Name { + case obj.NAME_EXTERN, obj.NAME_STATIC: + return REGSB + case obj.NAME_AUTO, obj.NAME_PARAM: + return REGSP + case obj.NAME_NONE: + return REGZERO + } + } + c.ctxt.Diag("failed to determine implied reg for class %v (%v)", DRconv(oclass(a)), p) + return 0 +} + func span9(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { p := cursym.Func().Text if p == nil || p.Link == nil { // handle external functions and ELF section symbols @@ -864,50 +785,42 @@ func (c *ctxt9) aclass(a *obj.Addr) int { if a.Reg == REG_FPSCR { return C_FPSCR } - if a.Reg == REG_MSR { - return C_MSR - } return C_GOK case obj.TYPE_MEM: switch a.Name { + case obj.NAME_GOTREF, obj.NAME_TOCREF: + return C_ADDR + case obj.NAME_EXTERN, obj.NAME_STATIC: + c.instoffset = a.Offset if a.Sym == nil { break - } - c.instoffset = a.Offset - if a.Sym != nil { // use relocation - if a.Sym.Type == objabi.STLSBSS { - if c.ctxt.Flag_shared { - return C_TLS_IE - } else { - return C_TLS_LE - } + } else if a.Sym.Type == objabi.STLSBSS { + // For PIC builds, use 12 byte got initial-exec TLS accesses. + if c.ctxt.Flag_shared { + return C_TLS_IE } + // Otherwise, use 8 byte local-exec TLS accesses. + return C_TLS_LE + } else { return C_ADDR } - return C_LEXT - - case obj.NAME_GOTREF: - return C_GOTADDR - - case obj.NAME_TOCREF: - return C_TOCADDR case obj.NAME_AUTO: c.instoffset = int64(c.autosize) + a.Offset if c.instoffset >= -BIG && c.instoffset < BIG { - return C_SAUTO + return C_SOREG } - return C_LAUTO + return C_LOREG case obj.NAME_PARAM: c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() if c.instoffset >= -BIG && c.instoffset < BIG { - return C_SAUTO + return C_SOREG } - return C_LAUTO + return C_LOREG case obj.NAME_NONE: c.instoffset = a.Offset @@ -958,11 +871,8 @@ func (c *ctxt9) aclass(a *obj.Addr) int { if s == nil { return C_GOK } - c.instoffset = a.Offset - - /* not sure why this barfs */ - return C_LCON + return C_LACON case obj.NAME_AUTO: c.instoffset = int64(c.autosize) + a.Offset @@ -1036,25 +946,28 @@ func (c *ctxt9) oplook(p *obj.Prog) *Optab { a1 = c.aclass(&p.From) + 1 p.From.Class = int8(a1) } - a1-- - a3 := C_NONE + 1 - if p.GetFrom3() != nil { - a3 = int(p.GetFrom3().Class) - if a3 == 0 { - a3 = c.aclass(p.GetFrom3()) + 1 - p.GetFrom3().Class = int8(a3) + + argsv := [3]int{C_NONE + 1, C_NONE + 1, C_NONE + 1} + for i, ap := range p.RestArgs { + argsv[i] = int(ap.Addr.Class) + if argsv[i] == 0 { + argsv[i] = c.aclass(&ap.Addr) + 1 + ap.Addr.Class = int8(argsv[i]) } - } - a3-- - a4 := int(p.To.Class) - if a4 == 0 { - a4 = c.aclass(&p.To) + 1 - p.To.Class = int8(a4) } + a3 := argsv[0] - 1 + a4 := argsv[1] - 1 + a5 := argsv[2] - 1 + + a6 := int(p.To.Class) + if a6 == 0 { + a6 = c.aclass(&p.To) + 1 + p.To.Class = int8(a6) + } + a6-- - a4-- a2 := C_NONE if p.Reg != 0 { if REG_R0 <= p.Reg && p.Reg <= REG_R31 { @@ -1068,20 +981,22 @@ func (c *ctxt9) oplook(p *obj.Prog) *Optab { } } - // c.ctxt.Logf("oplook %v %d %d %d %d\n", p, a1, a2, a3, a4) + // c.ctxt.Logf("oplook %v %d %d %d %d\n", p, a1, a2, a3, a4, a5, a6) ops := oprange[p.As&obj.AMask] c1 := &xcmp[a1] c3 := &xcmp[a3] c4 := &xcmp[a4] + c5 := &xcmp[a5] + c6 := &xcmp[a6] for i := range ops { op := &ops[i] - if int(op.a2) == a2 && c1[op.a1] && c3[op.a3] && c4[op.a4] { + if int(op.a2) == a2 && c1[op.a1] && c3[op.a3] && c4[op.a4] && c5[op.a5] && c6[op.a6] { p.Optab = uint16(cap(optab) - cap(ops) + i + 1) return op } } - c.ctxt.Diag("illegal combination %v %v %v %v %v", p.As, DRconv(a1), DRconv(a2), DRconv(a3), DRconv(a4)) + c.ctxt.Diag("illegal combination %v %v %v %v %v %v %v", p.As, DRconv(a1), DRconv(a2), DRconv(a3), DRconv(a4), DRconv(a5), DRconv(a6)) prasm(p) if ops == nil { ops = optab @@ -1134,13 +1049,13 @@ func cmp(a int, b int) bool { return true } - case C_LEXT: - if b == C_SEXT { + case C_SOREG: + if b == C_ZOREG { return true } - case C_LAUTO: - if b == C_SAUTO { + case C_LOREG: + if b == C_SOREG || b == C_ZOREG { return true } @@ -1149,16 +1064,6 @@ func cmp(a int, b int) bool { return r0iszero != 0 /*TypeKind(100016)*/ } - case C_LOREG: - if b == C_ZOREG || b == C_SOREG { - return true - } - - case C_SOREG: - if b == C_ZOREG { - return true - } - case C_ANY: return true } @@ -1211,6 +1116,14 @@ func (x ocmp) Less(i, j int) bool { if n != 0 { return n < 0 } + n = int(p1.a5) - int(p2.a5) + if n != 0 { + return n < 0 + } + n = int(p1.a6) - int(p2.a6) + if n != 0 { + return n < 0 + } return false } @@ -1985,6 +1898,9 @@ func buildop(ctxt *obj.Link) { case AFTSQRT: opset(AFTSQRT, r0) + case AMOVW: /* load/store/move word with sign extension; move 32-bit literals */ + opset(AMOVWZ, r0) /* Same as above, but zero extended */ + case AADD, AADDIS, AANDCC, /* and. Rb,Rs,Ra; andi. $uimm,Rs,Ra */ @@ -1992,9 +1908,6 @@ func buildop(ctxt *obj.Link) { AFMOVSX, AFMOVSZ, ALSW, - AMOVW, - /* load/store/move word with sign extension; special 32-bit move; move 32-bit literals */ - AMOVWZ, /* load/store/move word with zero extension; move 32-bit literals */ AMOVD, /* load/store/move 64-bit values, including 32-bit literals with/without sign-extension */ AMOVB, /* macro: move byte with sign extension */ AMOVBU, /* macro: move byte with sign extension & update */ @@ -2065,6 +1978,11 @@ func OPCC(o uint32, xo uint32, rc uint32) uint32 { return OPVCC(o, xo, 0, rc) } +/* Generate MD-form opcode */ +func OPMD(o, xo, rc uint32) uint32 { + return o<<26 | xo<<2 | rc&1 +} + /* the order is dest, a/s, b/imm for both arithmetic and logical operations */ func AOP_RRR(op uint32, d uint32, a uint32, b uint32) uint32 { return op | (d&31)<<21 | (a&31)<<16 | (b&31)<<11 @@ -2220,15 +2138,12 @@ const ( OP_MCRXR = 31<<26 | 512<<1 | 0<<10 | 0 OP_MFCR = 31<<26 | 19<<1 | 0<<10 | 0 OP_MFFS = 63<<26 | 583<<1 | 0<<10 | 0 - OP_MFMSR = 31<<26 | 83<<1 | 0<<10 | 0 OP_MFSPR = 31<<26 | 339<<1 | 0<<10 | 0 OP_MFSR = 31<<26 | 595<<1 | 0<<10 | 0 OP_MFSRIN = 31<<26 | 659<<1 | 0<<10 | 0 OP_MTCRF = 31<<26 | 144<<1 | 0<<10 | 0 OP_MTFSF = 63<<26 | 711<<1 | 0<<10 | 0 OP_MTFSFI = 63<<26 | 134<<1 | 0<<10 | 0 - OP_MTMSR = 31<<26 | 146<<1 | 0<<10 | 0 - OP_MTMSRD = 31<<26 | 178<<1 | 0<<10 | 0 OP_MTSPR = 31<<26 | 467<<1 | 0<<10 | 0 OP_MTSR = 31<<26 | 210<<1 | 0<<10 | 0 OP_MTSRIN = 31<<26 | 242<<1 | 0<<10 | 0 @@ -2305,7 +2220,7 @@ func (c *ctxt9) opform(insn uint32) int { // Encode instructions and create relocation for accessing s+d according to the // instruction op with source or destination (as appropriate) register reg. -func (c *ctxt9) symbolAccess(s *obj.LSym, d int64, reg int16, op uint32) (o1, o2 uint32) { +func (c *ctxt9) symbolAccess(s *obj.LSym, d int64, reg int16, op uint32, reuse bool) (o1, o2 uint32) { if c.ctxt.Headtype == objabi.Haix { // Every symbol access must be made via a TOC anchor. c.ctxt.Diag("symbolAccess called for %s", s.Name) @@ -2317,8 +2232,15 @@ func (c *ctxt9) symbolAccess(s *obj.LSym, d int64, reg int16, op uint32) (o1, o2 } else { base = REG_R0 } - o1 = AOP_IRR(OP_ADDIS, REGTMP, base, 0) - o2 = AOP_IRR(op, uint32(reg), REGTMP, 0) + // If reg can be reused when computing the symbol address, + // use it instead of REGTMP. + if !reuse { + o1 = AOP_IRR(OP_ADDIS, REGTMP, base, 0) + o2 = AOP_IRR(op, uint32(reg), REGTMP, 0) + } else { + o1 = AOP_IRR(OP_ADDIS, uint32(reg), base, 0) + o2 = AOP_IRR(op, uint32(reg), uint32(reg), 0) + } rel := obj.Addrel(c.cursym) rel.Off = int32(c.pc) rel.Siz = 8 @@ -2455,20 +2377,6 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { case 0: /* pseudo ops */ break - case 1: /* mov r1,r2 ==> OR Rs,Rs,Ra */ - if p.To.Reg == REGZERO && p.From.Type == obj.TYPE_CONST { - v := c.regoff(&p.From) - if r0iszero != 0 /*TypeKind(100016)*/ && v != 0 { - //nerrors--; - c.ctxt.Diag("literal operation on R0\n%v", p) - } - - o1 = LOP_IRR(OP_ADDI, REGZERO, REGZERO, uint32(v)) - break - } - - o1 = LOP_RRR(OP_OR, uint32(p.To.Reg), uint32(p.From.Reg), uint32(p.From.Reg)) - case 2: /* int/cr/fp op Rb,[Ra],Rd */ r := int(p.Reg) @@ -2483,7 +2391,7 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { v := int32(d) r := int(p.From.Reg) if r == 0 { - r = int(o.param) + r = c.getimpliedreg(&p.From, p) } if r0iszero != 0 /*TypeKind(100016)*/ && p.To.Reg == 0 && (r != 0 || v != 0) { c.ctxt.Diag("literal operation on R0\n%v", p) @@ -2556,25 +2464,13 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { r := int(p.To.Reg) if r == 0 { - r = int(o.param) + r = c.getimpliedreg(&p.To, p) } v := c.regoff(&p.To) if p.To.Type == obj.TYPE_MEM && p.To.Index != 0 { if v != 0 { c.ctxt.Diag("illegal indexed instruction\n%v", p) } - if c.ctxt.Flag_shared && r == REG_R13 { - rel := obj.Addrel(c.cursym) - rel.Off = int32(c.pc) - rel.Siz = 4 - // This (and the matching part in the load case - // below) are the only places in the ppc64 toolchain - // that knows the name of the tls variable. Possibly - // we could add some assembly syntax so that the name - // of the variable does not have to be assumed. - rel.Sym = c.ctxt.Lookup("runtime.tls_g") - rel.Type = objabi.R_POWER_TLS - } o1 = AOP_RRR(c.opstorex(p.As), uint32(p.From.Reg), uint32(p.To.Index), uint32(r)) } else { if int32(int16(v)) != v { @@ -2588,24 +2484,17 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { o1 = AOP_IRR(inst, uint32(p.From.Reg), uint32(r), uint32(v)) } - case 8: /* mov soreg, r ==> lbz/lhz/lwz o(r) */ + case 8: /* mov soreg, r ==> lbz/lhz/lwz o(r), lbz o(r) + extsb r,r */ r := int(p.From.Reg) if r == 0 { - r = int(o.param) + r = c.getimpliedreg(&p.From, p) } v := c.regoff(&p.From) if p.From.Type == obj.TYPE_MEM && p.From.Index != 0 { if v != 0 { c.ctxt.Diag("illegal indexed instruction\n%v", p) } - if c.ctxt.Flag_shared && r == REG_R13 { - rel := obj.Addrel(c.cursym) - rel.Off = int32(c.pc) - rel.Siz = 4 - rel.Sym = c.ctxt.Lookup("runtime.tls_g") - rel.Type = objabi.R_POWER_TLS - } o1 = AOP_RRR(c.oploadx(p.As), uint32(p.To.Reg), uint32(p.From.Index), uint32(r)) } else { if int32(int16(v)) != v { @@ -2619,21 +2508,7 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { o1 = AOP_IRR(inst, uint32(p.To.Reg), uint32(r), uint32(v)) } - case 9: /* movb soreg, r ==> lbz o(r),r2; extsb r2,r2 */ - r := int(p.From.Reg) - - if r == 0 { - r = int(o.param) - } - v := c.regoff(&p.From) - if p.From.Type == obj.TYPE_MEM && p.From.Index != 0 { - if v != 0 { - c.ctxt.Diag("illegal indexed instruction\n%v", p) - } - o1 = AOP_RRR(c.oploadx(p.As), uint32(p.To.Reg), uint32(p.From.Index), uint32(r)) - } else { - o1 = AOP_IRR(c.opload(p.As), uint32(p.To.Reg), uint32(r), uint32(v)) - } + // Sign extend MOVB operations. This is ignored for other cases (o.size == 4). o2 = LOP_RRR(OP_EXTSB, uint32(p.To.Reg), uint32(p.To.Reg), 0) case 10: /* sub Ra,[Rb],Rd => subf Rd,Ra,Rb */ @@ -2676,34 +2551,35 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { } o2 = 0x60000000 // nop, sometimes overwritten by ld r2, 24(r1) when dynamic linking - case 12: /* movb r,r (extsb); movw r,r (extsw) */ - if p.To.Reg == REGZERO && p.From.Type == obj.TYPE_CONST { - v := c.regoff(&p.From) - if r0iszero != 0 /*TypeKind(100016)*/ && v != 0 { - c.ctxt.Diag("literal operation on R0\n%v", p) - } - - o1 = LOP_IRR(OP_ADDI, REGZERO, REGZERO, uint32(v)) + case 13: /* mov[bhwd]{z,} r,r */ + // This needs to handle "MOV* $0, Rx". This shows up because $0 also + // matches C_REG if r0iszero. This happens because C_REG sorts before C_ANDCON + // TODO: fix the above behavior and cleanup this exception. + if p.From.Type == obj.TYPE_CONST { + o1 = LOP_IRR(OP_ADDI, REGZERO, uint32(p.To.Reg), 0) break } - - if p.As == AMOVW { - o1 = LOP_RRR(OP_EXTSW, uint32(p.To.Reg), uint32(p.From.Reg), 0) - } else { - o1 = LOP_RRR(OP_EXTSB, uint32(p.To.Reg), uint32(p.From.Reg), 0) + if p.To.Type == obj.TYPE_CONST { + c.ctxt.Diag("cannot move into constant 0\n%v", p) } - case 13: /* mov[bhw]z r,r; uses rlwinm not andi. to avoid changing CC */ - if p.As == AMOVBZ { + switch p.As { + case AMOVB: + o1 = LOP_RRR(OP_EXTSB, uint32(p.To.Reg), uint32(p.From.Reg), 0) + case AMOVBZ: o1 = OP_RLW(OP_RLWINM, uint32(p.To.Reg), uint32(p.From.Reg), 0, 24, 31) - } else if p.As == AMOVH { + case AMOVH: o1 = LOP_RRR(OP_EXTSH, uint32(p.To.Reg), uint32(p.From.Reg), 0) - } else if p.As == AMOVHZ { + case AMOVHZ: o1 = OP_RLW(OP_RLWINM, uint32(p.To.Reg), uint32(p.From.Reg), 0, 16, 31) - } else if p.As == AMOVWZ { + case AMOVW: + o1 = LOP_RRR(OP_EXTSW, uint32(p.To.Reg), uint32(p.From.Reg), 0) + case AMOVWZ: o1 = OP_RLW(OP_RLDIC, uint32(p.To.Reg), uint32(p.From.Reg), 0, 0, 0) | 1<<5 /* MB=32 */ - } else { - c.ctxt.Diag("internal: bad mov[bhw]z\n%v", p) + case AMOVD: + o1 = LOP_RRR(OP_OR, uint32(p.To.Reg), uint32(p.From.Reg), uint32(p.From.Reg)) + default: + c.ctxt.Diag("internal: bad register move/truncation\n%v", p) } case 14: /* rldc[lr] Rb,Rs,$mask,Ra -- left, right give different masks */ @@ -2883,13 +2759,8 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { case 19: /* mov $lcon,r ==> cau+or */ d := c.vregoff(&p.From) - - if p.From.Sym == nil { - o1 = loadu32(int(p.To.Reg), d) - o2 = LOP_IRR(OP_ORI, uint32(p.To.Reg), uint32(p.To.Reg), uint32(int32(d))) - } else { - o1, o2 = c.symbolAccess(p.From.Sym, d, p.To.Reg, OP_ADDI) - } + o1 = loadu32(int(p.To.Reg), d) + o2 = LOP_IRR(OP_ORI, uint32(p.To.Reg), uint32(p.To.Reg), uint32(int32(d))) case 20: /* add $ucon,,r | addis $addcon,r,r */ v := c.regoff(&p.From) @@ -3007,16 +2878,21 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { } case 26: /* mov $lsext/auto/oreg,,r2 ==> addis+addi */ - if p.To.Reg == REGTMP { - c.ctxt.Diag("can't synthesize large constant\n%v", p) - } - v := c.regoff(&p.From) + v := c.vregoff(&p.From) r := int(p.From.Reg) - if r == 0 { - r = int(o.param) + + switch p.From.Name { + case obj.NAME_EXTERN, obj.NAME_STATIC: + // Load a 32 bit constant, or relocation depending on if a symbol is attached + o1, o2 = c.symbolAccess(p.From.Sym, v, p.To.Reg, OP_ADDI, true) + default: + if r == 0 { + r = c.getimpliedreg(&p.From, p) + } + // Add a 32 bit offset to a register. + o1 = AOP_IRR(OP_ADDIS, uint32(p.To.Reg), uint32(r), uint32(high16adjusted(int32(v)))) + o2 = AOP_IRR(OP_ADDI, uint32(p.To.Reg), uint32(p.To.Reg), uint32(v)) } - o1 = AOP_IRR(OP_ADDIS, REGTMP, uint32(r), uint32(high16adjusted(v))) - o2 = AOP_IRR(OP_ADDI, uint32(p.To.Reg), REGTMP, uint32(v)) case 27: /* subc ra,$simm,rd => subfic rd,ra,$simm */ v := c.regoff(p.GetFrom3()) @@ -3157,7 +3033,7 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { r := int(p.To.Reg) if r == 0 { - r = int(o.param) + r = c.getimpliedreg(&p.To, p) } // Offsets in DS form stores must be a multiple of 4 inst := c.opstore(p.As) @@ -3167,25 +3043,17 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { o1 = AOP_IRR(OP_ADDIS, REGTMP, uint32(r), uint32(high16adjusted(v))) o2 = AOP_IRR(inst, uint32(p.From.Reg), REGTMP, uint32(v)) - case 36: /* mov bz/h/hz lext/lauto/lreg,r ==> lbz/lha/lhz etc */ + case 36: /* mov b/bz/h/hz lext/lauto/lreg,r ==> lbz+extsb/lbz/lha/lhz etc */ v := c.regoff(&p.From) r := int(p.From.Reg) if r == 0 { - r = int(o.param) + r = c.getimpliedreg(&p.From, p) } - o1 = AOP_IRR(OP_ADDIS, REGTMP, uint32(r), uint32(high16adjusted(v))) - o2 = AOP_IRR(c.opload(p.As), uint32(p.To.Reg), REGTMP, uint32(v)) - - case 37: /* movb lext/lauto/lreg,r ==> lbz o(reg),r; extsb r */ - v := c.regoff(&p.From) + o1 = AOP_IRR(OP_ADDIS, uint32(p.To.Reg), uint32(r), uint32(high16adjusted(v))) + o2 = AOP_IRR(c.opload(p.As), uint32(p.To.Reg), uint32(p.To.Reg), uint32(v)) - r := int(p.From.Reg) - if r == 0 { - r = int(o.param) - } - o1 = AOP_IRR(OP_ADDIS, REGTMP, uint32(r), uint32(high16adjusted(v))) - o2 = AOP_IRR(c.opload(p.As), uint32(p.To.Reg), REGTMP, uint32(v)) + // Sign extend MOVB if needed o3 = LOP_RRR(OP_EXTSB, uint32(p.To.Reg), uint32(p.To.Reg), 0) case 40: /* word */ @@ -3305,17 +3173,6 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { case 53: /* mffsX ,fr1 */ o1 = AOP_RRR(OP_MFFS, uint32(p.To.Reg), 0, 0) - case 54: /* mov msr,r1; mov r1, msr*/ - if oclass(&p.From) == C_REG { - if p.As == AMOVD { - o1 = AOP_RRR(OP_MTMSRD, uint32(p.From.Reg), 0, 0) - } else { - o1 = AOP_RRR(OP_MTMSR, uint32(p.From.Reg), 0, 0) - } - } else { - o1 = AOP_RRR(OP_MFMSR, uint32(p.To.Reg), 0, 0) - } - case 55: /* op Rb, Rd */ o1 = AOP_RRR(c.oprrr(p.As), uint32(p.To.Reg), 0, uint32(p.From.Reg)) @@ -3554,41 +3411,51 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { if c.opform(inst) == DS_FORM && v&0x3 != 0 { log.Fatalf("invalid offset for DS form load/store %v", p) } - o1, o2 = c.symbolAccess(p.To.Sym, v, p.From.Reg, inst) + // Can't reuse base for store instructions. + o1, o2 = c.symbolAccess(p.To.Sym, v, p.From.Reg, inst, false) - //if(dlm) reloc(&p->to, p->pc, 1); + case 75: // 32 bit offset symbol loads (got/toc/addr) + v := p.From.Offset - case 75: - v := c.vregoff(&p.From) // Offsets in DS form loads must be a multiple of 4 inst := c.opload(p.As) if c.opform(inst) == DS_FORM && v&0x3 != 0 { log.Fatalf("invalid offset for DS form load/store %v", p) } - o1, o2 = c.symbolAccess(p.From.Sym, v, p.To.Reg, inst) - - //if(dlm) reloc(&p->from, p->pc, 1); - - case 76: - v := c.vregoff(&p.From) - // Offsets in DS form loads must be a multiple of 4 - inst := c.opload(p.As) - if c.opform(inst) == DS_FORM && v&0x3 != 0 { - log.Fatalf("invalid offset for DS form load/store %v", p) + switch p.From.Name { + case obj.NAME_GOTREF, obj.NAME_TOCREF: + if v != 0 { + c.ctxt.Diag("invalid offset for GOT/TOC access %v", p) + } + o1 = AOP_IRR(OP_ADDIS, uint32(p.To.Reg), REG_R2, 0) + o2 = AOP_IRR(inst, uint32(p.To.Reg), uint32(p.To.Reg), 0) + rel := obj.Addrel(c.cursym) + rel.Off = int32(c.pc) + rel.Siz = 8 + rel.Sym = p.From.Sym + switch p.From.Name { + case obj.NAME_GOTREF: + rel.Type = objabi.R_ADDRPOWER_GOT + case obj.NAME_TOCREF: + rel.Type = objabi.R_ADDRPOWER_TOCREL_DS + } + default: + reuseBaseReg := p.As != AFMOVD && p.As != AFMOVS + // Reuse To.Reg as base register if not FP move. + o1, o2 = c.symbolAccess(p.From.Sym, v, p.To.Reg, inst, reuseBaseReg) } - o1, o2 = c.symbolAccess(p.From.Sym, v, p.To.Reg, inst) - o3 = LOP_RRR(OP_EXTSB, uint32(p.To.Reg), uint32(p.To.Reg), 0) - //if(dlm) reloc(&p->from, p->pc, 1); + o3 = LOP_RRR(OP_EXTSB, uint32(p.To.Reg), uint32(p.To.Reg), 0) case 79: if p.From.Offset != 0 { c.ctxt.Diag("invalid offset against tls var %v", p) } - o1 = AOP_IRR(OP_ADDI, uint32(p.To.Reg), REGZERO, 0) + o1 = AOP_IRR(OP_ADDIS, uint32(p.To.Reg), REG_R13, 0) + o2 = AOP_IRR(OP_ADDI, uint32(p.To.Reg), uint32(p.To.Reg), 0) rel := obj.Addrel(c.cursym) rel.Off = int32(c.pc) - rel.Siz = 4 + rel.Siz = 8 rel.Sym = p.From.Sym rel.Type = objabi.R_POWER_TLS_LE @@ -3598,25 +3465,18 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { } o1 = AOP_IRR(OP_ADDIS, uint32(p.To.Reg), REG_R2, 0) o2 = AOP_IRR(c.opload(AMOVD), uint32(p.To.Reg), uint32(p.To.Reg), 0) + o3 = AOP_RRR(OP_ADD, uint32(p.To.Reg), uint32(p.To.Reg), REG_R13) rel := obj.Addrel(c.cursym) rel.Off = int32(c.pc) rel.Siz = 8 rel.Sym = p.From.Sym rel.Type = objabi.R_POWER_TLS_IE - - case 81: - v := c.vregoff(&p.To) - if v != 0 { - c.ctxt.Diag("invalid offset against GOT slot %v", p) - } - - o1 = AOP_IRR(OP_ADDIS, uint32(p.To.Reg), REG_R2, 0) - o2 = AOP_IRR(c.opload(AMOVD), uint32(p.To.Reg), uint32(p.To.Reg), 0) - rel := obj.Addrel(c.cursym) - rel.Off = int32(c.pc) - rel.Siz = 8 + rel = obj.Addrel(c.cursym) + rel.Off = int32(c.pc) + 8 + rel.Siz = 4 rel.Sym = p.From.Sym - rel.Type = objabi.R_ADDRPOWER_GOT + rel.Type = objabi.R_POWER_TLS + case 82: /* vector instructions, VX-form and VC-form */ if p.From.Type == obj.TYPE_REG { /* reg reg none OR reg reg reg */ @@ -3785,26 +3645,6 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { cy := int(c.regoff(p.GetFrom3())) o1 = AOP_Z23I(c.oprrr(p.As), uint32(p.To.Reg), uint32(p.From.Reg), uint32(p.Reg), uint32(cy)) - case 95: /* Retrieve TOC relative symbol */ - /* This code is for AIX only */ - v := c.vregoff(&p.From) - if v != 0 { - c.ctxt.Diag("invalid offset against TOC slot %v", p) - } - - inst := c.opload(p.As) - if c.opform(inst) != DS_FORM { - c.ctxt.Diag("invalid form for a TOC access in %v", p) - } - - o1 = AOP_IRR(OP_ADDIS, uint32(p.To.Reg), REG_R2, 0) - o2 = AOP_IRR(inst, uint32(p.To.Reg), uint32(p.To.Reg), 0) - rel := obj.Addrel(c.cursym) - rel.Off = int32(c.pc) - rel.Siz = 8 - rel.Sym = p.From.Sym - rel.Type = objabi.R_ADDRPOWER_TOCREL_DS - case 96: /* VSX load, DQ-form */ /* reg imm reg */ /* operand order: (RA)(DQ), XT */ @@ -3840,6 +3680,17 @@ func (c *ctxt9) asmout(p *obj.Prog, o *Optab, out []uint32) { } case 101: o1 = AOP_XX2(c.oprrr(p.As), uint32(p.To.Reg), uint32(0), uint32(p.From.Reg)) + + case 102: /* RLWMI $sh,rs,$mb,$me,rt (M-form opcode)*/ + mb := uint32(c.regoff(&p.RestArgs[0].Addr)) + me := uint32(c.regoff(&p.RestArgs[1].Addr)) + sh := uint32(c.regoff(&p.From)) + o1 = OP_RLW(c.opirr(p.As), uint32(p.To.Reg), uint32(p.Reg), sh, mb, me) + + case 103: /* RLWMI rb,rs,$mb,$me,rt (M-form opcode)*/ + mb := uint32(c.regoff(&p.RestArgs[0].Addr)) + me := uint32(c.regoff(&p.RestArgs[1].Addr)) + o1 = OP_RLW(c.oprrr(p.As), uint32(p.To.Reg), uint32(p.Reg), uint32(p.From.Reg), mb, me) } out[0] = o1 @@ -4336,14 +4187,14 @@ func (c *ctxt9) oprrr(a obj.As) uint32 { case ARLDICLCC: return OPVCC(30, 0, 0, 1) case ARLDICR: - return OPVCC(30, 0, 0, 0) | 2<<1 // rldicr + return OPMD(30, 1, 0) // rldicr case ARLDICRCC: - return OPVCC(30, 0, 0, 1) | 2<<1 // rldicr. + return OPMD(30, 1, 1) // rldicr. case ARLDIC: - return OPVCC(30, 0, 0, 0) | 4<<1 // rldic + return OPMD(30, 2, 0) // rldic case ARLDICCC: - return OPVCC(30, 0, 0, 1) | 4<<1 // rldic. + return OPMD(30, 2, 1) // rldic. case ASYSCALL: return OPVCC(17, 1, 0, 0) @@ -5001,30 +4852,30 @@ func (c *ctxt9) opirr(a obj.As) uint32 { case ARLWMICC: return OPVCC(20, 0, 0, 1) case ARLDMI: - return OPVCC(30, 0, 0, 0) | 3<<2 /* rldimi */ + return OPMD(30, 3, 0) /* rldimi */ case ARLDMICC: - return OPVCC(30, 0, 0, 1) | 3<<2 + return OPMD(30, 3, 1) /* rldimi. */ case ARLDIMI: - return OPVCC(30, 0, 0, 0) | 3<<2 /* rldimi */ + return OPMD(30, 3, 0) /* rldimi */ case ARLDIMICC: - return OPVCC(30, 0, 0, 1) | 3<<2 + return OPMD(30, 3, 1) /* rldimi. */ case ARLWNM: return OPVCC(21, 0, 0, 0) /* rlwinm */ case ARLWNMCC: return OPVCC(21, 0, 0, 1) case ARLDCL: - return OPVCC(30, 0, 0, 0) /* rldicl */ + return OPMD(30, 0, 0) /* rldicl */ case ARLDCLCC: - return OPVCC(30, 0, 0, 1) + return OPMD(30, 0, 1) /* rldicl. */ case ARLDCR: - return OPVCC(30, 1, 0, 0) /* rldicr */ + return OPMD(30, 1, 0) /* rldicr */ case ARLDCRCC: - return OPVCC(30, 1, 0, 1) + return OPMD(30, 1, 1) /* rldicr. */ case ARLDC: - return OPVCC(30, 0, 0, 0) | 2<<2 + return OPMD(30, 2, 0) /* rldic */ case ARLDCCC: - return OPVCC(30, 0, 0, 1) | 2<<2 + return OPMD(30, 2, 1) /* rldic. */ case ASRAW: return OPVCC(31, 824, 0, 0) diff --git a/src/cmd/internal/obj/ppc64/obj9.go b/src/cmd/internal/obj/ppc64/obj9.go index fddf552156cfbbe831758a2fdb26b613f2027ab4..c2722b0afb05de401ecb5fc1ba2af29ac69c8465 100644 --- a/src/cmd/internal/obj/ppc64/obj9.go +++ b/src/cmd/internal/obj/ppc64/obj9.go @@ -34,6 +34,7 @@ import ( "cmd/internal/objabi" "cmd/internal/src" "cmd/internal/sys" + "log" ) func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { @@ -984,6 +985,21 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { p.From.Reg = REGSP } } + + if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.Spadj == 0 && p.As != ACMPU { + f := c.cursym.Func() + if f.FuncFlag&objabi.FuncFlag_SPWRITE == 0 { + c.cursym.Func().FuncFlag |= objabi.FuncFlag_SPWRITE + if ctxt.Debugvlog || !ctxt.IsAsm { + ctxt.Logf("auto-SPWRITE: %s %v\n", c.cursym.Name, p) + if !ctxt.IsAsm { + ctxt.Diag("invalid auto-SPWRITE in non-assembly") + ctxt.DiagFlush() + log.Fatalf("bad SPWRITE") + } + } + } + } } } @@ -1065,80 +1081,65 @@ func (c *ctxt9) stacksplit(p *obj.Prog, framesize int32) *obj.Prog { p.From.Reg = REG_R3 p.To.Type = obj.TYPE_REG p.To.Reg = REGSP - } else if framesize <= objabi.StackBig { + } else { // large stack: SP-framesize < stackguard-StackSmall - // ADD $-(framesize-StackSmall), SP, R4 - // CMP stackguard, R4 - p = obj.Appendp(p, c.newprog) - - p.As = AADD - p.From.Type = obj.TYPE_CONST - p.From.Offset = -(int64(framesize) - objabi.StackSmall) - p.Reg = REGSP - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R4 + offset := int64(framesize) - objabi.StackSmall + if framesize > objabi.StackBig { + // Such a large stack we need to protect against underflow. + // The runtime guarantees SP > objabi.StackBig, but + // framesize is large enough that SP-framesize may + // underflow, causing a direct comparison with the + // stack guard to incorrectly succeed. We explicitly + // guard against underflow. + // + // CMPU SP, $(framesize-StackSmall) + // BLT label-of-call-to-morestack + if offset <= 0xffff { + p = obj.Appendp(p, c.newprog) + p.As = ACMPU + p.From.Type = obj.TYPE_REG + p.From.Reg = REGSP + p.To.Type = obj.TYPE_CONST + p.To.Offset = offset + } else { + // Constant is too big for CMPU. + p = obj.Appendp(p, c.newprog) + p.As = AMOVD + p.From.Type = obj.TYPE_CONST + p.From.Offset = offset + p.To.Type = obj.TYPE_REG + p.To.Reg = REG_R4 - p = obj.Appendp(p, c.newprog) - p.As = ACMPU - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R3 - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R4 - } else { - // Such a large stack we need to protect against wraparound. - // If SP is close to zero: - // SP-stackguard+StackGuard <= framesize + (StackGuard-StackSmall) - // The +StackGuard on both sides is required to keep the left side positive: - // SP is allowed to be slightly below stackguard. See stack.h. - // - // Preemption sets stackguard to StackPreempt, a very large value. - // That breaks the math above, so we have to check for that explicitly. - // // stackguard is R3 - // CMP R3, $StackPreempt - // BEQ label-of-call-to-morestack - // ADD $StackGuard, SP, R4 - // SUB R3, R4 - // MOVD $(framesize+(StackGuard-StackSmall)), R31 - // CMPU R31, R4 - p = obj.Appendp(p, c.newprog) + p = obj.Appendp(p, c.newprog) + p.As = ACMPU + p.From.Type = obj.TYPE_REG + p.From.Reg = REGSP + p.To.Type = obj.TYPE_REG + p.To.Reg = REG_R4 + } - p.As = ACMP - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R3 - p.To.Type = obj.TYPE_CONST - p.To.Offset = objabi.StackPreempt + p = obj.Appendp(p, c.newprog) + q = p + p.As = ABLT + p.To.Type = obj.TYPE_BRANCH + } + // Check against the stack guard. We've ensured this won't underflow. + // ADD $-(framesize-StackSmall), SP, R4 + // CMPU stackguard, R4 p = obj.Appendp(p, c.newprog) - q = p - p.As = ABEQ - p.To.Type = obj.TYPE_BRANCH - p = obj.Appendp(p, c.newprog) p.As = AADD p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(objabi.StackGuard) + p.From.Offset = -offset p.Reg = REGSP p.To.Type = obj.TYPE_REG p.To.Reg = REG_R4 - p = obj.Appendp(p, c.newprog) - p.As = ASUB - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R3 - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R4 - - p = obj.Appendp(p, c.newprog) - p.As = AMOVD - p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(framesize) + int64(objabi.StackGuard) - objabi.StackSmall - p.To.Type = obj.TYPE_REG - p.To.Reg = REGTMP - p = obj.Appendp(p, c.newprog) p.As = ACMPU p.From.Type = obj.TYPE_REG - p.From.Reg = REGTMP + p.From.Reg = REG_R3 p.To.Type = obj.TYPE_REG p.To.Reg = REG_R4 } diff --git a/src/cmd/internal/obj/riscv/obj.go b/src/cmd/internal/obj/riscv/obj.go index 9257a6453aef81eb1bad7c46197d565a3160743e..a305edab4b39348dcb803815e6ab842d28d724a7 100644 --- a/src/cmd/internal/obj/riscv/obj.go +++ b/src/cmd/internal/obj/riscv/obj.go @@ -25,6 +25,7 @@ import ( "cmd/internal/objabi" "cmd/internal/sys" "fmt" + "log" ) func buildop(ctxt *obj.Link) {} @@ -119,7 +120,7 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { switch p.To.Name { case obj.NAME_NONE: p.As = AJALR - case obj.NAME_EXTERN: + case obj.NAME_EXTERN, obj.NAME_STATIC: // Handled in preprocess. default: ctxt.Diag("unsupported name %d for %v", p.To.Name, p) @@ -150,6 +151,15 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { case ASBREAK: // SBREAK is the old name for EBREAK. p.As = AEBREAK + + case AMOV: + // Put >32-bit constants in memory and load them. + if p.From.Type == obj.TYPE_CONST && p.From.Name == obj.NAME_NONE && p.From.Reg == 0 && int64(int32(p.From.Offset)) != p.From.Offset { + p.From.Type = obj.TYPE_MEM + p.From.Sym = ctxt.Int64Sym(p.From.Offset) + p.From.Name = obj.NAME_EXTERN + p.From.Offset = 0 + } } } @@ -267,7 +277,7 @@ func rewriteMOV(ctxt *obj.Link, newprog obj.ProgAlloc, p *obj.Prog) { p.As = movToStore(p.As) p.To.Reg = addrToReg(p.To) - case obj.NAME_EXTERN: + case obj.NAME_EXTERN, obj.NAME_STATIC: // AUIPC $off_hi, TMP // S $off_lo, TMP, R as := p.As @@ -301,7 +311,10 @@ func rewriteMOV(ctxt *obj.Link, newprog obj.ProgAlloc, p *obj.Prog) { // LUI top20bits(c), R // ADD bottom12bits(c), R, R if p.As != AMOV { - ctxt.Diag("unsupported constant load at %v", p) + ctxt.Diag("%v: unsupported constant load", p) + } + if p.To.Type != obj.TYPE_REG { + ctxt.Diag("%v: constant load must target register", p) } off := p.From.Offset to := p.To @@ -666,7 +679,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { switch p.To.Type { case obj.TYPE_MEM: switch p.To.Name { - case obj.NAME_EXTERN: + case obj.NAME_EXTERN, obj.NAME_STATIC: // JMP to symbol. jalrToSym(ctxt, p, newprog, REG_ZERO) } @@ -716,6 +729,21 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { p.Spadj = int32(-p.From.Offset) } } + + if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.Spadj == 0 { + f := cursym.Func() + if f.FuncFlag&objabi.FuncFlag_SPWRITE == 0 { + f.FuncFlag |= objabi.FuncFlag_SPWRITE + if ctxt.Debugvlog || !ctxt.IsAsm { + ctxt.Logf("auto-SPWRITE: %s %v\n", cursym.Name, p) + if !ctxt.IsAsm { + ctxt.Diag("invalid auto-SPWRITE in non-assembly") + ctxt.DiagFlush() + log.Fatalf("bad SPWRITE") + } + } + } + } } // Rewrite MOV pseudo-instructions. This cannot be done in @@ -956,8 +984,9 @@ func stacksplit(ctxt *obj.Link, p *obj.Prog, cursym *obj.LSym, newprog obj.ProgA var to_done, to_more *obj.Prog if framesize <= objabi.StackSmall { - // small stack: SP < stackguard - // BLTU SP, stackguard, done + // small stack + // // if SP > stackguard { goto done } + // BLTU stackguard, SP, done p = obj.Appendp(p, newprog) p.As = ABLTU p.From.Type = obj.TYPE_REG @@ -965,80 +994,48 @@ func stacksplit(ctxt *obj.Link, p *obj.Prog, cursym *obj.LSym, newprog obj.ProgA p.Reg = REG_SP p.To.Type = obj.TYPE_BRANCH to_done = p - } else if framesize <= objabi.StackBig { + } else { // large stack: SP-framesize < stackguard-StackSmall - // ADD $-(framesize-StackSmall), SP, X11 - // BLTU X11, stackguard, done - p = obj.Appendp(p, newprog) - // TODO(sorear): logic inconsistent with comment, but both match all non-x86 arches - p.As = AADDI - p.From.Type = obj.TYPE_CONST - p.From.Offset = -(int64(framesize) - objabi.StackSmall) - p.Reg = REG_SP - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_X11 + offset := int64(framesize) - objabi.StackSmall + if framesize > objabi.StackBig { + // Such a large stack we need to protect against underflow. + // The runtime guarantees SP > objabi.StackBig, but + // framesize is large enough that SP-framesize may + // underflow, causing a direct comparison with the + // stack guard to incorrectly succeed. We explicitly + // guard against underflow. + // + // MOV $(framesize-StackSmall), X11 + // BLTU SP, X11, label-of-call-to-morestack - p = obj.Appendp(p, newprog) - p.As = ABLTU - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_X10 - p.Reg = REG_X11 - p.To.Type = obj.TYPE_BRANCH - to_done = p - } else { - // Such a large stack we need to protect against wraparound. - // If SP is close to zero: - // SP-stackguard+StackGuard <= framesize + (StackGuard-StackSmall) - // The +StackGuard on both sides is required to keep the left side positive: - // SP is allowed to be slightly below stackguard. See stack.h. - // - // Preemption sets stackguard to StackPreempt, a very large value. - // That breaks the math above, so we have to check for that explicitly. - // // stackguard is X10 - // MOV $StackPreempt, X11 - // BEQ X10, X11, more - // ADD $StackGuard, SP, X11 - // SUB X10, X11 - // MOV $(framesize+(StackGuard-StackSmall)), X10 - // BGTU X11, X10, done - p = obj.Appendp(p, newprog) - p.As = AMOV - p.From.Type = obj.TYPE_CONST - p.From.Offset = objabi.StackPreempt - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_X11 + p = obj.Appendp(p, newprog) + p.As = AMOV + p.From.Type = obj.TYPE_CONST + p.From.Offset = offset + p.To.Type = obj.TYPE_REG + p.To.Reg = REG_X11 - p = obj.Appendp(p, newprog) - to_more = p - p.As = ABEQ - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_X10 - p.Reg = REG_X11 - p.To.Type = obj.TYPE_BRANCH + p = obj.Appendp(p, newprog) + p.As = ABLTU + p.From.Type = obj.TYPE_REG + p.From.Reg = REG_SP + p.Reg = REG_X11 + p.To.Type = obj.TYPE_BRANCH + to_more = p + } + // Check against the stack guard. We've ensured this won't underflow. + // ADD $-(framesize-StackSmall), SP, X11 + // // if X11 > stackguard { goto done } + // BLTU stackguard, X11, done p = obj.Appendp(p, newprog) p.As = AADDI p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(objabi.StackGuard) + p.From.Offset = -offset p.Reg = REG_SP p.To.Type = obj.TYPE_REG p.To.Reg = REG_X11 - p = obj.Appendp(p, newprog) - p.As = ASUB - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_X10 - p.Reg = REG_X11 - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_X11 - - p = obj.Appendp(p, newprog) - p.As = AMOV - p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(framesize) + int64(objabi.StackGuard) - objabi.StackSmall - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_X10 - p = obj.Appendp(p, newprog) p.As = ABLTU p.From.Type = obj.TYPE_REG diff --git a/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go b/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go index 279aeb2c32b9f65c82c67dfcd668eed5f74fe7be..de412c64a7832b4ab4dfed9c87dd25d446a25d0d 100644 --- a/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go +++ b/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go @@ -25,84 +25,73 @@ func testBLTU(a, b int64) (r bool) func testBLTZ(a int64) (r bool) func testBNEZ(a int64) (r bool) +func testGoBGE(a, b int64) bool { return a >= b } +func testGoBGEU(a, b int64) bool { return uint64(a) >= uint64(b) } +func testGoBGT(a, b int64) bool { return a > b } +func testGoBGTU(a, b int64) bool { return uint64(a) > uint64(b) } +func testGoBLE(a, b int64) bool { return a <= b } +func testGoBLEU(a, b int64) bool { return uint64(a) <= uint64(b) } +func testGoBLT(a, b int64) bool { return a < b } +func testGoBLTZ(a, b int64) bool { return uint64(a) < uint64(b) } + func TestBranchCondition(t *testing.T) { tests := []struct { ins string a int64 b int64 fn func(a, b int64) bool + goFn func(a, b int64) bool want bool }{ - {"BGE", 0, 1, testBGE, false}, - {"BGE", 0, 0, testBGE, true}, - {"BGE", 0, -1, testBGE, true}, - {"BGE", -1, 0, testBGE, false}, - {"BGE", 1, 0, testBGE, true}, - {"BGEU", 0, 1, testBGEU, false}, - {"BGEU", 0, 0, testBGEU, true}, - {"BGEU", 0, -1, testBGEU, false}, - {"BGEU", -1, 0, testBGEU, true}, - {"BGEU", 1, 0, testBGEU, true}, - {"BGT", 0, 1, testBGT, false}, - {"BGT", 0, 0, testBGT, false}, - {"BGT", 0, -1, testBGT, true}, - {"BGT", -1, 0, testBGT, false}, - {"BGT", 1, 0, testBGT, true}, - {"BGTU", 0, 1, testBGTU, false}, - {"BGTU", 0, 0, testBGTU, false}, - {"BGTU", 0, -1, testBGTU, false}, - {"BGTU", -1, 0, testBGTU, true}, - {"BGTU", 1, 0, testBGTU, true}, - {"BLE", 0, 1, testBLE, true}, - {"BLE", 0, 0, testBLE, true}, - {"BLE", 0, -1, testBLE, false}, - {"BLE", -1, 0, testBLE, true}, - {"BLE", 1, 0, testBLE, false}, - {"BLEU", 0, 1, testBLEU, true}, - {"BLEU", 0, 0, testBLEU, true}, - {"BLEU", 0, -1, testBLEU, true}, - {"BLEU", -1, 0, testBLEU, false}, - {"BLEU", 1, 0, testBLEU, false}, - {"BLT", 0, 1, testBLT, true}, - {"BLT", 0, 0, testBLT, false}, - {"BLT", 0, -1, testBLT, false}, - {"BLT", -1, 0, testBLT, true}, - {"BLT", 1, 0, testBLT, false}, - {"BLTU", 0, 1, testBLTU, true}, - {"BLTU", 0, 0, testBLTU, false}, - {"BLTU", 0, -1, testBLTU, true}, - {"BLTU", -1, 0, testBLTU, false}, - {"BLTU", 1, 0, testBLTU, false}, + {"BGE", 0, 1, testBGE, testGoBGE, false}, + {"BGE", 0, 0, testBGE, testGoBGE, true}, + {"BGE", 0, -1, testBGE, testGoBGE, true}, + {"BGE", -1, 0, testBGE, testGoBGE, false}, + {"BGE", 1, 0, testBGE, testGoBGE, true}, + {"BGEU", 0, 1, testBGEU, testGoBGEU, false}, + {"BGEU", 0, 0, testBGEU, testGoBGEU, true}, + {"BGEU", 0, -1, testBGEU, testGoBGEU, false}, + {"BGEU", -1, 0, testBGEU, testGoBGEU, true}, + {"BGEU", 1, 0, testBGEU, testGoBGEU, true}, + {"BGT", 0, 1, testBGT, testGoBGT, false}, + {"BGT", 0, 0, testBGT, testGoBGT, false}, + {"BGT", 0, -1, testBGT, testGoBGT, true}, + {"BGT", -1, 0, testBGT, testGoBGT, false}, + {"BGT", 1, 0, testBGT, testGoBGT, true}, + {"BGTU", 0, 1, testBGTU, testGoBGTU, false}, + {"BGTU", 0, 0, testBGTU, testGoBGTU, false}, + {"BGTU", 0, -1, testBGTU, testGoBGTU, false}, + {"BGTU", -1, 0, testBGTU, testGoBGTU, true}, + {"BGTU", 1, 0, testBGTU, testGoBGTU, true}, + {"BLE", 0, 1, testBLE, testGoBLE, true}, + {"BLE", 0, 0, testBLE, testGoBLE, true}, + {"BLE", 0, -1, testBLE, testGoBLE, false}, + {"BLE", -1, 0, testBLE, testGoBLE, true}, + {"BLE", 1, 0, testBLE, testGoBLE, false}, + {"BLEU", 0, 1, testBLEU, testGoBLEU, true}, + {"BLEU", 0, 0, testBLEU, testGoBLEU, true}, + {"BLEU", 0, -1, testBLEU, testGoBLEU, true}, + {"BLEU", -1, 0, testBLEU, testGoBLEU, false}, + {"BLEU", 1, 0, testBLEU, testGoBLEU, false}, + {"BLT", 0, 1, testBLT, testGoBLT, true}, + {"BLT", 0, 0, testBLT, testGoBLT, false}, + {"BLT", 0, -1, testBLT, testGoBLT, false}, + {"BLT", -1, 0, testBLT, testGoBLT, true}, + {"BLT", 1, 0, testBLT, testGoBLT, false}, + {"BLTU", 0, 1, testBLTU, testGoBLTU, true}, + {"BLTU", 0, 0, testBLTU, testGoBLTU, false}, + {"BLTU", 0, -1, testBLTU, testGoBLTU, true}, + {"BLTU", -1, 0, testBLTU, testGoBLTU, false}, + {"BLTU", 1, 0, testBLTU, testGoBLTU, false}, } for _, test := range tests { t.Run(test.ins, func(t *testing.T) { - var fn func(a, b int64) bool - switch test.ins { - case "BGE": - fn = func(a, b int64) bool { return a >= b } - case "BGEU": - fn = func(a, b int64) bool { return uint64(a) >= uint64(b) } - case "BGT": - fn = func(a, b int64) bool { return a > b } - case "BGTU": - fn = func(a, b int64) bool { return uint64(a) > uint64(b) } - case "BLE": - fn = func(a, b int64) bool { return a <= b } - case "BLEU": - fn = func(a, b int64) bool { return uint64(a) <= uint64(b) } - case "BLT": - fn = func(a, b int64) bool { return a < b } - case "BLTU": - fn = func(a, b int64) bool { return uint64(a) < uint64(b) } - default: - t.Fatalf("Unknown instruction %q", test.ins) - } - if got := fn(test.a, test.b); got != test.want { - t.Errorf("Go %v %v, %v = %v, want %v", test.ins, test.a, test.b, got, test.want) - } if got := test.fn(test.a, test.b); got != test.want { t.Errorf("Assembly %v %v, %v = %v, want %v", test.ins, test.a, test.b, got, test.want) } + if got := test.goFn(test.a, test.b); got != test.want { + t.Errorf("Go %v %v, %v = %v, want %v", test.ins, test.a, test.b, got, test.want) + } }) } } diff --git a/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.s b/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.s index 8dd6f563af68d786f52f31770435d40d07ad16c2..cce296feb5c16121955c881754c28cc8953f7cbd 100644 --- a/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.s +++ b/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.s @@ -7,7 +7,7 @@ #include "textflag.h" // func testBEQZ(a int64) (r bool) -TEXT ·testBEQZ(SB),NOSPLIT,$0-0 +TEXT ·testBEQZ(SB),NOSPLIT,$0-9 MOV a+0(FP), X5 MOV $1, X6 BEQZ X5, b @@ -17,7 +17,7 @@ b: RET // func testBGE(a, b int64) (r bool) -TEXT ·testBGE(SB),NOSPLIT,$0-0 +TEXT ·testBGE(SB),NOSPLIT,$0-17 MOV a+0(FP), X5 MOV b+8(FP), X6 MOV $1, X7 @@ -28,7 +28,7 @@ b: RET // func testBGEU(a, b int64) (r bool) -TEXT ·testBGEU(SB),NOSPLIT,$0-0 +TEXT ·testBGEU(SB),NOSPLIT,$0-17 MOV a+0(FP), X5 MOV b+8(FP), X6 MOV $1, X7 @@ -39,7 +39,7 @@ b: RET // func testBGEZ(a int64) (r bool) -TEXT ·testBGEZ(SB),NOSPLIT,$0-0 +TEXT ·testBGEZ(SB),NOSPLIT,$0-9 MOV a+0(FP), X5 MOV $1, X6 BGEZ X5, b @@ -49,7 +49,7 @@ b: RET // func testBGT(a, b int64) (r bool) -TEXT ·testBGT(SB),NOSPLIT,$0-0 +TEXT ·testBGT(SB),NOSPLIT,$0-17 MOV a+0(FP), X5 MOV b+8(FP), X6 MOV $1, X7 @@ -60,7 +60,7 @@ b: RET // func testBGTU(a, b int64) (r bool) -TEXT ·testBGTU(SB),NOSPLIT,$0-0 +TEXT ·testBGTU(SB),NOSPLIT,$0-17 MOV a+0(FP), X5 MOV b+8(FP), X6 MOV $1, X7 @@ -71,7 +71,7 @@ b: RET // func testBGTZ(a int64) (r bool) -TEXT ·testBGTZ(SB),NOSPLIT,$0-0 +TEXT ·testBGTZ(SB),NOSPLIT,$0-9 MOV a+0(FP), X5 MOV $1, X6 BGTZ X5, b @@ -81,7 +81,7 @@ b: RET // func testBLE(a, b int64) (r bool) -TEXT ·testBLE(SB),NOSPLIT,$0-0 +TEXT ·testBLE(SB),NOSPLIT,$0-17 MOV a+0(FP), X5 MOV b+8(FP), X6 MOV $1, X7 @@ -92,7 +92,7 @@ b: RET // func testBLEU(a, b int64) (r bool) -TEXT ·testBLEU(SB),NOSPLIT,$0-0 +TEXT ·testBLEU(SB),NOSPLIT,$0-17 MOV a+0(FP), X5 MOV b+8(FP), X6 MOV $1, X7 @@ -103,7 +103,7 @@ b: RET // func testBLEZ(a int64) (r bool) -TEXT ·testBLEZ(SB),NOSPLIT,$0-0 +TEXT ·testBLEZ(SB),NOSPLIT,$0-9 MOV a+0(FP), X5 MOV $1, X6 BLEZ X5, b @@ -113,7 +113,7 @@ b: RET // func testBLT(a, b int64) (r bool) -TEXT ·testBLT(SB),NOSPLIT,$0-0 +TEXT ·testBLT(SB),NOSPLIT,$0-17 MOV a+0(FP), X5 MOV b+8(FP), X6 MOV $1, X7 @@ -124,7 +124,7 @@ b: RET // func testBLTU(a, b int64) (r bool) -TEXT ·testBLTU(SB),NOSPLIT,$0-0 +TEXT ·testBLTU(SB),NOSPLIT,$0-17 MOV a+0(FP), X5 MOV b+8(FP), X6 MOV $1, X7 @@ -135,7 +135,7 @@ b: RET // func testBLTZ(a int64) (r bool) -TEXT ·testBLTZ(SB),NOSPLIT,$0-0 +TEXT ·testBLTZ(SB),NOSPLIT,$0-9 MOV a+0(FP), X5 MOV $1, X6 BLTZ X5, b @@ -145,7 +145,7 @@ b: RET // func testBNEZ(a int64) (r bool) -TEXT ·testBNEZ(SB),NOSPLIT,$0-0 +TEXT ·testBNEZ(SB),NOSPLIT,$0-9 MOV a+0(FP), X5 MOV $1, X6 BNEZ X5, b diff --git a/src/cmd/internal/obj/s390x/condition_code.go b/src/cmd/internal/obj/s390x/condition_code.go index 764fc5bc6a855c9f2b2149bfb22d4eef2233ffa9..f498fd6f7744214fa4c9f7ecadbbd59ef96065d2 100644 --- a/src/cmd/internal/obj/s390x/condition_code.go +++ b/src/cmd/internal/obj/s390x/condition_code.go @@ -124,3 +124,5 @@ func (c CCMask) String() string { // invalid return fmt.Sprintf("Invalid (%#x)", c) } + +func (CCMask) CanBeAnSSAAux() {} diff --git a/src/cmd/internal/obj/s390x/objz.go b/src/cmd/internal/obj/s390x/objz.go index 970cf827d6fee381b2a444b52ab0861ce92560cd..201163b0159a1883bde0570d9a516f6a49ab9113 100644 --- a/src/cmd/internal/obj/s390x/objz.go +++ b/src/cmd/internal/obj/s390x/objz.go @@ -33,6 +33,7 @@ import ( "cmd/internal/obj" "cmd/internal/objabi" "cmd/internal/sys" + "log" "math" ) @@ -545,6 +546,21 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { p.From.Reg = REGSP } } + + if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.Spadj == 0 { + f := c.cursym.Func() + if f.FuncFlag&objabi.FuncFlag_SPWRITE == 0 { + c.cursym.Func().FuncFlag |= objabi.FuncFlag_SPWRITE + if ctxt.Debugvlog || !ctxt.IsAsm { + ctxt.Logf("auto-SPWRITE: %s\n", c.cursym.Name) + if !ctxt.IsAsm { + ctxt.Diag("invalid auto-SPWRITE in non-assembly") + ctxt.DiagFlush() + log.Fatalf("bad SPWRITE") + } + } + } + } } if wasSplit { c.stacksplitPost(pLast, pPre, pPreempt, autosize) // emit post part of split check @@ -552,7 +568,6 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { } func (c *ctxtz) stacksplitPre(p *obj.Prog, framesize int32) (*obj.Prog, *obj.Prog) { - var q *obj.Prog // MOVD g_stackguard(g), R3 p = obj.Appendp(p, c.newprog) @@ -573,98 +588,69 @@ func (c *ctxtz) stacksplitPre(p *obj.Prog, framesize int32) (*obj.Prog, *obj.Pro // unnecessarily. See issue #35470. p = c.ctxt.StartUnsafePoint(p, c.newprog) - q = nil if framesize <= objabi.StackSmall { // small stack: SP < stackguard // CMPUBGE stackguard, SP, label-of-call-to-morestack p = obj.Appendp(p, c.newprog) - //q1 = p p.From.Type = obj.TYPE_REG p.From.Reg = REG_R3 p.Reg = REGSP p.As = ACMPUBGE p.To.Type = obj.TYPE_BRANCH - } else if framesize <= objabi.StackBig { - // large stack: SP-framesize < stackguard-StackSmall - // ADD $-(framesize-StackSmall), SP, R4 - // CMPUBGE stackguard, R4, label-of-call-to-morestack - p = obj.Appendp(p, c.newprog) - - p.As = AADD - p.From.Type = obj.TYPE_CONST - p.From.Offset = -(int64(framesize) - objabi.StackSmall) - p.Reg = REGSP - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R4 + return p, nil + } - p = obj.Appendp(p, c.newprog) - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R3 - p.Reg = REG_R4 - p.As = ACMPUBGE - p.To.Type = obj.TYPE_BRANCH + // large stack: SP-framesize < stackguard-StackSmall - } else { - // Such a large stack we need to protect against wraparound. - // If SP is close to zero: - // SP-stackguard+StackGuard <= framesize + (StackGuard-StackSmall) - // The +StackGuard on both sides is required to keep the left side positive: - // SP is allowed to be slightly below stackguard. See stack.h. + var q *obj.Prog + offset := int64(framesize) - objabi.StackSmall + if framesize > objabi.StackBig { + // Such a large stack we need to protect against underflow. + // The runtime guarantees SP > objabi.StackBig, but + // framesize is large enough that SP-framesize may + // underflow, causing a direct comparison with the + // stack guard to incorrectly succeed. We explicitly + // guard against underflow. // - // Preemption sets stackguard to StackPreempt, a very large value. - // That breaks the math above, so we have to check for that explicitly. - // // stackguard is R3 - // CMP R3, $StackPreempt - // BEQ label-of-call-to-morestack - // ADD $StackGuard, SP, R4 - // SUB R3, R4 - // MOVD $(framesize+(StackGuard-StackSmall)), TEMP - // CMPUBGE TEMP, R4, label-of-call-to-morestack - p = obj.Appendp(p, c.newprog) - - p.As = ACMP - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R3 - p.To.Type = obj.TYPE_CONST - p.To.Offset = objabi.StackPreempt - - p = obj.Appendp(p, c.newprog) - q = p - p.As = ABEQ - p.To.Type = obj.TYPE_BRANCH - - p = obj.Appendp(p, c.newprog) - p.As = AADD - p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(objabi.StackGuard) - p.Reg = REGSP - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R4 - - p = obj.Appendp(p, c.newprog) - p.As = ASUB - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_R3 - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_R4 + // MOVD $(framesize-StackSmall), R4 + // CMPUBLT SP, R4, label-of-call-to-morestack p = obj.Appendp(p, c.newprog) p.As = AMOVD p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(framesize) + int64(objabi.StackGuard) - objabi.StackSmall + p.From.Offset = offset p.To.Type = obj.TYPE_REG - p.To.Reg = REGTMP + p.To.Reg = REG_R4 p = obj.Appendp(p, c.newprog) + q = p + p.As = ACMPUBLT p.From.Type = obj.TYPE_REG - p.From.Reg = REGTMP + p.From.Reg = REGSP p.Reg = REG_R4 - p.As = ACMPUBGE p.To.Type = obj.TYPE_BRANCH } + // Check against the stack guard. We've ensured this won't underflow. + // ADD $-(framesize-StackSmall), SP, R4 + // CMPUBGE stackguard, R4, label-of-call-to-morestack + p = obj.Appendp(p, c.newprog) + p.As = AADD + p.From.Type = obj.TYPE_CONST + p.From.Offset = -offset + p.Reg = REGSP + p.To.Type = obj.TYPE_REG + p.To.Reg = REG_R4 + + p = obj.Appendp(p, c.newprog) + p.From.Type = obj.TYPE_REG + p.From.Reg = REG_R3 + p.Reg = REG_R4 + p.As = ACMPUBGE + p.To.Type = obj.TYPE_BRANCH + return p, q } diff --git a/src/cmd/internal/obj/s390x/rotate.go b/src/cmd/internal/obj/s390x/rotate.go index 388bd40f41b2dfe84d18e936a625b6764815777c..5407c8df1109993f2b86f32323b2f288036b108a 100644 --- a/src/cmd/internal/obj/s390x/rotate.go +++ b/src/cmd/internal/obj/s390x/rotate.go @@ -113,3 +113,5 @@ func (r RotateParams) OutMerge(mask uint64) *RotateParams { func (r RotateParams) InMerge(mask uint64) *RotateParams { return r.OutMerge(bits.RotateLeft64(mask, int(r.Amount))) } + +func (RotateParams) CanBeAnSSAAux() {} diff --git a/src/cmd/internal/obj/stringer.go b/src/cmd/internal/obj/stringer.go index f67b89091c1c64035fc6ba89dcbff9400c1f4e6e..a4d507d49aa40924615a590d111743461a3ab5c2 100644 --- a/src/cmd/internal/obj/stringer.go +++ b/src/cmd/internal/obj/stringer.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore // This is a mini version of the stringer tool customized for the Anames table diff --git a/src/cmd/internal/obj/sym.go b/src/cmd/internal/obj/sym.go index 4515bdd0d3a20cd8e3e6ee53d1d5b6046f54f5ad..9e8b4dd790f001435f4eac4a48d3d6a267797d32 100644 --- a/src/cmd/internal/obj/sym.go +++ b/src/cmd/internal/obj/sym.go @@ -35,6 +35,7 @@ import ( "cmd/internal/goobj" "cmd/internal/objabi" "fmt" + "internal/buildcfg" "log" "math" "sort" @@ -49,15 +50,15 @@ func Linknew(arch *LinkArch) *Link { ctxt.Arch = arch ctxt.Pathname = objabi.WorkingDir() - if err := ctxt.Headtype.Set(objabi.GOOS); err != nil { - log.Fatalf("unknown goos %s", objabi.GOOS) + if err := ctxt.Headtype.Set(buildcfg.GOOS); err != nil { + log.Fatalf("unknown goos %s", buildcfg.GOOS) } ctxt.Flag_optimize = true return ctxt } -// LookupDerived looks up or creates the symbol with name name derived from symbol s. +// LookupDerived looks up or creates the symbol with name derived from symbol s. // The resulting symbol will be static iff s is. func (ctxt *Link) LookupDerived(s *LSym, name string) *LSym { if s.Static() { diff --git a/src/cmd/internal/obj/textflag.go b/src/cmd/internal/obj/textflag.go index d2cec734b1c1b140b01be2ad8872163c99d43702..5ae75027c2f2b544f7b7eb91d3f03fe7d33a7353 100644 --- a/src/cmd/internal/obj/textflag.go +++ b/src/cmd/internal/obj/textflag.go @@ -27,13 +27,14 @@ const ( // This data contains no pointers. NOPTR = 16 - // This is a wrapper function and should not count as disabling 'recover'. + // This is a wrapper function and should not count as + // disabling 'recover' or appear in tracebacks by default. WRAPPER = 32 // This function uses its incoming context register. NEEDCTXT = 64 - // When passed to ggloblsym, causes Local to be set to true on the LSym it creates. + // When passed to objw.Global, causes Local to be set to true on the LSym it creates. LOCAL = 128 // Allocate a word of thread local storage and store the offset from the @@ -48,7 +49,10 @@ const ( // Function can call reflect.Type.Method or reflect.Type.MethodByName. REFLECTMETHOD = 1024 - // Function is the top of the call stack. Call stack unwinders should stop - // at this function. + // Function is the outermost frame of the call stack. Call stack unwinders + // should stop at this function. TOPFRAME = 2048 + + // Function is an ABI wrapper. + ABIWRAPPER = 4096 ) diff --git a/src/cmd/internal/obj/util.go b/src/cmd/internal/obj/util.go index b9bacb7a2279126d552603f38c8bbdbca2dcc4e1..e8441a69694452544092ef6225fdd8c7d8e3143f 100644 --- a/src/cmd/internal/obj/util.go +++ b/src/cmd/internal/obj/util.go @@ -8,6 +8,7 @@ import ( "bytes" "cmd/internal/objabi" "fmt" + "internal/buildcfg" "io" "strings" ) @@ -83,7 +84,7 @@ func CConv(s uint8) string { } for i := range opSuffixSpace { sset := &opSuffixSpace[i] - if sset.arch == objabi.GOARCH { + if sset.arch == buildcfg.GOARCH { return sset.cconv(s) } } @@ -187,7 +188,7 @@ func (p *Prog) WriteInstructionString(w io.Writer) { // In short, print one of these two: // TEXT foo(SB), DUPOK|NOSPLIT, $0 // TEXT foo(SB), $0 - s := p.From.Sym.Attribute.TextAttrString() + s := p.From.Sym.TextAttrString() if s != "" { fmt.Fprintf(w, "%s%s", sep, s) sep = ", " @@ -330,7 +331,7 @@ func writeDconv(w io.Writer, p *Prog, a *Addr, abiDetail bool) { case TYPE_SHIFT: v := int(a.Offset) ops := "<<>>->@>" - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "arm": op := ops[((v>>5)&3)<<1:] if v&(1<<4) != 0 { @@ -346,7 +347,7 @@ func writeDconv(w io.Writer, p *Prog, a *Addr, abiDetail bool) { r := (v >> 16) & 31 fmt.Fprintf(w, "%s%c%c%d", Rconv(r+RBaseARM64), op[0], op[1], (v>>10)&63) default: - panic("TYPE_SHIFT is not supported on " + objabi.GOARCH) + panic("TYPE_SHIFT is not supported on " + buildcfg.GOARCH) } case TYPE_REGREG: diff --git a/src/cmd/internal/obj/wasm/wasmobj.go b/src/cmd/internal/obj/wasm/wasmobj.go index 2e9890d86c074f1b56af47b4231766f0bd6a8396..ceeae7a257cdcc33313b85c31715fe53ead79a74 100644 --- a/src/cmd/internal/obj/wasm/wasmobj.go +++ b/src/cmd/internal/obj/wasm/wasmobj.go @@ -144,11 +144,9 @@ func instinit(ctxt *obj.Link) { gcWriteBarrier = ctxt.LookupABI("runtime.gcWriteBarrier", obj.ABIInternal) sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal) deferreturn = ctxt.LookupABI("runtime.deferreturn", obj.ABIInternal) - // jmpdefer is defined in assembly as ABI0, but what we're - // looking for is the *call* to jmpdefer from the Go function - // deferreturn, so we're looking for the ABIInternal version - // of jmpdefer that's called by Go. - jmpdefer = ctxt.LookupABI(`"".jmpdefer`, obj.ABIInternal) + // jmpdefer is defined in assembly as ABI0. The compiler will + // generate a direct ABI0 call from Go, so look for that. + jmpdefer = ctxt.LookupABI(`"".jmpdefer`, obj.ABI0) } func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { diff --git a/src/cmd/internal/obj/x86/a.out.go b/src/cmd/internal/obj/x86/a.out.go index 30c1a6a44547d8f84590ab78d53f77bc99ae2d91..b121f6df7b2f8e3113dbf8b9cb8a2bc5c466c749 100644 --- a/src/cmd/internal/obj/x86/a.out.go +++ b/src/cmd/internal/obj/x86/a.out.go @@ -258,22 +258,25 @@ const ( REG_DR = REG_DR0 REG_TR = REG_TR0 - REGARG = -1 - REGRET = REG_AX - FREGRET = REG_X0 - REGSP = REG_SP - REGCTXT = REG_DX - REGEXT = REG_R15 // compiler allocates external registers R15 down - FREGMIN = REG_X0 + 5 // first register variable - FREGEXT = REG_X0 + 15 // first external register - T_TYPE = 1 << 0 - T_INDEX = 1 << 1 - T_OFFSET = 1 << 2 - T_FCONST = 1 << 3 - T_SYM = 1 << 4 - T_SCONST = 1 << 5 - T_64 = 1 << 6 - T_GOTYPE = 1 << 7 + REGARG = -1 + REGRET = REG_AX + FREGRET = REG_X0 + REGSP = REG_SP + REGCTXT = REG_DX + REGENTRYTMP0 = REG_R12 // scratch register available at function entry in ABIInternal + REGENTRYTMP1 = REG_R13 // scratch register available at function entry in ABIInternal + REGG = REG_R14 // g register in ABIInternal + REGEXT = REG_R15 // compiler allocates external registers R15 down + FREGMIN = REG_X0 + 5 // first register variable + FREGEXT = REG_X0 + 15 // first external register + T_TYPE = 1 << 0 + T_INDEX = 1 << 1 + T_OFFSET = 1 << 2 + T_FCONST = 1 << 3 + T_SYM = 1 << 4 + T_SCONST = 1 << 5 + T_64 = 1 << 6 + T_GOTYPE = 1 << 7 ) // https://www.uclibc.org/docs/psABI-x86_64.pdf, figure 3.36 diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go index a6b85ac4a06bd8cceb65e4fe83f8aab892dd32b7..17fa76727e6212061f46548664530c88570ff734 100644 --- a/src/cmd/internal/obj/x86/asm6.go +++ b/src/cmd/internal/obj/x86/asm6.go @@ -36,6 +36,7 @@ import ( "cmd/internal/sys" "encoding/binary" "fmt" + "internal/buildcfg" "log" "strings" ) @@ -1887,7 +1888,7 @@ func lookForJCC(p *obj.Prog) *obj.Prog { func fusedJump(p *obj.Prog) (bool, uint8) { var fusedSize uint8 - // The first instruction in a macro fused pair may be preceeded by the LOCK prefix, + // The first instruction in a macro fused pair may be preceded by the LOCK prefix, // or possibly an XACQUIRE/XRELEASE prefix followed by a LOCK prefix. If it is, we // need to be careful to insert any padding before the locks rather than directly after them. @@ -2460,7 +2461,7 @@ func instinit(ctxt *obj.Link) { } } -var isAndroid = objabi.GOOS == "android" +var isAndroid = buildcfg.GOOS == "android" func prefixof(ctxt *obj.Link, a *obj.Addr) int { if a.Reg < REG_CS && a.Index < REG_CS { // fast path @@ -5306,7 +5307,7 @@ bad: } } - ctxt.Diag("invalid instruction: %v", p) + ctxt.Diag("%s: invalid instruction: %v", cursym.Name, p) } // byteswapreg returns a byte-addressable register (AX, BX, CX, DX) diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index 184fb4308bd01532369e74c30c4fc222074cf207..e2732d53e3067b4d21c7774d84b180a90908ee22 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -35,7 +35,10 @@ import ( "cmd/internal/objabi" "cmd/internal/src" "cmd/internal/sys" + "internal/buildcfg" + "log" "math" + "path" "strings" ) @@ -134,7 +137,7 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { p.To.Index = REG_NONE } } else { - // load_g_cx, below, always inserts the 1-instruction sequence. Rewrite it + // load_g, below, always inserts the 1-instruction sequence. Rewrite it // as the 2-instruction sequence if necessary. // MOVQ 0(TLS), BX // becomes @@ -562,6 +565,11 @@ func rewriteToPcrel(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { obj.Nopout(p) } +// Prog.mark +const ( + markBit = 1 << 0 // used in errorCheck to avoid duplicate work +) + func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { if cursym.Func().Text == nil || cursym.Func().Text.Link == nil { return @@ -637,13 +645,29 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { } } + var regg int16 if !p.From.Sym.NoSplit() || p.From.Sym.Wrapper() { - p = obj.Appendp(p, newprog) - p = load_g_cx(ctxt, p, newprog) // load g into CX + if ctxt.Arch.Family == sys.AMD64 && buildcfg.Experiment.RegabiG && cursym.ABI() == obj.ABIInternal { + regg = REGG // use the g register directly in ABIInternal + } else { + p = obj.Appendp(p, newprog) + regg = REG_CX + if ctxt.Arch.Family == sys.AMD64 { + // Using this register means that stacksplit works w/ //go:registerparams even when !buildcfg.Experiment.RegabiG + regg = REGG // == REG_R14 + } + p = load_g(ctxt, p, newprog, regg) // load g into regg + } + } + var regEntryTmp0, regEntryTmp1 int16 + if ctxt.Arch.Family == sys.AMD64 { + regEntryTmp0, regEntryTmp1 = REGENTRYTMP0, REGENTRYTMP1 + } else { + regEntryTmp0, regEntryTmp1 = REG_BX, REG_DI } if !cursym.Func().Text.From.Sym.NoSplit() { - p = stacksplit(ctxt, cursym, p, newprog, autoffset, int32(textarg)) // emit split check + p = stacksplit(ctxt, cursym, p, newprog, autoffset, int32(textarg), regg) // emit split check } // Delve debugger would like the next instruction to be noted as the end of the function prologue. @@ -695,17 +719,17 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { // g._panic.argp = bottom-of-frame // } // - // MOVQ g_panic(CX), BX - // TESTQ BX, BX + // MOVQ g_panic(g), regEntryTmp0 + // TESTQ regEntryTmp0, regEntryTmp0 // JNE checkargp // end: // NOP // ... rest of function ... // checkargp: - // LEAQ (autoffset+8)(SP), DI - // CMPQ panic_argp(BX), DI + // LEAQ (autoffset+8)(SP), regEntryTmp1 + // CMPQ panic_argp(regEntryTmp0), regEntryTmp1 // JNE end - // MOVQ SP, panic_argp(BX) + // MOVQ SP, panic_argp(regEntryTmp0) // JMP end // // The NOP is needed to give the jumps somewhere to land. @@ -714,25 +738,25 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { // The layout is chosen to help static branch prediction: // Both conditional jumps are unlikely, so they are arranged to be forward jumps. - // MOVQ g_panic(CX), BX + // MOVQ g_panic(g), regEntryTmp0 p = obj.Appendp(p, newprog) p.As = AMOVQ p.From.Type = obj.TYPE_MEM - p.From.Reg = REG_CX + p.From.Reg = regg p.From.Offset = 4 * int64(ctxt.Arch.PtrSize) // g_panic p.To.Type = obj.TYPE_REG - p.To.Reg = REG_BX + p.To.Reg = regEntryTmp0 if ctxt.Arch.Family == sys.I386 { p.As = AMOVL } - // TESTQ BX, BX + // TESTQ regEntryTmp0, regEntryTmp0 p = obj.Appendp(p, newprog) p.As = ATESTQ p.From.Type = obj.TYPE_REG - p.From.Reg = REG_BX + p.From.Reg = regEntryTmp0 p.To.Type = obj.TYPE_REG - p.To.Reg = REG_BX + p.To.Reg = regEntryTmp0 if ctxt.Arch.Family == sys.I386 { p.As = ATESTL } @@ -752,14 +776,14 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { for last = end; last.Link != nil; last = last.Link { } - // LEAQ (autoffset+8)(SP), DI + // LEAQ (autoffset+8)(SP), regEntryTmp1 p = obj.Appendp(last, newprog) p.As = ALEAQ p.From.Type = obj.TYPE_MEM p.From.Reg = REG_SP p.From.Offset = int64(autoffset) + int64(ctxt.Arch.RegSize) p.To.Type = obj.TYPE_REG - p.To.Reg = REG_DI + p.To.Reg = regEntryTmp1 if ctxt.Arch.Family == sys.I386 { p.As = ALEAL } @@ -767,14 +791,14 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { // Set jne branch target. jne.To.SetTarget(p) - // CMPQ panic_argp(BX), DI + // CMPQ panic_argp(regEntryTmp0), regEntryTmp1 p = obj.Appendp(p, newprog) p.As = ACMPQ p.From.Type = obj.TYPE_MEM - p.From.Reg = REG_BX + p.From.Reg = regEntryTmp0 p.From.Offset = 0 // Panic.argp p.To.Type = obj.TYPE_REG - p.To.Reg = REG_DI + p.To.Reg = regEntryTmp1 if ctxt.Arch.Family == sys.I386 { p.As = ACMPL } @@ -785,13 +809,13 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { p.To.Type = obj.TYPE_BRANCH p.To.SetTarget(end) - // MOVQ SP, panic_argp(BX) + // MOVQ SP, panic_argp(regEntryTmp0) p = obj.Appendp(p, newprog) p.As = AMOVQ p.From.Type = obj.TYPE_REG p.From.Reg = REG_SP p.To.Type = obj.TYPE_MEM - p.To.Reg = REG_BX + p.To.Reg = regEntryTmp0 p.To.Offset = 0 // Panic.argp if ctxt.Arch.Family == sys.I386 { p.As = AMOVL @@ -833,6 +857,20 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { switch p.As { default: + if p.To.Type == obj.TYPE_REG && p.To.Reg == REG_SP && p.As != ACMPL && p.As != ACMPQ { + f := cursym.Func() + if f.FuncFlag&objabi.FuncFlag_SPWRITE == 0 { + f.FuncFlag |= objabi.FuncFlag_SPWRITE + if ctxt.Debugvlog || !ctxt.IsAsm { + ctxt.Logf("auto-SPWRITE: %s %v\n", cursym.Name, p) + if !ctxt.IsAsm { + ctxt.Diag("invalid auto-SPWRITE in non-assembly") + ctxt.DiagFlush() + log.Fatalf("bad SPWRITE") + } + } + } + } continue case APUSHL, APUSHFL: @@ -875,7 +913,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { } if autoffset != deltasp { - ctxt.Diag("unbalanced PUSH/POP") + ctxt.Diag("%s: unbalanced PUSH/POP", cursym) } if autoffset != 0 { @@ -942,7 +980,7 @@ func indir_cx(ctxt *obj.Link, a *obj.Addr) { // Overwriting p is unusual but it lets use this in both the // prologue (caller must call appendp first) and in the epilogue. // Returns last new instruction. -func load_g_cx(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) *obj.Prog { +func load_g(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc, rg int16) *obj.Prog { p.As = AMOVQ if ctxt.Arch.PtrSize == 4 { p.As = AMOVL @@ -951,7 +989,7 @@ func load_g_cx(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) *obj.Prog { p.From.Reg = REG_TLS p.From.Offset = 0 p.To.Type = obj.TYPE_REG - p.To.Reg = REG_CX + p.To.Reg = rg next := p.Link progedit(ctxt, p, newprog) @@ -969,9 +1007,9 @@ func load_g_cx(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) *obj.Prog { // Append code to p to check for stack split. // Appends to (does not overwrite) p. -// Assumes g is in CX. +// Assumes g is in rg. // Returns last new instruction. -func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgAlloc, framesize int32, textarg int32) *obj.Prog { +func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgAlloc, framesize int32, textarg int32, rg int16) *obj.Prog { cmp := ACMPQ lea := ALEAQ mov := AMOVQ @@ -984,6 +1022,12 @@ func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgA sub = ASUBL } + tmp := int16(REG_AX) // use AX for 32-bit + if ctxt.Arch.Family == sys.AMD64 { + // Avoid register parameters. + tmp = int16(REGENTRYTMP0) + } + var q1 *obj.Prog if framesize <= objabi.StackSmall { // small stack: SP <= stackguard @@ -993,7 +1037,8 @@ func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgA p.As = cmp p.From.Type = obj.TYPE_REG p.From.Reg = REG_SP - indir_cx(ctxt, &p.To) + p.To.Type = obj.TYPE_MEM + p.To.Reg = rg p.To.Offset = 2 * int64(ctxt.Arch.PtrSize) // G.stackguard0 if cursym.CFunc() { p.To.Offset = 3 * int64(ctxt.Arch.PtrSize) // G.stackguard1 @@ -1006,8 +1051,8 @@ func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgA p = ctxt.StartUnsafePoint(p, newprog) } else if framesize <= objabi.StackBig { // large stack: SP-framesize <= stackguard-StackSmall - // LEAQ -xxx(SP), AX - // CMPQ AX, stackguard + // LEAQ -xxx(SP), tmp + // CMPQ tmp, stackguard p = obj.Appendp(p, newprog) p.As = lea @@ -1015,13 +1060,14 @@ func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgA p.From.Reg = REG_SP p.From.Offset = -(int64(framesize) - objabi.StackSmall) p.To.Type = obj.TYPE_REG - p.To.Reg = REG_AX + p.To.Reg = tmp p = obj.Appendp(p, newprog) p.As = cmp p.From.Type = obj.TYPE_REG - p.From.Reg = REG_AX - indir_cx(ctxt, &p.To) + p.From.Reg = tmp + p.To.Type = obj.TYPE_MEM + p.To.Reg = rg p.To.Offset = 2 * int64(ctxt.Arch.PtrSize) // G.stackguard0 if cursym.CFunc() { p.To.Offset = 3 * int64(ctxt.Arch.PtrSize) // G.stackguard1 @@ -1029,70 +1075,51 @@ func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgA p = ctxt.StartUnsafePoint(p, newprog) // see the comment above } else { - // Such a large stack we need to protect against wraparound. - // If SP is close to zero: - // SP-stackguard+StackGuard <= framesize + (StackGuard-StackSmall) - // The +StackGuard on both sides is required to keep the left side positive: - // SP is allowed to be slightly below stackguard. See stack.h. + // Such a large stack we need to protect against underflow. + // The runtime guarantees SP > objabi.StackBig, but + // framesize is large enough that SP-framesize may + // underflow, causing a direct comparison with the + // stack guard to incorrectly succeed. We explicitly + // guard against underflow. // - // Preemption sets stackguard to StackPreempt, a very large value. - // That breaks the math above, so we have to check for that explicitly. - // MOVQ stackguard, SI - // CMPQ SI, $StackPreempt - // JEQ label-of-call-to-morestack - // LEAQ StackGuard(SP), AX - // SUBQ SI, AX - // CMPQ AX, $(framesize+(StackGuard-StackSmall)) + // MOVQ SP, tmp + // SUBQ $(framesize - StackSmall), tmp + // // If subtraction wrapped (carry set), morestack. + // JCS label-of-call-to-morestack + // CMPQ tmp, stackguard p = obj.Appendp(p, newprog) p.As = mov - indir_cx(ctxt, &p.From) - p.From.Offset = 2 * int64(ctxt.Arch.PtrSize) // G.stackguard0 - if cursym.CFunc() { - p.From.Offset = 3 * int64(ctxt.Arch.PtrSize) // G.stackguard1 - } + p.From.Type = obj.TYPE_REG + p.From.Reg = REG_SP p.To.Type = obj.TYPE_REG - p.To.Reg = REG_SI + p.To.Reg = tmp p = ctxt.StartUnsafePoint(p, newprog) // see the comment above p = obj.Appendp(p, newprog) - p.As = cmp - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_SI - p.To.Type = obj.TYPE_CONST - p.To.Offset = objabi.StackPreempt - if ctxt.Arch.Family == sys.I386 { - p.To.Offset = int64(uint32(objabi.StackPreempt & (1<<32 - 1))) - } + p.As = sub + p.From.Type = obj.TYPE_CONST + p.From.Offset = int64(framesize) - objabi.StackSmall + p.To.Type = obj.TYPE_REG + p.To.Reg = tmp p = obj.Appendp(p, newprog) - p.As = AJEQ + p.As = AJCS p.To.Type = obj.TYPE_BRANCH q1 = p - p = obj.Appendp(p, newprog) - p.As = lea - p.From.Type = obj.TYPE_MEM - p.From.Reg = REG_SP - p.From.Offset = int64(objabi.StackGuard) - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_AX - - p = obj.Appendp(p, newprog) - p.As = sub - p.From.Type = obj.TYPE_REG - p.From.Reg = REG_SI - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_AX - p = obj.Appendp(p, newprog) p.As = cmp p.From.Type = obj.TYPE_REG - p.From.Reg = REG_AX - p.To.Type = obj.TYPE_CONST - p.To.Offset = int64(framesize) + (int64(objabi.StackGuard) - objabi.StackSmall) + p.From.Reg = tmp + p.To.Type = obj.TYPE_MEM + p.To.Reg = rg + p.To.Offset = 2 * int64(ctxt.Arch.PtrSize) // G.stackguard0 + if cursym.CFunc() { + p.To.Offset = 3 * int64(ctxt.Arch.PtrSize) // G.stackguard1 + } } // common @@ -1114,7 +1141,8 @@ func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgA spfix.Spadj = -framesize pcdata := ctxt.EmitEntryStackMap(cursym, spfix, newprog) - pcdata = ctxt.StartUnsafePoint(pcdata, newprog) + spill := ctxt.StartUnsafePoint(pcdata, newprog) + pcdata = cursym.Func().SpillRegisterArgs(spill, newprog) call := obj.Appendp(pcdata, newprog) call.Pos = cursym.Func().Text.Pos @@ -1139,7 +1167,8 @@ func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgA progedit(ctxt, callend.Link, newprog) } - pcdata = ctxt.EndUnsafePoint(callend, newprog, -1) + pcdata = cursym.Func().UnspillRegisterArgs(callend, newprog) + pcdata = ctxt.EndUnsafePoint(pcdata, newprog, -1) jmp := obj.Appendp(pcdata, newprog) jmp.As = obj.AJMP @@ -1147,14 +1176,122 @@ func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgA jmp.To.SetTarget(cursym.Func().Text.Link) jmp.Spadj = +framesize - jls.To.SetTarget(call) + jls.To.SetTarget(spill) if q1 != nil { - q1.To.SetTarget(call) + q1.To.SetTarget(spill) } return end } +func isR15(r int16) bool { + return r == REG_R15 || r == REG_R15B +} +func addrMentionsR15(a *obj.Addr) bool { + if a == nil { + return false + } + return isR15(a.Reg) || isR15(a.Index) +} +func progMentionsR15(p *obj.Prog) bool { + return addrMentionsR15(&p.From) || addrMentionsR15(&p.To) || isR15(p.Reg) || addrMentionsR15(p.GetFrom3()) +} + +// progOverwritesR15 reports whether p writes to R15 and does not depend on +// the previous value of R15. +func progOverwritesR15(p *obj.Prog) bool { + if !(p.To.Type == obj.TYPE_REG && isR15(p.To.Reg)) { + // Not writing to R15. + return false + } + if (p.As == AXORL || p.As == AXORQ) && p.From.Type == obj.TYPE_REG && isR15(p.From.Reg) { + // These look like uses of R15, but aren't, so we must detect these + // before the use check below. + return true + } + if addrMentionsR15(&p.From) || isR15(p.Reg) || addrMentionsR15(p.GetFrom3()) { + // use before overwrite + return false + } + if p.As == AMOVL || p.As == AMOVQ || p.As == APOPQ { + return true + // TODO: MOVB might be ok if we only ever use R15B. + } + return false +} + +func addrUsesGlobal(a *obj.Addr) bool { + if a == nil { + return false + } + return a.Name == obj.NAME_EXTERN && !a.Sym.Local() +} +func progUsesGlobal(p *obj.Prog) bool { + if p.As == obj.ACALL || p.As == obj.ATEXT || p.As == obj.AFUNCDATA || p.As == obj.ARET || p.As == obj.AJMP { + // These opcodes don't use a GOT to access their argument (see rewriteToUseGot), + // or R15 would be dead at them anyway. + return false + } + if p.As == ALEAQ { + // The GOT entry is placed directly in the destination register; R15 is not used. + return false + } + return addrUsesGlobal(&p.From) || addrUsesGlobal(&p.To) || addrUsesGlobal(p.GetFrom3()) +} + +func errorCheck(ctxt *obj.Link, s *obj.LSym) { + // When dynamic linking, R15 is used to access globals. Reject code that + // uses R15 after a global variable access. + if !ctxt.Flag_dynlink { + return + } + + // Flood fill all the instructions where R15's value is junk. + // If there are any uses of R15 in that set, report an error. + var work []*obj.Prog + var mentionsR15 bool + for p := s.Func().Text; p != nil; p = p.Link { + if progUsesGlobal(p) { + work = append(work, p) + p.Mark |= markBit + } + if progMentionsR15(p) { + mentionsR15 = true + } + } + if mentionsR15 { + for len(work) > 0 { + p := work[len(work)-1] + work = work[:len(work)-1] + if q := p.To.Target(); q != nil && q.Mark&markBit == 0 { + q.Mark |= markBit + work = append(work, q) + } + if p.As == obj.AJMP || p.As == obj.ARET { + continue // no fallthrough + } + if progMentionsR15(p) { + if progOverwritesR15(p) { + // R15 is overwritten by this instruction. Its value is not junk any more. + continue + } + pos := ctxt.PosTable.Pos(p.Pos) + ctxt.Diag("%s:%s: when dynamic linking, R15 is clobbered by a global variable access and is used here: %v", path.Base(pos.Filename()), pos.LineNumber(), p) + break // only report one error + } + if q := p.Link; q != nil && q.Mark&markBit == 0 { + q.Mark |= markBit + work = append(work, q) + } + } + } + + // Clean up. + for p := s.Func().Text; p != nil; p = p.Link { + p.Mark &^= markBit + } +} + var unaryDst = map[obj.As]bool{ ABSWAPL: true, ABSWAPQ: true, @@ -1243,6 +1380,7 @@ var unaryDst = map[obj.As]bool{ var Linkamd64 = obj.LinkArch{ Arch: sys.ArchAMD64, Init: instinit, + ErrorCheck: errorCheck, Preprocess: preprocess, Assemble: span6, Progedit: progedit, diff --git a/src/cmd/internal/objabi/flag.go b/src/cmd/internal/objabi/flag.go index 3fd73f3c576b0f407ad462a550e80cdd04ea6aca..e41fc570b0894926a29ccd0736fb4199f53b0c6c 100644 --- a/src/cmd/internal/objabi/flag.go +++ b/src/cmd/internal/objabi/flag.go @@ -8,6 +8,7 @@ import ( "bytes" "flag" "fmt" + "internal/buildcfg" "io" "io/ioutil" "log" @@ -91,16 +92,18 @@ func (versionFlag) Set(s string) error { name = name[strings.LastIndex(name, `\`)+1:] name = strings.TrimSuffix(name, ".exe") - // If there's an active experiment, include that, - // to distinguish go1.10.2 with an experiment - // from go1.10.2 without an experiment. - p := Expstring() - if p == DefaultExpstring() { - p = "" - } - sep := "" - if p != "" { - sep = " " + p := "" + + if s == "goexperiment" { + // test/run.go uses this to discover the full set of + // experiment tags. Report everything. + p = " X:" + strings.Join(buildcfg.AllExperiments(), ",") + } else { + // If the enabled experiments differ from the defaults, + // include that difference. + if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" { + p = " X:" + goexperiment + } } // The go command invokes -V=full to get a unique identifier @@ -109,12 +112,12 @@ func (versionFlag) Set(s string) error { // build ID of the binary, so that if the compiler is changed and // rebuilt, we notice and rebuild all packages. if s == "full" { - if strings.HasPrefix(Version, "devel") { + if strings.HasPrefix(buildcfg.Version, "devel") { p += " buildID=" + buildID } } - fmt.Printf("%s version %s%s%s\n", name, Version, sep, p) + fmt.Printf("%s version %s%s\n", name, buildcfg.Version, p) os.Exit(0) return nil } diff --git a/src/cmd/internal/objabi/funcdata.go b/src/cmd/internal/objabi/funcdata.go index faa2863325d1ee79ea50da42e88edb324e2c44a2..4ff0ebe13d8dba777a742e6ac1f622a6508e933e 100644 --- a/src/cmd/internal/objabi/funcdata.go +++ b/src/cmd/internal/objabi/funcdata.go @@ -20,6 +20,7 @@ const ( FUNCDATA_StackObjects = 2 FUNCDATA_InlTree = 3 FUNCDATA_OpenCodedDeferInfo = 4 + FUNCDATA_ArgInfo = 5 // ArgsSizeUnknown is set in Func.argsize to mark all functions // whose argument size is unknown (C vararg functions, and diff --git a/src/cmd/internal/objabi/funcid.go b/src/cmd/internal/objabi/funcid.go index 1d098ee17251b379c354b01f840f53aef119c058..93ebd7be943205e9044d5f0fe66fb66f9bda8d82 100644 --- a/src/cmd/internal/objabi/funcid.go +++ b/src/cmd/internal/objabi/funcid.go @@ -4,97 +4,90 @@ package objabi +import "strings" + +// A FuncFlag records bits about a function, passed to the runtime. +type FuncFlag uint8 + +// Note: This list must match the list in runtime/symtab.go. +const ( + FuncFlag_TOPFRAME = 1 << iota + FuncFlag_SPWRITE +) + // A FuncID identifies particular functions that need to be treated // specially by the runtime. // Note that in some situations involving plugins, there may be multiple // copies of a particular special runtime function. -// Note: this list must match the list in runtime/symtab.go. type FuncID uint8 +// Note: this list must match the list in runtime/symtab.go. const ( FuncID_normal FuncID = iota // not a special function - FuncID_runtime_main + FuncID_abort + FuncID_asmcgocall + FuncID_asyncPreempt + FuncID_cgocallback + FuncID_debugCallV2 + FuncID_gcBgMarkWorker FuncID_goexit + FuncID_gogo + FuncID_gopanic + FuncID_handleAsyncEvent FuncID_jmpdefer FuncID_mcall FuncID_morestack FuncID_mstart + FuncID_panicwrap FuncID_rt0_go - FuncID_asmcgocall - FuncID_sigpanic FuncID_runfinq - FuncID_gcBgMarkWorker - FuncID_systemstack_switch + FuncID_runtime_main + FuncID_sigpanic FuncID_systemstack - FuncID_cgocallback - FuncID_gogo - FuncID_externalthreadhandler - FuncID_debugCallV1 - FuncID_gopanic - FuncID_panicwrap - FuncID_handleAsyncEvent - FuncID_asyncPreempt + FuncID_systemstack_switch FuncID_wrapper // any autogenerated code (hash/eq algorithms, method wrappers, etc.) ) +var funcIDs = map[string]FuncID{ + "abort": FuncID_abort, + "asmcgocall": FuncID_asmcgocall, + "asyncPreempt": FuncID_asyncPreempt, + "cgocallback": FuncID_cgocallback, + "debugCallV2": FuncID_debugCallV2, + "gcBgMarkWorker": FuncID_gcBgMarkWorker, + "go": FuncID_rt0_go, + "goexit": FuncID_goexit, + "gogo": FuncID_gogo, + "gopanic": FuncID_gopanic, + "handleAsyncEvent": FuncID_handleAsyncEvent, + "jmpdefer": FuncID_jmpdefer, + "main": FuncID_runtime_main, + "mcall": FuncID_mcall, + "morestack": FuncID_morestack, + "mstart": FuncID_mstart, + "panicwrap": FuncID_panicwrap, + "runfinq": FuncID_runfinq, + "sigpanic": FuncID_sigpanic, + "switch": FuncID_systemstack_switch, + "systemstack": FuncID_systemstack, + + // Don't show in call stack but otherwise not special. + "deferreturn": FuncID_wrapper, + "runOpenDeferFrame": FuncID_wrapper, + "reflectcallSave": FuncID_wrapper, + "deferCallSave": FuncID_wrapper, +} + // Get the function ID for the named function in the named file. // The function should be package-qualified. func GetFuncID(name string, isWrapper bool) FuncID { if isWrapper { return FuncID_wrapper } - switch name { - case "runtime.main": - return FuncID_runtime_main - case "runtime.goexit": - return FuncID_goexit - case "runtime.jmpdefer": - return FuncID_jmpdefer - case "runtime.mcall": - return FuncID_mcall - case "runtime.morestack": - return FuncID_morestack - case "runtime.mstart": - return FuncID_mstart - case "runtime.rt0_go": - return FuncID_rt0_go - case "runtime.asmcgocall": - return FuncID_asmcgocall - case "runtime.sigpanic": - return FuncID_sigpanic - case "runtime.runfinq": - return FuncID_runfinq - case "runtime.gcBgMarkWorker": - return FuncID_gcBgMarkWorker - case "runtime.systemstack_switch": - return FuncID_systemstack_switch - case "runtime.systemstack": - return FuncID_systemstack - case "runtime.cgocallback": - return FuncID_cgocallback - case "runtime.gogo": - return FuncID_gogo - case "runtime.externalthreadhandler": - return FuncID_externalthreadhandler - case "runtime.debugCallV1": - return FuncID_debugCallV1 - case "runtime.gopanic": - return FuncID_gopanic - case "runtime.panicwrap": - return FuncID_panicwrap - case "runtime.handleAsyncEvent": - return FuncID_handleAsyncEvent - case "runtime.asyncPreempt": - return FuncID_asyncPreempt - case "runtime.deferreturn": - // Don't show in the call stack (used when invoking defer functions) - return FuncID_wrapper - case "runtime.runOpenDeferFrame": - // Don't show in the call stack (used when invoking defer functions) - return FuncID_wrapper - case "runtime.reflectcallSave": - // Don't show in the call stack (used when invoking defer functions) - return FuncID_wrapper + if strings.HasPrefix(name, "runtime.") { + if id, ok := funcIDs[name[len("runtime."):]]; ok { + return id + } } return FuncID_normal } diff --git a/src/cmd/internal/objabi/line.go b/src/cmd/internal/objabi/line.go index 0733b65138db50371baf7a3268d036bcc986f4f8..0b1e0bb181c4d3866d96a1a0077a1c678c942c08 100644 --- a/src/cmd/internal/objabi/line.go +++ b/src/cmd/internal/objabi/line.go @@ -5,6 +5,7 @@ package objabi import ( + "internal/buildcfg" "os" "path/filepath" "strings" @@ -38,8 +39,8 @@ func AbsFile(dir, file, rewrites string) string { } abs, rewritten := ApplyRewrites(abs, rewrites) - if !rewritten && hasPathPrefix(abs, GOROOT) { - abs = "$GOROOT" + abs[len(GOROOT):] + if !rewritten && hasPathPrefix(abs, buildcfg.GOROOT) { + abs = "$GOROOT" + abs[len(buildcfg.GOROOT):] } if abs == "" { diff --git a/src/cmd/internal/objabi/path.go b/src/cmd/internal/objabi/path.go index fd1c9981c69d0f9b1220d7e37de1e08068d4584a..aacab9a0ca7fa920d980e5dd940a7669cf7ac61a 100644 --- a/src/cmd/internal/objabi/path.go +++ b/src/cmd/internal/objabi/path.go @@ -47,6 +47,8 @@ func PathToPrefix(s string) string { // some cases need to be aware of when they are building such a // package, for example to enable features such as ABI selectors in // assembly sources. +// +// Keep in sync with cmd/dist/build.go:IsRuntimePackagePath. func IsRuntimePackagePath(pkgpath string) bool { rval := false switch pkgpath { @@ -56,6 +58,8 @@ func IsRuntimePackagePath(pkgpath string) bool { rval = true case "syscall": rval = true + case "internal/bytealg": + rval = true default: rval = strings.HasPrefix(pkgpath, "runtime/internal") } diff --git a/src/cmd/internal/objabi/reloctype.go b/src/cmd/internal/objabi/reloctype.go index 649f6901944f9876b4a30a6118786f4898766d60..52827a6deeec5c6a3bcc6606aef5201bdf785c12 100644 --- a/src/cmd/internal/objabi/reloctype.go +++ b/src/cmd/internal/objabi/reloctype.go @@ -50,11 +50,6 @@ const ( // R_ADDROFF resolves to a 32-bit offset from the beginning of the section // holding the data being relocated to the referenced symbol. R_ADDROFF - // R_WEAKADDROFF resolves just like R_ADDROFF but is a weak relocation. - // A weak relocation does not make the symbol it refers to reachable, - // and is only honored by the linker if the symbol is in some other way - // reachable. - R_WEAKADDROFF R_SIZE R_CALL R_CALLARM @@ -106,6 +101,9 @@ const ( // *rtype, and may be set to zero by the linker if it determines the method // text is unreachable by the linked program. R_METHODOFF + // R_KEEP tells the linker to keep the referred-to symbol in the final binary + // if the symbol containing the R_KEEP relocation is in the final binary. + R_KEEP R_POWER_TOC R_GOTPCREL // R_JMPMIPS (only used on mips64) resolves to non-PC-relative target address @@ -172,8 +170,8 @@ const ( // R_POWER_TLS_LE is used to implement the "local exec" model for tls // access. It resolves to the offset of the thread-local symbol from the - // thread pointer (R13) and inserts this value into the low 16 bits of an - // instruction word. + // thread pointer (R13) and is split against a pair of instructions to + // support a 32 bit displacement. R_POWER_TLS_LE // R_POWER_TLS_IE is used to implement the "initial exec" model for tls access. It @@ -183,10 +181,12 @@ const ( // symbol from the thread pointer (R13)). R_POWER_TLS_IE - // R_POWER_TLS marks an X-form instruction such as "MOVD 0(R13)(R31*1), g" as - // accessing a particular thread-local symbol. It does not affect code generation - // but is used by the system linker when relaxing "initial exec" model code to - // "local exec" model code. + // R_POWER_TLS marks an X-form instruction such as "ADD R3,R13,R4" as completing + // a sequence of GOT-relative relocations to compute a TLS address. This can be + // used by the system linker to to rewrite the GOT-relative TLS relocation into a + // simpler thread-pointer relative relocation. See table 3.26 and 3.28 in the + // ppc64 elfv2 1.4 ABI on this transformation. Likewise, the second argument + // (usually called RB in X-form instructions) is assumed to be R13. R_POWER_TLS // R_ADDRPOWER_DS is similar to R_ADDRPOWER above, but assumes the second @@ -256,6 +256,15 @@ const ( // of a symbol. This isn't a real relocation, it can be placed in anywhere // in a symbol and target any symbols. R_XCOFFREF + + // R_WEAK marks the relocation as a weak reference. + // A weak relocation does not make the symbol it refers to reachable, + // and is only honored by the linker if the symbol is in some other way + // reachable. + R_WEAK = -1 << 15 + + R_WEAKADDR = R_WEAK | R_ADDR + R_WEAKADDROFF = R_WEAK | R_ADDROFF ) // IsDirectCall reports whether r is a relocation for a direct call. diff --git a/src/cmd/internal/objabi/reloctype_string.go b/src/cmd/internal/objabi/reloctype_string.go index 658a44f8b81bfa4bf3abefc43f4d23f5b4f46fd6..4638ef14d91f62b58be4b6e199d79432d573040b 100644 --- a/src/cmd/internal/objabi/reloctype_string.go +++ b/src/cmd/internal/objabi/reloctype_string.go @@ -13,28 +13,28 @@ func _() { _ = x[R_ADDRARM64-3] _ = x[R_ADDRMIPS-4] _ = x[R_ADDROFF-5] - _ = x[R_WEAKADDROFF-6] - _ = x[R_SIZE-7] - _ = x[R_CALL-8] - _ = x[R_CALLARM-9] - _ = x[R_CALLARM64-10] - _ = x[R_CALLIND-11] - _ = x[R_CALLPOWER-12] - _ = x[R_CALLMIPS-13] - _ = x[R_CALLRISCV-14] - _ = x[R_CONST-15] - _ = x[R_PCREL-16] - _ = x[R_TLS_LE-17] - _ = x[R_TLS_IE-18] - _ = x[R_GOTOFF-19] - _ = x[R_PLT0-20] - _ = x[R_PLT1-21] - _ = x[R_PLT2-22] - _ = x[R_USEFIELD-23] - _ = x[R_USETYPE-24] - _ = x[R_USEIFACE-25] - _ = x[R_USEIFACEMETHOD-26] - _ = x[R_METHODOFF-27] + _ = x[R_SIZE-6] + _ = x[R_CALL-7] + _ = x[R_CALLARM-8] + _ = x[R_CALLARM64-9] + _ = x[R_CALLIND-10] + _ = x[R_CALLPOWER-11] + _ = x[R_CALLMIPS-12] + _ = x[R_CALLRISCV-13] + _ = x[R_CONST-14] + _ = x[R_PCREL-15] + _ = x[R_TLS_LE-16] + _ = x[R_TLS_IE-17] + _ = x[R_GOTOFF-18] + _ = x[R_PLT0-19] + _ = x[R_PLT1-20] + _ = x[R_PLT2-21] + _ = x[R_USEFIELD-22] + _ = x[R_USETYPE-23] + _ = x[R_USEIFACE-24] + _ = x[R_USEIFACEMETHOD-25] + _ = x[R_METHODOFF-26] + _ = x[R_KEEP-27] _ = x[R_POWER_TOC-28] _ = x[R_GOTPCREL-29] _ = x[R_JMPMIPS-30] @@ -70,9 +70,9 @@ func _() { _ = x[R_XCOFFREF-60] } -const _RelocType_name = "R_ADDRR_ADDRPOWERR_ADDRARM64R_ADDRMIPSR_ADDROFFR_WEAKADDROFFR_SIZER_CALLR_CALLARMR_CALLARM64R_CALLINDR_CALLPOWERR_CALLMIPSR_CALLRISCVR_CONSTR_PCRELR_TLS_LER_TLS_IER_GOTOFFR_PLT0R_PLT1R_PLT2R_USEFIELDR_USETYPER_USEIFACER_USEIFACEMETHODR_METHODOFFR_POWER_TOCR_GOTPCRELR_JMPMIPSR_DWARFSECREFR_DWARFFILEREFR_ARM64_TLS_LER_ARM64_TLS_IER_ARM64_GOTPCRELR_ARM64_GOTR_ARM64_PCRELR_ARM64_LDST8R_ARM64_LDST16R_ARM64_LDST32R_ARM64_LDST64R_ARM64_LDST128R_POWER_TLS_LER_POWER_TLS_IER_POWER_TLSR_ADDRPOWER_DSR_ADDRPOWER_GOTR_ADDRPOWER_PCRELR_ADDRPOWER_TOCRELR_ADDRPOWER_TOCREL_DSR_RISCV_PCREL_ITYPER_RISCV_PCREL_STYPER_RISCV_TLS_IE_ITYPER_RISCV_TLS_IE_STYPER_PCRELDBLR_ADDRMIPSUR_ADDRMIPSTLSR_ADDRCUOFFR_WASMIMPORTR_XCOFFREF" +const _RelocType_name = "R_ADDRR_ADDRPOWERR_ADDRARM64R_ADDRMIPSR_ADDROFFR_SIZER_CALLR_CALLARMR_CALLARM64R_CALLINDR_CALLPOWERR_CALLMIPSR_CALLRISCVR_CONSTR_PCRELR_TLS_LER_TLS_IER_GOTOFFR_PLT0R_PLT1R_PLT2R_USEFIELDR_USETYPER_USEIFACER_USEIFACEMETHODR_METHODOFFR_KEEPR_POWER_TOCR_GOTPCRELR_JMPMIPSR_DWARFSECREFR_DWARFFILEREFR_ARM64_TLS_LER_ARM64_TLS_IER_ARM64_GOTPCRELR_ARM64_GOTR_ARM64_PCRELR_ARM64_LDST8R_ARM64_LDST16R_ARM64_LDST32R_ARM64_LDST64R_ARM64_LDST128R_POWER_TLS_LER_POWER_TLS_IER_POWER_TLSR_ADDRPOWER_DSR_ADDRPOWER_GOTR_ADDRPOWER_PCRELR_ADDRPOWER_TOCRELR_ADDRPOWER_TOCREL_DSR_RISCV_PCREL_ITYPER_RISCV_PCREL_STYPER_RISCV_TLS_IE_ITYPER_RISCV_TLS_IE_STYPER_PCRELDBLR_ADDRMIPSUR_ADDRMIPSTLSR_ADDRCUOFFR_WASMIMPORTR_XCOFFREF" -var _RelocType_index = [...]uint16{0, 6, 17, 28, 38, 47, 60, 66, 72, 81, 92, 101, 112, 122, 133, 140, 147, 155, 163, 171, 177, 183, 189, 199, 208, 218, 234, 245, 256, 266, 275, 288, 302, 316, 330, 346, 357, 370, 383, 397, 411, 425, 440, 454, 468, 479, 493, 508, 525, 543, 564, 583, 602, 622, 642, 652, 663, 676, 687, 699, 709} +var _RelocType_index = [...]uint16{0, 6, 17, 28, 38, 47, 53, 59, 68, 79, 88, 99, 109, 120, 127, 134, 142, 150, 158, 164, 170, 176, 186, 195, 205, 221, 232, 238, 249, 259, 268, 281, 295, 309, 323, 339, 350, 363, 376, 390, 404, 418, 433, 447, 461, 472, 486, 501, 518, 536, 557, 576, 595, 615, 635, 645, 656, 669, 680, 692, 702} func (i RelocType) String() string { i -= 1 diff --git a/src/cmd/internal/objabi/stack.go b/src/cmd/internal/objabi/stack.go index 05a1d4a4b58caf1b36468b71e8ee9ecfa8b0ec42..0c82a7c6dd10e76285325776d3c0abdc2f6249b1 100644 --- a/src/cmd/internal/objabi/stack.go +++ b/src/cmd/internal/objabi/stack.go @@ -4,6 +4,8 @@ package objabi +import "internal/buildcfg" + // For the linkers. Must match Go definitions. const ( @@ -13,10 +15,6 @@ const ( StackSmall = 128 ) -const ( - StackPreempt = -1314 // 0xfff...fade -) - // Initialize StackGuard and StackLimit according to target system. var StackGuard = 928*stackGuardMultiplier() + StackSystem var StackLimit = StackGuard - StackSystem - StackSmall @@ -26,7 +24,7 @@ var StackLimit = StackGuard - StackSystem - StackSmall // builds that have larger stack frames or for specific targets. func stackGuardMultiplier() int { // On AIX, a larger stack is needed for syscalls. - if GOOS == "aix" { + if buildcfg.GOOS == "aix" { return 2 } return stackGuardMultiplierDefault diff --git a/src/cmd/internal/objabi/util.go b/src/cmd/internal/objabi/util.go index a73ab479a16337c49a32b38b2b5fcf272232326c..63640950d9f8ee609ad30e607c1528b4664d5b77 100644 --- a/src/cmd/internal/objabi/util.go +++ b/src/cmd/internal/objabi/util.go @@ -6,32 +6,9 @@ package objabi import ( "fmt" - "log" - "os" "strings" -) - -func envOr(key, value string) string { - if x := os.Getenv(key); x != "" { - return x - } - return value -} -var ( - defaultGOROOT string // set by linker - - GOROOT = envOr("GOROOT", defaultGOROOT) - GOARCH = envOr("GOARCH", defaultGOARCH) - GOOS = envOr("GOOS", defaultGOOS) - GO386 = envOr("GO386", defaultGO386) - GOARM = goarm() - GOMIPS = gomips() - GOMIPS64 = gomips64() - GOPPC64 = goppc64() - GOWASM = gowasm() - GO_LDSO = defaultGO_LDSO - Version = version + "internal/buildcfg" ) const ( @@ -39,163 +16,10 @@ const ( MachoRelocOffset = 2048 // reserve enough space for ELF relocations ) -func goarm() int { - def := defaultGOARM - if GOOS == "android" && GOARCH == "arm" { - // Android arm devices always support GOARM=7. - def = "7" - } - switch v := envOr("GOARM", def); v { - case "5": - return 5 - case "6": - return 6 - case "7": - return 7 - } - // Fail here, rather than validate at multiple call sites. - log.Fatalf("Invalid GOARM value. Must be 5, 6, or 7.") - panic("unreachable") -} - -func gomips() string { - switch v := envOr("GOMIPS", defaultGOMIPS); v { - case "hardfloat", "softfloat": - return v - } - log.Fatalf("Invalid GOMIPS value. Must be hardfloat or softfloat.") - panic("unreachable") -} - -func gomips64() string { - switch v := envOr("GOMIPS64", defaultGOMIPS64); v { - case "hardfloat", "softfloat": - return v - } - log.Fatalf("Invalid GOMIPS64 value. Must be hardfloat or softfloat.") - panic("unreachable") -} - -func goppc64() int { - switch v := envOr("GOPPC64", defaultGOPPC64); v { - case "power8": - return 8 - case "power9": - return 9 - } - log.Fatalf("Invalid GOPPC64 value. Must be power8 or power9.") - panic("unreachable") -} - -type gowasmFeatures struct { - SignExt bool - SatConv bool -} - -func (f gowasmFeatures) String() string { - var flags []string - if f.SatConv { - flags = append(flags, "satconv") - } - if f.SignExt { - flags = append(flags, "signext") - } - return strings.Join(flags, ",") -} - -func gowasm() (f gowasmFeatures) { - for _, opt := range strings.Split(envOr("GOWASM", ""), ",") { - switch opt { - case "satconv": - f.SatConv = true - case "signext": - f.SignExt = true - case "": - // ignore - default: - log.Fatalf("Invalid GOWASM value. No such feature: " + opt) - } - } - return -} - -func Getgoextlinkenabled() string { - return envOr("GO_EXTLINK_ENABLED", defaultGO_EXTLINK_ENABLED) -} - -func init() { - for _, f := range strings.Split(goexperiment, ",") { - if f != "" { - addexp(f) - } - } - - // regabi is only supported on amd64. - if GOARCH != "amd64" { - Regabi_enabled = 0 - } -} - -// Note: must agree with runtime.framepointer_enabled. -var Framepointer_enabled = GOARCH == "amd64" || GOARCH == "arm64" && (GOOS == "linux" || GOOS == "darwin" || GOOS == "ios") - -func addexp(s string) { - // Could do general integer parsing here, but the runtime copy doesn't yet. - v := 1 - name := s - if len(name) > 2 && name[:2] == "no" { - v = 0 - name = name[2:] - } - for i := 0; i < len(exper); i++ { - if exper[i].name == name { - if exper[i].val != nil { - *exper[i].val = v - } - return - } - } - - fmt.Printf("unknown experiment %s\n", s) - os.Exit(2) -} - -var ( - Fieldtrack_enabled int - Preemptibleloops_enabled int - Staticlockranking_enabled int - Regabi_enabled int -) - -// Toolchain experiments. -// These are controlled by the GOEXPERIMENT environment -// variable recorded when the toolchain is built. -// This list is also known to cmd/gc. -var exper = []struct { - name string - val *int -}{ - {"fieldtrack", &Fieldtrack_enabled}, - {"preemptibleloops", &Preemptibleloops_enabled}, - {"staticlockranking", &Staticlockranking_enabled}, - {"regabi", &Regabi_enabled}, -} - -var defaultExpstring = Expstring() - -func DefaultExpstring() string { - return defaultExpstring -} - -func Expstring() string { - buf := "X" - for i := range exper { - if *exper[i].val != 0 { - buf += "," + exper[i].name - } - } - if buf == "X" { - buf += ",none" - } - return "X:" + buf[2:] +// HeaderString returns the toolchain configuration string written in +// Go object headers. This string ensures we don't attempt to import +// or link object files that are incompatible with each other. This +// string always starts with "go object ". +func HeaderString() string { + return fmt.Sprintf("go object %s %s %s X:%s\n", buildcfg.GOOS, buildcfg.GOARCH, buildcfg.Version, strings.Join(buildcfg.EnabledExperiments(), ",")) } diff --git a/src/cmd/internal/objfile/goobj.go b/src/cmd/internal/objfile/goobj.go index f19bec5dcb2d8ab8d8f271ae4f7bd476d2d4debe..dd21d223511198448d127596c53095c1ab924700 100644 --- a/src/cmd/internal/objfile/goobj.go +++ b/src/cmd/internal/objfile/goobj.go @@ -168,7 +168,7 @@ func (f *goobjFile) symbols() ([]Sym, error) { code = 'T' case objabi.SRODATA: code = 'R' - case objabi.SDATA: + case objabi.SNOPTRDATA, objabi.SDATA: code = 'D' case objabi.SBSS, objabi.SNOPTRBSS, objabi.STLSBSS: code = 'B' diff --git a/src/cmd/internal/objfile/objfile.go b/src/cmd/internal/objfile/objfile.go index a58e0e159c8a5b98d0500b75aed04d3b350579c2..dcfd158ec209274891640b0cd74fc35611fb0026 100644 --- a/src/cmd/internal/objfile/objfile.go +++ b/src/cmd/internal/objfile/objfile.go @@ -6,6 +6,7 @@ package objfile import ( + "cmd/internal/archive" "debug/dwarf" "debug/gosym" "fmt" @@ -73,6 +74,8 @@ func Open(name string) (*File, error) { } if f, err := openGoFile(r); err == nil { return f, nil + } else if _, ok := err.(archive.ErrGoObjOtherVersion); ok { + return nil, fmt.Errorf("open %s: %v", name, err) } for _, try := range openers { if raw, err := try(r); err == nil { diff --git a/src/cmd/internal/objfile/pe.go b/src/cmd/internal/objfile/pe.go index b20cda9a44be994e4e225eccf2c205a65c77a187..9088866fcf5326f9b84757bf72aa582acbc77096 100644 --- a/src/cmd/internal/objfile/pe.go +++ b/src/cmd/internal/objfile/pe.go @@ -189,6 +189,8 @@ func (f *peFile) goarch() string { return "amd64" case pe.IMAGE_FILE_MACHINE_ARMNT: return "arm" + case pe.IMAGE_FILE_MACHINE_ARM64: + return "arm64" default: return "" } diff --git a/src/cmd/internal/sys/arch.go b/src/cmd/internal/sys/arch.go index e8687363defc502c17cdb97660b7c4ac886d799d..a3e39768b6f5057488be62711d80de8dcbe459ac 100644 --- a/src/cmd/internal/sys/arch.go +++ b/src/cmd/internal/sys/arch.go @@ -40,6 +40,12 @@ type Arch struct { // MinLC is the minimum length of an instruction code. MinLC int + + // Alignment is maximum alignment required by the architecture + // for any (compiler-generated) load or store instruction. + // Loads or stores smaller than Alignment must be naturally aligned. + // Loads or stores larger than Alignment need only be Alignment-aligned. + Alignment int8 } // InFamily reports whether a is a member of any of the specified @@ -60,6 +66,7 @@ var Arch386 = &Arch{ PtrSize: 4, RegSize: 4, MinLC: 1, + Alignment: 1, } var ArchAMD64 = &Arch{ @@ -69,6 +76,7 @@ var ArchAMD64 = &Arch{ PtrSize: 8, RegSize: 8, MinLC: 1, + Alignment: 1, } var ArchARM = &Arch{ @@ -78,6 +86,7 @@ var ArchARM = &Arch{ PtrSize: 4, RegSize: 4, MinLC: 4, + Alignment: 4, // TODO: just for arm5? } var ArchARM64 = &Arch{ @@ -87,6 +96,7 @@ var ArchARM64 = &Arch{ PtrSize: 8, RegSize: 8, MinLC: 4, + Alignment: 1, } var ArchMIPS = &Arch{ @@ -96,6 +106,7 @@ var ArchMIPS = &Arch{ PtrSize: 4, RegSize: 4, MinLC: 4, + Alignment: 4, } var ArchMIPSLE = &Arch{ @@ -105,6 +116,7 @@ var ArchMIPSLE = &Arch{ PtrSize: 4, RegSize: 4, MinLC: 4, + Alignment: 4, } var ArchMIPS64 = &Arch{ @@ -114,6 +126,7 @@ var ArchMIPS64 = &Arch{ PtrSize: 8, RegSize: 8, MinLC: 4, + Alignment: 8, } var ArchMIPS64LE = &Arch{ @@ -123,6 +136,7 @@ var ArchMIPS64LE = &Arch{ PtrSize: 8, RegSize: 8, MinLC: 4, + Alignment: 8, } var ArchPPC64 = &Arch{ @@ -132,6 +146,7 @@ var ArchPPC64 = &Arch{ PtrSize: 8, RegSize: 8, MinLC: 4, + Alignment: 1, } var ArchPPC64LE = &Arch{ @@ -141,6 +156,7 @@ var ArchPPC64LE = &Arch{ PtrSize: 8, RegSize: 8, MinLC: 4, + Alignment: 1, } var ArchRISCV64 = &Arch{ @@ -150,6 +166,7 @@ var ArchRISCV64 = &Arch{ PtrSize: 8, RegSize: 8, MinLC: 4, + Alignment: 8, // riscv unaligned loads work, but are really slow (trap + simulated by OS) } var ArchS390X = &Arch{ @@ -159,6 +176,7 @@ var ArchS390X = &Arch{ PtrSize: 8, RegSize: 8, MinLC: 2, + Alignment: 1, } var ArchWasm = &Arch{ @@ -168,6 +186,7 @@ var ArchWasm = &Arch{ PtrSize: 8, RegSize: 8, MinLC: 1, + Alignment: 1, } var Archs = [...]*Arch{ diff --git a/src/cmd/internal/sys/supported.go b/src/cmd/internal/sys/supported.go index ef7c017bd4abb37ba517dafdc2fd4f53193af97d..0d2bad961278f4392c3c27a1ecc4617ac2d9b74c 100644 --- a/src/cmd/internal/sys/supported.go +++ b/src/cmd/internal/sys/supported.go @@ -15,7 +15,7 @@ func RaceDetectorSupported(goos, goarch string) bool { return goarch == "amd64" || goarch == "ppc64le" || goarch == "arm64" case "darwin": return goarch == "amd64" || goarch == "arm64" - case "freebsd", "netbsd", "windows": + case "freebsd", "netbsd", "openbsd", "windows": return goarch == "amd64" default: return false @@ -23,7 +23,8 @@ func RaceDetectorSupported(goos, goarch string) bool { } // MSanSupported reports whether goos/goarch supports the memory -// sanitizer option. There is a copy of this function in cmd/dist/test.go. +// sanitizer option. +// There is a copy of this function in misc/cgo/testsanitizers/cc_test.go. func MSanSupported(goos, goarch string) bool { switch goos { case "linux": @@ -73,7 +74,7 @@ func BuildModeSupported(compiler, buildmode, goos, goarch string) bool { "android/amd64", "android/arm", "android/arm64", "android/386", "freebsd/amd64", "darwin/amd64", "darwin/arm64", - "windows/amd64", "windows/386": + "windows/amd64", "windows/386", "windows/arm64": return true } return false diff --git a/src/cmd/link/cgo_test.go b/src/cmd/link/cgo_test.go new file mode 100644 index 0000000000000000000000000000000000000000..26ab8024541d6cfc66b1b6694e4725779bd0d81a --- /dev/null +++ b/src/cmd/link/cgo_test.go @@ -0,0 +1,141 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "internal/testenv" + "os" + "os/exec" + "path/filepath" + "testing" +) + +// Issues 43830, 46295 +func TestCGOLTO(t *testing.T) { + testenv.MustHaveCGO(t) + testenv.MustHaveGoBuild(t) + + t.Parallel() + + for _, cc := range []string{"gcc", "clang"} { + for test := 0; test < 2; test++ { + t.Run(fmt.Sprintf("%s-%d", cc, test), func(t *testing.T) { + testCGOLTO(t, cc, test) + }) + } + } +} + +const test1_main = ` +package main + +/* +extern int myadd(int, int); +int c_add(int a, int b) { + return myadd(a, b); +} +*/ +import "C" + +func main() { + println(C.c_add(1, 2)) +} +` + +const test1_add = ` +package main + +import "C" + +/* test */ + +//export myadd +func myadd(a C.int, b C.int) C.int { + return a + b +} +` + +const test2_main = ` +package main + +import "fmt" + +/* +#include + +void hello(void) { + printf("hello\n"); +} +*/ +import "C" + +func main() { + hello := C.hello + fmt.Printf("%v\n", hello) +} +` + +func testCGOLTO(t *testing.T, cc string, test int) { + t.Parallel() + + if _, err := exec.LookPath(cc); err != nil { + t.Skipf("no %s compiler", cc) + } + + dir := t.TempDir() + + writeTempFile := func(name, contents string) { + if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil { + t.Fatal(err) + } + } + + writeTempFile("go.mod", "module cgolto\n") + + switch test { + case 0: + writeTempFile("main.go", test1_main) + writeTempFile("add.go", test1_add) + case 1: + writeTempFile("main.go", test2_main) + default: + t.Fatalf("bad case %d", test) + } + + cmd := exec.Command(testenv.GoToolPath(t), "build") + cmd.Dir = dir + cmd.Env = append(os.Environ(), + "CC="+cc, + "CGO_CFLAGS=-flto", + ) + + t.Log("go build") + out, err := cmd.CombinedOutput() + t.Logf("%s", out) + + if err != nil { + t.Logf("go build failed: %v", err) + + // Error messages we've seen indicating that LTO is not supported. + // These errors come from GCC or clang, not Go. + var noLTO = []string{ + `unrecognized command line option "-flto"`, + "unable to pass LLVM bit-code files to linker", + "file not recognized: File format not recognized", + "LTO support has not been enabled", + "linker command failed with exit code", + "gcc: can't load library", + } + for _, msg := range noLTO { + if bytes.Contains(out, []byte(msg)) { + t.Skipf("C compiler %v does not support LTO", cc) + } + } + + t.Error("failed") + } +} diff --git a/src/cmd/link/dwarf_test.go b/src/cmd/link/dwarf_test.go index db710bed6ab0621a014e6be58a7cdd0fa93309a6..3ca59bd47f025cf1eac24eac43fddec1700c2348 100644 --- a/src/cmd/link/dwarf_test.go +++ b/src/cmd/link/dwarf_test.go @@ -10,7 +10,6 @@ import ( "cmd/internal/objfile" "debug/dwarf" "internal/testenv" - "io/ioutil" "os" "os/exec" "path" @@ -20,6 +19,36 @@ import ( "testing" ) +// TestMain allows this test binary to run as a -toolexec wrapper for the 'go' +// command. If LINK_TEST_TOOLEXEC is set, TestMain runs the binary as if it were +// cmd/link, and otherwise runs the requested tool as a subprocess. +// +// This allows the test to verify the behavior of the current contents of the +// cmd/link package even if the installed cmd/link binary is stale. +func TestMain(m *testing.M) { + if os.Getenv("LINK_TEST_TOOLEXEC") == "" { + // Not running as a -toolexec wrapper. Just run the tests. + os.Exit(m.Run()) + } + + if strings.TrimSuffix(filepath.Base(os.Args[1]), ".exe") == "link" { + // Running as a -toolexec linker, and the tool is cmd/link. + // Substitute this test binary for the linker. + os.Args = os.Args[1:] + main() + os.Exit(0) + } + + cmd := exec.Command(os.Args[1], os.Args[2:]...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + os.Exit(1) + } + os.Exit(0) +} + func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) { testenv.MustHaveCGO(t) testenv.MustHaveGoBuild(t) @@ -30,17 +59,6 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) t.Parallel() - out, err := exec.Command(testenv.GoToolPath(t), "list", "-f", "{{.Stale}}", "cmd/link").CombinedOutput() - if err != nil { - t.Fatalf("go list: %v\n%s", err, out) - } - if string(out) != "false\n" { - if strings.HasPrefix(testenv.Builder(), "darwin-") { - t.Skipf("cmd/link is spuriously stale on Darwin builders - see #33598") - } - t.Fatalf("cmd/link is stale - run go install cmd/link") - } - for _, prog := range []string{"testprog", "testprogcgo"} { prog := prog expectDWARF := expectDWARF @@ -49,33 +67,28 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) if extld == "" { extld = "gcc" } + var err error expectDWARF, err = cmddwarf.IsDWARFEnabledOnAIXLd(extld) if err != nil { t.Fatal(err) } - } t.Run(prog, func(t *testing.T) { t.Parallel() - tmpDir, err := ioutil.TempDir("", "go-link-TestDWARF") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) + tmpDir := t.TempDir() exe := filepath.Join(tmpDir, prog+".exe") dir := "../../runtime/testdata/" + prog - cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe) + cmd := exec.Command(testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-o", exe) if buildmode != "" { cmd.Args = append(cmd.Args, "-buildmode", buildmode) } cmd.Args = append(cmd.Args, dir) - if env != nil { - cmd.Env = append(os.Environ(), env...) - cmd.Env = append(cmd.Env, "CGO_CFLAGS=") // ensure CGO_CFLAGS does not contain any flags. Issue #35459 - } + cmd.Env = append(os.Environ(), env...) + cmd.Env = append(cmd.Env, "CGO_CFLAGS=") // ensure CGO_CFLAGS does not contain any flags. Issue #35459 + cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out) @@ -91,7 +104,8 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) exe = filepath.Join(tmpDir, "go.o") } - if runtime.GOOS == "darwin" { + darwinSymbolTestIsTooFlaky := true // Turn this off, it is too flaky -- See #32218 + if runtime.GOOS == "darwin" && !darwinSymbolTestIsTooFlaky { if _, err = exec.LookPath("symbols"); err == nil { // Ensure Apple's tooling can parse our object for symbols. out, err = exec.Command("symbols", exe).CombinedOutput() diff --git a/src/cmd/link/elf_test.go b/src/cmd/link/elf_test.go index 20754d09f5ac9d0c085d251463d0e172a59bfedd..012c0b51696f8ddfcaae71d48ae8ba5ae315215e 100644 --- a/src/cmd/link/elf_test.go +++ b/src/cmd/link/elf_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build dragonfly || freebsd || linux || netbsd || openbsd // +build dragonfly freebsd linux netbsd openbsd package main @@ -69,11 +70,7 @@ func TestSectionsWithSameName(t *testing.T) { t.Skipf("can't find objcopy: %v", err) } - dir, err := ioutil.TempDir("", "go-link-TestSectionsWithSameName") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() gopath := filepath.Join(dir, "GOPATH") env := append(os.Environ(), "GOPATH="+gopath) @@ -143,11 +140,7 @@ func TestMinusRSymsWithSameName(t *testing.T) { testenv.MustHaveCGO(t) t.Parallel() - dir, err := ioutil.TempDir("", "go-link-TestMinusRSymsWithSameName") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() gopath := filepath.Join(dir, "GOPATH") env := append(os.Environ(), "GOPATH="+gopath) @@ -270,11 +263,7 @@ func TestPIESize(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - dir, err := ioutil.TempDir("", "go-link-"+name) - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() writeGo(t, dir) diff --git a/src/cmd/link/internal/amd64/asm.go b/src/cmd/link/internal/amd64/asm.go index 2d09a6160aaae191a05311dd82e770a6b163027c..fb960491de7215817ccbe9f285f8f8751ad63190 100644 --- a/src/cmd/link/internal/amd64/asm.go +++ b/src/cmd/link/internal/amd64/asm.go @@ -548,7 +548,7 @@ func archreloc(*ld.Target, *loader.Loader, *ld.ArchSyms, loader.Reloc, loader.Sy return -1, 0, false } -func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64) int64 { +func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64, []byte) int64 { log.Fatalf("unexpected relocation variant") return -1 } diff --git a/src/cmd/link/internal/arm/asm.go b/src/cmd/link/internal/arm/asm.go index 03caeae7bec820fad3343301cc448cc7e3ac6113..ab780214bb65f547c3dc89d7446939fade4e7c63 100644 --- a/src/cmd/link/internal/arm/asm.go +++ b/src/cmd/link/internal/arm/asm.go @@ -111,7 +111,7 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade return false } - // Handle relocations found in ELF object files. + // Handle relocations found in ELF object files. case objabi.ElfRelocOffset + objabi.RelocType(elf.R_ARM_PLT32): su := ldr.MakeSymbolUpdater(s) su.SetRelocType(rIdx, objabi.R_CALLARM) @@ -237,6 +237,21 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade su.SetRelocSym(rIdx, 0) return true } + + case objabi.R_GOTPCREL: + if target.IsExternal() { + // External linker will do this relocation. + return true + } + if targType != sym.SDYNIMPORT { + ldr.Errorf(s, "R_GOTPCREL target is not SDYNIMPORT symbol: %v", ldr.SymName(targ)) + } + ld.AddGotSym(target, ldr, syms, targ, uint32(elf.R_ARM_GLOB_DAT)) + su := ldr.MakeSymbolUpdater(s) + su.SetRelocType(rIdx, objabi.R_PCREL) + su.SetRelocSym(rIdx, syms.GOT) + su.SetRelocAdd(rIdx, r.Add()+int64(ldr.SymGot(targ))) + return true } return false @@ -369,6 +384,12 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { relocs := ldr.Relocs(s) r := relocs.At(ri) switch r.Type() { + case objabi.ElfRelocOffset + objabi.RelocType(elf.R_ARM_CALL), + objabi.ElfRelocOffset + objabi.RelocType(elf.R_ARM_PC24), + objabi.ElfRelocOffset + objabi.RelocType(elf.R_ARM_JUMP24): + // Host object relocations that will be turned into a PLT call. + // The PLT may be too far. Insert a trampoline for them. + fallthrough case objabi.R_CALLARM: var t int64 // ldr.SymValue(rs) == 0 indicates a cross-package jump to a function that is not yet @@ -415,7 +436,7 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { // trampoline does not exist, create one trampb := ldr.MakeSymbolUpdater(tramp) ctxt.AddTramp(trampb) - if ctxt.DynlinkingGo() { + if ctxt.DynlinkingGo() || ldr.SymType(rs) == sym.SDYNIMPORT { if immrot(uint32(offset)) == 0 { ctxt.Errorf(s, "odd offset in dynlink direct call: %v+%d", ldr.SymName(rs), offset) } @@ -443,8 +464,8 @@ func gentramp(arch *sys.Arch, linkmode ld.LinkMode, ldr *loader.Loader, tramp *l tramp.SetSize(12) // 3 instructions P := make([]byte, tramp.Size()) t := ldr.SymValue(target) + offset - o1 := uint32(0xe5900000 | 11<<12 | 15<<16) // MOVW (R15), R11 // R15 is actual pc + 8 - o2 := uint32(0xe12fff10 | 11) // JMP (R11) + o1 := uint32(0xe5900000 | 12<<12 | 15<<16) // MOVW (R15), R12 // R15 is actual pc + 8 + o2 := uint32(0xe12fff10 | 12) // JMP (R12) o3 := uint32(t) // WORD $target arch.ByteOrder.PutUint32(P, o1) arch.ByteOrder.PutUint32(P[4:], o2) @@ -464,9 +485,9 @@ func gentramp(arch *sys.Arch, linkmode ld.LinkMode, ldr *loader.Loader, tramp *l func gentramppic(arch *sys.Arch, tramp *loader.SymbolBuilder, target loader.Sym, offset int64) { tramp.SetSize(16) // 4 instructions P := make([]byte, tramp.Size()) - o1 := uint32(0xe5900000 | 11<<12 | 15<<16 | 4) // MOVW 4(R15), R11 // R15 is actual pc + 8 - o2 := uint32(0xe0800000 | 11<<12 | 15<<16 | 11) // ADD R15, R11, R11 - o3 := uint32(0xe12fff10 | 11) // JMP (R11) + o1 := uint32(0xe5900000 | 12<<12 | 15<<16 | 4) // MOVW 4(R15), R12 // R15 is actual pc + 8 + o2 := uint32(0xe0800000 | 12<<12 | 15<<16 | 12) // ADD R15, R12, R12 + o3 := uint32(0xe12fff10 | 12) // JMP (R12) o4 := uint32(0) // WORD $(target-pc) // filled in with relocation arch.ByteOrder.PutUint32(P, o1) arch.ByteOrder.PutUint32(P[4:], o2) @@ -484,10 +505,10 @@ func gentramppic(arch *sys.Arch, tramp *loader.SymbolBuilder, target loader.Sym, // generate a trampoline to target+offset in dynlink mode (using GOT) func gentrampdyn(arch *sys.Arch, tramp *loader.SymbolBuilder, target loader.Sym, offset int64) { tramp.SetSize(20) // 5 instructions - o1 := uint32(0xe5900000 | 11<<12 | 15<<16 | 8) // MOVW 8(R15), R11 // R15 is actual pc + 8 - o2 := uint32(0xe0800000 | 11<<12 | 15<<16 | 11) // ADD R15, R11, R11 - o3 := uint32(0xe5900000 | 11<<12 | 11<<16) // MOVW (R11), R11 - o4 := uint32(0xe12fff10 | 11) // JMP (R11) + o1 := uint32(0xe5900000 | 12<<12 | 15<<16 | 8) // MOVW 8(R15), R12 // R15 is actual pc + 8 + o2 := uint32(0xe0800000 | 12<<12 | 15<<16 | 12) // ADD R15, R12, R12 + o3 := uint32(0xe5900000 | 12<<12 | 12<<16) // MOVW (R12), R12 + o4 := uint32(0xe12fff10 | 12) // JMP (R12) o5 := uint32(0) // WORD $target@GOT // filled in with relocation o6 := uint32(0) if offset != 0 { @@ -495,8 +516,8 @@ func gentrampdyn(arch *sys.Arch, tramp *loader.SymbolBuilder, target loader.Sym, tramp.SetSize(24) // 6 instructions o6 = o5 o5 = o4 - o4 = 0xe2800000 | 11<<12 | 11<<16 | immrot(uint32(offset)) // ADD $offset, R11, R11 - o1 = uint32(0xe5900000 | 11<<12 | 15<<16 | 12) // MOVW 12(R15), R11 + o4 = 0xe2800000 | 12<<12 | 12<<16 | immrot(uint32(offset)) // ADD $offset, R12, R12 + o1 = uint32(0xe5900000 | 12<<12 | 15<<16 | 12) // MOVW 12(R15), R12 } P := make([]byte, tramp.Size()) arch.ByteOrder.PutUint32(P, o1) @@ -565,7 +586,7 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade return val, 0, false } -func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64) int64 { +func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64, []byte) int64 { log.Fatalf("unexpected relocation variant") return -1 } diff --git a/src/cmd/link/internal/arm/obj.go b/src/cmd/link/internal/arm/obj.go index fed8dce4de48c87d546b7939a97f586061c14891..b7d149851c4b07255f93a369668608dbc8c36a34 100644 --- a/src/cmd/link/internal/arm/obj.go +++ b/src/cmd/link/internal/arm/obj.go @@ -45,6 +45,7 @@ func Init() (*sys.Arch, ld.Arch) { Minalign: minAlign, Dwarfregsp: dwarfRegSP, Dwarfreglr: dwarfRegLR, + TrampLimit: 0x1c00000, // 24-bit signed offset * 4, leave room for PLT etc. Plan9Magic: 0x647, diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go index 14a20a17d53a49229583ec46a0849deff70215d5..c10bdc4120a601676efdede7bb6859d1b13229fe 100644 --- a/src/cmd/link/internal/arm64/asm.go +++ b/src/cmd/link/internal/arm64/asm.go @@ -413,6 +413,35 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade // (e.g. go version). return true } + + case objabi.R_ARM64_GOTPCREL: + if target.IsExternal() { + // External linker will do this relocation. + return true + } + if targType != sym.SDYNIMPORT { + ldr.Errorf(s, "R_ARM64_GOTPCREL target is not SDYNIMPORT symbol: %v", ldr.SymName(targ)) + } + if r.Add() != 0 { + ldr.Errorf(s, "R_ARM64_GOTPCREL with non-zero addend (%v)", r.Add()) + } + if target.IsElf() { + ld.AddGotSym(target, ldr, syms, targ, uint32(elf.R_AARCH64_GLOB_DAT)) + } else { + ld.AddGotSym(target, ldr, syms, targ, 0) + } + // turn into two relocations, one for each instruction. + su := ldr.MakeSymbolUpdater(s) + r.SetType(objabi.R_ARM64_GOT) + r.SetSiz(4) + r.SetSym(syms.GOT) + r.SetAdd(int64(ldr.SymGot(targ))) + r2, _ := su.AddRel(objabi.R_ARM64_GOT) + r2.SetSiz(4) + r2.SetOff(r.Off() + 4) + r2.SetSym(syms.GOT) + r2.SetAdd(int64(ldr.SymGot(targ))) + return true } return false } @@ -464,8 +493,9 @@ func elfreloc1(ctxt *ld.Link, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, return true } -// sign-extends from 24-bit. -func signext24(x int64) int64 { return x << 40 >> 40 } +// sign-extends from 21, 24-bit. +func signext21(x int64) int64 { return x << (64 - 21) >> (64 - 21) } +func signext24(x int64) int64 { return x << (64 - 24) >> (64 - 24) } func machoreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, r loader.ExtReloc, sectoff int64) bool { var v uint32 @@ -478,7 +508,7 @@ func machoreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sy if xadd != signext24(xadd) { // If the relocation target would overflow the addend, then target // a linker-manufactured label symbol with a smaller addend instead. - label := ldr.Lookup(machoLabelName(ldr, rs, xadd), ldr.SymVersion(rs)) + label := ldr.Lookup(offsetLabelName(ldr, rs, xadd/machoRelocLimit*machoRelocLimit), ldr.SymVersion(rs)) if label != 0 { xadd = ldr.SymValue(rs) + xadd - ldr.SymValue(label) rs = label @@ -568,6 +598,72 @@ func machoreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sy return true } +func pereloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, r loader.ExtReloc, sectoff int64) bool { + rs := r.Xsym + rt := r.Type + + if r.Xadd != signext21(r.Xadd) { + // If the relocation target would overflow the addend, then target + // a linker-manufactured label symbol with a smaller addend instead. + label := ldr.Lookup(offsetLabelName(ldr, rs, r.Xadd/peRelocLimit*peRelocLimit), ldr.SymVersion(rs)) + if label == 0 { + ldr.Errorf(s, "invalid relocation: %v %s+0x%x", rt, ldr.SymName(rs), r.Xadd) + return false + } + rs = label + } + if rt == objabi.R_CALLARM64 && r.Xadd != 0 { + label := ldr.Lookup(offsetLabelName(ldr, rs, r.Xadd), ldr.SymVersion(rs)) + if label == 0 { + ldr.Errorf(s, "invalid relocation: %v %s+0x%x", rt, ldr.SymName(rs), r.Xadd) + return false + } + rs = label + } + symdynid := ldr.SymDynid(rs) + if symdynid < 0 { + ldr.Errorf(s, "reloc %d (%s) to non-coff symbol %s type=%d (%s)", rt, sym.RelocName(arch, rt), ldr.SymName(rs), ldr.SymType(rs), ldr.SymType(rs)) + return false + } + + switch rt { + default: + return false + + case objabi.R_DWARFSECREF: + out.Write32(uint32(sectoff)) + out.Write32(uint32(symdynid)) + out.Write16(ld.IMAGE_REL_ARM64_SECREL) + + case objabi.R_ADDR: + out.Write32(uint32(sectoff)) + out.Write32(uint32(symdynid)) + if r.Size == 8 { + out.Write16(ld.IMAGE_REL_ARM64_ADDR64) + } else { + out.Write16(ld.IMAGE_REL_ARM64_ADDR32) + } + + case objabi.R_ADDRARM64: + // Note: r.Xadd has been taken care of below, in archreloc. + out.Write32(uint32(sectoff)) + out.Write32(uint32(symdynid)) + out.Write16(ld.IMAGE_REL_ARM64_PAGEBASE_REL21) + + out.Write32(uint32(sectoff + 4)) + out.Write32(uint32(symdynid)) + out.Write16(ld.IMAGE_REL_ARM64_PAGEOFFSET_12A) + + case objabi.R_CALLARM64: + // Note: r.Xadd has been taken care of above, by using a label pointing into the middle of the function. + out.Write32(uint32(sectoff)) + out.Write32(uint32(symdynid)) + out.Write16(ld.IMAGE_REL_ARM64_BRANCH26) + } + + return true +} + func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loader.Reloc, s loader.Sym, val int64) (int64, int, bool) { const noExtReloc = 0 const isOk = true @@ -594,14 +690,8 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade nExtReloc = 4 // need another two relocations for non-zero addend } - // Note: ld64 currently has a bug that any non-zero addend for BR26 relocation - // will make the linking fail because it thinks the code is not PIC even though - // the BR26 relocation should be fully resolved at link time. - // That is the reason why the next if block is disabled. When the bug in ld64 - // is fixed, we can enable this block and also enable duff's device in cmd/7g. - if false && target.IsDarwin() { + if target.IsWindows() { var o0, o1 uint32 - if target.IsBigEndian() { o0 = uint32(val >> 32) o1 = uint32(val) @@ -609,15 +699,20 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade o0 = uint32(val) o1 = uint32(val >> 32) } - // Mach-O wants the addend to be encoded in the instruction - // Note that although Mach-O supports ARM64_RELOC_ADDEND, it - // can only encode 24-bit of signed addend, but the instructions - // supports 33-bit of signed addend, so we always encode the - // addend in place. - o0 |= (uint32((xadd>>12)&3) << 29) | (uint32((xadd>>12>>2)&0x7ffff) << 5) - o1 |= uint32(xadd&0xfff) << 10 - - // when laid out, the instruction order must always be o1, o2. + + // The first instruction (ADRP) has a 21-bit immediate field, + // and the second (ADD) has a 12-bit immediate field. + // The first instruction is only for high bits, but to get the carry bits right we have + // to put the full addend, including the bottom 12 bits again. + // That limits the distance of any addend to only 21 bits. + // But we assume that LDRP's top bit will be interpreted as a sign bit, + // so we only use 20 bits. + // pereloc takes care of introducing new symbol labels + // every megabyte for longer relocations. + xadd := uint32(xadd) + o0 |= (xadd&3)<<29 | (xadd&0xffffc)<<3 + o1 |= (xadd & 0xfff) << 10 + if target.IsBigEndian() { val = int64(o0)<<32 | int64(o1) } else { @@ -634,6 +729,18 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade nExtReloc = 2 // need two ELF relocations. see elfreloc1 } return val, nExtReloc, isOk + + case objabi.R_ADDR: + if target.IsWindows() && r.Add() != 0 { + if r.Siz() == 8 { + val = r.Add() + } else if target.IsBigEndian() { + val = int64(uint32(val)) | int64(r.Add())<<32 + } else { + val = val>>32<<32 | int64(uint32(r.Add())) + } + return val, 1, true + } } } @@ -831,7 +938,7 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade return val, 0, false } -func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64) int64 { +func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64, []byte) int64 { log.Fatalf("unexpected relocation variant") return -1 } @@ -984,37 +1091,54 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade } } -const machoRelocLimit = 1 << 23 +const ( + machoRelocLimit = 1 << 23 + peRelocLimit = 1 << 20 +) func gensymlate(ctxt *ld.Link, ldr *loader.Loader) { // When external linking on darwin, Mach-O relocation has only signed 24-bit // addend. For large symbols, we generate "label" symbols in the middle, so // that relocations can target them with smaller addends. - if !ctxt.IsDarwin() || !ctxt.IsExternal() { + // On Windows, we only get 21 bits, again (presumably) signed. + if !ctxt.IsDarwin() && !ctxt.IsWindows() || !ctxt.IsExternal() { return } - big := false - for _, seg := range ld.Segments { - if seg.Length >= machoRelocLimit { - big = true - break - } + limit := int64(machoRelocLimit) + if ctxt.IsWindows() { + limit = peRelocLimit } - if !big { - return // skip work if nothing big + + if ctxt.IsDarwin() { + big := false + for _, seg := range ld.Segments { + if seg.Length >= machoRelocLimit { + big = true + break + } + } + if !big { + return // skip work if nothing big + } } - // addLabelSyms adds "label" symbols at s+machoRelocLimit, s+2*machoRelocLimit, etc. - addLabelSyms := func(s loader.Sym, sz int64) { + // addLabelSyms adds "label" symbols at s+limit, s+2*limit, etc. + addLabelSyms := func(s loader.Sym, limit, sz int64) { v := ldr.SymValue(s) - for off := int64(machoRelocLimit); off < sz; off += machoRelocLimit { - p := ldr.LookupOrCreateSym(machoLabelName(ldr, s, off), ldr.SymVersion(s)) + for off := limit; off < sz; off += limit { + p := ldr.LookupOrCreateSym(offsetLabelName(ldr, s, off), ldr.SymVersion(s)) ldr.SetAttrReachable(p, true) ldr.SetSymValue(p, v+off) ldr.SetSymSect(p, ldr.SymSect(s)) - ld.AddMachoSym(ldr, p) - //fmt.Printf("gensymlate %s %x\n", ldr.SymName(p), ldr.SymValue(p)) + if ctxt.IsDarwin() { + ld.AddMachoSym(ldr, p) + } else if ctxt.IsWindows() { + ld.AddPELabelSym(ldr, p) + } else { + panic("missing case in gensymlate") + } + // fmt.Printf("gensymlate %s %x\n", ldr.SymName(p), ldr.SymValue(p)) } } @@ -1023,26 +1147,150 @@ func gensymlate(ctxt *ld.Link, ldr *loader.Loader) { continue } if ldr.SymType(s) == sym.STEXT { - continue // we don't target the middle of a function + if ctxt.IsDarwin() || ctxt.IsWindows() { + // Cannot relocate into middle of function. + // Generate symbol names for every offset we need in duffcopy/duffzero (only 64 each). + switch ldr.SymName(s) { + case "runtime.duffcopy": + addLabelSyms(s, 8, 8*64) + case "runtime.duffzero": + addLabelSyms(s, 4, 4*64) + } + } + continue // we don't target the middle of other functions } sz := ldr.SymSize(s) - if sz <= machoRelocLimit { + if sz <= limit { continue } - addLabelSyms(s, sz) + addLabelSyms(s, limit, sz) } // Also for carrier symbols (for which SymSize is 0) for _, ss := range ld.CarrierSymByType { - if ss.Sym != 0 && ss.Size > machoRelocLimit { - addLabelSyms(ss.Sym, ss.Size) + if ss.Sym != 0 && ss.Size > limit { + addLabelSyms(ss.Sym, limit, ss.Size) } } } -// machoLabelName returns the name of the "label" symbol used for a -// relocation targeting s+off. The label symbols is used on darwin -// when external linking, so that the addend fits in a Mach-O relocation. -func machoLabelName(ldr *loader.Loader, s loader.Sym, off int64) string { - return fmt.Sprintf("%s.%d", ldr.SymExtname(s), off/machoRelocLimit) +// offsetLabelName returns the name of the "label" symbol used for a +// relocation targeting s+off. The label symbols is used on Darwin/Windows +// when external linking, so that the addend fits in a Mach-O/PE relocation. +func offsetLabelName(ldr *loader.Loader, s loader.Sym, off int64) string { + if off>>20<<20 == off { + return fmt.Sprintf("%s+%dMB", ldr.SymExtname(s), off>>20) + } + return fmt.Sprintf("%s+%d", ldr.SymExtname(s), off) +} + +// Convert the direct jump relocation r to refer to a trampoline if the target is too far +func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { + relocs := ldr.Relocs(s) + r := relocs.At(ri) + const pcrel = 1 + switch r.Type() { + case objabi.ElfRelocOffset + objabi.RelocType(elf.R_AARCH64_CALL26), + objabi.ElfRelocOffset + objabi.RelocType(elf.R_AARCH64_JUMP26), + objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_BRANCH26*2 + pcrel: + // Host object relocations that will be turned into a PLT call. + // The PLT may be too far. Insert a trampoline for them. + fallthrough + case objabi.R_CALLARM64: + var t int64 + // ldr.SymValue(rs) == 0 indicates a cross-package jump to a function that is not yet + // laid out. Conservatively use a trampoline. This should be rare, as we lay out packages + // in dependency order. + if ldr.SymValue(rs) != 0 { + t = ldr.SymValue(rs) + r.Add() - (ldr.SymValue(s) + int64(r.Off())) + } + if t >= 1<<27 || t < -1<<27 || ldr.SymValue(rs) == 0 || (*ld.FlagDebugTramp > 1 && (ldr.SymPkg(s) == "" || ldr.SymPkg(s) != ldr.SymPkg(rs))) { + // direct call too far, need to insert trampoline. + // look up existing trampolines first. if we found one within the range + // of direct call, we can reuse it. otherwise create a new one. + var tramp loader.Sym + for i := 0; ; i++ { + oName := ldr.SymName(rs) + name := oName + fmt.Sprintf("%+x-tramp%d", r.Add(), i) + tramp = ldr.LookupOrCreateSym(name, int(ldr.SymVersion(rs))) + ldr.SetAttrReachable(tramp, true) + if ldr.SymType(tramp) == sym.SDYNIMPORT { + // don't reuse trampoline defined in other module + continue + } + if oName == "runtime.deferreturn" { + ldr.SetIsDeferReturnTramp(tramp, true) + } + if ldr.SymValue(tramp) == 0 { + // either the trampoline does not exist -- we need to create one, + // or found one the address which is not assigned -- this will be + // laid down immediately after the current function. use this one. + break + } + + t = ldr.SymValue(tramp) - (ldr.SymValue(s) + int64(r.Off())) + if t >= -1<<27 && t < 1<<27 { + // found an existing trampoline that is not too far + // we can just use it + break + } + } + if ldr.SymType(tramp) == 0 { + // trampoline does not exist, create one + trampb := ldr.MakeSymbolUpdater(tramp) + ctxt.AddTramp(trampb) + if ldr.SymType(rs) == sym.SDYNIMPORT { + if r.Add() != 0 { + ctxt.Errorf(s, "nonzero addend for DYNIMPORT call: %v+%d", ldr.SymName(rs), r.Add()) + } + gentrampgot(ctxt, ldr, trampb, rs) + } else { + gentramp(ctxt, ldr, trampb, rs, r.Add()) + } + } + // modify reloc to point to tramp, which will be resolved later + sb := ldr.MakeSymbolUpdater(s) + relocs := sb.Relocs() + r := relocs.At(ri) + r.SetSym(tramp) + r.SetAdd(0) // clear the offset embedded in the instruction + } + default: + ctxt.Errorf(s, "trampoline called with non-jump reloc: %d (%s)", r.Type(), sym.RelocName(ctxt.Arch, r.Type())) + } +} + +// generate a trampoline to target+offset. +func gentramp(ctxt *ld.Link, ldr *loader.Loader, tramp *loader.SymbolBuilder, target loader.Sym, offset int64) { + tramp.SetSize(12) // 3 instructions + P := make([]byte, tramp.Size()) + o1 := uint32(0x90000010) // adrp x16, target + o2 := uint32(0x91000210) // add x16, pc-relative-offset + o3 := uint32(0xd61f0200) // br x16 + ctxt.Arch.ByteOrder.PutUint32(P, o1) + ctxt.Arch.ByteOrder.PutUint32(P[4:], o2) + ctxt.Arch.ByteOrder.PutUint32(P[8:], o3) + tramp.SetData(P) + + r, _ := tramp.AddRel(objabi.R_ADDRARM64) + r.SetSiz(8) + r.SetSym(target) + r.SetAdd(offset) +} + +// generate a trampoline to target+offset for a DYNIMPORT symbol via GOT. +func gentrampgot(ctxt *ld.Link, ldr *loader.Loader, tramp *loader.SymbolBuilder, target loader.Sym) { + tramp.SetSize(12) // 3 instructions + P := make([]byte, tramp.Size()) + o1 := uint32(0x90000010) // adrp x16, target@GOT + o2 := uint32(0xf9400210) // ldr x16, [x16, offset] + o3 := uint32(0xd61f0200) // br x16 + ctxt.Arch.ByteOrder.PutUint32(P, o1) + ctxt.Arch.ByteOrder.PutUint32(P[4:], o2) + ctxt.Arch.ByteOrder.PutUint32(P[8:], o3) + tramp.SetData(P) + + r, _ := tramp.AddRel(objabi.R_ARM64_GOTPCREL) + r.SetSiz(8) + r.SetSym(target) } diff --git a/src/cmd/link/internal/arm64/obj.go b/src/cmd/link/internal/arm64/obj.go index bd13295e6130bee64c6d7c44e3dfe0400c95d283..9c7459855c15e7992115fc2d811e9f58311657a9 100644 --- a/src/cmd/link/internal/arm64/obj.go +++ b/src/cmd/link/internal/arm64/obj.go @@ -45,6 +45,7 @@ func Init() (*sys.Arch, ld.Arch) { Minalign: minAlign, Dwarfregsp: dwarfRegSP, Dwarfreglr: dwarfRegLR, + TrampLimit: 0x7c00000, // 26-bit signed offset * 4, leave room for PLT etc. Adddynrel: adddynrel, Archinit: archinit, @@ -58,6 +59,8 @@ func Init() (*sys.Arch, ld.Arch) { GenSymsLate: gensymlate, Machoreloc1: machoreloc1, MachorelocSize: 8, + PEreloc1: pereloc1, + Trampoline: trampoline, Androiddynld: "/system/bin/linker64", Linuxdynld: "/lib/ld-linux-aarch64.so.1", @@ -108,5 +111,9 @@ func archinit(ctxt *ld.Link) { if *ld.FlagRound == -1 { *ld.FlagRound = 16384 // 16K page alignment } + + case objabi.Hwindows: /* PE executable */ + // ld.HEADR, ld.FlagTextAddr, ld.FlagRound are set in ld.Peinit + return } } diff --git a/src/cmd/link/internal/ld/ar.go b/src/cmd/link/internal/ld/ar.go index e4fd591676626ce56a6213a66b67274361c97d6c..23915f90329a0a81e74736815c678b1de876de75 100644 --- a/src/cmd/link/internal/ld/ar.go +++ b/src/cmd/link/internal/ld/ar.go @@ -32,10 +32,10 @@ package ld import ( "cmd/internal/bio" - "cmd/internal/objabi" "cmd/link/internal/sym" "encoding/binary" "fmt" + "internal/buildcfg" "io" "os" ) @@ -124,6 +124,10 @@ func hostArchive(ctxt *Link, name string) { libgcc := sym.Library{Pkg: "libgcc"} h := ldobj(ctxt, f, &libgcc, l, pname, name) + if h.ld == nil { + Errorf(nil, "%s unrecognized object file at offset %d", name, off) + continue + } f.MustSeek(h.off, 0) h.ld(ctxt, f, h.pkg, h.length, h.pn) } @@ -170,7 +174,7 @@ func readArmap(filename string, f *bio.Reader, arhdr ArHdr) archiveMap { // For Mach-O and PE/386 files we strip a leading // underscore from the symbol name. - if objabi.GOOS == "darwin" || objabi.GOOS == "ios" || (objabi.GOOS == "windows" && objabi.GOARCH == "386") { + if buildcfg.GOOS == "darwin" || buildcfg.GOOS == "ios" || (buildcfg.GOOS == "windows" && buildcfg.GOARCH == "386") { if name[0] == '_' && len(name) > 1 { name = name[1:] } diff --git a/src/cmd/link/internal/ld/asmb.go b/src/cmd/link/internal/ld/asmb.go index fda043945506ca7d0c5b6a4e021feaff78ece8f6..d6ecb2895bad329671fa6c7aa3f640115b0af36f 100644 --- a/src/cmd/link/internal/ld/asmb.go +++ b/src/cmd/link/internal/ld/asmb.go @@ -29,8 +29,6 @@ func asmb(ctxt *Link) { } var wg sync.WaitGroup - sect := Segtext.Sections[0] - offset := sect.Vaddr - Segtext.Vaddr + Segtext.Fileoff f := func(ctxt *Link, out *OutBuf, start, length int64) { pad := thearch.CodePad if pad == nil { @@ -39,23 +37,14 @@ func asmb(ctxt *Link) { CodeblkPad(ctxt, out, start, length, pad) } - if !thearch.WriteTextBlocks { - writeParallel(&wg, f, ctxt, offset, sect.Vaddr, sect.Length) - for _, sect := range Segtext.Sections[1:] { - offset := sect.Vaddr - Segtext.Vaddr + Segtext.Fileoff + for _, sect := range Segtext.Sections { + offset := sect.Vaddr - Segtext.Vaddr + Segtext.Fileoff + // Handle text sections with Codeblk + if sect.Name == ".text" { + writeParallel(&wg, f, ctxt, offset, sect.Vaddr, sect.Length) + } else { writeParallel(&wg, datblk, ctxt, offset, sect.Vaddr, sect.Length) } - } else { - // TODO why can't we handle all sections this way? - for _, sect := range Segtext.Sections { - offset := sect.Vaddr - Segtext.Vaddr + Segtext.Fileoff - // Handle additional text sections with Codeblk - if sect.Name == ".text" { - writeParallel(&wg, f, ctxt, offset, sect.Vaddr, sect.Length) - } else { - writeParallel(&wg, datblk, ctxt, offset, sect.Vaddr, sect.Length) - } - } } if Segrodata.Filelen > 0 { @@ -178,7 +167,10 @@ func sizeExtRelocs(ctxt *Link, relsize uint32) { } } filesz := ctxt.Out.Offset() + sz - ctxt.Out.Mmap(uint64(filesz)) + err := ctxt.Out.Mmap(uint64(filesz)) + if err != nil { + Exitf("mapping output file failed: %v", err) + } } // relocSectFn wraps the function writing relocations of a section diff --git a/src/cmd/link/internal/ld/config.go b/src/cmd/link/internal/ld/config.go index d1e06239a566f656ed67e69597b99bd4c790122e..20f1d0b8c12c6848fd68f83a7e4b41cd1c9f2b7a 100644 --- a/src/cmd/link/internal/ld/config.go +++ b/src/cmd/link/internal/ld/config.go @@ -5,10 +5,9 @@ package ld import ( - "cmd/internal/objabi" "cmd/internal/sys" "fmt" - "log" + "internal/buildcfg" ) // A BuildMode indicates the sort of object we are building. @@ -29,23 +28,23 @@ const ( func (mode *BuildMode) Set(s string) error { badmode := func() error { - return fmt.Errorf("buildmode %s not supported on %s/%s", s, objabi.GOOS, objabi.GOARCH) + return fmt.Errorf("buildmode %s not supported on %s/%s", s, buildcfg.GOOS, buildcfg.GOARCH) } switch s { default: return fmt.Errorf("invalid buildmode: %q", s) case "exe": - switch objabi.GOOS + "/" + objabi.GOARCH { - case "darwin/arm64", "windows/arm": // On these platforms, everything is PIE + switch buildcfg.GOOS + "/" + buildcfg.GOARCH { + case "darwin/arm64", "windows/arm", "windows/arm64": // On these platforms, everything is PIE *mode = BuildModePIE default: *mode = BuildModeExe } case "pie": - switch objabi.GOOS { + switch buildcfg.GOOS { case "aix", "android", "linux", "windows", "darwin", "ios": case "freebsd": - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "amd64": default: return badmode() @@ -55,17 +54,17 @@ func (mode *BuildMode) Set(s string) error { } *mode = BuildModePIE case "c-archive": - switch objabi.GOOS { + switch buildcfg.GOOS { case "aix", "darwin", "ios", "linux": case "freebsd": - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "amd64": default: return badmode() } case "windows": - switch objabi.GOARCH { - case "amd64", "386", "arm": + switch buildcfg.GOARCH { + case "amd64", "386", "arm", "arm64": default: return badmode() } @@ -74,16 +73,16 @@ func (mode *BuildMode) Set(s string) error { } *mode = BuildModeCArchive case "c-shared": - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "386", "amd64", "arm", "arm64", "ppc64le", "s390x": default: return badmode() } *mode = BuildModeCShared case "shared": - switch objabi.GOOS { + switch buildcfg.GOOS { case "linux": - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "386", "amd64", "arm", "arm64", "ppc64le", "s390x": default: return badmode() @@ -93,21 +92,21 @@ func (mode *BuildMode) Set(s string) error { } *mode = BuildModeShared case "plugin": - switch objabi.GOOS { + switch buildcfg.GOOS { case "linux": - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "386", "amd64", "arm", "arm64", "s390x", "ppc64le": default: return badmode() } case "darwin": - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "amd64", "arm64": default: return badmode() } case "freebsd": - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "amd64": default: return badmode() @@ -181,13 +180,13 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) { if ctxt.Debugvlog > 1 { defer func() { if res { - log.Printf("external linking is forced by: %s\n", reason) + ctxt.Logf("external linking is forced by: %s\n", reason) } }() } - if sys.MustLinkExternal(objabi.GOOS, objabi.GOARCH) { - return true, fmt.Sprintf("%s/%s requires external linking", objabi.GOOS, objabi.GOARCH) + if sys.MustLinkExternal(buildcfg.GOOS, buildcfg.GOARCH) { + return true, fmt.Sprintf("%s/%s requires external linking", buildcfg.GOOS, buildcfg.GOARCH) } if *flagMsan { @@ -198,17 +197,24 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) { // https://golang.org/issue/14449 // https://golang.org/issue/21961 if iscgo && ctxt.Arch.InFamily(sys.MIPS64, sys.MIPS, sys.PPC64, sys.RISCV64) { - return true, objabi.GOARCH + " does not support internal cgo" + return true, buildcfg.GOARCH + " does not support internal cgo" } - if iscgo && objabi.GOOS == "android" { - return true, objabi.GOOS + " does not support internal cgo" + if iscgo && (buildcfg.GOOS == "android" || buildcfg.GOOS == "dragonfly") { + // It seems that on Dragonfly thread local storage is + // set up by the dynamic linker, so internal cgo linking + // doesn't work. Test case is "go test runtime/cgo". + return true, buildcfg.GOOS + " does not support internal cgo" + } + if iscgo && buildcfg.GOOS == "windows" && buildcfg.GOARCH == "arm64" { + // windows/arm64 internal linking is not implemented. + return true, buildcfg.GOOS + "/" + buildcfg.GOARCH + " does not support internal cgo" } // When the race flag is set, the LLVM tsan relocatable file is linked // into the final binary, which means external linking is required because // internal linking does not support it. if *flagRace && ctxt.Arch.InFamily(sys.PPC64) { - return true, "race on " + objabi.GOARCH + return true, "race on " + buildcfg.GOARCH } // Some build modes require work the internal linker cannot do (yet). @@ -218,9 +224,9 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) { case BuildModeCShared: return true, "buildmode=c-shared" case BuildModePIE: - switch objabi.GOOS + "/" + objabi.GOARCH { + switch buildcfg.GOOS + "/" + buildcfg.GOARCH { case "linux/amd64", "linux/arm64", "android/arm64": - case "windows/386", "windows/amd64", "windows/arm": + case "windows/386", "windows/amd64", "windows/arm", "windows/arm64": case "darwin/amd64", "darwin/arm64": default: // Internal linking does not support TLS_IE. @@ -235,6 +241,10 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) { return true, "dynamically linking with a shared library" } + if unknownObjFormat { + return true, "some input objects have an unrecognized file format" + } + return false, "" } @@ -242,7 +252,7 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) { // // It is called after flags are processed and inputs are processed, // so the ctxt.LinkMode variable has an initial value from the -linkmode -// flag and the iscgo externalobj variables are set. +// flag and the iscgo, externalobj, and unknownObjFormat variables are set. func determineLinkMode(ctxt *Link) { extNeeded, extReason := mustLinkExternal(ctxt) via := "" @@ -252,7 +262,7 @@ func determineLinkMode(ctxt *Link) { // default value of -linkmode. If it is not set when the // linker is called we take the value it was set to when // cmd/link was compiled. (See make.bash.) - switch objabi.Getgoextlinkenabled() { + switch buildcfg.Getgoextlinkenabled() { case "0": ctxt.LinkMode = LinkInternal via = "via GO_EXTLINK_ENABLED " @@ -275,8 +285,8 @@ func determineLinkMode(ctxt *Link) { } case LinkExternal: switch { - case objabi.GOARCH == "ppc64" && objabi.GOOS != "aix": - Exitf("external linking not supported for %s/ppc64", objabi.GOOS) + case buildcfg.GOARCH == "ppc64" && buildcfg.GOOS != "aix": + Exitf("external linking not supported for %s/ppc64", buildcfg.GOOS) } } } diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 52035e96301cf4639ede631b6ce4d4a2f907108f..70fbb9dc4e24cf2d47c78c89b072684625735784 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -39,6 +39,7 @@ import ( "cmd/link/internal/loader" "cmd/link/internal/sym" "compress/zlib" + "debug/elf" "encoding/binary" "fmt" "log" @@ -55,6 +56,7 @@ func isRuntimeDepPkg(pkg string) bool { switch pkg { case "runtime", "sync/atomic", // runtime may call to sync/atomic, due to go:linkname + "internal/abi", // used by reflectcall (and maybe more) "internal/bytealg", // for IndexByte "internal/cpu": // for cpu features return true @@ -65,7 +67,7 @@ func isRuntimeDepPkg(pkg string) bool { // Estimate the max size needed to hold any new trampolines created for this function. This // is used to determine when the section can be split if it becomes too large, to ensure that // the trampolines are in the same section as the function that uses them. -func maxSizeTrampolinesPPC64(ldr *loader.Loader, s loader.Sym, isTramp bool) uint64 { +func maxSizeTrampolines(ctxt *Link, ldr *loader.Loader, s loader.Sym, isTramp bool) uint64 { // If thearch.Trampoline is nil, then trampoline support is not available on this arch. // A trampoline does not need any dependent trampolines. if thearch.Trampoline == nil || isTramp { @@ -80,8 +82,14 @@ func maxSizeTrampolinesPPC64(ldr *loader.Loader, s loader.Sym, isTramp bool) uin n++ } } - // Trampolines in ppc64 are 4 instructions. - return n * 16 + + if ctxt.IsPPC64() { + return n * 16 // Trampolines in PPC64 are 4 instructions. + } + if ctxt.IsARM64() { + return n * 12 // Trampolines in ARM64 are 3 instructions. + } + panic("unreachable") } // detect too-far jumps in function s, and add trampolines if necessary @@ -97,7 +105,8 @@ func trampoline(ctxt *Link, s loader.Sym) { relocs := ldr.Relocs(s) for ri := 0; ri < relocs.Count(); ri++ { r := relocs.At(ri) - if !r.Type().IsDirectCallOrJump() { + rt := r.Type() + if !rt.IsDirectCallOrJump() && !isPLTCall(rt) { continue } rs := r.Sym() @@ -106,8 +115,11 @@ func trampoline(ctxt *Link, s loader.Sym) { } rs = ldr.ResolveABIAlias(rs) if ldr.SymValue(rs) == 0 && (ldr.SymType(rs) != sym.SDYNIMPORT && ldr.SymType(rs) != sym.SUNDEFEXT) { - if ldr.SymPkg(rs) == ldr.SymPkg(s) { - continue // symbols in the same package are laid out together + if ldr.SymPkg(s) != "" && ldr.SymPkg(rs) == ldr.SymPkg(s) { + // Symbols in the same package are laid out together. + // Except that if SymPkg(s) == "", it is a host object symbol + // which may call an external symbol via PLT. + continue } if isRuntimeDepPkg(ldr.SymPkg(s)) && isRuntimeDepPkg(ldr.SymPkg(rs)) { continue // runtime packages are laid out together @@ -116,7 +128,27 @@ func trampoline(ctxt *Link, s loader.Sym) { thearch.Trampoline(ctxt, ldr, ri, rs, s) } +} + +// whether rt is a (host object) relocation that will be turned into +// a call to PLT. +func isPLTCall(rt objabi.RelocType) bool { + const pcrel = 1 + switch rt { + // ARM64 + case objabi.ElfRelocOffset + objabi.RelocType(elf.R_AARCH64_CALL26), + objabi.ElfRelocOffset + objabi.RelocType(elf.R_AARCH64_JUMP26), + objabi.MachoRelocOffset + MACHO_ARM64_RELOC_BRANCH26*2 + pcrel: + return true + // ARM + case objabi.ElfRelocOffset + objabi.RelocType(elf.R_ARM_CALL), + objabi.ElfRelocOffset + objabi.RelocType(elf.R_ARM_PC24), + objabi.ElfRelocOffset + objabi.RelocType(elf.R_ARM_JUMP24): + return true + } + // TODO: other architectures. + return false } // FoldSubSymbolOffset computes the offset of symbol s to its top-level outer @@ -164,6 +196,7 @@ func (st *relocSymState) relocsym(s loader.Sym, P []byte) { rs := r.Sym() rs = ldr.ResolveABIAlias(rs) rt := r.Type() + weak := r.Weak() if off < 0 || off+siz > int32(len(P)) { rname := "" if rs != 0 { @@ -210,7 +243,7 @@ func (st *relocSymState) relocsym(s loader.Sym, P []byte) { st.err.Errorf(s, "unhandled relocation for %s (type %d (%s) rtype %d (%s))", ldr.SymName(rs), rst, rst, rt, sym.RelocName(target.Arch, rt)) } } - if rs != 0 && rst != sym.STLSBSS && rt != objabi.R_WEAKADDROFF && rt != objabi.R_METHODOFF && !ldr.AttrReachable(rs) { + if rs != 0 && rst != sym.STLSBSS && !weak && rt != objabi.R_METHODOFF && !ldr.AttrReachable(rs) { st.err.Errorf(s, "unreachable sym in relocation: %s", ldr.SymName(rs)) } @@ -304,6 +337,11 @@ func (st *relocSymState) relocsym(s loader.Sym, P []byte) { log.Fatalf("cannot handle R_TLS_IE (sym %s) when linking internally", ldr.SymName(s)) } case objabi.R_ADDR: + if weak && !ldr.AttrReachable(rs) { + // Redirect it to runtime.unreachableMethod, which will throw if called. + rs = syms.unreachableMethod + rs = ldr.ResolveABIAlias(rs) + } if target.IsExternal() { nExtReloc++ @@ -386,18 +424,18 @@ func (st *relocSymState) relocsym(s loader.Sym, P []byte) { break } o = ldr.SymValue(rs) + r.Add() - int64(ldr.SymSect(rs).Vaddr) - case objabi.R_WEAKADDROFF, objabi.R_METHODOFF: + case objabi.R_METHODOFF: if !ldr.AttrReachable(rs) { - if rt == objabi.R_METHODOFF { - // Set it to a sentinel value. The runtime knows this is not pointing to - // anything valid. - o = -1 - break - } - continue + // Set it to a sentinel value. The runtime knows this is not pointing to + // anything valid. + o = -1 + break } fallthrough case objabi.R_ADDROFF: + if weak && !ldr.AttrReachable(rs) { + continue + } // The method offset tables using this relocation expect the offset to be relative // to the start of the first text section, even if there are multiple. if ldr.SymSect(rs).Name == ".text" { @@ -505,7 +543,7 @@ func (st *relocSymState) relocsym(s loader.Sym, P []byte) { if target.IsPPC64() || target.IsS390X() { if rv != sym.RV_NONE { - o = thearch.Archrelocvariant(target, ldr, r, rv, s, o) + o = thearch.Archrelocvariant(target, ldr, r, rv, s, o, P) } } @@ -584,6 +622,10 @@ func extreloc(ctxt *Link, ldr *loader.Loader, s loader.Sym, r loader.Reloc) (loa case objabi.R_ADDR: // set up addend for eventual relocation via outer symbol. rs := ldr.ResolveABIAlias(r.Sym()) + if r.Weak() && !ldr.AttrReachable(rs) { + rs = ctxt.ArchSyms.unreachableMethod + rs = ldr.ResolveABIAlias(rs) + } rs, off := FoldSubSymbolOffset(ldr, rs) rr.Xadd = r.Add() + off rr.Xsym = rs @@ -634,7 +676,7 @@ func extreloc(ctxt *Link, ldr *loader.Loader, s loader.Sym, r loader.Reloc) (loa return ExtrelocSimple(ldr, r), true // These reloc types don't need external relocations. - case objabi.R_ADDROFF, objabi.R_WEAKADDROFF, objabi.R_METHODOFF, objabi.R_ADDRCUOFF, + case objabi.R_ADDROFF, objabi.R_METHODOFF, objabi.R_ADDRCUOFF, objabi.R_SIZE, objabi.R_CONST, objabi.R_GOTOFF: return rr, false } @@ -709,9 +751,8 @@ func windynrelocsym(ctxt *Link, rel *loader.SymbolBuilder, s loader.Sym) { if targ == 0 { continue } - rt := r.Type() if !ctxt.loader.AttrReachable(targ) { - if rt == objabi.R_WEAKADDROFF { + if r.Weak() { continue } ctxt.Errorf(s, "dynamic relocation to unreachable symbol %s", @@ -785,6 +826,10 @@ func dynrelocsym(ctxt *Link, s loader.Sym) { if r.IsMarker() { continue // skip marker relocations } + rSym := r.Sym() + if r.Weak() && !ldr.AttrReachable(rSym) { + continue + } if ctxt.BuildMode == BuildModePIE && ctxt.LinkMode == LinkInternal { // It's expected that some relocations will be done // later by relocsym (R_TLS_LE, R_ADDROFF), so @@ -793,7 +838,6 @@ func dynrelocsym(ctxt *Link, s loader.Sym) { continue } - rSym := r.Sym() if rSym != 0 && ldr.SymType(rSym) == sym.SDYNIMPORT || r.Type() >= objabi.ElfRelocOffset { if rSym != 0 && !ldr.AttrReachable(rSym) { ctxt.Errorf(s, "dynamic relocation to unreachable symbol %s", ldr.SymName(rSym)) @@ -1506,7 +1550,7 @@ func (ctxt *Link) dodata(symGroupType []sym.SymKind) { if ctxt.HeadType == objabi.Haix && ctxt.LinkMode == LinkExternal { // These symbols must have the same alignment as their section. - // Otherwize, ld might change the layout of Go sections. + // Otherwise, ld might change the layout of Go sections. ldr.SetSymAlign(ldr.Lookup("runtime.data", 0), state.dataMaxAlign[sym.SDATA]) ldr.SetSymAlign(ldr.Lookup("runtime.bss", 0), state.dataMaxAlign[sym.SBSS]) } @@ -2192,23 +2236,87 @@ func (ctxt *Link) textaddress() { ctxt.Textp[0] = text } - va := uint64(Rnd(*FlagTextAddr, int64(Funcalign))) + start := uint64(Rnd(*FlagTextAddr, int64(Funcalign))) + va := start n := 1 sect.Vaddr = va - ntramps := 0 - for _, s := range ctxt.Textp { - sect, n, va = assignAddress(ctxt, sect, n, s, va, false) - trampoline(ctxt, s) // resolve jumps, may add trampolines if jump too far + limit := thearch.TrampLimit + if limit == 0 { + limit = 1 << 63 // unlimited + } + if *FlagDebugTextSize != 0 { + limit = uint64(*FlagDebugTextSize) + } + if *FlagDebugTramp > 1 { + limit = 1 // debug mode, force generating trampolines for everything + } + + if ctxt.IsAIX() && ctxt.IsExternal() { + // On AIX, normally we won't generate direct calls to external symbols, + // except in one test, cmd/go/testdata/script/link_syso_issue33139.txt. + // That test doesn't make much sense, and I'm not sure it ever works. + // Just generate trampoline for now (which will turn a direct call to + // an indirect call, which at least builds). + limit = 1 + } + + // First pass: assign addresses assuming the program is small and + // don't generate trampolines. + big := false + for _, s := range ctxt.Textp { + sect, n, va = assignAddress(ctxt, sect, n, s, va, false, big) + if va-start >= limit { + big = true + break + } + } - // lay down trampolines after each function - for ; ntramps < len(ctxt.tramps); ntramps++ { - tramp := ctxt.tramps[ntramps] - if ctxt.IsAIX() && strings.HasPrefix(ldr.SymName(tramp), "runtime.text.") { - // Already set in assignAddress + // Second pass: only if it is too big, insert trampolines for too-far + // jumps and targets with unknown addresses. + if big { + // reset addresses + for _, s := range ctxt.Textp { + if ldr.OuterSym(s) != 0 || s == text { continue } - sect, n, va = assignAddress(ctxt, sect, n, tramp, va, true) + oldv := ldr.SymValue(s) + for sub := s; sub != 0; sub = ldr.SubSym(sub) { + ldr.SetSymValue(sub, ldr.SymValue(sub)-oldv) + } + } + va = start + + ntramps := 0 + for _, s := range ctxt.Textp { + sect, n, va = assignAddress(ctxt, sect, n, s, va, false, big) + + trampoline(ctxt, s) // resolve jumps, may add trampolines if jump too far + + // lay down trampolines after each function + for ; ntramps < len(ctxt.tramps); ntramps++ { + tramp := ctxt.tramps[ntramps] + if ctxt.IsAIX() && strings.HasPrefix(ldr.SymName(tramp), "runtime.text.") { + // Already set in assignAddress + continue + } + sect, n, va = assignAddress(ctxt, sect, n, tramp, va, true, big) + } + } + + // merge tramps into Textp, keeping Textp in address order + if ntramps != 0 { + newtextp := make([]loader.Sym, 0, len(ctxt.Textp)+ntramps) + i := 0 + for _, s := range ctxt.Textp { + for ; i < ntramps && ldr.SymValue(ctxt.tramps[i]) < ldr.SymValue(s); i++ { + newtextp = append(newtextp, ctxt.tramps[i]) + } + newtextp = append(newtextp, s) + } + newtextp = append(newtextp, ctxt.tramps[i:ntramps]...) + + ctxt.Textp = newtextp } } @@ -2220,25 +2328,10 @@ func (ctxt *Link) textaddress() { ldr.SetSymValue(etext, int64(va)) ldr.SetSymValue(text, int64(Segtext.Sections[0].Vaddr)) } - - // merge tramps into Textp, keeping Textp in address order - if ntramps != 0 { - newtextp := make([]loader.Sym, 0, len(ctxt.Textp)+ntramps) - i := 0 - for _, s := range ctxt.Textp { - for ; i < ntramps && ldr.SymValue(ctxt.tramps[i]) < ldr.SymValue(s); i++ { - newtextp = append(newtextp, ctxt.tramps[i]) - } - newtextp = append(newtextp, s) - } - newtextp = append(newtextp, ctxt.tramps[i:ntramps]...) - - ctxt.Textp = newtextp - } } // assigns address for a text symbol, returns (possibly new) section, its number, and the address -func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64, isTramp bool) (*sym.Section, int, uint64) { +func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64, isTramp, big bool) (*sym.Section, int, uint64) { ldr := ctxt.loader if thearch.AssignAddress != nil { return thearch.AssignAddress(ldr, sect, n, s, va, isTramp) @@ -2263,36 +2356,46 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64 funcsize = uint64(ldr.SymSize(s)) } - // On ppc64x a text section should not be larger than 2^26 bytes due to the size of - // call target offset field in the bl instruction. Splitting into smaller text - // sections smaller than this limit allows the GNU linker to modify the long calls - // appropriately. The limit allows for the space needed for tables inserted by the linker. - - // If this function doesn't fit in the current text section, then create a new one. - + // If we need to split text sections, and this function doesn't fit in the current + // section, then create a new one. + // // Only break at outermost syms. + if big && splitTextSections(ctxt) && ldr.OuterSym(s) == 0 { + // For debugging purposes, allow text size limit to be cranked down, + // so as to stress test the code that handles multiple text sections. + var textSizelimit uint64 = thearch.TrampLimit + if *FlagDebugTextSize != 0 { + textSizelimit = uint64(*FlagDebugTextSize) + } - // For debugging purposes, allow text size limit to be cranked down, - // so as to stress test the code that handles multiple text sections. - var textSizelimit uint64 = 0x1c00000 - if *FlagDebugTextSize != 0 { - textSizelimit = uint64(*FlagDebugTextSize) - } - - if ctxt.Arch.InFamily(sys.PPC64) && ldr.OuterSym(s) == 0 && ctxt.IsExternal() { // Sanity check: make sure the limit is larger than any // individual text symbol. if funcsize > textSizelimit { - panic(fmt.Sprintf("error: ppc64 text size limit %d less than text symbol %s size of %d", textSizelimit, ldr.SymName(s), funcsize)) + panic(fmt.Sprintf("error: text size limit %d less than text symbol %s size of %d", textSizelimit, ldr.SymName(s), funcsize)) } - if va-sect.Vaddr+funcsize+maxSizeTrampolinesPPC64(ldr, s, isTramp) > textSizelimit { + if va-sect.Vaddr+funcsize+maxSizeTrampolines(ctxt, ldr, s, isTramp) > textSizelimit { + sectAlign := int32(thearch.Funcalign) + if ctxt.IsPPC64() { + // Align the next text section to the worst case function alignment likely + // to be encountered when processing function symbols. The start address + // is rounded against the final alignment of the text section later on in + // (*Link).address. This may happen due to usage of PCALIGN directives + // larger than Funcalign, or usage of ISA 3.1 prefixed instructions + // (see ISA 3.1 Book I 1.9). + const ppc64maxFuncalign = 64 + sectAlign = ppc64maxFuncalign + va = uint64(Rnd(int64(va), ppc64maxFuncalign)) + } + // Set the length for the previous text section sect.Length = va - sect.Vaddr // Create new section, set the starting Vaddr sect = addsection(ctxt.loader, ctxt.Arch, &Segtext, ".text", 05) + sect.Vaddr = va + sect.Align = sectAlign ldr.SetSymSect(s, sect) // Create a symbol for the start of the secondary text sections @@ -2305,6 +2408,7 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64 ntext.SetType(sym.STEXT) ntext.SetSize(int64(MINFUNC)) ntext.SetOnList(true) + ntext.SetAlign(sectAlign) ctxt.tramps = append(ctxt.tramps, ntext.Sym()) ntext.SetValue(int64(va)) @@ -2323,6 +2427,9 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64 ldr.SetSymValue(s, 0) for sub := s; sub != 0; sub = ldr.SubSym(sub) { ldr.SetSymValue(sub, ldr.SymValue(sub)+int64(va)) + if ctxt.Debugvlog > 2 { + fmt.Println("assign text address:", ldr.SymName(sub), ldr.SymValue(sub)) + } } va += funcsize @@ -2330,6 +2437,19 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64 return sect, n, va } +// Return whether we may need to split text sections. +// +// On PPC64x whem external linking a text section should not be larger than 2^25 bytes +// due to the size of call target offset field in the bl instruction. Splitting into +// smaller text sections smaller than this limit allows the system linker to modify the long +// calls appropriately. The limit allows for the space needed for tables inserted by the +// linker. +// +// The same applies to Darwin/ARM64, with 2^27 byte threshold. +func splitTextSections(ctxt *Link) bool { + return (ctxt.IsPPC64() || (ctxt.IsARM64() && ctxt.IsDarwin())) && ctxt.IsExternal() +} + // address assigns virtual addresses to all segments and sections and // returns all segments in file order. func (ctxt *Link) address() []*sym.Segment { diff --git a/src/cmd/link/internal/ld/data_test.go b/src/cmd/link/internal/ld/data_test.go index 7c46307bd8c4aa4e0e246aa9878b639e73840a7f..f91493bc417db47b87f2e0b6c5f024fd494501c1 100644 --- a/src/cmd/link/internal/ld/data_test.go +++ b/src/cmd/link/internal/ld/data_test.go @@ -8,6 +8,7 @@ import ( "cmd/internal/objabi" "cmd/internal/sys" "cmd/link/internal/loader" + "internal/buildcfg" "testing" ) @@ -63,14 +64,14 @@ func TestAddGotSym(t *testing.T) { } // Save the architecture as we're going to set it on each test run. - origArch := objabi.GOARCH + origArch := buildcfg.GOARCH defer func() { - objabi.GOARCH = origArch + buildcfg.GOARCH = origArch }() for i, test := range tests { iself := len(test.rel) != 0 - objabi.GOARCH = test.arch.Name + buildcfg.GOARCH = test.arch.Name ctxt := setUpContext(test.arch, iself, test.ht, test.bm, test.lm) foo := ctxt.loader.CreateSymForUpdate("foo", 0) ctxt.loader.CreateExtSym("bar", 0) diff --git a/src/cmd/link/internal/ld/deadcode.go b/src/cmd/link/internal/ld/deadcode.go index bfa8640ba99a04b492d6a63f29c2f8f5913dd0c5..416e5da39838fe73a68f663c6914869571a444ba 100644 --- a/src/cmd/link/internal/ld/deadcode.go +++ b/src/cmd/link/internal/ld/deadcode.go @@ -11,6 +11,7 @@ import ( "cmd/link/internal/loader" "cmd/link/internal/sym" "fmt" + "internal/buildcfg" "unicode" ) @@ -19,7 +20,7 @@ var _ = fmt.Print type deadcodePass struct { ctxt *Link ldr *loader.Loader - wq heap // work queue, using min-heap for beter locality + wq heap // work queue, using min-heap for better locality ifaceMethod map[methodsig]bool // methods declared in reached interfaces markableMethods []methodref // methods of reached types @@ -32,7 +33,7 @@ type deadcodePass struct { func (d *deadcodePass) init() { d.ldr.InitReachable() d.ifaceMethod = make(map[methodsig]bool) - if objabi.Fieldtrack_enabled != 0 { + if buildcfg.Experiment.FieldTrack { d.ldr.Reachparent = make([]loader.Sym, d.ldr.NSym()) } d.dynlink = d.ctxt.DynlinkingGo() @@ -64,33 +65,32 @@ func (d *deadcodePass) init() { } } names = append(names, *flagEntrySymbol) - if !d.ctxt.linkShared && d.ctxt.BuildMode != BuildModePlugin { - // runtime.buildVersion and runtime.modinfo are referenced in .go.buildinfo section - // (see function buildinfo in data.go). They should normally be reachable from the - // runtime. Just make it explicit, in case. - names = append(names, "runtime.buildVersion", "runtime.modinfo") - } - if d.ctxt.BuildMode == BuildModePlugin { - names = append(names, objabi.PathToPrefix(*flagPluginPath)+"..inittask", objabi.PathToPrefix(*flagPluginPath)+".main", "go.plugin.tabs") - - // We don't keep the go.plugin.exports symbol, - // but we do keep the symbols it refers to. - exportsIdx := d.ldr.Lookup("go.plugin.exports", 0) - if exportsIdx != 0 { - relocs := d.ldr.Relocs(exportsIdx) - for i := 0; i < relocs.Count(); i++ { - d.mark(relocs.At(i).Sym(), 0) - } + } + // runtime.unreachableMethod is a function that will throw if called. + // We redirect unreachable methods to it. + names = append(names, "runtime.unreachableMethod") + if !d.ctxt.linkShared && d.ctxt.BuildMode != BuildModePlugin { + // runtime.buildVersion and runtime.modinfo are referenced in .go.buildinfo section + // (see function buildinfo in data.go). They should normally be reachable from the + // runtime. Just make it explicit, in case. + names = append(names, "runtime.buildVersion", "runtime.modinfo") + } + if d.ctxt.BuildMode == BuildModePlugin { + names = append(names, objabi.PathToPrefix(*flagPluginPath)+"..inittask", objabi.PathToPrefix(*flagPluginPath)+".main", "go.plugin.tabs") + + // We don't keep the go.plugin.exports symbol, + // but we do keep the symbols it refers to. + exportsIdx := d.ldr.Lookup("go.plugin.exports", 0) + if exportsIdx != 0 { + relocs := d.ldr.Relocs(exportsIdx) + for i := 0; i < relocs.Count(); i++ { + d.mark(relocs.At(i).Sym(), 0) } } } - dynexpMap := d.ctxt.cgo_export_dynamic - if d.ctxt.LinkMode == LinkExternal { - dynexpMap = d.ctxt.cgo_export_static - } - for exp := range dynexpMap { - names = append(names, exp) + if d.ctxt.Debugvlog > 1 { + d.ctxt.Logf("deadcode start names: %v\n", names) } for _, name := range names { @@ -99,6 +99,14 @@ func (d *deadcodePass) init() { // Also mark any Go functions (internal ABI). d.mark(d.ldr.Lookup(name, sym.SymVerABIInternal), 0) } + + // All dynamic exports are roots. + for _, s := range d.ctxt.dynexp { + if d.ctxt.Debugvlog > 1 { + d.ctxt.Logf("deadcode start dynexp: %s<%d>\n", d.ldr.SymName(s), d.ldr.SymVersion(s)) + } + d.mark(s, 0) + } } func (d *deadcodePass) flood() { @@ -114,7 +122,7 @@ func (d *deadcodePass) flood() { if isgotype { if d.dynlink { - // When dynaamic linking, a type may be passed across DSO + // When dynamic linking, a type may be passed across DSO // boundary and get converted to interface at the other side. d.ldr.SetAttrUsedInIface(symIdx, true) } @@ -124,10 +132,11 @@ func (d *deadcodePass) flood() { methods = methods[:0] for i := 0; i < relocs.Count(); i++ { r := relocs.At(i) + if r.Weak() { + continue + } t := r.Type() switch t { - case objabi.R_WEAKADDROFF: - continue case objabi.R_METHODOFF: if i+2 >= relocs.Count() { panic("expect three consecutive R_METHODOFF relocs") @@ -250,7 +259,7 @@ func (d *deadcodePass) mark(symIdx, parent loader.Sym) { if symIdx != 0 && !d.ldr.AttrReachable(symIdx) { d.wq.push(symIdx) d.ldr.SetAttrReachable(symIdx, true) - if objabi.Fieldtrack_enabled != 0 && d.ldr.Reachparent[symIdx] == 0 { + if buildcfg.Experiment.FieldTrack && d.ldr.Reachparent[symIdx] == 0 { d.ldr.Reachparent[symIdx] = parent } if *flagDumpDep { diff --git a/src/cmd/link/internal/ld/deadcode_test.go b/src/cmd/link/internal/ld/deadcode_test.go index b756091613cc50dea15b6a1cca1324db24d0dd67..6e128432dcb932a62fd46b04b5e46429d69dd8d9 100644 --- a/src/cmd/link/internal/ld/deadcode_test.go +++ b/src/cmd/link/internal/ld/deadcode_test.go @@ -7,8 +7,6 @@ package ld import ( "bytes" "internal/testenv" - "io/ioutil" - "os" "os/exec" "path/filepath" "testing" @@ -18,11 +16,7 @@ func TestDeadcode(t *testing.T) { testenv.MustHaveGoBuild(t) t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestDeadcode") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() tests := []struct { src string @@ -46,10 +40,10 @@ func TestDeadcode(t *testing.T) { if err != nil { t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) } - if test.pos != "" && !bytes.Contains(out, []byte(test.pos)) { + if test.pos != "" && !bytes.Contains(out, []byte(test.pos+"\n")) { t.Errorf("%s should be reachable. Output:\n%s", test.pos, out) } - if test.neg != "" && bytes.Contains(out, []byte(test.neg)) { + if test.neg != "" && bytes.Contains(out, []byte(test.neg+"\n")) { t.Errorf("%s should not be reachable. Output:\n%s", test.neg, out) } }) diff --git a/src/cmd/link/internal/ld/decodesym.go b/src/cmd/link/internal/ld/decodesym.go index fc179fc6e44109e28134070ae122d3f93fa0de01..629bdcf4cac7a9178dfe155972160c208887efca 100644 --- a/src/cmd/link/internal/ld/decodesym.go +++ b/src/cmd/link/internal/ld/decodesym.go @@ -10,17 +10,18 @@ import ( "cmd/link/internal/loader" "cmd/link/internal/sym" "debug/elf" + "encoding/binary" "log" ) // Decoding the type.* symbols. This has to be in sync with // ../../runtime/type.go, or more specifically, with what -// cmd/compile/internal/gc/reflect.go stuffs in these. +// cmd/compile/internal/reflectdata/reflect.go stuffs in these. // tflag is documented in reflect/type.go. // // tflag values must be kept in sync with copies in: -// cmd/compile/internal/gc/reflect.go +// cmd/compile/internal/reflectdata/reflect.go // cmd/link/internal/ld/decodesym.go // reflect/type.go // runtime/type.go @@ -126,8 +127,8 @@ func decodetypeName(ldr *loader.Loader, symIdx loader.Sym, relocs *loader.Relocs } data := ldr.Data(r) - namelen := int(uint16(data[1])<<8 | uint16(data[2])) - return string(data[3 : 3+namelen]) + nameLen, nameLenLen := binary.Uvarint(data[1:]) + return string(data[1+nameLenLen : 1+nameLenLen+int(nameLen)]) } func decodetypeFuncInType(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, relocs *loader.Relocs, i int) loader.Sym { diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 2ab9a55e969b8c0f868e7aa5f4936b2f28e2b22a..c53d2408cbe5105fd9011b416e2d2d738241200d 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -22,6 +22,7 @@ import ( "cmd/link/internal/loader" "cmd/link/internal/sym" "fmt" + "internal/buildcfg" "log" "path" "runtime" @@ -458,12 +459,6 @@ func newmemberoffsetattr(die *dwarf.DWDie, offs int32) { newattr(die, dwarf.DW_AT_data_member_location, dwarf.DW_CLS_CONSTANT, int64(offs), nil) } -// GDB doesn't like FORM_addr for AT_location, so emit a -// location expression that evals to a const. -func (d *dwctxt) newabslocexprattr(die *dwarf.DWDie, addr int64, symIdx loader.Sym) { - newattr(die, dwarf.DW_AT_location, dwarf.DW_CLS_ADDRESS, addr, dwSym(symIdx)) -} - func (d *dwctxt) lookupOrDiag(n string) loader.Sym { symIdx := d.ldr.Lookup(n, 0) if symIdx == 0 { @@ -1020,25 +1015,6 @@ func (d *dwctxt) synthesizechantypes(ctxt *Link, die *dwarf.DWDie) { } } -func (d *dwctxt) dwarfDefineGlobal(ctxt *Link, symIdx loader.Sym, str string, v int64, gotype loader.Sym) { - // Find a suitable CU DIE to include the global. - // One would think it's as simple as just looking at the unit, but that might - // not have any reachable code. So, we go to the runtime's CU if our unit - // isn't otherwise reachable. - unit := d.ldr.SymUnit(symIdx) - if unit == nil { - unit = ctxt.runtimeCU - } - ver := d.ldr.SymVersion(symIdx) - dv := d.newdie(unit.DWInfo, dwarf.DW_ABRV_VARIABLE, str, int(ver)) - d.newabslocexprattr(dv, v, symIdx) - if d.ldr.SymVersion(symIdx) < sym.SymVerStatic { - newattr(dv, dwarf.DW_AT_external, dwarf.DW_CLS_FLAG, 1, 0) - } - dt := d.defgotype(gotype) - d.newrefattr(dv, dwarf.DW_AT_type, dt) -} - // createUnitLength creates the initial length field with value v and update // offset of unit_length if needed. func (d *dwctxt) createUnitLength(su *loader.SymbolBuilder, v uint64) { @@ -1454,7 +1430,7 @@ func (d *dwctxt) writeframes(fs loader.Sym) dwarfSecInfo { // Emit a FDE, Section 6.4.1. // First build the section contents into a byte buffer. deltaBuf = deltaBuf[:0] - if haslr && d.ldr.AttrTopFrame(fn) { + if haslr && fi.TopFrame() { // Mark the link register as having an undefined value. // This stops call stack unwinders progressing any further. // TODO: similar mark on non-LR architectures. @@ -1480,7 +1456,7 @@ func (d *dwctxt) writeframes(fs loader.Sym) dwarfSecInfo { spdelta += int64(d.arch.PtrSize) } - if haslr && !d.ldr.AttrTopFrame(fn) { + if haslr && !fi.TopFrame() { // TODO(bryanpkc): This is imprecise. In general, the instruction // that stores the return address to the stack frame is not the // same one that allocates the frame. @@ -1552,7 +1528,7 @@ func appendSyms(syms []loader.Sym, src []sym.LoaderSym) []loader.Sym { func (d *dwctxt) writeUnitInfo(u *sym.CompilationUnit, abbrevsym loader.Sym, infoEpilog loader.Sym) []loader.Sym { syms := []loader.Sym{} - if len(u.Textp) == 0 && u.DWInfo.Child == nil { + if len(u.Textp) == 0 && u.DWInfo.Child == nil && len(u.VarDIEs) == 0 { return syms } @@ -1583,6 +1559,7 @@ func (d *dwctxt) writeUnitInfo(u *sym.CompilationUnit, abbrevsym loader.Sym, inf if u.Consts != 0 { cu = append(cu, loader.Sym(u.Consts)) } + cu = appendSyms(cu, u.VarDIEs) var cusize int64 for _, child := range cu { cusize += int64(len(d.ldr.Data(child))) @@ -1867,7 +1844,7 @@ func dwarfGenerateDebugInfo(ctxt *Link) { if producerExtra := d.ldr.Lookup(dwarf.CUInfoPrefix+"producer."+unit.Lib.Pkg, 0); producerExtra != 0 { peData = d.ldr.Data(producerExtra) } - producer := "Go cmd/compile " + objabi.Version + producer := "Go cmd/compile " + buildcfg.Version if len(peData) > 0 { // We put a semicolon before the flags to clearly // separate them from the version, which can be long @@ -1907,10 +1884,11 @@ func dwarfGenerateDebugInfo(ctxt *Link) { checkStrictDups = 1 } - // Create DIEs for global variables and the types they use. - // FIXME: ideally this should be done in the compiler, since - // for globals there isn't any abiguity about which package - // a global belongs to. + // Make a pass through all data symbols, looking for those + // corresponding to reachable, Go-generated, user-visible + // global variables. For each global of this sort, locate + // the corresponding compiler-generated DIE symbol and tack + // it onto the list associated with the unit. for idx := loader.Sym(1); idx < loader.Sym(d.ldr.NDef()); idx++ { if !d.ldr.AttrReachable(idx) || d.ldr.AttrNotInSymbolTable(idx) || @@ -1925,7 +1903,8 @@ func dwarfGenerateDebugInfo(ctxt *Link) { continue } // Skip things with no type - if d.ldr.SymGoType(idx) == 0 { + gt := d.ldr.SymGoType(idx) + if gt == 0 { continue } // Skip file local symbols (this includes static tmps, stack @@ -1939,10 +1918,20 @@ func dwarfGenerateDebugInfo(ctxt *Link) { continue } - // Create DIE for global. - sv := d.ldr.SymValue(idx) - gt := d.ldr.SymGoType(idx) - d.dwarfDefineGlobal(ctxt, idx, sn, sv, gt) + // Find compiler-generated DWARF info sym for global in question, + // and tack it onto the appropriate unit. Note that there are + // circumstances under which we can't find the compiler-generated + // symbol-- this typically happens as a result of compiler options + // (e.g. compile package X with "-dwarf=0"). + + // FIXME: use an aux sym or a relocation here instead of a + // name lookup. + varDIE := d.ldr.Lookup(dwarf.InfoPrefix+sn, 0) + if varDIE != 0 { + unit := d.ldr.SymUnit(idx) + d.defgotype(gt) + unit.VarDIEs = append(unit.VarDIEs, sym.LoaderSym(varDIE)) + } } d.synthesizestringtypes(ctxt, dwtypes.Child) @@ -2302,12 +2291,6 @@ func getDwsectCUSize(sname string, pkgname string) uint64 { return dwsectCUSize[sname+"."+pkgname] } -func saveDwsectCUSize(sname string, pkgname string, size uint64) { - dwsectCUSizeMu.Lock() - defer dwsectCUSizeMu.Unlock() - dwsectCUSize[sname+"."+pkgname] = size -} - func addDwsectCUSize(sname string, pkgname string, size uint64) { dwsectCUSizeMu.Lock() defer dwsectCUSizeMu.Unlock() diff --git a/src/cmd/link/internal/ld/dwarf_test.go b/src/cmd/link/internal/ld/dwarf_test.go index a66506d39284a81086b6348e4a229bd1f4ce39b1..2f59c2fe0aa9484a0294170ad7e21fa6a0710ca0 100644 --- a/src/cmd/link/internal/ld/dwarf_test.go +++ b/src/cmd/link/internal/ld/dwarf_test.go @@ -19,6 +19,7 @@ import ( "path/filepath" "reflect" "runtime" + "sort" "strconv" "strings" "testing" @@ -39,11 +40,7 @@ func TestRuntimeTypesPresent(t *testing.T) { t.Skip("skipping on plan9; no DWARF symbol table in executables") } - dir, err := ioutil.TempDir("", "TestRuntimeTypesPresent") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() f := gobuild(t, dir, `package main; func main() { }`, NoOpt) defer f.Close() @@ -171,11 +168,7 @@ func main() { "main.Baz": {"Foo": true, "name": false}, } - dir, err := ioutil.TempDir("", "TestEmbeddedStructMarker") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() f := gobuild(t, dir, prog, NoOpt) @@ -255,11 +248,8 @@ func main() { y[0] = nil } ` - dir, err := ioutil.TempDir("", "TestSizes") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() + f := gobuild(t, dir, prog, NoOpt) defer f.Close() d, err := f.DWARF() @@ -303,11 +293,7 @@ func main() { c <- "foo" } ` - dir, err := ioutil.TempDir("", "TestFieldOverlap") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() f := gobuild(t, dir, prog, NoOpt) defer f.Close() @@ -351,13 +337,10 @@ func varDeclCoordsAndSubrogramDeclFile(t *testing.T, testpoint string, expectFil prog := fmt.Sprintf("package main\n%s\nfunc main() {\n\nvar i int\ni = i\n}\n", directive) - dir, err := ioutil.TempDir("", testpoint) - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() f := gobuild(t, dir, prog, NoOpt) + defer f.Close() d, err := f.DWARF() if err != nil { @@ -628,8 +611,8 @@ func TestInlinedRoutineRecords(t *testing.T) { if runtime.GOOS == "plan9" { t.Skip("skipping on plan9; no DWARF symbol table in executables") } - if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" || runtime.GOOS == "darwin" { - t.Skip("skipping on solaris, illumos, and darwin, pending resolution of issue #23168") + if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" { + t.Skip("skipping on solaris, illumos, pending resolution of issue #23168") } t.Parallel() @@ -653,11 +636,7 @@ func main() { G = x } ` - dir, err := ioutil.TempDir("", "TestInlinedRoutineRecords") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() // Note: this is a build with "-l=4", as opposed to "-l -N". The // test is intended to verify DWARF that is only generated when @@ -665,6 +644,7 @@ func main() { // main.main, however, hence we build with "-gcflags=-l=4" as opposed // to "-gcflags=all=-l=4". f := gobuild(t, dir, prog, OptInl4) + defer f.Close() d, err := f.DWARF() if err != nil { @@ -788,14 +768,11 @@ func main() { func abstractOriginSanity(t *testing.T, pkgDir string, flags string) { t.Parallel() - dir, err := ioutil.TempDir("", "TestAbstractOriginSanity") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() // Build with inlining, to exercise DWARF inlining support. f := gobuildTestdata(t, dir, filepath.Join(pkgDir, "main"), flags) + defer f.Close() d, err := f.DWARF() if err != nil { @@ -871,8 +848,8 @@ func TestAbstractOriginSanity(t *testing.T) { if runtime.GOOS == "plan9" { t.Skip("skipping on plan9; no DWARF symbol table in executables") } - if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" || runtime.GOOS == "darwin" { - t.Skip("skipping on solaris, illumos, and darwin, pending resolution of issue #23168") + if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" { + t.Skip("skipping on solaris, illumos, pending resolution of issue #23168") } if wd, err := os.Getwd(); err == nil { @@ -889,11 +866,11 @@ func TestAbstractOriginSanityIssue25459(t *testing.T) { if runtime.GOOS == "plan9" { t.Skip("skipping on plan9; no DWARF symbol table in executables") } - if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" || runtime.GOOS == "darwin" { - t.Skip("skipping on solaris, illumos, and darwin, pending resolution of issue #23168") + if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" { + t.Skip("skipping on solaris, illumos, pending resolution of issue #23168") } - if runtime.GOARCH != "amd64" && runtime.GOARCH != "x86" { - t.Skip("skipping on not-amd64 not-x86; location lists not supported") + if runtime.GOARCH != "amd64" && runtime.GOARCH != "386" { + t.Skip("skipping on not-amd64 not-386; location lists not supported") } if wd, err := os.Getwd(); err == nil { @@ -910,8 +887,8 @@ func TestAbstractOriginSanityIssue26237(t *testing.T) { if runtime.GOOS == "plan9" { t.Skip("skipping on plan9; no DWARF symbol table in executables") } - if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" || runtime.GOOS == "darwin" { - t.Skip("skipping on solaris, illumos, and darwin, pending resolution of issue #23168") + if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" { + t.Skip("skipping on solaris, illumos, pending resolution of issue #23168") } if wd, err := os.Getwd(); err == nil { gopathdir := filepath.Join(wd, "testdata", "issue26237") @@ -973,13 +950,11 @@ func main() { print(p) } ` - dir, err := ioutil.TempDir("", "TestRuntimeType") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() f := gobuild(t, dir, prog, flags) + defer f.Close() + out, err := exec.Command(f.path).CombinedOutput() if err != nil { t.Fatalf("could not run test program: %v", err) @@ -1043,11 +1018,7 @@ func TestIssue27614(t *testing.T) { t.Parallel() - dir, err := ioutil.TempDir("", "go-build") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() const prog = `package main @@ -1161,11 +1132,7 @@ func TestStaticTmp(t *testing.T) { t.Parallel() - dir, err := ioutil.TempDir("", "go-build") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() const prog = `package main @@ -1243,11 +1210,7 @@ func TestPackageNameAttr(t *testing.T) { t.Parallel() - dir, err := ioutil.TempDir("", "go-build") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() const prog = "package main\nfunc main() {\nprintln(\"hello world\")\n}\n" @@ -1307,14 +1270,10 @@ func TestMachoIssue32233(t *testing.T) { t.Skip("skipping; test only interesting on darwin") } - tmpdir, err := ioutil.TempDir("", "TestMachoIssue32233") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() - wd, err2 := os.Getwd() - if err2 != nil { + wd, err := os.Getwd() + if err != nil { t.Fatalf("where am I? %v", err) } pdir := filepath.Join(wd, "testdata", "issue32233", "main") @@ -1328,11 +1287,7 @@ func TestWindowsIssue36495(t *testing.T) { t.Skip("skipping: test only on windows") } - dir, err := ioutil.TempDir("", "TestEmbeddedStructMarker") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() prog := ` package main @@ -1343,10 +1298,12 @@ func main() { fmt.Println("Hello World") }` f := gobuild(t, dir, prog, NoOpt) + defer f.Close() exe, err := pe.Open(f.path) if err != nil { t.Fatalf("error opening pe file: %v", err) } + defer exe.Close() dw, err := exe.DWARF() if err != nil { t.Fatalf("error parsing DWARF: %v", err) @@ -1397,17 +1354,14 @@ func TestIssue38192(t *testing.T) { // Build a test program that contains a translation unit whose // text (from am assembly source) contains only a single instruction. - tmpdir, err := ioutil.TempDir("", "TestIssue38192") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() wd, err := os.Getwd() if err != nil { t.Fatalf("where am I? %v", err) } pdir := filepath.Join(wd, "testdata", "issue38192") f := gobuildTestdata(t, tmpdir, pdir, DefaultOpt) + defer f.Close() // Open the resulting binary and examine the DWARF it contains. // Look for the function of interest ("main.singleInstruction") @@ -1520,17 +1474,15 @@ func TestIssue39757(t *testing.T) { // compiler/runtime in ways that aren't happening now, so this // might be something to check for if it does start failing. - tmpdir, err := ioutil.TempDir("", "TestIssue38192") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() + wd, err := os.Getwd() if err != nil { t.Fatalf("where am I? %v", err) } pdir := filepath.Join(wd, "testdata", "issue39757") f := gobuildTestdata(t, tmpdir, pdir, DefaultOpt) + defer f.Close() syms, err := f.Symbols() if err != nil { @@ -1619,3 +1571,187 @@ func TestIssue39757(t *testing.T) { } } } + +func TestIssue42484(t *testing.T) { + testenv.MustHaveGoBuild(t) + + if runtime.GOOS == "plan9" { + t.Skip("skipping on plan9; no DWARF symbol table in executables") + } + + t.Parallel() + + tmpdir, err := ioutil.TempDir("", "TestIssue42484") + if err != nil { + t.Fatalf("could not create directory: %v", err) + } + defer os.RemoveAll(tmpdir) + wd, err := os.Getwd() + if err != nil { + t.Fatalf("where am I? %v", err) + } + pdir := filepath.Join(wd, "testdata", "issue42484") + f := gobuildTestdata(t, tmpdir, pdir, NoOpt) + + var lastAddr uint64 + var lastFile string + var lastLine int + + dw, err := f.DWARF() + if err != nil { + t.Fatalf("error parsing DWARF: %v", err) + } + rdr := dw.Reader() + for { + e, err := rdr.Next() + if err != nil { + t.Fatalf("error reading DWARF: %v", err) + } + if e == nil { + break + } + if e.Tag != dwarf.TagCompileUnit { + continue + } + lnrdr, err := dw.LineReader(e) + if err != nil { + t.Fatalf("error creating DWARF line reader: %v", err) + } + if lnrdr != nil { + var lne dwarf.LineEntry + for { + err := lnrdr.Next(&lne) + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("error reading next DWARF line: %v", err) + } + if lne.EndSequence { + continue + } + if lne.Address == lastAddr && (lne.File.Name != lastFile || lne.Line != lastLine) { + t.Errorf("address %#x is assigned to both %s:%d and %s:%d", lastAddr, lastFile, lastLine, lne.File.Name, lne.Line) + } + lastAddr = lne.Address + lastFile = lne.File.Name + lastLine = lne.Line + } + } + rdr.SkipChildren() + } + f.Close() +} + +func TestOutputParamAbbrevAndAttr(t *testing.T) { + testenv.MustHaveGoBuild(t) + + if runtime.GOOS == "plan9" { + t.Skip("skipping on plan9; no DWARF symbol table in executables") + } + t.Parallel() + + // This test verifies that the compiler is selecting the correct + // DWARF abbreviation for output parameters, and that the + // variable parameter attribute is correct for in-params and + // out-params. + + const prog = ` +package main + +//go:noinline +func ABC(c1, c2, c3 int, d1, d2, d3, d4 string, f1, f2, f3 float32, g1 [1024]int) (r1 int, r2 int, r3 [1024]int, r4 byte, r5 string, r6 float32) { + g1[0] = 6 + r1, r2, r3, r4, r5, r6 = c3, c2+c1, g1, 'a', d1+d2+d3+d4, f1+f2+f3 + return +} + +func main() { + a := [1024]int{} + v1, v2, v3, v4, v5, v6 := ABC(1, 2, 3, "a", "b", "c", "d", 1.0, 2.0, 1.0, a) + println(v1, v2, v3[0], v4, v5, v6) +} +` + dir := t.TempDir() + f := gobuild(t, dir, prog, NoOpt) + defer f.Close() + + d, err := f.DWARF() + if err != nil { + t.Fatalf("error reading DWARF: %v", err) + } + + rdr := d.Reader() + ex := examiner{} + if err := ex.populate(rdr); err != nil { + t.Fatalf("error reading DWARF: %v", err) + } + + // Locate the main.ABC DIE + abcs := ex.Named("main.ABC") + if len(abcs) == 0 { + t.Fatalf("unable to locate DIE for main.ABC") + } + if len(abcs) != 1 { + t.Fatalf("more than one main.ABC DIE") + } + abcdie := abcs[0] + + // Vet the DIE + if abcdie.Tag != dwarf.TagSubprogram { + t.Fatalf("unexpected tag %v on main.ABC DIE", abcdie.Tag) + } + + // A setting of DW_AT_variable_parameter indicates that the + // param in question is an output parameter; we want to see this + // attribute set to TRUE for all Go return params. It would be + // OK to have it missing for input parameters, but for the moment + // we verify that the attr is present but set to false. + + // Values in this map are of the form : + // where order is the order within the child DIE list of the param, + // and is an integer: + // + // -1: varparm attr not found + // 1: varparm found with value false + // 2: varparm found with value true + // + foundParams := make(map[string]string) + + // Walk ABCs's children looking for params. + abcIdx := ex.idxFromOffset(abcdie.Offset) + childDies := ex.Children(abcIdx) + idx := 0 + for _, child := range childDies { + if child.Tag == dwarf.TagFormalParameter { + st := -1 + if vp, ok := child.Val(dwarf.AttrVarParam).(bool); ok { + if vp { + st = 2 + } else { + st = 1 + } + } + if name, ok := child.Val(dwarf.AttrName).(string); ok { + foundParams[name] = fmt.Sprintf("%d:%d", idx, st) + idx++ + } + } + } + + // Digest the result. + found := make([]string, 0, len(foundParams)) + for k, v := range foundParams { + found = append(found, fmt.Sprintf("%s:%s", k, v)) + } + sort.Strings(found) + + // Make sure we see all of the expected params in the proper + // order, that they have the varparam attr, and the varparm is set + // for the returns. + expected := "[c1:0:1 c2:1:1 c3:2:1 d1:3:1 d2:4:1 d3:5:1 d4:6:1 f1:7:1 f2:8:1 f3:9:1 g1:10:1 r1:11:2 r2:12:2 r3:13:2 r4:14:2 r5:15:2 r6:16:2]" + if fmt.Sprintf("%+v", found) != expected { + t.Errorf("param check failed, wanted %s got %s\n", + expected, found) + } +} diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index 37b2dc640d44472275d3a7ed8053f845dd63ec3e..81011638bc5fae6b52cf2898d2849dced105c74d 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -14,6 +14,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "internal/buildcfg" "path/filepath" "sort" "strings" @@ -519,6 +520,90 @@ func elfwriteinterp(out *OutBuf) int { return int(sh.Size) } +// member of .gnu.attributes of MIPS for fpAbi +const ( + // No floating point is present in the module (default) + MIPS_FPABI_NONE = 0 + // FP code in the module uses the FP32 ABI for a 32-bit ABI + MIPS_FPABI_ANY = 1 + // FP code in the module only uses single precision ABI + MIPS_FPABI_SINGLE = 2 + // FP code in the module uses soft-float ABI + MIPS_FPABI_SOFT = 3 + // FP code in the module assumes an FPU with FR=1 and has 12 + // callee-saved doubles. Historic, no longer supported. + MIPS_FPABI_HIST = 4 + // FP code in the module uses the FPXX ABI + MIPS_FPABI_FPXX = 5 + // FP code in the module uses the FP64 ABI + MIPS_FPABI_FP64 = 6 + // FP code in the module uses the FP64A ABI + MIPS_FPABI_FP64A = 7 +) + +func elfMipsAbiFlags(sh *ElfShdr, startva uint64, resoff uint64) int { + n := 24 + sh.Addr = startva + resoff - uint64(n) + sh.Off = resoff - uint64(n) + sh.Size = uint64(n) + sh.Type = uint32(elf.SHT_MIPS_ABIFLAGS) + sh.Flags = uint64(elf.SHF_ALLOC) + + return n +} + +//typedef struct +//{ +// /* Version of flags structure. */ +// uint16_t version; +// /* The level of the ISA: 1-5, 32, 64. */ +// uint8_t isa_level; +// /* The revision of ISA: 0 for MIPS V and below, 1-n otherwise. */ +// uint8_t isa_rev; +// /* The size of general purpose registers. */ +// uint8_t gpr_size; +// /* The size of co-processor 1 registers. */ +// uint8_t cpr1_size; +// /* The size of co-processor 2 registers. */ +// uint8_t cpr2_size; +// /* The floating-point ABI. */ +// uint8_t fp_abi; +// /* Processor-specific extension. */ +// uint32_t isa_ext; +// /* Mask of ASEs used. */ +// uint32_t ases; +// /* Mask of general flags. */ +// uint32_t flags1; +// uint32_t flags2; +//} Elf_Internal_ABIFlags_v0; +func elfWriteMipsAbiFlags(ctxt *Link) int { + sh := elfshname(".MIPS.abiflags") + ctxt.Out.SeekSet(int64(sh.Off)) + ctxt.Out.Write16(0) // version + ctxt.Out.Write8(32) // isaLevel + ctxt.Out.Write8(1) // isaRev + ctxt.Out.Write8(1) // gprSize + ctxt.Out.Write8(1) // cpr1Size + ctxt.Out.Write8(0) // cpr2Size + if buildcfg.GOMIPS == "softfloat" { + ctxt.Out.Write8(MIPS_FPABI_SOFT) // fpAbi + } else { + // Go cannot make sure non odd-number-fpr is used (ie, in load a double from memory). + // So, we mark the object is MIPS I style paired float/double register scheme, + // aka MIPS_FPABI_ANY. If we mark the object as FPXX, the kernel may use FR=1 mode, + // then we meet some problem. + // Note: MIPS_FPABI_ANY is bad naming: in fact it is MIPS I style FPR usage. + // It is not for 'ANY'. + // TODO: switch to FPXX after be sure that no odd-number-fpr is used. + ctxt.Out.Write8(MIPS_FPABI_ANY) // fpAbi + } + ctxt.Out.Write32(0) // isaExt + ctxt.Out.Write32(0) // ases + ctxt.Out.Write32(0) // flags1 + ctxt.Out.Write32(0) // flags2 + return int(sh.Size) +} + func elfnote(sh *ElfShdr, startva uint64, resoff uint64, sz int) int { n := 3*4 + uint64(sz) + resoff%4 @@ -865,6 +950,11 @@ func elfdynhash(ctxt *Link) { } s = ldr.CreateSymForUpdate(".dynamic", 0) + if ctxt.BuildMode == BuildModePIE { + // https://github.com/bminor/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/elf/elf.h#L986 + const DTFLAGS_1_PIE = 0x08000000 + Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(DTFLAGS_1_PIE)) + } elfverneed = nfile if elfverneed != 0 { elfWriteDynEntSym(ctxt, s, elf.DT_VERNEED, gnuVersionR.Sym()) @@ -1086,7 +1176,7 @@ func elfrelocsect(ctxt *Link, out *OutBuf, sect *sym.Section, syms []loader.Sym) } } - eaddr := int32(sect.Vaddr + sect.Length) + eaddr := sect.Vaddr + sect.Length for _, s := range syms { if !ldr.AttrReachable(s) { continue @@ -1204,6 +1294,10 @@ func (ctxt *Link) doelf() { shstrtab.Addstring(".noptrbss") shstrtab.Addstring("__libfuzzer_extra_counters") shstrtab.Addstring(".go.buildinfo") + if ctxt.IsMIPS() { + shstrtab.Addstring(".MIPS.abiflags") + shstrtab.Addstring(".gnu.attributes") + } // generate .tbss section for dynamic internal linker or external // linking, so that various binutils could correctly calculate @@ -1254,6 +1348,10 @@ func (ctxt *Link) doelf() { shstrtab.Addstring(elfRelType + ".data.rel.ro") } shstrtab.Addstring(elfRelType + ".go.buildinfo") + if ctxt.IsMIPS() { + shstrtab.Addstring(elfRelType + ".MIPS.abiflags") + shstrtab.Addstring(elfRelType + ".gnu.attributes") + } // add a .note.GNU-stack section to mark the stack as non-executable shstrtab.Addstring(".note.GNU-stack") @@ -1445,6 +1543,35 @@ func (ctxt *Link) doelf() { if ctxt.LinkMode == LinkExternal && *flagBuildid != "" { addgonote(ctxt, ".note.go.buildid", ELF_NOTE_GOBUILDID_TAG, []byte(*flagBuildid)) } + + //type mipsGnuAttributes struct { + // version uint8 // 'A' + // length uint32 // 15 including itself + // gnu [4]byte // "gnu\0" + // tag uint8 // 1:file, 2: section, 3: symbol, 1 here + // taglen uint32 // tag length, including tag, 7 here + // tagfp uint8 // 4 + // fpAbi uint8 // see .MIPS.abiflags + //} + if ctxt.IsMIPS() { + gnuattributes := ldr.CreateSymForUpdate(".gnu.attributes", 0) + gnuattributes.SetType(sym.SELFROSECT) + gnuattributes.SetReachable(true) + gnuattributes.AddUint8('A') // version 'A' + gnuattributes.AddUint32(ctxt.Arch, 15) // length 15 including itself + gnuattributes.AddBytes([]byte("gnu\x00")) // "gnu\0" + gnuattributes.AddUint8(1) // 1:file, 2: section, 3: symbol, 1 here + gnuattributes.AddUint32(ctxt.Arch, 7) // tag length, including tag, 7 here + gnuattributes.AddUint8(4) // 4 for FP, 8 for MSA + if buildcfg.GOMIPS == "softfloat" { + gnuattributes.AddUint8(MIPS_FPABI_SOFT) + } else { + // Note: MIPS_FPABI_ANY is bad naming: in fact it is MIPS I style FPR usage. + // It is not for 'ANY'. + // TODO: switch to FPXX after be sure that no odd-number-fpr is used. + gnuattributes.AddUint8(MIPS_FPABI_ANY) + } + } } // Do not write DT_NULL. elfdynhash will finish it. @@ -1622,14 +1749,14 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Addralign = 1 - if interpreter == "" && objabi.GO_LDSO != "" { - interpreter = objabi.GO_LDSO + if interpreter == "" && buildcfg.GO_LDSO != "" { + interpreter = buildcfg.GO_LDSO } if interpreter == "" { switch ctxt.HeadType { case objabi.Hlinux: - if objabi.GOOS == "android" { + if buildcfg.GOOS == "android" { interpreter = thearch.Androiddynld if interpreter == "" { Exitf("ELF interpreter not set") @@ -1910,6 +2037,25 @@ elfobj: shsym(sh, ldr, ldr.Lookup(".shstrtab", 0)) eh.Shstrndx = uint16(sh.shnum) + if ctxt.IsMIPS() { + sh = elfshname(".MIPS.abiflags") + sh.Type = uint32(elf.SHT_MIPS_ABIFLAGS) + sh.Flags = uint64(elf.SHF_ALLOC) + sh.Addralign = 8 + resoff -= int64(elfMipsAbiFlags(sh, uint64(startva), uint64(resoff))) + + ph := newElfPhdr() + ph.Type = elf.PT_MIPS_ABIFLAGS + ph.Flags = elf.PF_R + phsh(ph, sh) + + sh = elfshname(".gnu.attributes") + sh.Type = uint32(elf.SHT_GNU_ATTRIBUTES) + sh.Addralign = 1 + ldr := ctxt.loader + shsym(sh, ldr, ldr.Lookup(".gnu.attributes", 0)) + } + // put these sections early in the list if !*FlagS { elfshname(".symtab") @@ -2029,6 +2175,10 @@ elfobj: if !*FlagD { a += int64(elfwriteinterp(ctxt.Out)) } + if ctxt.IsMIPS() { + a += int64(elfWriteMipsAbiFlags(ctxt)) + } + if ctxt.LinkMode != LinkExternal { if ctxt.HeadType == objabi.Hnetbsd { a += int64(elfwritenetbsdsig(ctxt.Out)) @@ -2050,6 +2200,12 @@ elfobj: if a > elfreserve { Errorf(nil, "ELFRESERVE too small: %d > %d with %d text sections", a, elfreserve, numtext) } + + // Verify the amount of space allocated for the elf header is sufficient. The file offsets are + // already computed in layout, so we could spill into another section. + if a > int64(HEADR) { + Errorf(nil, "HEADR too small: %d > %d with %d text sections", a, HEADR, numtext) + } } func elfadddynsym(ldr *loader.Loader, target *Target, syms *ArchSyms, s loader.Sym) { diff --git a/src/cmd/link/internal/ld/elf_test.go b/src/cmd/link/internal/ld/elf_test.go index 776fc1b4f9793245f7704474e641efe32ad18e6b..d86ebb89e041a16e06fd0d741c1955cb5cb9dbe8 100644 --- a/src/cmd/link/internal/ld/elf_test.go +++ b/src/cmd/link/internal/ld/elf_test.go @@ -1,3 +1,4 @@ +//go:build cgo // +build cgo // Copyright 2019 The Go Authors. All rights reserved. @@ -21,11 +22,7 @@ import ( func TestDynSymShInfo(t *testing.T) { t.Parallel() testenv.MustHaveGoBuild(t) - dir, err := ioutil.TempDir("", "go-build-issue33358") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() const prog = ` package main @@ -51,6 +48,7 @@ func main() { if err != nil { t.Fatalf("failed to open built file: %v", err) } + defer fi.Close() elfFile, err := elf.NewFile(fi) if err != nil { @@ -95,11 +93,7 @@ func TestNoDuplicateNeededEntries(t *testing.T) { t.Parallel() - dir, err := ioutil.TempDir("", "no-dup-needed") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() wd, err := os.Getwd() if err != nil { diff --git a/src/cmd/link/internal/ld/execarchive.go b/src/cmd/link/internal/ld/execarchive.go index 4687c624de464611e7de9036ae187c3e94be6023..918b86cdc5bbaf7a293bdb33dc1c02c99d2a6bb7 100644 --- a/src/cmd/link/internal/ld/execarchive.go +++ b/src/cmd/link/internal/ld/execarchive.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !wasm && !windows // +build !wasm,!windows package ld diff --git a/src/cmd/link/internal/ld/execarchive_noexec.go b/src/cmd/link/internal/ld/execarchive_noexec.go index a70dea9fda3b13ff366850245731a8d9d5918fbe..5e1f2669d3c67fc942458bacc511066df7108982 100644 --- a/src/cmd/link/internal/ld/execarchive_noexec.go +++ b/src/cmd/link/internal/ld/execarchive_noexec.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build wasm || windows // +build wasm windows package ld diff --git a/src/cmd/link/internal/ld/fallocate_test.go b/src/cmd/link/internal/ld/fallocate_test.go index 244b70f0615067d724febf3fbb8ed91e2edb250a..1ed0eb2ca74f9d5ca9a9c483085b9b7acdabc902 100644 --- a/src/cmd/link/internal/ld/fallocate_test.go +++ b/src/cmd/link/internal/ld/fallocate_test.go @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || linux // +build darwin linux package ld import ( - "io/ioutil" "os" "path/filepath" "syscall" @@ -15,14 +15,10 @@ import ( ) func TestFallocate(t *testing.T) { - dir, err := ioutil.TempDir("", "TestFallocate") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() filename := filepath.Join(dir, "a.out") out := NewOutBuf(nil) - err = out.Open(filename) + err := out.Open(filename) if err != nil { t.Fatalf("Open file failed: %v", err) } diff --git a/src/cmd/link/internal/ld/go.go b/src/cmd/link/internal/ld/go.go index fbc7a78d0ed299b98811419fb77c32366e3c96c0..fc63b30c80f1b8a6dd5c31fe1e9ad503efd2a7ff 100644 --- a/src/cmd/link/internal/ld/go.go +++ b/src/cmd/link/internal/ld/go.go @@ -9,6 +9,7 @@ package ld import ( "bytes" "cmd/internal/bio" + "cmd/internal/obj" "cmd/internal/objabi" "cmd/internal/sys" "cmd/link/internal/loader" @@ -101,36 +102,13 @@ func loadcgo(ctxt *Link, file string, pkg string, p string) { return } - // Find cgo_export symbols. They are roots in the deadcode pass. - for _, f := range directives { - switch f[0] { - case "cgo_export_static", "cgo_export_dynamic": - if len(f) < 2 || len(f) > 3 { - continue - } - local := f[1] - switch ctxt.BuildMode { - case BuildModeCShared, BuildModeCArchive, BuildModePlugin: - if local == "main" { - continue - } - } - local = expandpkg(local, pkg) - if f[0] == "cgo_export_static" { - ctxt.cgo_export_static[local] = true - } else { - ctxt.cgo_export_dynamic[local] = true - } - } - } - // Record the directives. We'll process them later after Symbols are created. ctxt.cgodata = append(ctxt.cgodata, cgodata{file, pkg, directives}) } // Set symbol attributes or flags based on cgo directives. // Any newly discovered HOSTOBJ syms are added to 'hostObjSyms'. -func setCgoAttr(ctxt *Link, lookup func(string, int) loader.Sym, file string, pkg string, directives [][]string, hostObjSyms map[loader.Sym]struct{}) { +func setCgoAttr(ctxt *Link, file string, pkg string, directives [][]string, hostObjSyms map[loader.Sym]struct{}) { l := ctxt.loader for _, f := range directives { switch f[0] { @@ -173,7 +151,7 @@ func setCgoAttr(ctxt *Link, lookup func(string, int) loader.Sym, file string, pk if i := strings.Index(remote, "#"); i >= 0 { remote, q = remote[:i], remote[i+1:] } - s := lookup(local, 0) + s := l.LookupOrCreateSym(local, 0) st := l.SymType(s) if st == 0 || st == sym.SXREF || st == sym.SBSS || st == sym.SNOPTRBSS || st == sym.SHOSTOBJ { l.SetSymDynimplib(s, lib) @@ -199,7 +177,7 @@ func setCgoAttr(ctxt *Link, lookup func(string, int) loader.Sym, file string, pk } local := f[1] - s := lookup(local, 0) + s := l.LookupOrCreateSym(local, 0) su := l.MakeSymbolUpdater(s) su.SetType(sym.SHOSTOBJ) su.SetSize(0) @@ -207,7 +185,7 @@ func setCgoAttr(ctxt *Link, lookup func(string, int) loader.Sym, file string, pk continue case "cgo_export_static", "cgo_export_dynamic": - if len(f) < 2 || len(f) > 3 { + if len(f) < 2 || len(f) > 4 { break } local := f[1] @@ -216,13 +194,20 @@ func setCgoAttr(ctxt *Link, lookup func(string, int) loader.Sym, file string, pk remote = f[2] } local = expandpkg(local, pkg) + // The compiler adds a fourth argument giving + // the definition ABI of function symbols. + abi := obj.ABI0 + if len(f) > 3 { + var ok bool + abi, ok = obj.ParseABI(f[3]) + if !ok { + fmt.Fprintf(os.Stderr, "%s: bad ABI in cgo_export directive %s\n", os.Args[0], f) + nerrors++ + return + } + } - // The compiler arranges for an ABI0 wrapper - // to be available for all cgo-exported - // functions. Link.loadlib will resolve any - // ABI aliases we find here (since we may not - // yet know it's an alias). - s := lookup(local, 0) + s := l.LookupOrCreateSym(local, sym.ABIToVersion(abi)) if l.SymType(s) == sym.SHOSTOBJ { hostObjSyms[s] = struct{}{} @@ -230,7 +215,7 @@ func setCgoAttr(ctxt *Link, lookup func(string, int) loader.Sym, file string, pk switch ctxt.BuildMode { case BuildModeCShared, BuildModeCArchive, BuildModePlugin: - if s == lookup("main", 0) { + if s == l.Lookup("main", 0) { continue } } @@ -254,11 +239,32 @@ func setCgoAttr(ctxt *Link, lookup func(string, int) loader.Sym, file string, pk return } + // Mark exported symbols and also add them to + // the lists used for roots in the deadcode pass. if f[0] == "cgo_export_static" { + if ctxt.LinkMode == LinkExternal && !l.AttrCgoExportStatic(s) { + // Static cgo exports appear + // in the exported symbol table. + ctxt.dynexp = append(ctxt.dynexp, s) + } + if ctxt.LinkMode == LinkInternal { + // For internal linking, we're + // responsible for resolving + // relocations from host objects. + // Record the right Go symbol + // version to use. + l.AddCgoExport(s) + } l.SetAttrCgoExportStatic(s, true) } else { + if ctxt.LinkMode == LinkInternal && !l.AttrCgoExportDynamic(s) { + // Dynamic cgo exports appear + // in the exported symbol table. + ctxt.dynexp = append(ctxt.dynexp, s) + } l.SetAttrCgoExportDynamic(s, true) } + continue case "cgo_dynamic_linker": @@ -440,9 +446,16 @@ func (ctxt *Link) addexport() { return } - for _, exp := range ctxt.dynexp { - Adddynsym(ctxt.loader, &ctxt.Target, &ctxt.ArchSyms, exp) + // Add dynamic symbols. + for _, s := range ctxt.dynexp { + // Consistency check. + if !ctxt.loader.AttrReachable(s) { + panic("dynexp entry not reachable") + } + + Adddynsym(ctxt.loader, &ctxt.Target, &ctxt.ArchSyms, s) } + for _, lib := range dedupLibraries(ctxt, dynlib) { adddynlib(ctxt, lib) } diff --git a/src/cmd/link/internal/ld/go_test.go b/src/cmd/link/internal/ld/go_test.go index 0197196023b3d0896118ba09703147e4c3189d80..230f85a0e5f2b9bc8eb0860a164d830ced3665ac 100644 --- a/src/cmd/link/internal/ld/go_test.go +++ b/src/cmd/link/internal/ld/go_test.go @@ -8,7 +8,6 @@ import ( "cmd/internal/objabi" "internal/testenv" "io/ioutil" - "os" "os/exec" "path/filepath" "reflect" @@ -86,11 +85,7 @@ func TestDedupLibrariesOpenBSDLink(t *testing.T) { testenv.MustHaveCGO(t) t.Parallel() - dir, err := ioutil.TempDir("", "dedup-build") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() // cgo_import_dynamic both the unversioned libraries and pull in the // net package to get a cgo package with a versioned library. diff --git a/src/cmd/link/internal/ld/issue33808_test.go b/src/cmd/link/internal/ld/issue33808_test.go index 92a47faa4a09b22252bb43b672d66ed2afa9a383..43f4540a022aee1594143b88d378af526a791499 100644 --- a/src/cmd/link/internal/ld/issue33808_test.go +++ b/src/cmd/link/internal/ld/issue33808_test.go @@ -6,8 +6,6 @@ package ld import ( "internal/testenv" - "io/ioutil" - "os" "runtime" "strings" "testing" @@ -31,11 +29,7 @@ func TestIssue33808(t *testing.T) { testenv.MustHaveCGO(t) t.Parallel() - dir, err := ioutil.TempDir("", "TestIssue33808") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() f := gobuild(t, dir, prog, "-ldflags=-linkmode=external") f.Close() diff --git a/src/cmd/link/internal/ld/ld_test.go b/src/cmd/link/internal/ld/ld_test.go index cdfaadb17de7d1a554e1861b43d8e5906f02f5cb..3702a4d08f586f3674da2f083cbdc493a939586c 100644 --- a/src/cmd/link/internal/ld/ld_test.go +++ b/src/cmd/link/internal/ld/ld_test.go @@ -9,7 +9,6 @@ import ( "fmt" "internal/testenv" "io/ioutil" - "os" "os/exec" "path/filepath" "runtime" @@ -25,11 +24,6 @@ func TestUndefinedRelocErrors(t *testing.T) { testenv.MustInternalLink(t) t.Parallel() - dir, err := ioutil.TempDir("", "go-build") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) out, err := exec.Command(testenv.GoToolPath(t), "build", "./testdata/issue10978").CombinedOutput() if err == nil { @@ -108,11 +102,7 @@ func TestArchiveBuildInvokeWithExec(t *testing.T) { case "openbsd", "windows": t.Skip("c-archive unsupported") } - dir, err := ioutil.TempDir("", "go-build") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() srcfile := filepath.Join(dir, "test.go") arfile := filepath.Join(dir, "test.a") @@ -141,34 +131,36 @@ func TestArchiveBuildInvokeWithExec(t *testing.T) { } } -func TestPPC64LargeTextSectionSplitting(t *testing.T) { - // The behavior we're checking for is of interest only on ppc64. - if !strings.HasPrefix(runtime.GOARCH, "ppc64") { - t.Skip("test useful only for ppc64") +func TestLargeTextSectionSplitting(t *testing.T) { + switch runtime.GOARCH { + case "ppc64", "ppc64le": + case "arm64": + if runtime.GOOS == "darwin" { + break + } + fallthrough + default: + t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH) } testenv.MustHaveGoBuild(t) testenv.MustHaveCGO(t) t.Parallel() - dir, err := ioutil.TempDir("", "go-build") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() - // NB: the use of -ldflags=-debugppc64textsize=1048576 tells the linker to + // NB: the use of -ldflags=-debugtextsize=1048576 tells the linker to // split text sections at a size threshold of 1M instead of the - // architected limit of 67M. The choice of building cmd/go is - // arbitrary; we just need something sufficiently large that uses + // architected limit of 67M or larger. The choice of building cmd/go + // is arbitrary; we just need something sufficiently large that uses // external linking. exe := filepath.Join(dir, "go.exe") - out, eerr := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugppc64textsize=1048576", "cmd/go").CombinedOutput() + out, eerr := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugtextsize=1048576", "cmd/go").CombinedOutput() if eerr != nil { t.Fatalf("build failure: %s\n%s\n", eerr, string(out)) } // Result should be runnable. - _, err = exec.Command(exe, "version").CombinedOutput() + _, err := exec.Command(exe, "version").CombinedOutput() if err != nil { t.Fatal(err) } @@ -182,6 +174,8 @@ func TestWindowsBuildmodeCSharedASLR(t *testing.T) { t.Skip("skipping windows amd64/386 only test") } + testenv.MustHaveCGO(t) + t.Run("aslr", func(t *testing.T) { testWindowsBuildmodeCSharedASLR(t, true) }) @@ -194,11 +188,7 @@ func testWindowsBuildmodeCSharedASLR(t *testing.T, useASLR bool) { t.Parallel() testenv.MustHaveGoBuild(t) - dir, err := ioutil.TempDir("", "go-build") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() srcfile := filepath.Join(dir, "test.go") objfile := filepath.Join(dir, "test.dll") @@ -242,3 +232,103 @@ func testWindowsBuildmodeCSharedASLR(t *testing.T, useASLR bool) { t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag should not be set") } } + +// TestMemProfileCheck tests that cmd/link sets +// runtime.disableMemoryProfiling if the runtime.MemProfile +// symbol is unreachable after deadcode (and not dynlinking). +// The runtime then uses that to set the default value of +// runtime.MemProfileRate, which this test checks. +func TestMemProfileCheck(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + tests := []struct { + name string + prog string + wantOut string + }{ + { + "no_memprofile", + ` +package main +import "runtime" +func main() { + println(runtime.MemProfileRate) +} +`, + "0", + }, + { + "with_memprofile", + ` +package main +import "runtime" +func main() { + runtime.MemProfile(nil, false) + println(runtime.MemProfileRate) +} +`, + "524288", + }, + { + "with_memprofile_indirect", + ` +package main +import "runtime" +var f = runtime.MemProfile +func main() { + if f == nil { + panic("no f") + } + println(runtime.MemProfileRate) +} +`, + "524288", + }, + { + "with_memprofile_runtime_pprof", + ` +package main +import "runtime" +import "runtime/pprof" +func main() { + _ = pprof.Profiles() + println(runtime.MemProfileRate) +} +`, + "524288", + }, + { + "with_memprofile_http_pprof", + ` +package main +import "runtime" +import _ "net/http/pprof" +func main() { + println(runtime.MemProfileRate) +} +`, + "524288", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + tempDir := t.TempDir() + src := filepath.Join(tempDir, "x.go") + if err := ioutil.WriteFile(src, []byte(tt.prog), 0644); err != nil { + t.Fatal(err) + } + cmd := exec.Command(testenv.GoToolPath(t), "run", src) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + got := strings.TrimSpace(string(out)) + if got != tt.wantOut { + t.Errorf("got %q; want %q", got, tt.wantOut) + } + }) + } +} diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 17d5040827c0e4b03f708b719bf7752c486f3cf6..644faeb2fbc2fd039b65681621a296cc700d2e53 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -49,6 +49,7 @@ import ( "encoding/base64" "encoding/binary" "fmt" + "internal/buildcfg" exec "internal/execabs" "io" "io/ioutil" @@ -56,7 +57,6 @@ import ( "os" "path/filepath" "runtime" - "sort" "strings" "sync" ) @@ -118,6 +118,8 @@ type ArchSyms struct { Dynamic loader.Sym DynSym loader.Sym DynStr loader.Sym + + unreachableMethod loader.Sym } // mkArchSym is a helper for setArchSyms, to set up a special symbol. @@ -142,6 +144,7 @@ func (ctxt *Link) setArchSyms() { ctxt.mkArchSym(".dynamic", 0, &ctxt.Dynamic) ctxt.mkArchSym(".dynsym", 0, &ctxt.DynSym) ctxt.mkArchSym(".dynstr", 0, &ctxt.DynStr) + ctxt.mkArchSym("runtime.unreachableMethod", sym.SymVerABIInternal, &ctxt.unreachableMethod) if ctxt.IsPPC64() { ctxt.mkArchSym("TOC", 0, &ctxt.TOC) @@ -174,11 +177,18 @@ func (ctxt *Link) setArchSyms() { } type Arch struct { - Funcalign int - Maxalign int - Minalign int - Dwarfregsp int - Dwarfreglr int + Funcalign int + Maxalign int + Minalign int + Dwarfregsp int + Dwarfreglr int + + // Threshold of total text size, used for trampoline insertion. If the total + // text size is smaller than TrampLimit, we won't need to insert trampolines. + // It is pretty close to the offset range of a direct CALL machine instruction. + // We leave some room for extra stuff like PLT stubs. + TrampLimit uint64 + Androiddynld string Linuxdynld string Freebsddynld string @@ -193,9 +203,6 @@ type Arch struct { // are padded with zeros. CodePad []byte - // Set to true to write all text blocks in with CodeBlkWrite - WriteTextBlocks bool - // Plan 9 variables. Plan9Magic uint32 Plan9_64Bit bool @@ -223,7 +230,7 @@ type Arch struct { // to-be-relocated data item (from sym.P). Return is an updated // offset value. Archrelocvariant func(target *Target, ldr *loader.Loader, rel loader.Reloc, - rv sym.RelocVariant, sym loader.Sym, offset int64) (relocatedOffset int64) + rv sym.RelocVariant, sym loader.Sym, offset int64, data []byte) (relocatedOffset int64) // Generate a trampoline for a call from s to rs if necessary. ri is // index of the relocation. @@ -336,10 +343,16 @@ var ( const pkgdef = "__.PKGDEF" var ( - // Set if we see an object compiled by the host compiler that is not - // from a package that is known to support internal linking mode. + // externalobj is set to true if we see an object compiled by + // the host compiler that is not from a package that is known + // to support internal linking mode. externalobj = false - theline string + + // unknownObjFormat is set to true if we see an object whose + // format we don't recognize. + unknownObjFormat = false + + theline string ) func Lflag(ctxt *Link, arg string) { @@ -377,7 +390,7 @@ func libinit(ctxt *Link) { suffix = "msan" } - Lflag(ctxt, filepath.Join(objabi.GOROOT, "pkg", fmt.Sprintf("%s_%s%s%s", objabi.GOOS, objabi.GOARCH, suffixsep, suffix))) + Lflag(ctxt, filepath.Join(buildcfg.GOROOT, "pkg", fmt.Sprintf("%s_%s%s%s", buildcfg.GOOS, buildcfg.GOARCH, suffixsep, suffix))) mayberemoveoutfile() @@ -388,9 +401,9 @@ func libinit(ctxt *Link) { if *flagEntrySymbol == "" { switch ctxt.BuildMode { case BuildModeCShared, BuildModeCArchive: - *flagEntrySymbol = fmt.Sprintf("_rt0_%s_%s_lib", objabi.GOARCH, objabi.GOOS) + *flagEntrySymbol = fmt.Sprintf("_rt0_%s_%s_lib", buildcfg.GOARCH, buildcfg.GOOS) case BuildModeExe, BuildModePIE: - *flagEntrySymbol = fmt.Sprintf("_rt0_%s_%s", objabi.GOARCH, objabi.GOOS) + *flagEntrySymbol = fmt.Sprintf("_rt0_%s_%s", buildcfg.GOARCH, buildcfg.GOOS) case BuildModeShared, BuildModePlugin: // No *flagEntrySymbol for -buildmode=shared and plugin default: @@ -489,19 +502,20 @@ func (ctxt *Link) loadlib() { case 0: // nothing to do case 1, 2: - flags = loader.FlagStrictDups + flags |= loader.FlagStrictDups default: log.Fatalf("invalid -strictdups flag value %d", *FlagStrictDups) } + if !buildcfg.Experiment.RegabiWrappers { + // Use ABI aliases if ABI wrappers are not used. + flags |= loader.FlagUseABIAlias + } elfsetstring1 := func(str string, off int) { elfsetstring(ctxt, 0, str, off) } ctxt.loader = loader.NewLoader(flags, elfsetstring1, &ctxt.ErrorReporter.ErrorReporter) ctxt.ErrorReporter.SymName = func(s loader.Sym) string { return ctxt.loader.SymName(s) } - ctxt.cgo_export_static = make(map[string]bool) - ctxt.cgo_export_dynamic = make(map[string]bool) - // ctxt.Library grows during the loop, so not a range loop. i := 0 for ; i < len(ctxt.Library); i++ { @@ -533,12 +547,15 @@ func (ctxt *Link) loadlib() { // up symbol by name may not get expected result. iscgo = ctxt.LibraryByPkg["runtime/cgo"] != nil - ctxt.canUsePlugins = ctxt.LibraryByPkg["plugin"] != nil + + // Plugins a require cgo support to function. Similarly, plugins may require additional + // internal linker support on some platforms which may not be implemented. + ctxt.canUsePlugins = ctxt.LibraryByPkg["plugin"] != nil && iscgo // We now have enough information to determine the link mode. determineLinkMode(ctxt) - if ctxt.LinkMode == LinkExternal && !iscgo && !(objabi.GOOS == "darwin" && ctxt.BuildMode != BuildModePlugin && ctxt.Arch.Family == sys.AMD64) { + if ctxt.LinkMode == LinkExternal && !iscgo && !(buildcfg.GOOS == "darwin" && ctxt.BuildMode != BuildModePlugin && ctxt.Arch.Family == sys.AMD64) { // This indicates a user requested -linkmode=external. // The startup code uses an import of runtime/cgo to decide // whether to initialize the TLS. So give it one. This could @@ -547,7 +564,12 @@ func (ctxt *Link) loadlib() { if ctxt.BuildMode == BuildModeShared || ctxt.linkShared { Exitf("cannot implicitly include runtime/cgo in a shared library") } - loadobjfile(ctxt, lib) + for ; i < len(ctxt.Library); i++ { + lib := ctxt.Library[i] + if lib.Shlib == "" { + loadobjfile(ctxt, lib) + } + } } } @@ -589,9 +611,6 @@ func (ctxt *Link) loadlib() { // errors - see if we can find libcompiler_rt.a instead. *flagLibGCC = ctxt.findLibPathCmd("--print-file-name=libcompiler_rt.a", "libcompiler_rt") } - if *flagLibGCC != "none" { - hostArchive(ctxt, *flagLibGCC) - } if ctxt.HeadType == objabi.Hwindows { if p := ctxt.findLibPath("libmingwex.a"); p != "none" { hostArchive(ctxt, p) @@ -613,6 +632,9 @@ func (ctxt *Link) loadlib() { libmsvcrt.a libm.a */ } + if *flagLibGCC != "none" { + hostArchive(ctxt, *flagLibGCC) + } } } @@ -624,50 +646,13 @@ func (ctxt *Link) loadlib() { strictDupMsgCount = ctxt.loader.NStrictDupMsgs() } -// setupdynexp constructs ctxt.dynexp, a list of loader.Sym. -func setupdynexp(ctxt *Link) { - dynexpMap := ctxt.cgo_export_dynamic - if ctxt.LinkMode == LinkExternal { - dynexpMap = ctxt.cgo_export_static - } - d := make([]loader.Sym, 0, len(dynexpMap)) - for exp := range dynexpMap { - s := ctxt.loader.LookupOrCreateSym(exp, 0) - d = append(d, s) - // sanity check - if !ctxt.loader.AttrReachable(s) { - panic("dynexp entry not reachable") - } - } - sort.Slice(d, func(i, j int) bool { - return ctxt.loader.SymName(d[i]) < ctxt.loader.SymName(d[j]) - }) - - // Resolve ABI aliases in the list of cgo-exported functions. - // This is necessary because we load the ABI0 symbol for all - // cgo exports. - for i, s := range d { - if ctxt.loader.SymType(s) != sym.SABIALIAS { - continue - } - t := ctxt.loader.ResolveABIAlias(s) - ctxt.loader.CopyAttributes(s, t) - ctxt.loader.SetSymExtname(t, ctxt.loader.SymExtname(s)) - d[i] = t - } - ctxt.dynexp = d - - ctxt.cgo_export_static = nil - ctxt.cgo_export_dynamic = nil -} - // loadcgodirectives reads the previously discovered cgo directives, creating // symbols in preparation for host object loading or use later in the link. func (ctxt *Link) loadcgodirectives() { l := ctxt.loader hostObjSyms := make(map[loader.Sym]struct{}) for _, d := range ctxt.cgodata { - setCgoAttr(ctxt, ctxt.loader.LookupOrCreateSym, d.file, d.pkg, d.directives, hostObjSyms) + setCgoAttr(ctxt, d.file, d.pkg, d.directives, hostObjSyms) } ctxt.cgodata = nil @@ -732,7 +717,7 @@ func (ctxt *Link) linksetup() { } } - if ctxt.LinkMode == LinkExternal && ctxt.Arch.Family == sys.PPC64 && objabi.GOOS != "aix" { + if ctxt.LinkMode == LinkExternal && ctxt.Arch.Family == sys.PPC64 && buildcfg.GOOS != "aix" { toc := ctxt.loader.LookupOrCreateSym(".TOC.", 0) sb := ctxt.loader.MakeSymbolUpdater(toc) sb.SetType(sym.SDYNIMPORT) @@ -741,7 +726,7 @@ func (ctxt *Link) linksetup() { // The Android Q linker started to complain about underalignment of the our TLS // section. We don't actually use the section on android, so don't // generate it. - if objabi.GOOS != "android" { + if buildcfg.GOOS != "android" { tlsg := ctxt.loader.LookupOrCreateSym("runtime.tlsg", 0) sb := ctxt.loader.MakeSymbolUpdater(tlsg) @@ -782,7 +767,19 @@ func (ctxt *Link) linksetup() { sb := ctxt.loader.MakeSymbolUpdater(goarm) sb.SetType(sym.SDATA) sb.SetSize(0) - sb.AddUint8(uint8(objabi.GOARM)) + sb.AddUint8(uint8(buildcfg.GOARM)) + } + + // Set runtime.disableMemoryProfiling bool if + // runtime.MemProfile is not retained in the binary after + // deadcode (and we're not dynamically linking). + memProfile := ctxt.loader.Lookup("runtime.MemProfile", sym.SymVerABIInternal) + if memProfile != 0 && !ctxt.loader.AttrReachable(memProfile) && !ctxt.DynlinkingGo() { + memProfSym := ctxt.loader.LookupOrCreateSym("runtime.disableMemoryProfiling", 0) + sb := ctxt.loader.MakeSymbolUpdater(memProfSym) + sb.SetType(sym.SDATA) + sb.SetSize(0) + sb.AddUint8(1) // true bool } } else { // If OTOH the module does not contain the runtime package, @@ -1074,6 +1071,10 @@ func hostobjs(ctxt *Link) { } f.MustSeek(h.off, 0) + if h.ld == nil { + Errorf(nil, "%s: unrecognized object file format", h.pn) + continue + } h.ld(ctxt, f, h.pkg, h.length, h.pn) f.Close() } @@ -1263,7 +1264,7 @@ func (ctxt *Link) hostlink() { // -headerpad is incompatible with -fembed-bitcode. argv = append(argv, "-Wl,-headerpad,1144") } - if ctxt.DynlinkingGo() && objabi.GOOS != "ios" { + if ctxt.DynlinkingGo() && buildcfg.GOOS != "ios" { // -flat_namespace is deprecated on iOS. // It is useful for supporting plugins. We don't support plugins on iOS. argv = append(argv, "-Wl,-flat_namespace") @@ -1337,8 +1338,6 @@ func (ctxt *Link) hostlink() { if ctxt.HeadType == objabi.Hdarwin { argv = append(argv, "-dynamiclib") } else { - // ELF. - argv = append(argv, "-Wl,-Bsymbolic") if ctxt.UseRelro() { argv = append(argv, "-Wl,-z,relro") } @@ -1351,6 +1350,8 @@ func (ctxt *Link) hostlink() { // Pass -z nodelete to mark the shared library as // non-closeable: a dlclose will do nothing. argv = append(argv, "-Wl,-z,nodelete") + // Only pass Bsymbolic on non-Windows. + argv = append(argv, "-Wl,-Bsymbolic") } } case BuildModeShared: @@ -1381,12 +1382,12 @@ func (ctxt *Link) hostlink() { // from the beginning of the section (like sym.STYPE). argv = append(argv, "-Wl,-znocopyreloc") - if objabi.GOOS == "android" { + if buildcfg.GOOS == "android" { // Use lld to avoid errors from default linker (issue #38838) altLinker = "lld" } - if ctxt.Arch.InFamily(sys.ARM, sys.ARM64) && objabi.GOOS == "linux" { + if ctxt.Arch.InFamily(sys.ARM, sys.ARM64) && buildcfg.GOOS == "linux" { // On ARM, the GNU linker will generate COPY relocations // even with -znocopyreloc set. // https://sourceware.org/bugzilla/show_bug.cgi?id=19962 @@ -1408,7 +1409,7 @@ func (ctxt *Link) hostlink() { } } } - if ctxt.Arch.Family == sys.ARM64 && objabi.GOOS == "freebsd" { + if ctxt.Arch.Family == sys.ARM64 && buildcfg.GOOS == "freebsd" { // Switch to ld.bfd on freebsd/arm64. altLinker = "bfd" @@ -1435,7 +1436,7 @@ func (ctxt *Link) hostlink() { // only want to do this when producing a Windows output file // on a Windows host. outopt := *flagOutfile - if objabi.GOOS == "windows" && runtime.GOOS == "windows" && filepath.Ext(outopt) == "" { + if buildcfg.GOOS == "windows" && runtime.GOOS == "windows" && filepath.Ext(outopt) == "" { outopt += "." } argv = append(argv, "-o") @@ -1445,6 +1446,14 @@ func (ctxt *Link) hostlink() { argv = append(argv, fmt.Sprintf("-Wl,-rpath,%s", rpath.val)) } + if *flagInterpreter != "" { + // Many linkers support both -I and the --dynamic-linker flags + // to set the ELF interpreter, but lld only supports + // --dynamic-linker so prefer that (ld on very old Solaris only + // supports -I but that seems less important). + argv = append(argv, fmt.Sprintf("-Wl,--dynamic-linker,%s", *flagInterpreter)) + } + // Force global symbols to be exported for dlopen, etc. if ctxt.IsELF { argv = append(argv, "-rdynamic") @@ -1454,8 +1463,9 @@ func (ctxt *Link) hostlink() { argv = append(argv, "-Wl,-bE:"+fileName) } - if strings.Contains(argv[0], "clang") { - argv = append(argv, "-Qunused-arguments") + const unusedArguments = "-Qunused-arguments" + if linkerFlagSupported(ctxt.Arch, argv[0], altLinker, unusedArguments) { + argv = append(argv, unusedArguments) } const compressDWARF = "-Wl,--compress-debug-sections=zlib-gnu" @@ -1522,12 +1532,13 @@ func (ctxt *Link) hostlink() { // even when linking with -static, causing a linker // error when using GNU ld. So take out -rdynamic if // we added it. We do it in this order, rather than - // only adding -rdynamic later, so that -*extldflags + // only adding -rdynamic later, so that -extldflags // can override -rdynamic without using -static. + // Similarly for -Wl,--dynamic-linker. checkStatic := func(arg string) { if ctxt.IsELF && arg == "-static" { for i := range argv { - if argv[i] == "-rdynamic" { + if argv[i] == "-rdynamic" || strings.HasPrefix(argv[i], "-Wl,--dynamic-linker,") { argv[i] = "-static" } } @@ -1751,7 +1762,7 @@ func hostlinkArchArgs(arch *sys.Arch) []string { case sys.I386: return []string{"-m32"} case sys.AMD64: - if objabi.GOOS == "darwin" { + if buildcfg.GOOS == "darwin" { return []string{"-arch", "x86_64", "-m64"} } return []string{"-m64"} @@ -1760,7 +1771,7 @@ func hostlinkArchArgs(arch *sys.Arch) []string { case sys.ARM: return []string{"-marm"} case sys.ARM64: - if objabi.GOOS == "darwin" { + if buildcfg.GOOS == "darwin" { return []string{"-arch", "arm64"} } case sys.MIPS64: @@ -1768,7 +1779,7 @@ func hostlinkArchArgs(arch *sys.Arch) []string { case sys.MIPS: return []string{"-mabi=32"} case sys.PPC64: - if objabi.GOOS == "aix" { + if buildcfg.GOOS == "aix" { return []string{"-maix64"} } else { return []string{"-m64"} @@ -1778,6 +1789,8 @@ func hostlinkArchArgs(arch *sys.Arch) []string { return nil } +var wantHdr = objabi.HeaderString() + // ldobj loads an input object. If it is a host object (an object // compiled by a non-Go compiler) it returns the Hostobj pointer. If // it is a Go object, it returns nil. @@ -1821,7 +1834,11 @@ func ldobj(ctxt *Link, f *bio.Reader, lib *sym.Library, length int64, pn string, return ldhostobj(ldmacho, ctxt.HeadType, f, pkg, length, pn, file) } - if /* x86 */ c1 == 0x4c && c2 == 0x01 || /* x86_64 */ c1 == 0x64 && c2 == 0x86 || /* armv7 */ c1 == 0xc4 && c2 == 0x01 { + switch c1<<8 | c2 { + case 0x4c01, // 386 + 0x6486, // amd64 + 0xc401, // arm + 0x64aa: // arm64 ldpe := func(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) { textp, rsrc, err := loadpe.Load(ctxt.loader, ctxt.Arch, ctxt.IncVersion(), f, pkg, length, pn) if err != nil { @@ -1848,6 +1865,14 @@ func ldobj(ctxt *Link, f *bio.Reader, lib *sym.Library, length int64, pn string, return ldhostobj(ldxcoff, ctxt.HeadType, f, pkg, length, pn, file) } + if c1 != 'g' || c2 != 'o' || c3 != ' ' || c4 != 'o' { + // An unrecognized object is just passed to the external linker. + // If we try to read symbols from this object, we will + // report an error at that time. + unknownObjFormat = true + return ldhostobj(nil, ctxt.HeadType, f, pkg, length, pn, file) + } + /* check the header */ line, err := f.ReadString('\n') if err != nil { @@ -1867,29 +1892,13 @@ func ldobj(ctxt *Link, f *bio.Reader, lib *sym.Library, length int64, pn string, return nil } - Errorf(nil, "%s: not an object file", pn) + Errorf(nil, "%s: not an object file: @%d %q", pn, start, line) return nil } // First, check that the basic GOOS, GOARCH, and Version match. - t := fmt.Sprintf("%s %s %s ", objabi.GOOS, objabi.GOARCH, objabi.Version) - - line = strings.TrimRight(line, "\n") - if !strings.HasPrefix(line[10:]+" ", t) && !*flagF { - Errorf(nil, "%s: object is [%s] expected [%s]", pn, line[10:], t) - return nil - } - - // Second, check that longer lines match each other exactly, - // so that the Go compiler and write additional information - // that must be the same from run to run. - if len(line) >= len(t)+10 { - if theline == "" { - theline = line[10:] - } else if theline != line[10:] { - Errorf(nil, "%s: object is [%s] expected [%s]", pn, line[10:], theline) - return nil - } + if line != wantHdr { + Errorf(nil, "%s: linked object header mismatch:\nhave %q\nwant %q\n", pn, line, wantHdr) } // Skip over exports and other info -- ends with \n!\n. @@ -2091,6 +2100,7 @@ func ldshlibsyms(ctxt *Link, shlib string) { Errorf(nil, "cannot read symbols from shared library: %s", libpath) return } + for _, elfsym := range syms { if elf.ST_TYPE(elfsym.Info) == elf.STT_NOTYPE || elf.ST_TYPE(elfsym.Info) == elf.STT_SECTION { continue @@ -2099,12 +2109,22 @@ func ldshlibsyms(ctxt *Link, shlib string) { // Symbols whose names start with "type." are compiler // generated, so make functions with that prefix internal. ver := 0 + symname := elfsym.Name // (unmangled) symbol name if elf.ST_TYPE(elfsym.Info) == elf.STT_FUNC && strings.HasPrefix(elfsym.Name, "type.") { ver = sym.SymVerABIInternal + } else if buildcfg.Experiment.RegabiWrappers && elf.ST_TYPE(elfsym.Info) == elf.STT_FUNC { + // Demangle the ABI name. Keep in sync with symtab.go:mangleABIName. + if strings.HasSuffix(elfsym.Name, ".abiinternal") { + ver = sym.SymVerABIInternal + symname = strings.TrimSuffix(elfsym.Name, ".abiinternal") + } else if strings.HasSuffix(elfsym.Name, ".abi0") { + ver = 0 + symname = strings.TrimSuffix(elfsym.Name, ".abi0") + } } l := ctxt.loader - s := l.LookupOrCreateSym(elfsym.Name, ver) + s := l.LookupOrCreateSym(symname, ver) // Because loadlib above loads all .a files before loading // any shared libraries, any non-dynimport symbols we find @@ -2129,15 +2149,14 @@ func ldshlibsyms(ctxt *Link, shlib string) { } } - // For function symbols, we don't know what ABI is - // available, so alias it under both ABIs. - // - // TODO(austin): This is almost certainly wrong once - // the ABIs are actually different. We might have to - // mangle Go function names in the .so to include the - // ABI. - if elf.ST_TYPE(elfsym.Info) == elf.STT_FUNC && ver == 0 { - alias := ctxt.loader.LookupOrCreateSym(elfsym.Name, sym.SymVerABIInternal) + if symname != elfsym.Name { + l.SetSymExtname(s, elfsym.Name) + } + + // For function symbols, if ABI wrappers are not used, we don't + // know what ABI is available, so alias it under both ABIs. + if !buildcfg.Experiment.RegabiWrappers && elf.ST_TYPE(elfsym.Info) == elf.STT_FUNC && ver == 0 { + alias := ctxt.loader.LookupOrCreateSym(symname, sym.SymVerABIInternal) if l.SymType(alias) != 0 { continue } @@ -2204,7 +2223,7 @@ func (ctxt *Link) dostkcheck() { // of non-splitting functions. var ch chain ch.limit = objabi.StackLimit - callsize(ctxt) - if objabi.GOARCH == "arm64" { + if buildcfg.GOARCH == "arm64" { // need extra 8 bytes below SP to save FP ch.limit -= 8 } @@ -2354,7 +2373,7 @@ func (sc *stkChk) print(ch *chain, limit int) { ctxt := sc.ctxt var name string if ch.sym != 0 { - name = ldr.SymName(ch.sym) + name = fmt.Sprintf("%s<%d>", ldr.SymName(ch.sym), ldr.SymVersion(ch.sym)) if ldr.IsNoSplit(ch.sym) { name += " (nosplit)" } diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go index f26d051a491bbc3671cdb3c81a2a94f6000e6966..13618beff977a5a5aef902edd354bf6a644d7660 100644 --- a/src/cmd/link/internal/ld/link.go +++ b/src/cmd/link/internal/ld/link.go @@ -84,9 +84,6 @@ type Link struct { loader *loader.Loader cgodata []cgodata // cgo directives to load, three strings are args for loadcgo - cgo_export_static map[string]bool - cgo_export_dynamic map[string]bool - datap []loader.Sym dynexp []loader.Sym diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go index 3630e67c25d60c7b1fddb843a57b8edb80c8884b..45a3971c33f5463ffc52094baedde2aa5e40696e 100644 --- a/src/cmd/link/internal/ld/macho.go +++ b/src/cmd/link/internal/ld/macho.go @@ -7,6 +7,7 @@ package ld import ( "bytes" "cmd/internal/codesign" + "cmd/internal/obj" "cmd/internal/objabi" "cmd/internal/sys" "cmd/link/internal/loader" @@ -14,6 +15,7 @@ import ( "debug/macho" "encoding/binary" "fmt" + "internal/buildcfg" "io" "os" "sort" @@ -86,6 +88,8 @@ const ( MACHO_SUBCPU_ARMV7 = 9 MACHO_CPU_ARM64 = 1<<24 | 12 MACHO_SUBCPU_ARM64_ALL = 0 + MACHO_SUBCPU_ARM64_V8 = 1 + MACHO_SUBCPU_ARM64E = 2 MACHO32SYMSIZE = 12 MACHO64SYMSIZE = 16 MACHO_X86_64_RELOC_UNSIGNED = 0 @@ -176,6 +180,8 @@ const ( LC_VERSION_MIN_WATCHOS = 0x30 LC_VERSION_NOTE = 0x31 LC_BUILD_VERSION = 0x32 + LC_DYLD_EXPORTS_TRIE = 0x80000033 + LC_DYLD_CHAINED_FIXUPS = 0x80000034 ) const ( @@ -466,26 +472,24 @@ func (ctxt *Link) domacho() { } } if machoPlatform == 0 { - switch ctxt.Arch.Family { - default: - machoPlatform = PLATFORM_MACOS - if ctxt.LinkMode == LinkInternal { - // For lldb, must say LC_VERSION_MIN_MACOSX or else - // it won't know that this Mach-O binary is from OS X - // (could be iOS or WatchOS instead). - // Go on iOS uses linkmode=external, and linkmode=external - // adds this itself. So we only need this code for linkmode=internal - // and we can assume OS X. - // - // See golang.org/issues/12941. - // + machoPlatform = PLATFORM_MACOS + if buildcfg.GOOS == "ios" { + machoPlatform = PLATFORM_IOS + } + if ctxt.LinkMode == LinkInternal && machoPlatform == PLATFORM_MACOS { + var version uint32 + switch ctxt.Arch.Family { + case sys.AMD64: // The version must be at least 10.9; see golang.org/issues/30488. - ml := newMachoLoad(ctxt.Arch, LC_VERSION_MIN_MACOSX, 2) - ml.data[0] = 10<<16 | 9<<8 | 0<<0 // OS X version 10.9.0 - ml.data[1] = 10<<16 | 9<<8 | 0<<0 // SDK 10.9.0 + version = 10<<16 | 9<<8 | 0<<0 // 10.9.0 + case sys.ARM64: + version = 11<<16 | 0<<8 | 0<<0 // 11.0.0 } - case sys.ARM, sys.ARM64: - machoPlatform = PLATFORM_IOS + ml := newMachoLoad(ctxt.Arch, LC_BUILD_VERSION, 4) + ml.data[0] = uint32(machoPlatform) + ml.data[1] = version // OS version + ml.data[2] = version // SDK version + ml.data[3] = 0 // ntools } } @@ -535,12 +539,31 @@ func (ctxt *Link) domacho() { sb.AddUint8(0) } - // Do not export C symbols dynamically in plugins, as runtime C symbols like crosscall2 - // are in pclntab and end up pointing at the host binary, breaking unwinding. - // See Issue #18190. + // Un-export runtime symbols from plugins. Since the runtime + // is included in both the main binary and each plugin, these + // symbols appear in both images. If we leave them exported in + // the plugin, then the dynamic linker will resolve + // relocations to these functions in the plugin's functab to + // point to the main image, causing the runtime to think the + // plugin's functab is corrupted. By unexporting them, these + // become static references, which are resolved to the + // plugin's text. + // + // It would be better to omit the runtime from plugins. (Using + // relative PCs in the functab instead of relocations would + // also address this.) + // + // See issue #18190. if ctxt.BuildMode == BuildModePlugin { for _, name := range []string{"_cgo_topofstack", "__cgo_topofstack", "_cgo_panic", "crosscall2"} { - s := ctxt.loader.Lookup(name, 0) + // Most of these are data symbols or C + // symbols, so they have symbol version 0. + ver := 0 + // _cgo_panic is a Go function, so it uses ABIInternal. + if name == "_cgo_panic" { + ver = sym.ABIToVersion(obj.ABIInternal) + } + s := ctxt.loader.Lookup(name, ver) if s != 0 { ctxt.loader.SetAttrCgoExportDynamic(s, false) } @@ -925,12 +948,12 @@ func collectmachosyms(ctxt *Link) { if machoPlatform == PLATFORM_MACOS { switch n := ldr.SymExtname(s); n { case "fdopendir": - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "amd64": ldr.SetSymExtname(s, n+"$INODE64") } case "readdir_r", "getfsstat": - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "amd64": ldr.SetSymExtname(s, n+"$INODE64") } @@ -1022,7 +1045,10 @@ func machosymtab(ctxt *Link) { symstr.AddUint8('_') // replace "·" as ".", because DTrace cannot handle it. - symstr.Addstring(strings.Replace(ldr.SymExtname(s), "·", ".", -1)) + name := strings.Replace(ldr.SymExtname(s), "·", ".", -1) + + name = mangleABIName(ctxt, ldr, s, name) + symstr.Addstring(name) if t := ldr.SymType(s); t == sym.SDYNIMPORT || t == sym.SHOSTOBJ || t == sym.SUNDEFEXT { symtab.AddUint8(0x01) // type N_EXT, external symbol @@ -1168,7 +1194,7 @@ func machorelocsect(ctxt *Link, out *OutBuf, sect *sym.Section, syms []loader.Sy } } - eaddr := int32(sect.Vaddr + sect.Length) + eaddr := sect.Vaddr + sect.Length for _, s := range syms { if !ldr.AttrReachable(s) { continue @@ -1215,7 +1241,11 @@ func machoEmitReloc(ctxt *Link) { relocSect(ctxt, Segtext.Sections[0], ctxt.Textp) for _, sect := range Segtext.Sections[1:] { - relocSect(ctxt, sect, ctxt.datap) + if sect.Name == ".text" { + relocSect(ctxt, sect, ctxt.Textp) + } else { + relocSect(ctxt, sect, ctxt.datap) + } } for _, sect := range Segrelrodata.Sections { relocSect(ctxt, sect, ctxt.datap) diff --git a/src/cmd/link/internal/ld/macho_combine_dwarf.go b/src/cmd/link/internal/ld/macho_combine_dwarf.go index 77ee8a4d62bde49786dad003f99b4053f31e27c4..2ab7da967a211e1c44ce65b3ebe4a8d282f4ef75 100644 --- a/src/cmd/link/internal/ld/macho_combine_dwarf.go +++ b/src/cmd/link/internal/ld/macho_combine_dwarf.go @@ -222,11 +222,19 @@ func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &macho.SymtabCmd{}, "Symoff", "Stroff") case macho.LoadCmdDysymtab: err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &macho.DysymtabCmd{}, "Tocoffset", "Modtaboff", "Extrefsymoff", "Indirectsymoff", "Extreloff", "Locreloff") - case LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS: + case LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS, + LC_DYLD_EXPORTS_TRIE, LC_DYLD_CHAINED_FIXUPS: err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &linkEditDataCmd{}, "DataOff") case LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64: err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &encryptionInfoCmd{}, "CryptOff") - case macho.LoadCmdDylib, macho.LoadCmdThread, macho.LoadCmdUnixThread, LC_PREBOUND_DYLIB, LC_UUID, LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS, LC_SOURCE_VERSION, LC_MAIN, LC_LOAD_DYLINKER, LC_LOAD_WEAK_DYLIB, LC_REEXPORT_DYLIB, LC_RPATH, LC_ID_DYLIB, LC_SYMSEG, LC_LOADFVMLIB, LC_IDFVMLIB, LC_IDENT, LC_FVMFILE, LC_PREPAGE, LC_ID_DYLINKER, LC_ROUTINES, LC_SUB_FRAMEWORK, LC_SUB_UMBRELLA, LC_SUB_CLIENT, LC_SUB_LIBRARY, LC_TWOLEVEL_HINTS, LC_PREBIND_CKSUM, LC_ROUTINES_64, LC_LAZY_LOAD_DYLIB, LC_LOAD_UPWARD_DYLIB, LC_DYLD_ENVIRONMENT, LC_LINKER_OPTION, LC_LINKER_OPTIMIZATION_HINT, LC_VERSION_MIN_TVOS, LC_VERSION_MIN_WATCHOS, LC_VERSION_NOTE, LC_BUILD_VERSION: + case macho.LoadCmdDylib, macho.LoadCmdThread, macho.LoadCmdUnixThread, + LC_PREBOUND_DYLIB, LC_UUID, LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS, LC_SOURCE_VERSION, + LC_MAIN, LC_LOAD_DYLINKER, LC_LOAD_WEAK_DYLIB, LC_REEXPORT_DYLIB, LC_RPATH, LC_ID_DYLIB, + LC_SYMSEG, LC_LOADFVMLIB, LC_IDFVMLIB, LC_IDENT, LC_FVMFILE, LC_PREPAGE, LC_ID_DYLINKER, + LC_ROUTINES, LC_SUB_FRAMEWORK, LC_SUB_UMBRELLA, LC_SUB_CLIENT, LC_SUB_LIBRARY, LC_TWOLEVEL_HINTS, + LC_PREBIND_CKSUM, LC_ROUTINES_64, LC_LAZY_LOAD_DYLIB, LC_LOAD_UPWARD_DYLIB, LC_DYLD_ENVIRONMENT, + LC_LINKER_OPTION, LC_LINKER_OPTIMIZATION_HINT, LC_VERSION_MIN_TVOS, LC_VERSION_MIN_WATCHOS, + LC_VERSION_NOTE, LC_BUILD_VERSION: // Nothing to update default: err = fmt.Errorf("unknown load command 0x%x (%s)", int(cmd.Cmd), cmd.Cmd) @@ -394,7 +402,7 @@ func machoUpdateDwarfHeader(r *loadCmdReader, compressedSects []*macho.Section, // We want the DWARF segment to be considered non-loadable, so // force vmaddr and vmsize to zero. In addition, set the initial // protection to zero so as to make the dynamic loader happy, - // since otherwise it may complain that that the vm size and file + // since otherwise it may complain that the vm size and file // size don't match for the segment. See issues 21647 and 32673 // for more context. Also useful to refer to the Apple dynamic // loader source, specifically ImageLoaderMachO::sniffLoadCommands diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index 5a096f1b3b9fd2c27e564f4f0e60992d71f48e4f..cba0e3d81feaea6fafcba964c200b50d96e6d714 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -37,6 +37,7 @@ import ( "cmd/internal/sys" "cmd/link/internal/benchmark" "flag" + "internal/buildcfg" "log" "os" "runtime" @@ -87,16 +88,14 @@ var ( flag8 bool // use 64-bit addresses in symbol table flagInterpreter = flag.String("I", "", "use `linker` as ELF dynamic linker") FlagDebugTramp = flag.Int("debugtramp", 0, "debug trampolines") - FlagDebugTextSize = flag.Int("debugppc64textsize", 0, "debug PPC64 text section max") + FlagDebugTextSize = flag.Int("debugtextsize", 0, "debug text section max size") FlagStrictDups = flag.Int("strictdups", 0, "sanity check duplicate symbol contents during object file reading (1=warn 2=err).") FlagRound = flag.Int("R", -1, "set address rounding `quantum`") FlagTextAddr = flag.Int64("T", -1, "set text segment `address`") flagEntrySymbol = flag.String("E", "", "set `entry` symbol name") - - cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") - memprofile = flag.String("memprofile", "", "write memory profile to `file`") - memprofilerate = flag.Int64("memprofilerate", 0, "set runtime.MemProfileRate to `rate`") - + cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") + memprofile = flag.String("memprofile", "", "write memory profile to `file`") + memprofilerate = flag.Int64("memprofilerate", 0, "set runtime.MemProfileRate to `rate`") benchmarkFlag = flag.String("benchmark", "", "set to 'mem' or 'cpu' to enable phase benchmarking") benchmarkFileFlag = flag.String("benchmarkprofile", "", "emit phase profiles to `base`_phase.{cpu,mem}prof") ) @@ -117,11 +116,17 @@ func Main(arch *sys.Arch, theArch Arch) { } final := gorootFinal() - addstrdata1(ctxt, "runtime/internal/sys.DefaultGoroot="+final) - addstrdata1(ctxt, "cmd/internal/objabi.defaultGOROOT="+final) + addstrdata1(ctxt, "runtime.defaultGOROOT="+final) + addstrdata1(ctxt, "internal/buildcfg.defaultGOROOT="+final) + + buildVersion := buildcfg.Version + if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" { + buildVersion += " X:" + goexperiment + } + addstrdata1(ctxt, "runtime.buildVersion="+buildVersion) // TODO(matloob): define these above and then check flag values here - if ctxt.Arch.Family == sys.AMD64 && objabi.GOOS == "plan9" { + if ctxt.Arch.Family == sys.AMD64 && buildcfg.GOOS == "plan9" { flag.BoolVar(&flag8, "8", false, "use 64-bit addresses in symbol table") } flagHeadType := flag.String("H", "", "set header `type`") @@ -155,7 +160,7 @@ func Main(arch *sys.Arch, theArch Arch) { } } if ctxt.HeadType == objabi.Hunknown { - ctxt.HeadType.Set(objabi.GOOS) + ctxt.HeadType.Set(buildcfg.GOOS) } if !*flagAslr && ctxt.BuildMode != BuildModeCShared { @@ -251,7 +256,7 @@ func Main(arch *sys.Arch, theArch Arch) { bench.Start("dostrdata") ctxt.dostrdata() - if objabi.Fieldtrack_enabled != 0 { + if buildcfg.Experiment.FieldTrack { bench.Start("fieldtrack") fieldtrack(ctxt.Arch, ctxt.loader) } @@ -290,7 +295,6 @@ func Main(arch *sys.Arch, theArch Arch) { bench.Start("textbuildid") ctxt.textbuildid() bench.Start("addexport") - setupdynexp(ctxt) ctxt.setArchSyms() ctxt.addexport() bench.Start("Gentext") @@ -330,7 +334,7 @@ func Main(arch *sys.Arch, theArch Arch) { // Don't mmap if we're building for Wasm. Wasm file // layout is very different so filesize is meaningless. if err := ctxt.Out.Mmap(filesize); err != nil { - panic(err) + Exitf("mapping output file failed: %v", err) } } // asmb will redirect symbols to the output file mmap, and relocations diff --git a/src/cmd/link/internal/ld/nooptcgolink_test.go b/src/cmd/link/internal/ld/nooptcgolink_test.go index 4d2ff1acf223f51fab9326124833d91591bbcc40..73548dabd4f56a44c3d05ba38a7a8acf77f5d7e5 100644 --- a/src/cmd/link/internal/ld/nooptcgolink_test.go +++ b/src/cmd/link/internal/ld/nooptcgolink_test.go @@ -6,8 +6,6 @@ package ld import ( "internal/testenv" - "io/ioutil" - "os" "os/exec" "path/filepath" "runtime" @@ -22,11 +20,7 @@ func TestNooptCgoBuild(t *testing.T) { testenv.MustHaveGoBuild(t) testenv.MustHaveCGO(t) - dir, err := ioutil.TempDir("", "go-build") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=-N -l", "-o", filepath.Join(dir, "a.out")) cmd.Dir = filepath.Join(runtime.GOROOT(), "src", "runtime", "testdata", "testprogcgo") out, err := cmd.CombinedOutput() diff --git a/src/cmd/link/internal/ld/outbuf.go b/src/cmd/link/internal/ld/outbuf.go index 530836ef7cf292fb7849089be5d501cfb574d3e6..9d5e8854fea65418c75c30717f865f80cfbd03bc 100644 --- a/src/cmd/link/internal/ld/outbuf.go +++ b/src/cmd/link/internal/ld/outbuf.go @@ -160,7 +160,7 @@ func (out *OutBuf) copyHeap() bool { total := uint64(bufLen + heapLen) if heapLen != 0 { if err := out.Mmap(total); err != nil { // Mmap will copy out.heap over to out.buf - panic(err) + Exitf("mapping output file failed: %v", err) } } return true diff --git a/src/cmd/link/internal/ld/outbuf_mmap.go b/src/cmd/link/internal/ld/outbuf_mmap.go index 807fe24375220c45edbb3a241993500bf2906b31..40a3222788ecb925ef32738bcf54db9a5eee01da 100644 --- a/src/cmd/link/internal/ld/outbuf_mmap.go +++ b/src/cmd/link/internal/ld/outbuf_mmap.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd // +build aix darwin dragonfly freebsd linux netbsd openbsd package ld diff --git a/src/cmd/link/internal/ld/outbuf_nofallocate.go b/src/cmd/link/internal/ld/outbuf_nofallocate.go index 6bf96bcb2baea2ce9867a8bb77b6efca306dd94e..6564bd54a3d1bcf27977493fedff76c829c34d0d 100644 --- a/src/cmd/link/internal/ld/outbuf_nofallocate.go +++ b/src/cmd/link/internal/ld/outbuf_nofallocate.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !darwin && !linux // +build !darwin,!linux package ld diff --git a/src/cmd/link/internal/ld/outbuf_nommap.go b/src/cmd/link/internal/ld/outbuf_nommap.go index 6b4025384b42788e7405fc9036c74ebea2447c9a..c870fa2c1823942106ca920c65b0295333128791 100644 --- a/src/cmd/link/internal/ld/outbuf_nommap.go +++ b/src/cmd/link/internal/ld/outbuf_nommap.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !windows // +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!windows package ld diff --git a/src/cmd/link/internal/ld/outbuf_notdarwin.go b/src/cmd/link/internal/ld/outbuf_notdarwin.go index 8c5666f216d2c4905bc52db71807de99a709f3b6..f9caa413e3c3c198a906d362e2a0e4aa7e5d8b99 100644 --- a/src/cmd/link/internal/ld/outbuf_notdarwin.go +++ b/src/cmd/link/internal/ld/outbuf_notdarwin.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !darwin // +build !darwin package ld diff --git a/src/cmd/link/internal/ld/outbuf_test.go b/src/cmd/link/internal/ld/outbuf_test.go index e6643da396e4972cb332009f43e2833ad2c4b8f4..a7b105f887a3d08db332e5ae62ea4b3d0e5968c1 100644 --- a/src/cmd/link/internal/ld/outbuf_test.go +++ b/src/cmd/link/internal/ld/outbuf_test.go @@ -5,8 +5,6 @@ package ld import ( - "io/ioutil" - "os" "path/filepath" "runtime" "testing" @@ -19,11 +17,7 @@ func TestMMap(t *testing.T) { t.Skip("unsupported OS") case "aix", "darwin", "ios", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "windows": } - dir, err := ioutil.TempDir("", "TestMMap") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() filename := filepath.Join(dir, "foo.out") ob := NewOutBuf(nil) if err := ob.Open(filename); err != nil { diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index 72bf33e611e59ea5b249e9639b0baacb73258a89..05fd30236949671419e6fd0e7efa13b7763b575c 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -11,6 +11,7 @@ import ( "cmd/link/internal/loader" "cmd/link/internal/sym" "fmt" + "internal/buildcfg" "os" "path/filepath" ) @@ -50,7 +51,7 @@ type pclntab struct { } // addGeneratedSym adds a generator symbol to pclntab, returning the new Sym. -// It is the caller's responsibilty to save they symbol in state. +// It is the caller's responsibility to save they symbol in state. func (state *pclntab) addGeneratedSym(ctxt *Link, name string, size int64, f generatorFunc) loader.Sym { size = Rnd(size, int64(ctxt.Arch.PtrSize)) state.size += size @@ -360,7 +361,7 @@ func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.Compilat // then not loading extra filenames), and just use the hash value of the // symbol name to do this cataloging. // - // TOOD: Store filenames as symbols. (Note this would be easiest if you + // TODO: Store filenames as symbols. (Note this would be easiest if you // also move strings to ALWAYS using the larger content addressable hash // function, and use that hash value for uniqueness testing.) cuEntries := make([]goobj.CUFileIndex, len(compUnits)) @@ -589,6 +590,7 @@ func (state *pclntab) generateFunctab(ctxt *Link, funcs []loader.Sym, inlSyms ma if !useSymValue { // Generate relocations for funcdata when externally linking. state.writeFuncData(ctxt, sb, funcs, inlSyms, startLocations, setAddr, setUintNOP) + sb.SortRelocs() } } @@ -796,7 +798,14 @@ func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSym } off = uint32(sb.SetUint8(ctxt.Arch, int64(off), uint8(funcID))) - off += 2 // pad + // flag uint8 + var flag objabi.FuncFlag + if fi.Valid() { + flag = fi.FuncFlag() + } + off = uint32(sb.SetUint8(ctxt.Arch, int64(off), uint8(flag))) + + off += 1 // pad // nfuncdata must be the final entry. funcdata, funcdataoff = funcData(fi, 0, funcdata, funcdataoff) @@ -872,7 +881,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { } func gorootFinal() string { - root := objabi.GOROOT + root := buildcfg.GOROOT if final := os.Getenv("GOROOT_FINAL"); final != "" { root = final } diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go index 5edaf54dd2f1949d5964934ee0b8519f850f0618..8eb4231c3ab2895f095e2b1197e92d60659eb177 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // PE (Portable Executable) file writing -// https://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx +// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format package ld @@ -15,6 +15,7 @@ import ( "debug/pe" "encoding/binary" "fmt" + "internal/buildcfg" "sort" "strconv" "strings" @@ -42,11 +43,11 @@ type IMAGE_EXPORT_DIRECTORY struct { AddressOfNameOrdinals uint32 } -const ( - PEBASE = 0x00400000 -) - var ( + // PEBASE is the base address for the executable. + // It is small for 32-bit and large for 64-bit. + PEBASE int64 + // SectionAlignment must be greater than or equal to FileAlignment. // The default is the page size for the architecture. PESECTALIGN int64 = 0x1000 @@ -61,14 +62,40 @@ const ( IMAGE_SCN_CNT_CODE = 0x00000020 IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080 + IMAGE_SCN_LNK_OTHER = 0x00000100 + IMAGE_SCN_LNK_INFO = 0x00000200 + IMAGE_SCN_LNK_REMOVE = 0x00000800 + IMAGE_SCN_LNK_COMDAT = 0x00001000 + IMAGE_SCN_GPREL = 0x00008000 + IMAGE_SCN_MEM_PURGEABLE = 0x00020000 + IMAGE_SCN_MEM_16BIT = 0x00020000 + IMAGE_SCN_MEM_LOCKED = 0x00040000 + IMAGE_SCN_MEM_PRELOAD = 0x00080000 + IMAGE_SCN_ALIGN_1BYTES = 0x00100000 + IMAGE_SCN_ALIGN_2BYTES = 0x00200000 + IMAGE_SCN_ALIGN_4BYTES = 0x00300000 + IMAGE_SCN_ALIGN_8BYTES = 0x00400000 + IMAGE_SCN_ALIGN_16BYTES = 0x00500000 + IMAGE_SCN_ALIGN_32BYTES = 0x00600000 + IMAGE_SCN_ALIGN_64BYTES = 0x00700000 + IMAGE_SCN_ALIGN_128BYTES = 0x00800000 + IMAGE_SCN_ALIGN_256BYTES = 0x00900000 + IMAGE_SCN_ALIGN_512BYTES = 0x00A00000 + IMAGE_SCN_ALIGN_1024BYTES = 0x00B00000 + IMAGE_SCN_ALIGN_2048BYTES = 0x00C00000 + IMAGE_SCN_ALIGN_4096BYTES = 0x00D00000 + IMAGE_SCN_ALIGN_8192BYTES = 0x00E00000 + IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000 + IMAGE_SCN_MEM_DISCARDABLE = 0x02000000 + IMAGE_SCN_MEM_NOT_CACHED = 0x04000000 + IMAGE_SCN_MEM_NOT_PAGED = 0x08000000 + IMAGE_SCN_MEM_SHARED = 0x10000000 IMAGE_SCN_MEM_EXECUTE = 0x20000000 IMAGE_SCN_MEM_READ = 0x40000000 IMAGE_SCN_MEM_WRITE = 0x80000000 - IMAGE_SCN_MEM_DISCARDABLE = 0x2000000 - IMAGE_SCN_LNK_NRELOC_OVFL = 0x1000000 - IMAGE_SCN_ALIGN_32BYTES = 0x600000 ) +// See https://docs.microsoft.com/en-us/windows/win32/debug/pe-format. // TODO(crawshaw): add these constants to debug/pe. const ( // TODO: the Microsoft doco says IMAGE_SYM_DTYPE_ARRAY is 3 and IMAGE_SYM_DTYPE_FUNCTION is 2 @@ -95,6 +122,25 @@ const ( IMAGE_REL_ARM_BRANCH11 = 0x0004 IMAGE_REL_ARM_SECREL = 0x000F + IMAGE_REL_ARM64_ABSOLUTE = 0x0000 + IMAGE_REL_ARM64_ADDR32 = 0x0001 + IMAGE_REL_ARM64_ADDR32NB = 0x0002 + IMAGE_REL_ARM64_BRANCH26 = 0x0003 + IMAGE_REL_ARM64_PAGEBASE_REL21 = 0x0004 + IMAGE_REL_ARM64_REL21 = 0x0005 + IMAGE_REL_ARM64_PAGEOFFSET_12A = 0x0006 + IMAGE_REL_ARM64_PAGEOFFSET_12L = 0x0007 + IMAGE_REL_ARM64_SECREL = 0x0008 + IMAGE_REL_ARM64_SECREL_LOW12A = 0x0009 + IMAGE_REL_ARM64_SECREL_HIGH12A = 0x000A + IMAGE_REL_ARM64_SECREL_LOW12L = 0x000B + IMAGE_REL_ARM64_TOKEN = 0x000C + IMAGE_REL_ARM64_SECTION = 0x000D + IMAGE_REL_ARM64_ADDR64 = 0x000E + IMAGE_REL_ARM64_BRANCH19 = 0x000F + IMAGE_REL_ARM64_BRANCH14 = 0x0010 + IMAGE_REL_ARM64_REL32 = 0x0011 + IMAGE_REL_BASED_HIGHLOW = 3 IMAGE_REL_BASED_DIR64 = 10 ) @@ -316,8 +362,8 @@ func (sect *peSection) checkOffset(off int64) { // checkSegment verifies COFF section sect matches address // and file offset provided in segment seg. func (sect *peSection) checkSegment(seg *sym.Segment) { - if seg.Vaddr-PEBASE != uint64(sect.virtualAddress) { - Errorf(nil, "%s.VirtualAddress = %#x, want %#x", sect.name, uint64(int64(sect.virtualAddress)), uint64(int64(seg.Vaddr-PEBASE))) + if seg.Vaddr-uint64(PEBASE) != uint64(sect.virtualAddress) { + Errorf(nil, "%s.VirtualAddress = %#x, want %#x", sect.name, uint64(int64(sect.virtualAddress)), uint64(int64(seg.Vaddr-uint64(PEBASE)))) errorexit() } if seg.Fileoff != uint64(sect.pointerToRawData) { @@ -398,14 +444,16 @@ func (f *peFile) addSection(name string, sectsize int, filesize int) *peSection name: name, shortName: name, index: len(f.sections) + 1, - virtualSize: uint32(sectsize), virtualAddress: f.nextSectOffset, pointerToRawData: f.nextFileOffset, } f.nextSectOffset = uint32(Rnd(int64(f.nextSectOffset)+int64(sectsize), PESECTALIGN)) if filesize > 0 { + sect.virtualSize = uint32(sectsize) sect.sizeOfRawData = uint32(Rnd(int64(filesize), PEFILEALIGN)) f.nextFileOffset += sect.sizeOfRawData + } else { + sect.sizeOfRawData = uint32(sectsize) } f.sections = append(f.sections, sect) return sect @@ -427,7 +475,7 @@ func (f *peFile) addDWARFSection(name string, size int) *peSection { off := f.stringTable.add(name) h := f.addSection(name, size, size) h.shortName = fmt.Sprintf("/%d", off) - h.characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE + h.characteristics = IMAGE_SCN_ALIGN_1BYTES | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE | IMAGE_SCN_CNT_INITIALIZED_DATA return h } @@ -456,28 +504,29 @@ func (f *peFile) addInitArray(ctxt *Link) *peSection { // However, the entire Go runtime is initialized from just one function, so it is unlikely // that this will need to grow in the future. var size int - switch objabi.GOARCH { + var alignment uint32 + switch buildcfg.GOARCH { default: - Exitf("peFile.addInitArray: unsupported GOARCH=%q\n", objabi.GOARCH) - case "386": + Exitf("peFile.addInitArray: unsupported GOARCH=%q\n", buildcfg.GOARCH) + case "386", "arm": size = 4 - case "amd64": + alignment = IMAGE_SCN_ALIGN_4BYTES + case "amd64", "arm64": size = 8 - case "arm": - size = 4 + alignment = IMAGE_SCN_ALIGN_8BYTES } sect := f.addSection(".ctors", size, size) - sect.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ + sect.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | alignment sect.sizeOfRawData = uint32(size) ctxt.Out.SeekSet(int64(sect.pointerToRawData)) sect.checkOffset(ctxt.Out.Offset()) init_entry := ctxt.loader.Lookup(*flagEntrySymbol, 0) addr := uint64(ctxt.loader.SymValue(init_entry)) - ctxt.loader.SymSect(init_entry).Vaddr - switch objabi.GOARCH { + switch buildcfg.GOARCH { case "386", "arm": ctxt.Out.Write32(uint32(addr)) - case "amd64": + case "amd64", "arm64": ctxt.Out.Write64(addr) } return sect @@ -498,7 +547,6 @@ func (f *peFile) emitRelocations(ctxt *Link) { if sect.Vaddr >= sect.Seg.Vaddr+sect.Seg.Filelen { return 0 } - nrelocs := 0 sect.Reloff = uint64(ctxt.Out.Offset()) for i, s := range syms { if !ldr.AttrReachable(s) { @@ -509,12 +557,12 @@ func (f *peFile) emitRelocations(ctxt *Link) { break } } - eaddr := int32(sect.Vaddr + sect.Length) + eaddr := int64(sect.Vaddr + sect.Length) for _, s := range syms { if !ldr.AttrReachable(s) { continue } - if ldr.SymValue(s) >= int64(eaddr) { + if ldr.SymValue(s) >= eaddr { break } // Compute external relocations on the go, and pass to PEreloc1 @@ -534,13 +582,13 @@ func (f *peFile) emitRelocations(ctxt *Link) { ctxt.Errorf(s, "reloc %d to non-coff symbol %s (outer=%s) %d", r.Type(), ldr.SymName(r.Sym()), ldr.SymName(rr.Xsym), ldr.SymType(r.Sym())) } if !thearch.PEreloc1(ctxt.Arch, ctxt.Out, ldr, s, rr, int64(uint64(ldr.SymValue(s)+int64(r.Off()))-base)) { - ctxt.Errorf(s, "unsupported obj reloc %d/%d to %s", r.Type(), r.Siz(), ldr.SymName(r.Sym())) + ctxt.Errorf(s, "unsupported obj reloc %v/%d to %s", r.Type(), r.Siz(), ldr.SymName(r.Sym())) } - nrelocs++ } } sect.Rellen = uint64(ctxt.Out.Offset()) - sect.Reloff - return nrelocs + const relocLen = 4 + 4 + 2 + return int(sect.Rellen / relocLen) } sects := []struct { @@ -581,19 +629,25 @@ dwarfLoop: Errorf(nil, "emitRelocations: could not find %q section", sect.Name) } + if f.ctorsSect == nil { + return + } + f.ctorsSect.emitRelocations(ctxt.Out, func() int { dottext := ldr.Lookup(".text", 0) ctxt.Out.Write32(0) ctxt.Out.Write32(uint32(ldr.SymDynid(dottext))) - switch objabi.GOARCH { + switch buildcfg.GOARCH { default: - ctxt.Errorf(dottext, "unknown architecture for PE: %q\n", objabi.GOARCH) + ctxt.Errorf(dottext, "unknown architecture for PE: %q\n", buildcfg.GOARCH) case "386": ctxt.Out.Write16(IMAGE_REL_I386_DIR32) case "amd64": ctxt.Out.Write16(IMAGE_REL_AMD64_ADDR64) case "arm": ctxt.Out.Write16(IMAGE_REL_ARM_ADDR32) + case "arm64": + ctxt.Out.Write16(IMAGE_REL_ARM64_ADDR64) } return 1 }) @@ -650,6 +704,12 @@ func (f *peFile) mapToPESection(ldr *loader.Loader, s loader.Sym, linkmode LinkM return f.bssSect.index, int64(v - Segdata.Filelen), nil } +var isLabel = make(map[loader.Sym]bool) + +func AddPELabelSym(ldr *loader.Loader, s loader.Sym) { + isLabel[s] = true +} + // writeSymbols writes all COFF symbol table records. func (f *peFile) writeSymbols(ctxt *Link) { ldr := ctxt.loader @@ -667,6 +727,8 @@ func (f *peFile) writeSymbols(ctxt *Link) { name = "_" + name } + name = mangleABIName(ctxt, ldr, s, name) + var peSymType uint16 if ctxt.IsExternal() { peSymType = IMAGE_SYM_TYPE_NULL @@ -744,6 +806,10 @@ func (f *peFile) writeSymbols(ctxt *Link) { switch t { case sym.SDYNIMPORT, sym.SHOSTOBJ, sym.SUNDEFEXT: addsym(s) + default: + if len(isLabel) > 0 && isLabel[s] { + addsym(s) + } } } } @@ -788,6 +854,8 @@ func (f *peFile) writeFileHeader(ctxt *Link) { fh.Machine = pe.IMAGE_FILE_MACHINE_I386 case sys.ARM: fh.Machine = pe.IMAGE_FILE_MACHINE_ARMNT + case sys.ARM64: + fh.Machine = pe.IMAGE_FILE_MACHINE_ARM64 } fh.NumberOfSections = uint16(len(f.sections)) @@ -852,8 +920,8 @@ func (f *peFile) writeOptionalHeader(ctxt *Link) { } oh64.BaseOfCode = f.textSect.virtualAddress oh.BaseOfCode = f.textSect.virtualAddress - oh64.ImageBase = PEBASE - oh.ImageBase = PEBASE + oh64.ImageBase = uint64(PEBASE) + oh.ImageBase = uint32(PEBASE) oh64.SectionAlignment = uint32(PESECTALIGN) oh.SectionAlignment = uint32(PESECTALIGN) oh64.FileAlignment = uint32(PEFILEALIGN) @@ -891,13 +959,7 @@ func (f *peFile) writeOptionalHeader(ctxt *Link) { oh.DllCharacteristics |= pe.IMAGE_DLLCHARACTERISTICS_NX_COMPAT // The DLL can be relocated at load time. - switch ctxt.Arch.Family { - case sys.AMD64, sys.I386: - if ctxt.BuildMode == BuildModePIE { - oh64.DllCharacteristics |= pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE - oh.DllCharacteristics |= pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE - } - case sys.ARM: + if needPEBaseReloc(ctxt) { oh64.DllCharacteristics |= pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE oh.DllCharacteristics |= pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE } @@ -922,9 +984,7 @@ func (f *peFile) writeOptionalHeader(ctxt *Link) { // calls that may need more stack than we think. // // The default stack reserve size directly affects only the main - // thread, ctrlhandler thread, and profileloop thread. For - // these, it must be greater than the stack size assumed by - // externalthreadhandler. + // thread. // // For other threads, the runtime explicitly asks the kernel // to use the default stack size so that all stacks are @@ -975,18 +1035,23 @@ var pefile peFile func Peinit(ctxt *Link) { var l int - switch ctxt.Arch.Family { - // 64-bit architectures - case sys.AMD64: + if ctxt.Arch.PtrSize == 8 { + // 64-bit architectures pe64 = 1 + PEBASE = 1 << 32 + if ctxt.Arch.Family == sys.AMD64 { + // TODO(rsc): For cgo we currently use 32-bit relocations + // that fail when PEBASE is too large. + // We need to fix this, but for now, use a smaller PEBASE. + PEBASE = 1 << 22 + } var oh64 pe.OptionalHeader64 l = binary.Size(&oh64) - - // 32-bit architectures - default: + } else { + // 32-bit architectures + PEBASE = 1 << 22 var oh pe.OptionalHeader32 l = binary.Size(&oh) - } if ctxt.LinkMode == LinkExternal { @@ -1210,7 +1275,7 @@ func addimports(ctxt *Link, datsect *peSection) { endoff := ctxt.Out.Offset() // write FirstThunks (allocated in .data section) - ftbase := uint64(ldr.SymValue(dynamic)) - uint64(datsect.virtualAddress) - PEBASE + ftbase := uint64(ldr.SymValue(dynamic)) - uint64(datsect.virtualAddress) - uint64(PEBASE) ctxt.Out.SeekSet(int64(uint64(datsect.pointerToRawData) + ftbase)) for d := dr; d != nil; d = d.next { @@ -1463,17 +1528,18 @@ func addPEBaseRelocSym(ldr *loader.Loader, s loader.Sym, rt *peBaseRelocTable) { } } +func needPEBaseReloc(ctxt *Link) bool { + // Non-PIE x86 binaries don't need the base relocation table. + // Everyone else does. + if (ctxt.Arch.Family == sys.I386 || ctxt.Arch.Family == sys.AMD64) && ctxt.BuildMode != BuildModePIE { + return false + } + return true +} + func addPEBaseReloc(ctxt *Link) { - // Arm does not work without base relocation table. - // 386 and amd64 will only require the table for BuildModePIE. - switch ctxt.Arch.Family { - default: + if !needPEBaseReloc(ctxt) { return - case sys.I386, sys.AMD64: - if ctxt.BuildMode != BuildModePIE { - return - } - case sys.ARM: } var rt peBaseRelocTable @@ -1562,14 +1628,8 @@ func addpersrc(ctxt *Link) { } func asmbPe(ctxt *Link) { - switch ctxt.Arch.Family { - default: - Exitf("unknown PE architecture: %v", ctxt.Arch.Family) - case sys.AMD64, sys.I386, sys.ARM: - } - t := pefile.addSection(".text", int(Segtext.Length), int(Segtext.Length)) - t.characteristics = IMAGE_SCN_CNT_CODE | IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ + t.characteristics = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ if ctxt.LinkMode == LinkExternal { // some data symbols (e.g. masks) end up in the .text section, and they normally // expect larger alignment requirement than the default text section alignment. diff --git a/src/cmd/link/internal/ld/sym.go b/src/cmd/link/internal/ld/sym.go index 75489720cc750823ae61786f0a545da908f9adf5..72639962e2ffa6b76a5a40a1219f61c183ef9a3a 100644 --- a/src/cmd/link/internal/ld/sym.go +++ b/src/cmd/link/internal/ld/sym.go @@ -36,6 +36,7 @@ import ( "cmd/internal/sys" "cmd/link/internal/loader" "cmd/link/internal/sym" + "internal/buildcfg" "log" "runtime" ) @@ -53,8 +54,8 @@ func linknew(arch *sys.Arch) *Link { generatorSyms: make(map[loader.Sym]generatorFunc), } - if objabi.GOARCH != arch.Name { - log.Fatalf("invalid objabi.GOARCH %s (want %s)", objabi.GOARCH, arch.Name) + if buildcfg.GOARCH != arch.Name { + log.Fatalf("invalid buildcfg.GOARCH %s (want %s)", buildcfg.GOARCH, arch.Name) } AtExit(func() { diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index f54cf9ea2f5b3165c3ce2955895043dac7c8fc19..00f557875a939a0dc1addf0d9319ae64e628b1c0 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -37,6 +37,7 @@ import ( "cmd/link/internal/sym" "debug/elf" "fmt" + "internal/buildcfg" "path/filepath" "strings" ) @@ -104,6 +105,7 @@ func putelfsym(ctxt *Link, x loader.Sym, typ elf.SymType, curbind elf.SymBind) { } sname := ldr.SymExtname(x) + sname = mangleABIName(ctxt, ldr, x, sname) // One pass for each binding: elf.STB_LOCAL, elf.STB_GLOBAL, // maybe one day elf.STB_WEAK. @@ -583,7 +585,9 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { strings.HasPrefix(name, "gclocals."), strings.HasPrefix(name, "gclocals·"), ldr.SymType(s) == sym.SGOFUNC && s != symgofunc, - strings.HasSuffix(name, ".opendefer"): + strings.HasSuffix(name, ".opendefer"), + strings.HasSuffix(name, ".arginfo0"), + strings.HasSuffix(name, ".arginfo1"): symGroupType[s] = sym.SGOFUNC ldr.SetAttrNotInSymbolTable(s, true) ldr.SetCarrierSym(s, symgofunc) @@ -830,3 +834,39 @@ func setCarrierSize(typ sym.SymKind, sz int64) { func isStaticTmp(name string) bool { return strings.Contains(name, "."+obj.StaticNamePref) } + +// Mangle function name with ABI information. +func mangleABIName(ctxt *Link, ldr *loader.Loader, x loader.Sym, name string) string { + // For functions with ABI wrappers, we have to make sure that we + // don't wind up with two symbol table entries with the same + // name (since this will generated an error from the external + // linker). If we have wrappers, keep the ABIInternal name + // unmangled since we want cross-load-module calls to target + // ABIInternal, and rename other symbols. + // + // TODO: avoid the ldr.Lookup calls below by instead using an aux + // sym or marker relocation to associate the wrapper with the + // wrapped function. + if !buildcfg.Experiment.RegabiWrappers { + return name + } + + if !ldr.IsExternal(x) && ldr.SymType(x) == sym.STEXT && ldr.SymVersion(x) != sym.SymVerABIInternal { + if s2 := ldr.Lookup(name, sym.SymVerABIInternal); s2 != 0 && ldr.SymType(s2) == sym.STEXT { + name = fmt.Sprintf("%s.abi%d", name, ldr.SymVersion(x)) + } + } + + // When loading a shared library, if a symbol has only one ABI, + // and the name is not mangled, we don't know what ABI it is. + // So we always mangle ABIInternal function name in shared linkage, + // except symbols that are exported to C. Type symbols are always + // ABIInternal so they are not mangled. + if ctxt.IsShared() { + if ldr.SymType(x) == sym.STEXT && ldr.SymVersion(x) == sym.SymVerABIInternal && !ldr.AttrCgoExport(x) && !strings.HasPrefix(name, "type.") { + name = fmt.Sprintf("%s.abiinternal", name) + } + } + + return name +} diff --git a/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod3.go b/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod3.go index 9a8dfbce5facba86bf038d5c690b3c3f73156a2c..37c89374cbebf8c2685fbec59411a9aa9be4eb11 100644 --- a/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod3.go +++ b/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod3.go @@ -14,7 +14,7 @@ type S int func (s S) M() { println("S.M") } -type I interface { M() } +type I interface{ M() } type T float64 diff --git a/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod4.go b/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod4.go index 52ee2e3d860fa2bd2b19e719ccee52be75fc2eed..4af47ad1fa53eccb792aff52ad9e9ea6a46ce77a 100644 --- a/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod4.go +++ b/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod4.go @@ -10,6 +10,7 @@ package main type T int +//go:noinline func (T) M() {} type I interface{ M() } @@ -20,4 +21,5 @@ var pp *I func main() { p = new(T) // use type T pp = new(I) // use type I + *pp = *p // convert T to I, build itab } diff --git a/src/cmd/link/internal/ld/testdata/issue42484/main.go b/src/cmd/link/internal/ld/testdata/issue42484/main.go new file mode 100644 index 0000000000000000000000000000000000000000..60fc110ffaa5158e653be75e74034bec10be0a63 --- /dev/null +++ b/src/cmd/link/internal/ld/testdata/issue42484/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" +) + +func main() { + a := 0 + a++ + b := 0 + f1(a, b) +} + +func f1(a, b int) { + fmt.Printf("%d %d\n", a, b) +} diff --git a/src/cmd/link/internal/ld/util.go b/src/cmd/link/internal/ld/util.go index 9228ed163d0fc3d8f98936196584905bf9660146..779f4988b68a3aa107843c063aff6297d17b04da 100644 --- a/src/cmd/link/internal/ld/util.go +++ b/src/cmd/link/internal/ld/util.go @@ -57,7 +57,7 @@ func afterErrorAction() { // Logging an error means that on exit cmd/link will delete any // output file and return a non-zero error code. // -// TODO: remove. Use ctxt.Errof instead. +// TODO: remove. Use ctxt.Errorf instead. // All remaining calls use nil as first arg. func Errorf(dummy *int, format string, args ...interface{}) { format += "\n" diff --git a/src/cmd/link/internal/ld/xcoff.go b/src/cmd/link/internal/ld/xcoff.go index ba818eaa961e525293d4f573bdf7f62af56bfae1..12bd23f7e57e1d9410d3c2309ab1938c2b8d3bc9 100644 --- a/src/cmd/link/internal/ld/xcoff.go +++ b/src/cmd/link/internal/ld/xcoff.go @@ -28,8 +28,11 @@ const ( // Total amount of space to reserve at the start of the file // for File Header, Auxiliary Header, and Section Headers. // May waste some. - XCOFFHDRRESERVE = FILHSZ_64 + AOUTHSZ_EXEC64 + SCNHSZ_64*23 - XCOFFSECTALIGN int64 = 32 // base on dump -o + XCOFFHDRRESERVE = FILHSZ_64 + AOUTHSZ_EXEC64 + SCNHSZ_64*23 + + // base on dump -o, then rounded from 32B to 64B to + // match worst case elf text section alignment on ppc64. + XCOFFSECTALIGN int64 = 64 // XCOFF binaries should normally have all its sections position-independent. // However, this is not yet possible for .text because of some R_ADDR relocations @@ -555,11 +558,12 @@ func Xcoffinit(ctxt *Link) { // type records C_FILE information needed for genasmsym in XCOFF. type xcoffSymSrcFile struct { - name string - file *XcoffSymEnt64 // Symbol of this C_FILE - csectAux *XcoffAuxCSect64 // Symbol for the current .csect - csectSymNb uint64 // Symbol number for the current .csect - csectSize int64 + name string + file *XcoffSymEnt64 // Symbol of this C_FILE + csectAux *XcoffAuxCSect64 // Symbol for the current .csect + csectSymNb uint64 // Symbol number for the current .csect + csectVAStart int64 + csectVAEnd int64 } var ( @@ -746,7 +750,8 @@ func (f *xcoffFile) writeSymbolNewFile(ctxt *Link, name string, firstEntry uint6 f.addSymbol(aux) currSymSrcFile.csectAux = aux - currSymSrcFile.csectSize = 0 + currSymSrcFile.csectVAStart = int64(firstEntry) + currSymSrcFile.csectVAEnd = int64(firstEntry) } // Update values for the previous package. @@ -768,8 +773,9 @@ func (f *xcoffFile) updatePreviousFile(ctxt *Link, last bool) { // update csect scnlen in this auxiliary entry aux := currSymSrcFile.csectAux - aux.Xscnlenlo = uint32(currSymSrcFile.csectSize & 0xFFFFFFFF) - aux.Xscnlenhi = uint32(currSymSrcFile.csectSize >> 32) + csectSize := currSymSrcFile.csectVAEnd - currSymSrcFile.csectVAStart + aux.Xscnlenlo = uint32(csectSize & 0xFFFFFFFF) + aux.Xscnlenhi = uint32(csectSize >> 32) } // Write symbol representing a .text function. @@ -825,15 +831,20 @@ func (f *xcoffFile) writeSymbolFunc(ctxt *Link, x loader.Sym) []xcoffSym { Nnumaux: 2, } - if ldr.SymVersion(x) != 0 || ldr.AttrVisibilityHidden(x) || ldr.AttrLocal(x) { + if ldr.IsFileLocal(x) || ldr.AttrVisibilityHidden(x) || ldr.AttrLocal(x) { s.Nsclass = C_HIDEXT } ldr.SetSymDynid(x, int32(xfile.symbolCount)) syms = append(syms, s) - // Update current csect size - currSymSrcFile.csectSize += ldr.SymSize(x) + // Keep track of the section size by tracking the VA range. Individual + // alignment differences may introduce a few extra bytes of padding + // which are not fully accounted for by ldr.SymSize(x). + sv := ldr.SymValue(x) + ldr.SymSize(x) + if currSymSrcFile.csectVAEnd < sv { + currSymSrcFile.csectVAEnd = sv + } // create auxiliary entries a2 := &XcoffAuxFcn64{ @@ -914,7 +925,7 @@ func putaixsym(ctxt *Link, x loader.Sym, t SymbolType) { Nnumaux: 1, } - if ldr.SymVersion(x) != 0 || ldr.AttrVisibilityHidden(x) || ldr.AttrLocal(x) { + if ldr.IsFileLocal(x) || ldr.AttrVisibilityHidden(x) || ldr.AttrLocal(x) { // There is more symbols in the case of a global data // which are related to the assembly generated // to access such symbols. @@ -1318,19 +1329,14 @@ func (ctxt *Link) doxcoff() { if !ldr.AttrCgoExport(s) { continue } - if ldr.SymVersion(s) != 0 { // sanity check - panic("cgo_export on non-version 0 symbol") + if ldr.IsFileLocal(s) { + panic("cgo_export on static symbol") } if ldr.SymType(s) == sym.STEXT || ldr.SymType(s) == sym.SABIALIAS { // On AIX, a exported function must have two symbols: // - a .text symbol which must start with a ".". // - a .data symbol which is a function descriptor. - // - // CgoExport attribute should only be set on a version 0 - // symbol, which can be TEXT or ABIALIAS. - // (before, setupdynexp copies the attribute from the - // alias to the aliased. Now we are before setupdynexp.) name := ldr.SymExtname(s) ldr.SetSymExtname(s, "."+name) @@ -1554,7 +1560,7 @@ func (f *xcoffFile) writeFileHeader(ctxt *Link) { f.xahdr.Otoc = uint64(ldr.SymValue(toc)) f.xahdr.Osntoc = f.getXCOFFscnum(ldr.SymSect(toc)) - f.xahdr.Oalgntext = int16(logBase2(int(Funcalign))) + f.xahdr.Oalgntext = int16(logBase2(int(XCOFFSECTALIGN))) f.xahdr.Oalgndata = 0x5 binary.Write(ctxt.Out, binary.BigEndian, &f.xfhdr) @@ -1787,8 +1793,8 @@ func xcoffCreateExportFile(ctxt *Link) (fname string) { if !strings.HasPrefix(extname, "._cgoexp_") { continue } - if ldr.SymVersion(s) != 0 { - continue // Only export version 0 symbols. See the comment in doxcoff. + if ldr.IsFileLocal(s) { + continue // Only export non-static symbols } // Retrieve the name of the initial symbol diff --git a/src/cmd/link/internal/loadelf/ldelf.go b/src/cmd/link/internal/loadelf/ldelf.go index c698874b3255ffd3a0e9ca9baa021373ca6905f6..c6956297f6c79f8ba62b7e68a6609a0579353b97 100644 --- a/src/cmd/link/internal/loadelf/ldelf.go +++ b/src/cmd/link/internal/loadelf/ldelf.go @@ -245,13 +245,13 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader, newSym := func(name string, version int) loader.Sym { return l.CreateStaticSym(name) } - lookup := func(name string, version int) loader.Sym { - return l.LookupOrCreateSym(name, version) - } + lookup := l.LookupOrCreateCgoExport errorf := func(str string, args ...interface{}) ([]loader.Sym, uint32, error) { return nil, 0, fmt.Errorf("loadelf: %s: %v", pn, fmt.Sprintf(str, args...)) } + ehdrFlags = initEhdrFlags + base := f.Offset() var hdrbuf [64]byte @@ -753,7 +753,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader, } rType := objabi.ElfRelocOffset + objabi.RelocType(relocType) - rSize, err := relSize(arch, pn, uint32(relocType)) + rSize, addendSize, err := relSize(arch, pn, uint32(relocType)) if err != nil { return nil, 0, err } @@ -770,10 +770,10 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader, } } - if rSize == 2 { + if addendSize == 2 { rAdd = int64(int16(rAdd)) } - if rSize == 4 { + if addendSize == 4 { rAdd = int64(int32(rAdd)) } @@ -945,7 +945,10 @@ func readelfsym(newSym, lookup func(string, int) loader.Sym, l *loader.Loader, a return nil } -func relSize(arch *sys.Arch, pn string, elftype uint32) (uint8, error) { +// Return the size of the relocated field, and the size of the addend as the first +// and second values. Note, the addend may be larger than the relocation field in +// some cases when a relocated value is split across multiple relocations. +func relSize(arch *sys.Arch, pn string, elftype uint32) (uint8, uint8, error) { // TODO(mdempsky): Replace this with a struct-valued switch statement // once golang.org/issue/15164 is fixed or found to not impair cmd/link // performance. @@ -964,7 +967,7 @@ func relSize(arch *sys.Arch, pn string, elftype uint32) (uint8, error) { switch uint32(arch.Family) | elftype<<16 { default: - return 0, fmt.Errorf("%s: unknown relocation type %d; compiled without -fpic?", pn, elftype) + return 0, 0, fmt.Errorf("%s: unknown relocation type %d; compiled without -fpic?", pn, elftype) case MIPS | uint32(elf.R_MIPS_HI16)<<16, MIPS | uint32(elf.R_MIPS_LO16)<<16, @@ -983,27 +986,23 @@ func relSize(arch *sys.Arch, pn string, elftype uint32) (uint8, error) { MIPS64 | uint32(elf.R_MIPS_GPREL16)<<16, MIPS64 | uint32(elf.R_MIPS_GOT_PAGE)<<16, MIPS64 | uint32(elf.R_MIPS_JALR)<<16, - MIPS64 | uint32(elf.R_MIPS_GOT_OFST)<<16: - return 4, nil + MIPS64 | uint32(elf.R_MIPS_GOT_OFST)<<16, + MIPS64 | uint32(elf.R_MIPS_CALL16)<<16, + MIPS64 | uint32(elf.R_MIPS_GPREL32)<<16, + MIPS64 | uint32(elf.R_MIPS_64)<<16, + MIPS64 | uint32(elf.R_MIPS_GOT_DISP)<<16: + return 4, 4, nil case S390X | uint32(elf.R_390_8)<<16: - return 1, nil + return 1, 1, nil case PPC64 | uint32(elf.R_PPC64_TOC16)<<16, - PPC64 | uint32(elf.R_PPC64_TOC16_LO)<<16, - PPC64 | uint32(elf.R_PPC64_TOC16_HI)<<16, - PPC64 | uint32(elf.R_PPC64_TOC16_HA)<<16, - PPC64 | uint32(elf.R_PPC64_TOC16_DS)<<16, - PPC64 | uint32(elf.R_PPC64_TOC16_LO_DS)<<16, - PPC64 | uint32(elf.R_PPC64_REL16_LO)<<16, - PPC64 | uint32(elf.R_PPC64_REL16_HI)<<16, - PPC64 | uint32(elf.R_PPC64_REL16_HA)<<16, S390X | uint32(elf.R_390_16)<<16, S390X | uint32(elf.R_390_GOT16)<<16, S390X | uint32(elf.R_390_PC16)<<16, S390X | uint32(elf.R_390_PC16DBL)<<16, S390X | uint32(elf.R_390_PLT16DBL)<<16: - return 2, nil + return 2, 2, nil case ARM | uint32(elf.R_ARM_ABS32)<<16, ARM | uint32(elf.R_ARM_GOT32)<<16, @@ -1051,7 +1050,7 @@ func relSize(arch *sys.Arch, pn string, elftype uint32) (uint8, error) { S390X | uint32(elf.R_390_PLT32DBL)<<16, S390X | uint32(elf.R_390_GOTPCDBL)<<16, S390X | uint32(elf.R_390_GOTENT)<<16: - return 4, nil + return 4, 4, nil case AMD64 | uint32(elf.R_X86_64_64)<<16, AMD64 | uint32(elf.R_X86_64_PC64)<<16, @@ -1066,11 +1065,11 @@ func relSize(arch *sys.Arch, pn string, elftype uint32) (uint8, error) { S390X | uint32(elf.R_390_PC64)<<16, S390X | uint32(elf.R_390_GOT64)<<16, S390X | uint32(elf.R_390_PLT64)<<16: - return 8, nil + return 8, 8, nil case RISCV64 | uint32(elf.R_RISCV_RVC_BRANCH)<<16, RISCV64 | uint32(elf.R_RISCV_RVC_JUMP)<<16: - return 2, nil + return 2, 2, nil case RISCV64 | uint32(elf.R_RISCV_32)<<16, RISCV64 | uint32(elf.R_RISCV_BRANCH)<<16, @@ -1082,12 +1081,22 @@ func relSize(arch *sys.Arch, pn string, elftype uint32) (uint8, error) { RISCV64 | uint32(elf.R_RISCV_PCREL_LO12_I)<<16, RISCV64 | uint32(elf.R_RISCV_PCREL_LO12_S)<<16, RISCV64 | uint32(elf.R_RISCV_RELAX)<<16: - return 4, nil + return 4, 4, nil case RISCV64 | uint32(elf.R_RISCV_64)<<16, RISCV64 | uint32(elf.R_RISCV_CALL)<<16, RISCV64 | uint32(elf.R_RISCV_CALL_PLT)<<16: - return 8, nil + return 8, 8, nil + + case PPC64 | uint32(elf.R_PPC64_TOC16_LO)<<16, + PPC64 | uint32(elf.R_PPC64_TOC16_HI)<<16, + PPC64 | uint32(elf.R_PPC64_TOC16_HA)<<16, + PPC64 | uint32(elf.R_PPC64_TOC16_DS)<<16, + PPC64 | uint32(elf.R_PPC64_TOC16_LO_DS)<<16, + PPC64 | uint32(elf.R_PPC64_REL16_LO)<<16, + PPC64 | uint32(elf.R_PPC64_REL16_HI)<<16, + PPC64 | uint32(elf.R_PPC64_REL16_HA)<<16: + return 2, 4, nil } } diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index 971cc432ff54b10921202e6a96df60494131a039..9d5319c31207887082b64f290b3e78de0e09044e 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -51,30 +51,14 @@ type Reloc struct { *goobj.Reloc r *oReader l *Loader - - // External reloc types may not fit into a uint8 which the Go object file uses. - // Store it here, instead of in the byte of goobj.Reloc. - // For Go symbols this will always be zero. - // goobj.Reloc.Type() + typ is always the right type, for both Go and external - // symbols. - typ objabi.RelocType } -func (rel Reloc) Type() objabi.RelocType { return objabi.RelocType(rel.Reloc.Type()) + rel.typ } -func (rel Reloc) Sym() Sym { return rel.l.resolve(rel.r, rel.Reloc.Sym()) } -func (rel Reloc) SetSym(s Sym) { rel.Reloc.SetSym(goobj.SymRef{PkgIdx: 0, SymIdx: uint32(s)}) } -func (rel Reloc) IsMarker() bool { return rel.Siz() == 0 } - -func (rel Reloc) SetType(t objabi.RelocType) { - if t != objabi.RelocType(uint8(t)) { - panic("SetType: type doesn't fit into Reloc") - } - rel.Reloc.SetType(uint8(t)) - if rel.typ != 0 { - // should use SymbolBuilder.SetRelocType - panic("wrong method to set reloc type") - } -} +func (rel Reloc) Type() objabi.RelocType { return objabi.RelocType(rel.Reloc.Type()) &^ objabi.R_WEAK } +func (rel Reloc) Weak() bool { return objabi.RelocType(rel.Reloc.Type())&objabi.R_WEAK != 0 } +func (rel Reloc) SetType(t objabi.RelocType) { rel.Reloc.SetType(uint16(t)) } +func (rel Reloc) Sym() Sym { return rel.l.resolve(rel.r, rel.Reloc.Sym()) } +func (rel Reloc) SetSym(s Sym) { rel.Reloc.SetSym(goobj.SymRef{PkgIdx: 0, SymIdx: uint32(s)}) } +func (rel Reloc) IsMarker() bool { return rel.Siz() == 0 } // Aux holds a "handle" to access an aux symbol record from an // object file. @@ -241,7 +225,6 @@ type Loader struct { attrExternal Bitmap // external symbols, indexed by ext sym index attrReadOnly map[Sym]bool // readonly data for this sym - attrTopFrame map[Sym]struct{} // top frame symbols attrSpecial map[Sym]struct{} // "special" frame symbols attrCgoExportDynamic map[Sym]struct{} // "cgo_export_dynamic" symbols attrCgoExportStatic map[Sym]struct{} // "cgo_export_static" symbols @@ -274,6 +257,9 @@ type Loader struct { // the symbol that triggered the marking of symbol K as live. Reachparent []Sym + // CgoExports records cgo-exported symbols by SymName. + CgoExports map[string]Sym + flags uint32 hasUnknownPkgPath bool // if any Go object has unknown package path @@ -308,20 +294,20 @@ type elfsetstringFunc func(str string, off int) // extSymPayload holds the payload (data + relocations) for linker-synthesized // external symbols (note that symbol value is stored in a separate slice). type extSymPayload struct { - name string // TODO: would this be better as offset into str table? - size int64 - ver int - kind sym.SymKind - objidx uint32 // index of original object if sym made by cloneToExternal - relocs []goobj.Reloc - reltypes []objabi.RelocType // relocation types - data []byte - auxs []goobj.Aux + name string // TODO: would this be better as offset into str table? + size int64 + ver int + kind sym.SymKind + objidx uint32 // index of original object if sym made by cloneToExternal + relocs []goobj.Reloc + data []byte + auxs []goobj.Aux } const ( // Loader.flags FlagStrictDups = 1 << iota + FlagUseABIAlias ) func NewLoader(flags uint32, elfsetstring elfsetstringFunc, reporter *ErrorReporter) *Loader { @@ -348,7 +334,6 @@ func NewLoader(flags uint32, elfsetstring elfsetstringFunc, reporter *ErrorRepor plt: make(map[Sym]int32), got: make(map[Sym]int32), dynid: make(map[Sym]int32), - attrTopFrame: make(map[Sym]struct{}), attrSpecial: make(map[Sym]struct{}), attrCgoExportDynamic: make(map[Sym]struct{}), attrCgoExportStatic: make(map[Sym]struct{}), @@ -474,6 +459,15 @@ func (st *loadState) addSym(name string, ver int, r *oReader, li uint32, kind in if l.flags&FlagStrictDups != 0 { l.checkdup(name, r, li, oldi) } + // Fix for issue #47185 -- given two dupok symbols with + // different sizes, favor symbol with larger size. See + // also issue #46653. + szdup := l.SymSize(oldi) + sz := int64(r.Sym(li).Siz()) + if szdup < sz { + // new symbol overwrites old symbol. + l.objSyms[oldi] = objSym{r.objidx, li} + } return oldi } oldr, oldli := l.toLocal(oldi) @@ -486,14 +480,14 @@ func (st *loadState) addSym(name string, ver int, r *oReader, li uint32, kind in // new symbol overwrites old symbol. oldtyp := sym.AbiSymKindToSymKind[objabi.SymKind(oldsym.Type())] if !(oldtyp.IsData() && oldr.DataSize(oldli) == 0) { - log.Fatalf("duplicated definition of symbol " + name) + log.Fatalf("duplicated definition of symbol %s, from %s and %s", name, r.unit.Lib.Pkg, oldr.unit.Lib.Pkg) } l.objSyms[oldi] = objSym{r.objidx, li} } else { // old symbol overwrites new symbol. typ := sym.AbiSymKindToSymKind[objabi.SymKind(oldsym.Type())] if !typ.IsData() { // only allow overwriting data symbol - log.Fatalf("duplicated definition of symbol " + name) + log.Fatalf("duplicated definition of symbol %s, from %s and %s", name, r.unit.Lib.Pkg, oldr.unit.Lib.Pkg) } } return oldi @@ -532,6 +526,36 @@ func (l *Loader) LookupOrCreateSym(name string, ver int) Sym { return i } +// AddCgoExport records a cgo-exported symbol in l.CgoExports. +// This table is used to identify the correct Go symbol ABI to use +// to resolve references from host objects (which don't have ABIs). +func (l *Loader) AddCgoExport(s Sym) { + if l.CgoExports == nil { + l.CgoExports = make(map[string]Sym) + } + l.CgoExports[l.SymName(s)] = s +} + +// LookupOrCreateCgoExport is like LookupOrCreateSym, but if ver +// indicates a global symbol, it uses the CgoExport table to determine +// the appropriate symbol version (ABI) to use. ver must be either 0 +// or a static symbol version. +func (l *Loader) LookupOrCreateCgoExport(name string, ver int) Sym { + if ver >= sym.SymVerStatic { + return l.LookupOrCreateSym(name, ver) + } + if ver != 0 { + panic("ver must be 0 or a static version") + } + // Look for a cgo-exported symbol from Go. + if s, ok := l.CgoExports[name]; ok { + return s + } + // Otherwise, this must just be a symbol in the host object. + // Create a version 0 symbol for it. + return l.LookupOrCreateSym(name, 0) +} + func (l *Loader) IsExternal(i Sym) bool { r, _ := l.toLocal(i) return l.isExtReader(r) @@ -684,12 +708,18 @@ func (l *Loader) checkdup(name string, r *oReader, li uint32, dup Sym) { p := r.Data(li) rdup, ldup := l.toLocal(dup) pdup := rdup.Data(ldup) - if bytes.Equal(p, pdup) { - return - } reason := "same length but different contents" if len(p) != len(pdup) { reason = fmt.Sprintf("new length %d != old length %d", len(p), len(pdup)) + } else if bytes.Equal(p, pdup) { + // For BSS symbols, we need to check size as well, see issue 46653. + szdup := l.SymSize(dup) + sz := int64(r.Sym(li).Siz()) + if szdup == sz { + return + } + reason = fmt.Sprintf("different sizes: new size %d != old size %d", + sz, szdup) } fmt.Fprintf(os.Stderr, "cmd/link: while reading object for '%v': duplicate symbol '%s', previous def at '%v', with mismatched payload: %s\n", r.unit.Lib, name, rdup.unit.Lib, reason) @@ -756,6 +786,9 @@ func (l *Loader) SymName(i Sym) string { return pp.name } r, li := l.toLocal(i) + if r == nil { + return "?" + } name := r.Sym(li).Name(r.Reader) if !r.NeedNameExpansion() { return name @@ -1008,24 +1041,6 @@ func (l *Loader) SetAttrExternal(i Sym, v bool) { } } -// AttrTopFrame returns true for a function symbol that is an entry -// point, meaning that unwinders should stop when they hit this -// function. -func (l *Loader) AttrTopFrame(i Sym) bool { - _, ok := l.attrTopFrame[i] - return ok -} - -// SetAttrTopFrame sets the "top frame" property for a symbol (see -// AttrTopFrame). -func (l *Loader) SetAttrTopFrame(i Sym, v bool) { - if v { - l.attrTopFrame[i] = struct{}{} - } else { - delete(l.attrTopFrame, i) - } -} - // AttrSpecial returns true for a symbols that do not have their // address (i.e. Value) computed by the usual mechanism of // data.go:dodata() & data.go:address(). @@ -1446,7 +1461,7 @@ func (l *Loader) SetSymLocalElfSym(i Sym, es int32) { } } -// SymPlt returns the plt value for pe symbols. +// SymPlt returns the PLT offset of symbol s. func (l *Loader) SymPlt(s Sym) int32 { if v, ok := l.plt[s]; ok { return v @@ -1454,7 +1469,7 @@ func (l *Loader) SymPlt(s Sym) int32 { return -1 } -// SetPlt sets the plt value for pe symbols. +// SetPlt sets the PLT offset of symbol i. func (l *Loader) SetPlt(i Sym, v int32) { if i >= Sym(len(l.objSyms)) || i == 0 { panic("bad symbol for SetPlt") @@ -1466,7 +1481,7 @@ func (l *Loader) SetPlt(i Sym, v int32) { } } -// SymGot returns the got value for pe symbols. +// SymGot returns the GOT offset of symbol s. func (l *Loader) SymGot(s Sym) int32 { if v, ok := l.got[s]; ok { return v @@ -1474,7 +1489,7 @@ func (l *Loader) SymGot(s Sym) int32 { return -1 } -// SetGot sets the got value for pe symbols. +// SetGot sets the GOT offset of symbol i. func (l *Loader) SetGot(i Sym, v int32) { if i >= Sym(len(l.objSyms)) || i == 0 { panic("bad symbol for SetGot") @@ -1566,7 +1581,7 @@ func (l *Loader) SymUnit(i Sym) *sym.CompilationUnit { // regular compiler-generated Go symbols), but in the case of // building with "-linkshared" (when a symbol is read from a // shared library), will hold the library name. -// NOTE: this correspondes to sym.Symbol.File field. +// NOTE: this corresponds to sym.Symbol.File field. func (l *Loader) SymPkg(i Sym) string { if f, ok := l.symPkg[i]; ok { return f @@ -1852,10 +1867,9 @@ func (relocs *Relocs) Count() int { return len(relocs.rs) } // At returns the j-th reloc for a global symbol. func (relocs *Relocs) At(j int) Reloc { if relocs.l.isExtReader(relocs.r) { - pp := relocs.l.payloads[relocs.li] - return Reloc{&relocs.rs[j], relocs.r, relocs.l, pp.reltypes[j]} + return Reloc{&relocs.rs[j], relocs.r, relocs.l} } - return Reloc{&relocs.rs[j], relocs.r, relocs.l, 0} + return Reloc{&relocs.rs[j], relocs.r, relocs.l} } // Relocs returns a Relocs object for the given global sym. @@ -1904,7 +1918,11 @@ func (fi *FuncInfo) Locals() int { } func (fi *FuncInfo) FuncID() objabi.FuncID { - return objabi.FuncID((*goobj.FuncInfo)(nil).ReadFuncID(fi.data)) + return (*goobj.FuncInfo)(nil).ReadFuncID(fi.data) +} + +func (fi *FuncInfo) FuncFlag() objabi.FuncFlag { + return (*goobj.FuncInfo)(nil).ReadFuncFlag(fi.data) } func (fi *FuncInfo) Pcsp() Sym { @@ -1991,6 +2009,13 @@ func (fi *FuncInfo) File(k int) goobj.CUFileIndex { return (*goobj.FuncInfo)(nil).ReadFile(fi.data, fi.lengths.FileOff, uint32(k)) } +// TopFrame returns true if the function associated with this FuncInfo +// is an entry point, meaning that unwinders should stop when they hit +// this function. +func (fi *FuncInfo) TopFrame() bool { + return (fi.FuncFlag() & objabi.FuncFlag_TOPFRAME) != 0 +} + type InlTreeNode struct { Parent int32 File goobj.CUFileIndex @@ -2150,9 +2175,6 @@ func (st *loadState) preloadSyms(r *oReader, kind int) { } gi := st.addSym(name, v, r, i, kind, osym) r.syms[i] = gi - if osym.TopFrame() { - l.SetAttrTopFrame(gi, true) - } if osym.Local() { l.SetAttrLocal(gi, true) } @@ -2237,7 +2259,7 @@ func loadObjRefs(l *Loader, r *oReader, arch *sys.Arch) { pkg := r.Pkg(i) objidx, ok := l.objByPkg[pkg] if !ok { - log.Fatalf("reference of nonexisted package %s, from %v", pkg, r.unit.Lib) + log.Fatalf("%v: reference to nonexistent package %s", r.unit.Lib, pkg) } r.pkg[i] = objidx } @@ -2270,6 +2292,9 @@ func abiToVer(abi uint16, localSymVersion int) int { // symbol. If the sym in question is not an alias, the sym itself is // returned. func (l *Loader) ResolveABIAlias(s Sym) Sym { + if l.flags&FlagUseABIAlias == 0 { + return s + } if s == 0 { return 0 } @@ -2345,13 +2370,11 @@ func (l *Loader) cloneToExternal(symIdx Sym) { // Copy relocations relocs := l.Relocs(symIdx) pp.relocs = make([]goobj.Reloc, relocs.Count()) - pp.reltypes = make([]objabi.RelocType, relocs.Count()) for i := range pp.relocs { // Copy the relocs slice. // Convert local reference to global reference. rel := relocs.At(i) - pp.relocs[i].Set(rel.Off(), rel.Siz(), 0, rel.Add(), goobj.SymRef{PkgIdx: 0, SymIdx: uint32(rel.Sym())}) - pp.reltypes[i] = rel.Type() + pp.relocs[i].Set(rel.Off(), rel.Siz(), uint16(rel.Type()), rel.Add(), goobj.SymRef{PkgIdx: 0, SymIdx: uint32(rel.Sym())}) } // Copy data @@ -2407,7 +2430,6 @@ func (l *Loader) CopyAttributes(src Sym, dst Sym) { // when copying attributes from a dupOK ABI wrapper symbol to // the real target symbol (which may not be marked dupOK). } - l.SetAttrTopFrame(dst, l.AttrTopFrame(src)) l.SetAttrSpecial(dst, l.AttrSpecial(src)) l.SetAttrCgoExportDynamic(dst, l.AttrCgoExportDynamic(src)) l.SetAttrCgoExportStatic(dst, l.AttrCgoExportStatic(src)) @@ -2567,7 +2589,7 @@ func (l *Loader) AssignTextSymbolOrder(libs []*sym.Library, intlibs []bool, exts for i, list := range lists { for _, s := range list { sym := Sym(s) - if l.attrReachable.Has(sym) && !assignedToUnit.Has(sym) { + if !assignedToUnit.Has(sym) { textp = append(textp, sym) unit := l.SymUnit(sym) if unit != nil { diff --git a/src/cmd/link/internal/loader/loader_test.go b/src/cmd/link/internal/loader/loader_test.go index 1371c2a5410b798e8be959a0ee665f714a64406e..15ae830dc948af487c7a3313bed61367b52181df 100644 --- a/src/cmd/link/internal/loader/loader_test.go +++ b/src/cmd/link/internal/loader/loader_test.go @@ -237,7 +237,8 @@ func sameRelocSlice(s1 *Relocs, s2 []Reloc) bool { type addFunc func(l *Loader, s Sym, s2 Sym) Sym func mkReloc(l *Loader, typ objabi.RelocType, off int32, siz uint8, add int64, sym Sym) Reloc { - r := Reloc{&goobj.Reloc{}, l.extReader, l, typ} + r := Reloc{&goobj.Reloc{}, l.extReader, l} + r.SetType(typ) r.SetOff(off) r.SetSiz(siz) r.SetAdd(add) diff --git a/src/cmd/link/internal/loader/symbolbuilder.go b/src/cmd/link/internal/loader/symbolbuilder.go index 5d37da8ac6fac22d80a2316411766f1c16b6aee2..204d04412dc24985fa3cb7a560989a840026170e 100644 --- a/src/cmd/link/internal/loader/symbolbuilder.go +++ b/src/cmd/link/internal/loader/symbolbuilder.go @@ -121,13 +121,11 @@ func (sb *SymbolBuilder) Relocs() Relocs { // ResetRelocs removes all relocations on this symbol. func (sb *SymbolBuilder) ResetRelocs() { sb.relocs = sb.relocs[:0] - sb.reltypes = sb.reltypes[:0] } // SetRelocType sets the type of the 'i'-th relocation on this sym to 't' func (sb *SymbolBuilder) SetRelocType(i int, t objabi.RelocType) { - sb.relocs[i].SetType(0) - sb.reltypes[i] = t + sb.relocs[i].SetType(uint16(t)) } // SetRelocSym sets the target sym of the 'i'-th relocation on this sym to 's' @@ -143,7 +141,6 @@ func (sb *SymbolBuilder) SetRelocAdd(i int, a int64) { // Add n relocations, return a handle to the relocations. func (sb *SymbolBuilder) AddRelocs(n int) Relocs { sb.relocs = append(sb.relocs, make([]goobj.Reloc, n)...) - sb.reltypes = append(sb.reltypes, make([]objabi.RelocType, n)...) return sb.l.Relocs(sb.symIdx) } @@ -152,7 +149,7 @@ func (sb *SymbolBuilder) AddRelocs(n int) Relocs { func (sb *SymbolBuilder) AddRel(typ objabi.RelocType) (Reloc, int) { j := len(sb.relocs) sb.relocs = append(sb.relocs, goobj.Reloc{}) - sb.reltypes = append(sb.reltypes, typ) + sb.relocs[j].SetType(uint16(typ)) relocs := sb.Relocs() return relocs.At(j), j } @@ -169,7 +166,6 @@ func (p *relocsByOff) Len() int { return len(p.relocs) } func (p *relocsByOff) Less(i, j int) bool { return p.relocs[i].Off() < p.relocs[j].Off() } func (p *relocsByOff) Swap(i, j int) { p.relocs[i], p.relocs[j] = p.relocs[j], p.relocs[i] - p.reltypes[i], p.reltypes[j] = p.reltypes[j], p.reltypes[i] } func (sb *SymbolBuilder) Reachable() bool { diff --git a/src/cmd/link/internal/loadmacho/ldmacho.go b/src/cmd/link/internal/loadmacho/ldmacho.go index 6d1d9bb29edd5646994e454a2b9a5da1dcacc338..e7d9eebc33f792236e84bc0a9ae725756963f098 100644 --- a/src/cmd/link/internal/loadmacho/ldmacho.go +++ b/src/cmd/link/internal/loadmacho/ldmacho.go @@ -607,7 +607,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader, if machsym.type_&N_EXT == 0 { v = localSymVersion } - s := l.LookupOrCreateSym(name, v) + s := l.LookupOrCreateCgoExport(name, v) if machsym.type_&N_EXT == 0 { l.SetAttrDuplicateOK(s, true) } diff --git a/src/cmd/link/internal/loadpe/ldpe.go b/src/cmd/link/internal/loadpe/ldpe.go index a5c025de8fff54d5884fc3066f79744b8526ad1b..9cc7effe1f4b858385b09dfc56565bf4c516f9b0 100644 --- a/src/cmd/link/internal/loadpe/ldpe.go +++ b/src/cmd/link/internal/loadpe/ldpe.go @@ -115,6 +115,24 @@ const ( IMAGE_REL_THUMB_BRANCH24 = 0x0014 IMAGE_REL_THUMB_BLX23 = 0x0015 IMAGE_REL_ARM_PAIR = 0x0016 + IMAGE_REL_ARM64_ABSOLUTE = 0x0000 + IMAGE_REL_ARM64_ADDR32 = 0x0001 + IMAGE_REL_ARM64_ADDR32NB = 0x0002 + IMAGE_REL_ARM64_BRANCH26 = 0x0003 + IMAGE_REL_ARM64_PAGEBASE_REL21 = 0x0004 + IMAGE_REL_ARM64_REL21 = 0x0005 + IMAGE_REL_ARM64_PAGEOFFSET_12A = 0x0006 + IMAGE_REL_ARM64_PAGEOFFSET_12L = 0x0007 + IMAGE_REL_ARM64_SECREL = 0x0008 + IMAGE_REL_ARM64_SECREL_LOW12A = 0x0009 + IMAGE_REL_ARM64_SECREL_HIGH12A = 0x000A + IMAGE_REL_ARM64_SECREL_LOW12L = 0x000B + IMAGE_REL_ARM64_TOKEN = 0x000C + IMAGE_REL_ARM64_SECTION = 0x000D + IMAGE_REL_ARM64_ADDR64 = 0x000E + IMAGE_REL_ARM64_BRANCH19 = 0x000F + IMAGE_REL_ARM64_BRANCH14 = 0x0010 + IMAGE_REL_ARM64_REL32 = 0x0011 ) // TODO(crawshaw): de-duplicate these symbols with cmd/internal/ld, ideally in debug/pe. @@ -160,11 +178,7 @@ func makeUpdater(l *loader.Loader, bld *loader.SymbolBuilder, s loader.Sym) *loa // If an .rsrc section or set of .rsrc$xx sections is found, its symbols are // returned as rsrc. func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Reader, pkg string, length int64, pn string) (textp []loader.Sym, rsrc []loader.Sym, err error) { - lookup := func(name string, version int) (*loader.SymbolBuilder, loader.Sym) { - s := l.LookupOrCreateSym(name, version) - sb := l.MakeSymbolUpdater(s) - return sb, s - } + lookup := l.LookupOrCreateCgoExport sectsyms := make(map[*pe.Section]loader.Sym) sectdata := make(map[*pe.Section][]byte) @@ -196,7 +210,8 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read } name := fmt.Sprintf("%s(%s)", pkg, sect.Name) - bld, s := lookup(name, localSymVersion) + s := lookup(name, localSymVersion) + bld := l.MakeSymbolUpdater(s) switch sect.Characteristics & (IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE) { case IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ: //.rdata @@ -254,7 +269,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read return nil, nil, fmt.Errorf("relocation number %d symbol index idx=%d cannot be large then number of symbols %d", j, r.SymbolTableIndex, len(f.COFFSymbols)) } pesym := &f.COFFSymbols[r.SymbolTableIndex] - _, gosym, err := readpesym(l, arch, l.LookupOrCreateSym, f, pesym, sectsyms, localSymVersion) + _, gosym, err := readpesym(l, arch, lookup, f, pesym, sectsyms, localSymVersion) if err != nil { return nil, nil, err } @@ -319,6 +334,17 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read case IMAGE_REL_ARM_BRANCH24: rType = objabi.R_CALLARM + rAdd = int64(int32(binary.LittleEndian.Uint32(sectdata[rsect][rOff:]))) + } + + case sys.ARM64: + switch r.Type { + default: + return nil, nil, fmt.Errorf("%s: %v: unknown ARM64 relocation type %v", pn, sectsyms[rsect], r.Type) + + case IMAGE_REL_ARM64_ADDR32, IMAGE_REL_ARM64_ADDR32NB: + rType = objabi.R_ADDR + rAdd = int64(int32(binary.LittleEndian.Uint32(sectdata[rsect][rOff:]))) } } @@ -385,7 +411,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read } } - bld, s, err := readpesym(l, arch, l.LookupOrCreateSym, f, pesym, sectsyms, localSymVersion) + bld, s, err := readpesym(l, arch, lookup, f, pesym, sectsyms, localSymVersion) if err != nil { return nil, nil, err } diff --git a/src/cmd/link/internal/loadxcoff/ldxcoff.go b/src/cmd/link/internal/loadxcoff/ldxcoff.go index a5744216d66ae108d6ec1f8a652fb9f2b6a1ce4b..920e1c85fd4d7c6ba366d4aa2009ddd6ff8247b8 100644 --- a/src/cmd/link/internal/loadxcoff/ldxcoff.go +++ b/src/cmd/link/internal/loadxcoff/ldxcoff.go @@ -121,7 +121,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read } sb := l.MakeSymbolUpdater(sect.sym) for _, rx := range sect.Relocs { - rSym := l.LookupOrCreateSym(rx.Symbol.Name, 0) + rSym := l.LookupOrCreateCgoExport(rx.Symbol.Name, 0) if uint64(int32(rx.VirtualAddress)) != rx.VirtualAddress { return errorf("virtual address of a relocation is too big: 0x%x", rx.VirtualAddress) } diff --git a/src/cmd/link/internal/mips/asm.go b/src/cmd/link/internal/mips/asm.go index 17b1b20aff322b5e97d03fc992ea0fc3c3ef285a..8505dc61093a6de0726f3f61b9947c8815e5b64a 100644 --- a/src/cmd/link/internal/mips/asm.go +++ b/src/cmd/link/internal/mips/asm.go @@ -140,7 +140,7 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade return val, 0, false } -func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64) int64 { +func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64, []byte) int64 { return -1 } diff --git a/src/cmd/link/internal/mips/obj.go b/src/cmd/link/internal/mips/obj.go index f20597c0f51e6dc97b6b21359419e3331c32794c..5ca75825293fb93459d871672bbb3d94df740da1 100644 --- a/src/cmd/link/internal/mips/obj.go +++ b/src/cmd/link/internal/mips/obj.go @@ -34,11 +34,12 @@ import ( "cmd/internal/objabi" "cmd/internal/sys" "cmd/link/internal/ld" + "internal/buildcfg" ) func Init() (*sys.Arch, ld.Arch) { arch := sys.ArchMIPS - if objabi.GOARCH == "mipsle" { + if buildcfg.GOARCH == "mipsle" { arch = sys.ArchMIPSLE } diff --git a/src/cmd/link/internal/mips64/asm.go b/src/cmd/link/internal/mips64/asm.go index 4789b411eb5c94f070d6a2fdf98f55de88dfb088..f7f91d1e8b2c8b98ed6eaf14f33c43eee4c1ec64 100644 --- a/src/cmd/link/internal/mips64/asm.go +++ b/src/cmd/link/internal/mips64/asm.go @@ -52,6 +52,8 @@ func elfreloc1(ctxt *ld.Link, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, // type uint8 // addend int64 + addend := r.Xadd + out.Write64(uint64(sectoff)) elfsym := ld.ElfSymForReloc(ctxt, r.Xsym) @@ -77,11 +79,17 @@ func elfreloc1(ctxt *ld.Link, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, out.Write8(uint8(elf.R_MIPS_HI16)) case objabi.R_ADDRMIPSTLS: out.Write8(uint8(elf.R_MIPS_TLS_TPREL_LO16)) + if ctxt.Target.IsOpenbsd() { + // OpenBSD mips64 does not currently offset TLS by 0x7000, + // as such we need to add this back to get the correct offset + // via the external linker. + addend += 0x7000 + } case objabi.R_CALLMIPS, objabi.R_JMPMIPS: out.Write8(uint8(elf.R_MIPS_26)) } - out.Write64(uint64(r.Xadd)) + out.Write64(uint64(addend)) return true } @@ -124,6 +132,11 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade case objabi.R_ADDRMIPSTLS: // thread pointer is at 0x7000 offset from the start of TLS data area t := ldr.SymValue(rs) + r.Add() - 0x7000 + if target.IsOpenbsd() { + // OpenBSD mips64 does not currently offset TLS by 0x7000, + // as such we need to add this back to get the correct offset. + t += 0x7000 + } if t < -32768 || t >= 32678 { ldr.Errorf(s, "TLS offset out of range %d", t) } @@ -138,7 +151,7 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade return val, 0, false } -func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64) int64 { +func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64, []byte) int64 { return -1 } diff --git a/src/cmd/link/internal/mips64/obj.go b/src/cmd/link/internal/mips64/obj.go index 01d89a209cabab6b45d44110c2f14149368dd70b..544e1ef7bedc448bee626b2ff0d01fff953223c3 100644 --- a/src/cmd/link/internal/mips64/obj.go +++ b/src/cmd/link/internal/mips64/obj.go @@ -34,11 +34,12 @@ import ( "cmd/internal/objabi" "cmd/internal/sys" "cmd/link/internal/ld" + "internal/buildcfg" ) func Init() (*sys.Arch, ld.Arch) { arch := sys.ArchMIPS64 - if objabi.GOARCH == "mips64le" { + if buildcfg.GOARCH == "mips64le" { arch = sys.ArchMIPS64LE } diff --git a/src/cmd/link/internal/ppc64/asm.go b/src/cmd/link/internal/ppc64/asm.go index 602f0b52999a377af6fb7f823560d8cdfeb44c94..b877864b759beca3a1fe7eab405ed5459c1f7da8 100644 --- a/src/cmd/link/internal/ppc64/asm.go +++ b/src/cmd/link/internal/ppc64/asm.go @@ -121,16 +121,21 @@ func genplt(ctxt *ld.Link, ldr *loader.Loader) { // Update the relocation to use the call stub r.SetSym(stub.Sym()) - // make sure the data is writeable - if ldr.AttrReadOnly(s) { - panic("can't write to read-only sym data") + // Make the symbol writeable so we can fixup toc. + su := ldr.MakeSymbolUpdater(s) + su.MakeWritable() + p := su.Data() + + // Check for toc restore slot (a nop), and replace with toc restore. + var nop uint32 + if len(p) >= int(r.Off()+8) { + nop = ctxt.Arch.ByteOrder.Uint32(p[r.Off()+4:]) + } + if nop != 0x60000000 { + ldr.Errorf(s, "Symbol %s is missing toc restoration slot at offset %d", ldr.SymName(s), r.Off()+4) } - - // Restore TOC after bl. The compiler put a - // nop here for us to overwrite. - sp := ldr.Data(s) const o1 = 0xe8410018 // ld r2,24(r1) - ctxt.Arch.ByteOrder.PutUint32(sp[r.Off()+4:], o1) + ctxt.Arch.ByteOrder.PutUint32(p[r.Off()+4:], o1) } } // Put call stubs at the beginning (instead of the end). @@ -413,6 +418,7 @@ func xcoffreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sy emitReloc(ld.XCOFF_R_TOCU|(0x0F<<8), 2) emitReloc(ld.XCOFF_R_TOCL|(0x0F<<8), 6) case objabi.R_POWER_TLS_LE: + // This only supports 16b relocations. It is fixed up in archreloc. emitReloc(ld.XCOFF_R_TLS_LE|0x0F<<8, 2) case objabi.R_CALLPOWER: if r.Size != 4 { @@ -453,7 +459,10 @@ func elfreloc1(ctxt *ld.Link, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, case objabi.R_POWER_TLS: out.Write64(uint64(elf.R_PPC64_TLS) | uint64(elfsym)<<32) case objabi.R_POWER_TLS_LE: - out.Write64(uint64(elf.R_PPC64_TPREL16) | uint64(elfsym)<<32) + out.Write64(uint64(elf.R_PPC64_TPREL16_HA) | uint64(elfsym)<<32) + out.Write64(uint64(r.Xadd)) + out.Write64(uint64(sectoff + 4)) + out.Write64(uint64(elf.R_PPC64_TPREL16_LO) | uint64(elfsym)<<32) case objabi.R_POWER_TLS_IE: out.Write64(uint64(elf.R_PPC64_GOT_TPREL16_HA) | uint64(elfsym)<<32) out.Write64(uint64(r.Xadd)) @@ -642,6 +651,16 @@ func archrelocaddr(ldr *loader.Loader, target *ld.Target, syms *ld.ArchSyms, r l return int64(o2)<<32 | int64(o1) } +// Determine if the code was compiled so that the TOC register R2 is initialized and maintained +func r2Valid(ctxt *ld.Link) bool { + switch ctxt.BuildMode { + case ld.BuildModeCArchive, ld.BuildModeCShared, ld.BuildModePIE, ld.BuildModeShared, ld.BuildModePlugin: + return true + } + // -linkshared option + return ctxt.IsSharedGoLink() +} + // resolve direct jump relocation r in s, and add trampoline if necessary func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { @@ -649,7 +668,7 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { // For internal linking, trampolines are always created for long calls. // For external linking, the linker can insert a call stub to handle a long call, but depends on having the TOC address in // r2. For those build modes with external linking where the TOC address is not maintained in r2, trampolines must be created. - if ctxt.IsExternal() && (ctxt.DynlinkingGo() || ctxt.BuildMode == ld.BuildModeCArchive || ctxt.BuildMode == ld.BuildModeCShared || ctxt.BuildMode == ld.BuildModePIE) { + if ctxt.IsExternal() && r2Valid(ctxt) { // No trampolines needed since r2 contains the TOC return } @@ -703,7 +722,7 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { } } if ldr.SymType(tramp) == 0 { - if ctxt.DynlinkingGo() || ctxt.BuildMode == ld.BuildModeCArchive || ctxt.BuildMode == ld.BuildModeCShared || ctxt.BuildMode == ld.BuildModePIE { + if r2Valid(ctxt) { // Should have returned for above cases ctxt.Errorf(s, "unexpected trampoline for shared or dynamic linking") } else { @@ -792,11 +811,25 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade if !target.IsAIX() { return val, nExtReloc, false } - case objabi.R_POWER_TLS, objabi.R_POWER_TLS_LE, objabi.R_POWER_TLS_IE: - // check Outer is nil, Type is TLSBSS? + case objabi.R_POWER_TLS: nExtReloc = 1 - if rt == objabi.R_POWER_TLS_IE { - nExtReloc = 2 // need two ELF relocations, see elfreloc1 + return val, nExtReloc, true + case objabi.R_POWER_TLS_LE, objabi.R_POWER_TLS_IE: + if target.IsAIX() && rt == objabi.R_POWER_TLS_LE { + // Fixup val, an addis/addi pair of instructions, which generate a 32b displacement + // from the threadpointer (R13), into a 16b relocation. XCOFF only supports 16b + // TLS LE relocations. Likewise, verify this is an addis/addi sequence. + const expectedOpcodes = 0x3C00000038000000 + const expectedOpmasks = 0xFC000000FC000000 + if uint64(val)&expectedOpmasks != expectedOpcodes { + ldr.Errorf(s, "relocation for %s+%d is not an addis/addi pair: %16x", ldr.SymName(rs), r.Off(), uint64(val)) + } + nval := (int64(uint32(0x380d0000)) | val&0x03e00000) << 32 // addi rX, r13, $0 + nval |= int64(0x60000000) // nop + val = nval + nExtReloc = 1 + } else { + nExtReloc = 2 } return val, nExtReloc, true case objabi.R_ADDRPOWER, @@ -850,16 +883,32 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade // the TLS. v -= 0x800 } - if int64(int16(v)) != v { + + var o1, o2 uint32 + if int64(int32(v)) != v { ldr.Errorf(s, "TLS offset out of range %d", v) } - return (val &^ 0xffff) | (v & 0xffff), nExtReloc, true + if target.IsBigEndian() { + o1 = uint32(val >> 32) + o2 = uint32(val) + } else { + o1 = uint32(val) + o2 = uint32(val >> 32) + } + + o1 |= uint32(((v + 0x8000) >> 16) & 0xFFFF) + o2 |= uint32(v & 0xFFFF) + + if target.IsBigEndian() { + return int64(o1)<<32 | int64(o2), nExtReloc, true + } + return int64(o2)<<32 | int64(o1), nExtReloc, true } return val, nExtReloc, false } -func archrelocvariant(target *ld.Target, ldr *loader.Loader, r loader.Reloc, rv sym.RelocVariant, s loader.Sym, t int64) (relocatedOffset int64) { +func archrelocvariant(target *ld.Target, ldr *loader.Loader, r loader.Reloc, rv sym.RelocVariant, s loader.Sym, t int64, p []byte) (relocatedOffset int64) { rs := ldr.ResolveABIAlias(r.Sym()) switch rv & sym.RV_TYPE_MASK { default: @@ -875,9 +924,10 @@ func archrelocvariant(target *ld.Target, ldr *loader.Loader, r loader.Reloc, rv // overflow depends on the instruction var o1 uint32 if target.IsBigEndian() { - o1 = binary.BigEndian.Uint32(ldr.Data(s)[r.Off()-2:]) + o1 = binary.BigEndian.Uint32(p[r.Off()-2:]) + } else { - o1 = binary.LittleEndian.Uint32(ldr.Data(s)[r.Off():]) + o1 = binary.LittleEndian.Uint32(p[r.Off():]) } switch o1 >> 26 { case 24, // ori @@ -909,9 +959,9 @@ func archrelocvariant(target *ld.Target, ldr *loader.Loader, r loader.Reloc, rv // overflow depends on the instruction var o1 uint32 if target.IsBigEndian() { - o1 = binary.BigEndian.Uint32(ldr.Data(s)[r.Off()-2:]) + o1 = binary.BigEndian.Uint32(p[r.Off()-2:]) } else { - o1 = binary.LittleEndian.Uint32(ldr.Data(s)[r.Off():]) + o1 = binary.LittleEndian.Uint32(p[r.Off():]) } switch o1 >> 26 { case 25, // oris @@ -933,9 +983,9 @@ func archrelocvariant(target *ld.Target, ldr *loader.Loader, r loader.Reloc, rv case sym.RV_POWER_DS: var o1 uint32 if target.IsBigEndian() { - o1 = uint32(binary.BigEndian.Uint16(ldr.Data(s)[r.Off():])) + o1 = uint32(binary.BigEndian.Uint16(p[r.Off():])) } else { - o1 = uint32(binary.LittleEndian.Uint16(ldr.Data(s)[r.Off():])) + o1 = uint32(binary.LittleEndian.Uint16(p[r.Off():])) } if t&3 != 0 { ldr.Errorf(s, "relocation for %s+%d is not aligned: %d", ldr.SymName(rs), r.Off(), t) @@ -1039,8 +1089,11 @@ func ensureglinkresolver(ctxt *ld.Link, ldr *loader.Loader) *loader.SymbolBuilde glink.AddUint32(ctxt.Arch, 0x7800f082) // srdi r0,r0,2 // r11 = address of the first byte of the PLT - glink.AddSymRef(ctxt.Arch, ctxt.PLT, 0, objabi.R_ADDRPOWER, 8) - + r, _ := glink.AddRel(objabi.R_ADDRPOWER) + r.SetSym(ctxt.PLT) + r.SetSiz(8) + r.SetOff(int32(glink.Size())) + r.SetAdd(0) glink.AddUint32(ctxt.Arch, 0x3d600000) // addis r11,0,.plt@ha glink.AddUint32(ctxt.Arch, 0x396b0000) // addi r11,r11,.plt@l diff --git a/src/cmd/link/internal/ppc64/obj.go b/src/cmd/link/internal/ppc64/obj.go index ef4393f48935b6505d2c42b5f463392d2e183421..b6d5ad92afa11d00923464d55522ea5a7e65184e 100644 --- a/src/cmd/link/internal/ppc64/obj.go +++ b/src/cmd/link/internal/ppc64/obj.go @@ -34,21 +34,22 @@ import ( "cmd/internal/objabi" "cmd/internal/sys" "cmd/link/internal/ld" + "internal/buildcfg" ) func Init() (*sys.Arch, ld.Arch) { arch := sys.ArchPPC64 - if objabi.GOARCH == "ppc64le" { + if buildcfg.GOARCH == "ppc64le" { arch = sys.ArchPPC64LE } theArch := ld.Arch{ - Funcalign: funcAlign, - Maxalign: maxAlign, - Minalign: minAlign, - Dwarfregsp: dwarfRegSP, - Dwarfreglr: dwarfRegLR, - WriteTextBlocks: true, + Funcalign: funcAlign, + Maxalign: maxAlign, + Minalign: minAlign, + Dwarfregsp: dwarfRegSP, + Dwarfreglr: dwarfRegLR, + TrampLimit: 0x1c00000, Adddynrel: adddynrel, Archinit: archinit, diff --git a/src/cmd/link/internal/riscv64/asm.go b/src/cmd/link/internal/riscv64/asm.go index c18e0540d8c313d66cff7a17f8b726628c06c0d5..6eace617dc0a94fea3b1157b308a72c59a1bad8d 100644 --- a/src/cmd/link/internal/riscv64/asm.go +++ b/src/cmd/link/internal/riscv64/asm.go @@ -230,7 +230,7 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade return val, 0, false } -func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64) int64 { +func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64, []byte) int64 { log.Fatalf("archrelocvariant") return -1 } diff --git a/src/cmd/link/internal/s390x/asm.go b/src/cmd/link/internal/s390x/asm.go index 78d2cc81e441cedb62f38b19da01aa47bb775082..1952971dcb9156fa07d341650de448daa129e209 100644 --- a/src/cmd/link/internal/s390x/asm.go +++ b/src/cmd/link/internal/s390x/asm.go @@ -371,7 +371,7 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade return val, 0, false } -func archrelocvariant(target *ld.Target, ldr *loader.Loader, r loader.Reloc, rv sym.RelocVariant, s loader.Sym, t int64) int64 { +func archrelocvariant(target *ld.Target, ldr *loader.Loader, r loader.Reloc, rv sym.RelocVariant, s loader.Sym, t int64, p []byte) int64 { switch rv & sym.RV_TYPE_MASK { default: ldr.Errorf(s, "unexpected relocation variant %d", rv) diff --git a/src/cmd/link/internal/sym/compilation_unit.go b/src/cmd/link/internal/sym/compilation_unit.go index 5d7206db66e27b80ffe1c5a9944a9b954aaeeca4..3bad5bf3f4fb1b0ed648cfb42638d71ae701c6e0 100644 --- a/src/cmd/link/internal/sym/compilation_unit.go +++ b/src/cmd/link/internal/sym/compilation_unit.go @@ -20,7 +20,6 @@ type LoaderSym int // // These are used for both DWARF and pclntab generation. type CompilationUnit struct { - Pkg string // The package name, eg ("fmt", or "runtime") Lib *Library // Our library PclnIndex int // Index of this CU in pclntab PCs []dwarf.Range // PC ranges, relative to Textp[0] @@ -29,6 +28,7 @@ type CompilationUnit struct { Consts LoaderSym // Package constants DIEs FuncDIEs []LoaderSym // Function DIE subtrees + VarDIEs []LoaderSym // Global variable DIEs AbsFnDIEs []LoaderSym // Abstract function DIE subtrees RangeSyms []LoaderSym // Symbols for debug_range Textp []LoaderSym // Text symbols in this CU diff --git a/src/cmd/link/internal/wasm/asm.go b/src/cmd/link/internal/wasm/asm.go index 31851fbb56775259c5a882911621f806c0070e3e..5bdfdbaee63e9bc42626d7057124dd18d108dd1c 100644 --- a/src/cmd/link/internal/wasm/asm.go +++ b/src/cmd/link/internal/wasm/asm.go @@ -10,6 +10,7 @@ import ( "cmd/link/internal/ld" "cmd/link/internal/loader" "cmd/link/internal/sym" + "internal/buildcfg" "io" "regexp" ) @@ -506,15 +507,15 @@ func writeProducerSec(ctxt *ld.Link) { writeUleb128(ctxt.Out, 2) // number of fields - writeName(ctxt.Out, "language") // field name - writeUleb128(ctxt.Out, 1) // number of values - writeName(ctxt.Out, "Go") // value: name - writeName(ctxt.Out, objabi.Version) // value: version + writeName(ctxt.Out, "language") // field name + writeUleb128(ctxt.Out, 1) // number of values + writeName(ctxt.Out, "Go") // value: name + writeName(ctxt.Out, buildcfg.Version) // value: version writeName(ctxt.Out, "processed-by") // field name writeUleb128(ctxt.Out, 1) // number of values writeName(ctxt.Out, "Go cmd/compile") // value: name - writeName(ctxt.Out, objabi.Version) // value: version + writeName(ctxt.Out, buildcfg.Version) // value: version writeSecSize(ctxt, sizeOffset) } diff --git a/src/cmd/link/internal/x86/asm.go b/src/cmd/link/internal/x86/asm.go index af0ce11255f74b363f85a8c7b1ab3f2918db94bb..5f6bcfb8b1abf0c6d374148e4aefa10bfe2413d4 100644 --- a/src/cmd/link/internal/x86/asm.go +++ b/src/cmd/link/internal/x86/asm.go @@ -415,7 +415,7 @@ func archreloc(*ld.Target, *loader.Loader, *ld.ArchSyms, loader.Reloc, loader.Sy return -1, 0, false } -func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64) int64 { +func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64, []byte) int64 { log.Fatalf("unexpected relocation variant") return -1 } diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 08ddd00a0c7639b1abc008275bbbc3f586717d05..7230054bedd2fa3149e4990a2e7d61eafbad31ad 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -48,13 +48,9 @@ const X = "\n!\n" func main() {} ` - tmpdir, err := ioutil.TempDir("", "issue21703") - if err != nil { - t.Fatalf("failed to create temp dir: %v\n", err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() - err = ioutil.WriteFile(filepath.Join(tmpdir, "main.go"), []byte(source), 0666) + err := ioutil.WriteFile(filepath.Join(tmpdir, "main.go"), []byte(source), 0666) if err != nil { t.Fatalf("failed to write main.go: %v\n", err) } @@ -83,11 +79,7 @@ func TestIssue28429(t *testing.T) { testenv.MustHaveGoBuild(t) - tmpdir, err := ioutil.TempDir("", "issue28429-") - if err != nil { - t.Fatalf("failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() write := func(name, content string) { err := ioutil.WriteFile(filepath.Join(tmpdir, name), []byte(content), 0666) @@ -126,11 +118,7 @@ func TestUnresolved(t *testing.T) { t.Parallel() - tmpdir, err := ioutil.TempDir("", "unresolved-") - if err != nil { - t.Fatalf("failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() write := func(name, content string) { err := ioutil.WriteFile(filepath.Join(tmpdir, name), []byte(content), 0666) @@ -189,17 +177,14 @@ func TestIssue33979(t *testing.T) { case "mips", "mipsle", "mips64", "mips64le": t.Skipf("Skipping on %s/%s", runtime.GOOS, runtime.GOARCH) } - if runtime.GOOS == "aix" { + if runtime.GOOS == "aix" || + runtime.GOOS == "windows" && runtime.GOARCH == "arm64" { t.Skipf("Skipping on %s/%s", runtime.GOOS, runtime.GOARCH) } t.Parallel() - tmpdir, err := ioutil.TempDir("", "unresolved-") - if err != nil { - t.Fatalf("failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() write := func(name, content string) { err := ioutil.WriteFile(filepath.Join(tmpdir, name), []byte(content), 0666) @@ -300,11 +285,7 @@ func TestBuildForTvOS(t *testing.T) { "-framework", "CoreFoundation", } lib := filepath.Join("testdata", "testBuildFortvOS", "lib.go") - tmpDir, err := ioutil.TempDir("", "go-link-TestBuildFortvOS") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) + tmpDir := t.TempDir() ar := filepath.Join(tmpDir, "lib.a") cmd := exec.Command(testenv.GoToolPath(t), "build", "-buildmode=c-archive", "-o", ar, lib) @@ -339,14 +320,10 @@ func TestXFlag(t *testing.T) { t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestXFlag") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() src := filepath.Join(tmpdir, "main.go") - err = ioutil.WriteFile(src, []byte(testXFlagSrc), 0666) + err := ioutil.WriteFile(src, []byte(testXFlagSrc), 0666) if err != nil { t.Fatal(err) } @@ -357,24 +334,20 @@ func TestXFlag(t *testing.T) { } } -var testMacOSVersionSrc = ` +var testMachOBuildVersionSrc = ` package main func main() { } ` -func TestMacOSVersion(t *testing.T) { +func TestMachOBuildVersion(t *testing.T) { testenv.MustHaveGoBuild(t) t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestMacOSVersion") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() src := filepath.Join(tmpdir, "main.go") - err = ioutil.WriteFile(src, []byte(testMacOSVersionSrc), 0666) + err := ioutil.WriteFile(src, []byte(testMachOBuildVersionSrc), 0666) if err != nil { t.Fatal(err) } @@ -393,33 +366,34 @@ func TestMacOSVersion(t *testing.T) { if err != nil { t.Fatal(err) } + defer exef.Close() exem, err := macho.NewFile(exef) if err != nil { t.Fatal(err) } found := false - const LC_VERSION_MIN_MACOSX = 0x24 + const LC_BUILD_VERSION = 0x32 checkMin := func(ver uint32) { major, minor := (ver>>16)&0xff, (ver>>8)&0xff if major != 10 || minor < 9 { - t.Errorf("LC_VERSION_MIN_MACOSX version %d.%d < 10.9", major, minor) + t.Errorf("LC_BUILD_VERSION version %d.%d < 10.9", major, minor) } } for _, cmd := range exem.Loads { raw := cmd.Raw() type_ := exem.ByteOrder.Uint32(raw) - if type_ != LC_VERSION_MIN_MACOSX { + if type_ != LC_BUILD_VERSION { continue } - osVer := exem.ByteOrder.Uint32(raw[8:]) + osVer := exem.ByteOrder.Uint32(raw[12:]) checkMin(osVer) - sdkVer := exem.ByteOrder.Uint32(raw[12:]) + sdkVer := exem.ByteOrder.Uint32(raw[16:]) checkMin(sdkVer) found = true break } if !found { - t.Errorf("no LC_VERSION_MIN_MACOSX load command found") + t.Errorf("no LC_BUILD_VERSION load command found") } } @@ -446,14 +420,10 @@ func TestIssue34788Android386TLSSequence(t *testing.T) { t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestIssue34788Android386TLSSequence") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() src := filepath.Join(tmpdir, "blah.go") - err = ioutil.WriteFile(src, []byte(Issue34788src), 0666) + err := ioutil.WriteFile(src, []byte(Issue34788src), 0666) if err != nil { t.Fatal(err) } @@ -500,32 +470,45 @@ TEXT ·f(SB), NOSPLIT|DUPOK, $0-0 JMP 0(PC) ` +const testStrictDupAsmSrc3 = ` +#include "textflag.h" +GLOBL ·rcon(SB), RODATA|DUPOK, $64 +` + +const testStrictDupAsmSrc4 = ` +#include "textflag.h" +GLOBL ·rcon(SB), RODATA|DUPOK, $32 +` + func TestStrictDup(t *testing.T) { // Check that -strictdups flag works. testenv.MustHaveGoBuild(t) + asmfiles := []struct { + fname string + payload string + }{ + {"a", testStrictDupAsmSrc1}, + {"b", testStrictDupAsmSrc2}, + {"c", testStrictDupAsmSrc3}, + {"d", testStrictDupAsmSrc4}, + } + t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestStrictDup") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() src := filepath.Join(tmpdir, "x.go") - err = ioutil.WriteFile(src, []byte(testStrictDupGoSrc), 0666) + err := ioutil.WriteFile(src, []byte(testStrictDupGoSrc), 0666) if err != nil { t.Fatal(err) } - src = filepath.Join(tmpdir, "a.s") - err = ioutil.WriteFile(src, []byte(testStrictDupAsmSrc1), 0666) - if err != nil { - t.Fatal(err) - } - src = filepath.Join(tmpdir, "b.s") - err = ioutil.WriteFile(src, []byte(testStrictDupAsmSrc2), 0666) - if err != nil { - t.Fatal(err) + for _, af := range asmfiles { + src = filepath.Join(tmpdir, af.fname+".s") + err = ioutil.WriteFile(src, []byte(af.payload), 0666) + if err != nil { + t.Fatal(err) + } } src = filepath.Join(tmpdir, "go.mod") err = ioutil.WriteFile(src, []byte("module teststrictdup\n"), 0666) @@ -537,7 +520,7 @@ func TestStrictDup(t *testing.T) { cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { - t.Errorf("linking with -strictdups=1 failed: %v", err) + t.Errorf("linking with -strictdups=1 failed: %v\n%s", err, string(out)) } if !bytes.Contains(out, []byte("mismatched payload")) { t.Errorf("unexpected output:\n%s", out) @@ -549,7 +532,11 @@ func TestStrictDup(t *testing.T) { if err == nil { t.Errorf("linking with -strictdups=2 did not fail") } - if !bytes.Contains(out, []byte("mismatched payload")) { + // NB: on amd64 we get the 'new length' error, on arm64 the 'different + // contents' error. + if !(bytes.Contains(out, []byte("mismatched payload: new length")) || + bytes.Contains(out, []byte("mismatched payload: same length but different contents"))) || + !bytes.Contains(out, []byte("mismatched payload: different sizes")) { t.Errorf("unexpected output:\n%s", out) } } @@ -592,14 +579,10 @@ func TestFuncAlign(t *testing.T) { t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestFuncAlign") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() src := filepath.Join(tmpdir, "go.mod") - err = ioutil.WriteFile(src, []byte("module cmd/link/TestFuncAlign/falign"), 0666) + err := ioutil.WriteFile(src, []byte("module cmd/link/TestFuncAlign/falign"), 0666) if err != nil { t.Fatal(err) } @@ -656,7 +639,7 @@ func TestTrampoline(t *testing.T) { // threshold for trampoline generation, and essentially all cross-package // calls will use trampolines. switch runtime.GOARCH { - case "arm", "ppc64", "ppc64le": + case "arm", "arm64", "ppc64", "ppc64le": default: t.Skipf("trampoline insertion is not implemented on %s", runtime.GOARCH) } @@ -665,14 +648,62 @@ func TestTrampoline(t *testing.T) { t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestTrampoline") + tmpdir := t.TempDir() + + src := filepath.Join(tmpdir, "hello.go") + err := ioutil.WriteFile(src, []byte(testTrampSrc), 0666) if err != nil { t.Fatal(err) } - defer os.RemoveAll(tmpdir) + exe := filepath.Join(tmpdir, "hello.exe") + + cmd := exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-debugtramp=2", "-o", exe, src) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("build failed: %v\n%s", err, out) + } + cmd = exec.Command(exe) + out, err = cmd.CombinedOutput() + if err != nil { + t.Errorf("executable failed to run: %v\n%s", err, out) + } + if string(out) != "hello\n" { + t.Errorf("unexpected output:\n%s", out) + } +} + +const testTrampCgoSrc = ` +package main + +// #include +// void CHello() { printf("hello\n"); fflush(stdout); } +import "C" + +func main() { + C.CHello() +} +` + +func TestTrampolineCgo(t *testing.T) { + // Test that trampoline insertion works for cgo code. + // For stress test, we set -debugtramp=2 flag, which sets a very low + // threshold for trampoline generation, and essentially all cross-package + // calls will use trampolines. + switch runtime.GOARCH { + case "arm", "arm64", "ppc64", "ppc64le": + default: + t.Skipf("trampoline insertion is not implemented on %s", runtime.GOARCH) + } + + testenv.MustHaveGoBuild(t) + testenv.MustHaveCGO(t) + + t.Parallel() + + tmpdir := t.TempDir() src := filepath.Join(tmpdir, "hello.go") - err = ioutil.WriteFile(src, []byte(testTrampSrc), 0666) + err := ioutil.WriteFile(src, []byte(testTrampCgoSrc), 0666) if err != nil { t.Fatal(err) } @@ -688,7 +719,26 @@ func TestTrampoline(t *testing.T) { if err != nil { t.Errorf("executable failed to run: %v\n%s", err, out) } - if string(out) != "hello\n" { + if string(out) != "hello\n" && string(out) != "hello\r\n" { + t.Errorf("unexpected output:\n%s", out) + } + + // Test internal linking mode. + + if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || (runtime.GOARCH == "arm64" && runtime.GOOS == "windows") || !testenv.CanInternalLink() { + return // internal linking cgo is not supported + } + cmd = exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-debugtramp=2 -linkmode=internal", "-o", exe, src) + out, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("build failed: %v\n%s", err, out) + } + cmd = exec.Command(exe) + out, err = cmd.CombinedOutput() + if err != nil { + t.Errorf("executable failed to run: %v\n%s", err, out) + } + if string(out) != "hello\n" && string(out) != "hello\r\n" { t.Errorf("unexpected output:\n%s", out) } } @@ -701,11 +751,7 @@ func TestIndexMismatch(t *testing.T) { t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestIndexMismatch") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() aSrc := filepath.Join("testdata", "testIndexMismatch", "a.go") bSrc := filepath.Join("testdata", "testIndexMismatch", "b.go") @@ -753,23 +799,20 @@ func TestIndexMismatch(t *testing.T) { } } -func TestPErsrc(t *testing.T) { +func TestPErsrcBinutils(t *testing.T) { // Test that PE rsrc section is handled correctly (issue 39658). testenv.MustHaveGoBuild(t) - if runtime.GOARCH != "amd64" || runtime.GOOS != "windows" { - t.Skipf("this is a windows/amd64-only test") + if (runtime.GOARCH != "386" && runtime.GOARCH != "amd64") || runtime.GOOS != "windows" { + // This test is limited to amd64 and 386, because binutils is limited as such + t.Skipf("this is only for windows/amd64 and windows/386") } t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestPErsrc") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() - pkgdir := filepath.Join("testdata", "testPErsrc") + pkgdir := filepath.Join("testdata", "pe-binutils") exe := filepath.Join(tmpdir, "a.exe") cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe) cmd.Dir = pkgdir @@ -787,19 +830,32 @@ func TestPErsrc(t *testing.T) { if !bytes.Contains(b, []byte("Hello Gophers!")) { t.Fatalf("binary does not contain expected content") } +} + +func TestPErsrcLLVM(t *testing.T) { + // Test that PE rsrc section is handled correctly (issue 39658). + testenv.MustHaveGoBuild(t) + + if runtime.GOOS != "windows" { + t.Skipf("this is a windows-only test") + } + + t.Parallel() + + tmpdir := t.TempDir() - pkgdir = filepath.Join("testdata", "testPErsrc-complex") - exe = filepath.Join(tmpdir, "a.exe") - cmd = exec.Command(testenv.GoToolPath(t), "build", "-o", exe) + pkgdir := filepath.Join("testdata", "pe-llvm") + exe := filepath.Join(tmpdir, "a.exe") + cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe) cmd.Dir = pkgdir // cmd.Env = append(os.Environ(), "GOOS=windows", "GOARCH=amd64") // uncomment if debugging in a cross-compiling environment - out, err = cmd.CombinedOutput() + out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("building failed: %v, output:\n%s", err, out) } // Check that the binary contains the rsrc data - b, err = ioutil.ReadFile(exe) + b, err := ioutil.ReadFile(exe) if err != nil { t.Fatalf("reading output failed: %v", err) } @@ -814,12 +870,6 @@ func TestContentAddressableSymbols(t *testing.T) { t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestContentAddressableSymbols") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) - src := filepath.Join("testdata", "testHashedSyms", "p.go") cmd := exec.Command(testenv.GoToolPath(t), "run", src) out, err := cmd.CombinedOutput() @@ -863,14 +913,10 @@ func TestIssue38554(t *testing.T) { t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestIssue38554") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() src := filepath.Join(tmpdir, "x.go") - err = ioutil.WriteFile(src, []byte(testIssue38554Src), 0666) + err := ioutil.WriteFile(src, []byte(testIssue38554Src), 0666) if err != nil { t.Fatalf("failed to write source file: %v", err) } @@ -917,14 +963,10 @@ func TestIssue42396(t *testing.T) { t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestIssue42396") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() src := filepath.Join(tmpdir, "main.go") - err = ioutil.WriteFile(src, []byte(testIssue42396src), 0666) + err := ioutil.WriteFile(src, []byte(testIssue42396src), 0666) if err != nil { t.Fatalf("failed to write source file: %v", err) } @@ -974,14 +1016,10 @@ func TestLargeReloc(t *testing.T) { testenv.MustHaveGoBuild(t) t.Parallel() - tmpdir, err := ioutil.TempDir("", "TestIssue42396") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir := t.TempDir() src := filepath.Join(tmpdir, "x.go") - err = ioutil.WriteFile(src, []byte(testLargeRelocSrc), 0666) + err := ioutil.WriteFile(src, []byte(testLargeRelocSrc), 0666) if err != nil { t.Fatalf("failed to write source file: %v", err) } diff --git a/src/cmd/link/linkbig_test.go b/src/cmd/link/linkbig_test.go index 78d2bc1afecdd0f241525fed65c59c18832f06dd..9a4430c162b99de9b56985856b123d306e976619 100644 --- a/src/cmd/link/linkbig_test.go +++ b/src/cmd/link/linkbig_test.go @@ -10,29 +10,27 @@ package main import ( "bytes" - "cmd/internal/objabi" "fmt" + "internal/buildcfg" "internal/testenv" "io/ioutil" - "os" "os/exec" "testing" ) func TestLargeText(t *testing.T) { - if testing.Short() || (objabi.GOARCH != "ppc64le" && objabi.GOARCH != "ppc64" && objabi.GOARCH != "arm") { - t.Skipf("Skipping large text section test in short mode or on %s", objabi.GOARCH) + if testing.Short() || (buildcfg.GOARCH != "ppc64le" && buildcfg.GOARCH != "ppc64" && buildcfg.GOARCH != "arm") { + t.Skipf("Skipping large text section test in short mode or on %s", buildcfg.GOARCH) } testenv.MustHaveGoBuild(t) var w bytes.Buffer const FN = 4 - tmpdir, err := ioutil.TempDir("", "bigtext") - if err != nil { - t.Fatalf("can't create temp directory: %v\n", err) - } + tmpdir := t.TempDir() - defer os.RemoveAll(tmpdir) + if err := ioutil.WriteFile(tmpdir+"/go.mod", []byte("module big_test\n"), 0666); err != nil { + t.Fatal(err) + } // Generate the scenario where the total amount of text exceeds the // limit for the jmp/call instruction, on RISC architectures like ppc64le, @@ -44,7 +42,7 @@ func TestLargeText(t *testing.T) { "ppc64le": "\tMOVD\tR0,R3\n", "arm": "\tMOVW\tR0,R1\n", } - inst := instOnArch[objabi.GOARCH] + inst := instOnArch[buildcfg.GOARCH] for j := 0; j < FN; j++ { testname := fmt.Sprintf("bigfn%d", j) fmt.Fprintf(&w, "TEXT ·%s(SB),$0\n", testname) @@ -79,32 +77,34 @@ func TestLargeText(t *testing.T) { fmt.Fprintf(&w, "\t}\n") fmt.Fprintf(&w, "\tfmt.Printf(\"PASS\\n\")\n") fmt.Fprintf(&w, "}") - err = ioutil.WriteFile(tmpdir+"/bigfn.go", w.Bytes(), 0666) + err := ioutil.WriteFile(tmpdir+"/bigfn.go", w.Bytes(), 0666) if err != nil { t.Fatalf("can't write output: %v\n", err) } // Build and run with internal linking. - os.Chdir(tmpdir) cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "bigtext") + cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("Build failed for big text program with internal linking: %v, output: %s", err, out) } - cmd = exec.Command(tmpdir + "/bigtext") + cmd = exec.Command("./bigtext") + cmd.Dir = tmpdir out, err = cmd.CombinedOutput() if err != nil { t.Fatalf("Program built with internal linking failed to run with err %v, output: %s", err, out) } // Build and run with external linking - os.Chdir(tmpdir) cmd = exec.Command(testenv.GoToolPath(t), "build", "-o", "bigtext", "-ldflags", "'-linkmode=external'") + cmd.Dir = tmpdir out, err = cmd.CombinedOutput() if err != nil { t.Fatalf("Build failed for big text program with external linking: %v, output: %s", err, out) } - cmd = exec.Command(tmpdir + "/bigtext") + cmd = exec.Command("./bigtext") + cmd.Dir = tmpdir out, err = cmd.CombinedOutput() if err != nil { t.Fatalf("Program built with external linking failed to run with err %v, output: %s", err, out) diff --git a/src/cmd/link/main.go b/src/cmd/link/main.go index 6b4ca9706d016e7ddd384bbd7f04b9f4a8b016b9..d92478e61e236858995765665f753fdb19abc36b 100644 --- a/src/cmd/link/main.go +++ b/src/cmd/link/main.go @@ -5,7 +5,6 @@ package main import ( - "cmd/internal/objabi" "cmd/internal/sys" "cmd/link/internal/amd64" "cmd/link/internal/arm" @@ -19,6 +18,7 @@ import ( "cmd/link/internal/wasm" "cmd/link/internal/x86" "fmt" + "internal/buildcfg" "os" ) @@ -40,9 +40,10 @@ func main() { var arch *sys.Arch var theArch ld.Arch - switch objabi.GOARCH { + buildcfg.Check() + switch buildcfg.GOARCH { default: - fmt.Fprintf(os.Stderr, "link: unknown architecture %q\n", objabi.GOARCH) + fmt.Fprintf(os.Stderr, "link: unknown architecture %q\n", buildcfg.GOARCH) os.Exit(2) case "386": arch, theArch = x86.Init() diff --git a/src/cmd/link/testdata/testPErsrc/main.go b/src/cmd/link/testdata/pe-binutils/main.go similarity index 65% rename from src/cmd/link/testdata/testPErsrc/main.go rename to src/cmd/link/testdata/pe-binutils/main.go index 5eb66fb9cce0d0e121ee10a991f0cc27d04dfbc2..14ea6f9e0ff8376915972bb7a5bbd7e468fa7237 100644 --- a/src/cmd/link/testdata/testPErsrc/main.go +++ b/src/cmd/link/testdata/pe-binutils/main.go @@ -4,10 +4,9 @@ // Test that a PE rsrc section is handled correctly (issue 39658). // -// rsrc.syso is created with: -// windres -i a.rc -o rsrc.syso -O coff -// on windows-amd64-2016 builder, where a.rc is a text file with -// the following content: +// rsrc.syso is created using binutils with: +// {x86_64,i686}-w64-mingw32-windres -i a.rc -o rsrc_$GOARCH.syso -O coff +// where a.rc is a text file with the following content: // // resname RCDATA { // "Hello Gophers!\0", diff --git a/src/cmd/link/testdata/pe-binutils/rsrc_386.syso b/src/cmd/link/testdata/pe-binutils/rsrc_386.syso new file mode 100644 index 0000000000000000000000000000000000000000..b4abc58abee609976b890aa7dafe197431d7e011 Binary files /dev/null and b/src/cmd/link/testdata/pe-binutils/rsrc_386.syso differ diff --git a/src/cmd/link/testdata/testPErsrc/rsrc.syso b/src/cmd/link/testdata/pe-binutils/rsrc_amd64.syso similarity index 100% rename from src/cmd/link/testdata/testPErsrc/rsrc.syso rename to src/cmd/link/testdata/pe-binutils/rsrc_amd64.syso diff --git a/src/cmd/link/testdata/testPErsrc-complex/main.go b/src/cmd/link/testdata/pe-llvm/main.go similarity index 92% rename from src/cmd/link/testdata/testPErsrc-complex/main.go rename to src/cmd/link/testdata/pe-llvm/main.go index affd6eada2eab387c3fadfba970b0d8e8a1424a6..099a71a3fffaaab67aec72b6705f8ee1c9ab42f4 100644 --- a/src/cmd/link/testdata/testPErsrc-complex/main.go +++ b/src/cmd/link/testdata/pe-llvm/main.go @@ -6,8 +6,8 @@ // have been created by llvm-rc or msvc's rc.exe, which means there's the // @feat.00 symbol as well as split .rsrc$00 and .rsrc$01 section to deal with. // -// rsrc.syso is created with: -// windres -i a.rc -o rsrc.syso -O coff +// rsrc.syso is created using llvm with: +// {i686,x86_64,armv7,arm64}-w64-mingw32-windres -i a.rc -o rsrc_$GOARCH.syso -O coff // where this windres calls into llvm-rc and llvm-cvtres. The source file, // a.rc, simply contains a reference to its own bytes: // diff --git a/src/cmd/link/testdata/pe-llvm/rsrc_386.syso b/src/cmd/link/testdata/pe-llvm/rsrc_386.syso new file mode 100644 index 0000000000000000000000000000000000000000..21126c9954fd595269f7e68ffaca4dc32ee3e25d Binary files /dev/null and b/src/cmd/link/testdata/pe-llvm/rsrc_386.syso differ diff --git a/src/cmd/link/testdata/testPErsrc-complex/rsrc.syso b/src/cmd/link/testdata/pe-llvm/rsrc_amd64.syso similarity index 81% rename from src/cmd/link/testdata/testPErsrc-complex/rsrc.syso rename to src/cmd/link/testdata/pe-llvm/rsrc_amd64.syso index eff630b8a23de71fa27da5bf0b49965bf4cea69a..56f9260b0a3b76ad84ac48b2c5f0a41bb538dee5 100644 Binary files a/src/cmd/link/testdata/testPErsrc-complex/rsrc.syso and b/src/cmd/link/testdata/pe-llvm/rsrc_amd64.syso differ diff --git a/src/cmd/link/testdata/pe-llvm/rsrc_arm.syso b/src/cmd/link/testdata/pe-llvm/rsrc_arm.syso new file mode 100644 index 0000000000000000000000000000000000000000..c93a1e9ba0ffa390e122f211ca55ace3355ce27b Binary files /dev/null and b/src/cmd/link/testdata/pe-llvm/rsrc_arm.syso differ diff --git a/src/cmd/link/testdata/pe-llvm/rsrc_arm64.syso b/src/cmd/link/testdata/pe-llvm/rsrc_arm64.syso new file mode 100644 index 0000000000000000000000000000000000000000..7849638ddd42e7e24efcc18665e265ac01f8edcd Binary files /dev/null and b/src/cmd/link/testdata/pe-llvm/rsrc_arm64.syso differ diff --git a/src/cmd/nm/nm_cgo_test.go b/src/cmd/nm/nm_cgo_test.go index e0414e6b2221f73595ed460ec662f1bd41662a07..1544be041a8174eee4b2849d354ecd0274f8cb76 100644 --- a/src/cmd/nm/nm_cgo_test.go +++ b/src/cmd/nm/nm_cgo_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cgo // +build cgo package main @@ -28,6 +29,11 @@ func canInternalLink() bool { return false } case "openbsd": + switch runtime.GOARCH { + case "arm64", "mips64": + return false + } + case "windows": switch runtime.GOARCH { case "arm64": return false diff --git a/src/cmd/objdump/objdump_test.go b/src/cmd/objdump/objdump_test.go index 1748e13a537806bfa725ef54b792e2e95b2b0e30..f231a7c6e0e2e5c084cb162c1b6c3540d4677b85 100644 --- a/src/cmd/objdump/objdump_test.go +++ b/src/cmd/objdump/objdump_test.go @@ -64,13 +64,13 @@ var x86Need = []string{ // for both 386 and AMD64 } var amd64GnuNeed = []string{ - "movq", + "jmp", "callq", "cmpb", } var i386GnuNeed = []string{ - "mov", + "jmp", "call", "cmp", } @@ -345,3 +345,18 @@ func TestGoobjFileNumber(t *testing.T) { t.Logf("output:\n%s", text) } } + +func TestGoObjOtherVersion(t *testing.T) { + testenv.MustHaveExec(t) + t.Parallel() + + obj := filepath.Join("testdata", "go116.o") + cmd := exec.Command(exe, obj) + out, err := cmd.CombinedOutput() + if err == nil { + t.Fatalf("objdump go116.o succeeded unexpectly") + } + if !strings.Contains(string(out), "go object of a different version") { + t.Errorf("unexpected error message:\n%s", out) + } +} diff --git a/src/cmd/objdump/testdata/go116.o b/src/cmd/objdump/testdata/go116.o new file mode 100644 index 0000000000000000000000000000000000000000..6434d5c8cffe54de18af70cf255f2da061e4b1cb Binary files /dev/null and b/src/cmd/objdump/testdata/go116.o differ diff --git a/src/cmd/pack/pack_test.go b/src/cmd/pack/pack_test.go index 118376f9df6b88337a0362a29282dc51fbe3961c..7842b562dc3c1406895292865f4c4e4c4d4c08e3 100644 --- a/src/cmd/pack/pack_test.go +++ b/src/cmd/pack/pack_test.go @@ -19,15 +19,6 @@ import ( "time" ) -// tmpDir creates a temporary directory and returns its name. -func tmpDir(t *testing.T) string { - name, err := os.MkdirTemp("", "pack") - if err != nil { - t.Fatal(err) - } - return name -} - // testCreate creates an archive in the specified directory. func testCreate(t *testing.T, dir string) { name := filepath.Join(dir, "pack.a") @@ -57,15 +48,13 @@ func testCreate(t *testing.T, dir string) { // Test that we can create an archive, write to it, and get the same contents back. // Tests the rv and then the pv command on a new archive. func TestCreate(t *testing.T) { - dir := tmpDir(t) - defer os.RemoveAll(dir) + dir := t.TempDir() testCreate(t, dir) } // Test that we can create an archive twice with the same name (Issue 8369). func TestCreateTwice(t *testing.T) { - dir := tmpDir(t) - defer os.RemoveAll(dir) + dir := t.TempDir() testCreate(t, dir) testCreate(t, dir) } @@ -73,8 +62,7 @@ func TestCreateTwice(t *testing.T) { // Test that we can create an archive, put some files in it, and get back a correct listing. // Tests the tv command. func TestTableOfContents(t *testing.T) { - dir := tmpDir(t) - defer os.RemoveAll(dir) + dir := t.TempDir() name := filepath.Join(dir, "pack.a") ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil) @@ -131,8 +119,7 @@ func TestTableOfContents(t *testing.T) { // Test that we can create an archive, put some files in it, and get back a file. // Tests the x command. func TestExtract(t *testing.T) { - dir := tmpDir(t) - defer os.RemoveAll(dir) + dir := t.TempDir() name := filepath.Join(dir, "pack.a") ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil) // Add some entries by hand. @@ -173,8 +160,7 @@ func TestExtract(t *testing.T) { func TestHello(t *testing.T) { testenv.MustHaveGoBuild(t) - dir := tmpDir(t) - defer os.RemoveAll(dir) + dir := t.TempDir() hello := filepath.Join(dir, "hello.go") prog := ` package main @@ -209,8 +195,7 @@ func TestLargeDefs(t *testing.T) { } testenv.MustHaveGoBuild(t) - dir := tmpDir(t) - defer os.RemoveAll(dir) + dir := t.TempDir() large := filepath.Join(dir, "large.go") f, err := os.Create(large) if err != nil { @@ -276,8 +261,7 @@ func TestLargeDefs(t *testing.T) { func TestIssue21703(t *testing.T) { testenv.MustHaveGoBuild(t) - dir := tmpDir(t) - defer os.RemoveAll(dir) + dir := t.TempDir() const aSrc = `package a; const X = "\n!\n"` err := os.WriteFile(filepath.Join(dir, "a.go"), []byte(aSrc), 0666) @@ -307,8 +291,7 @@ func TestIssue21703(t *testing.T) { func TestCreateWithCompilerObj(t *testing.T) { testenv.MustHaveGoBuild(t) - dir := tmpDir(t) - defer os.RemoveAll(dir) + dir := t.TempDir() src := filepath.Join(dir, "p.go") prog := "package p; var X = 42\n" err := os.WriteFile(src, []byte(prog), 0666) @@ -372,8 +355,7 @@ func TestCreateWithCompilerObj(t *testing.T) { func TestRWithNonexistentFile(t *testing.T) { testenv.MustHaveGoBuild(t) - dir := tmpDir(t) - defer os.RemoveAll(dir) + dir := t.TempDir() src := filepath.Join(dir, "p.go") prog := "package p; var X = 42\n" err := os.WriteFile(src, []byte(prog), 0666) diff --git a/src/cmd/pprof/pprof.go b/src/cmd/pprof/pprof.go index 11f91cbedb9940b59f6450226c6bfe77c7be33af..e72c765adc30515d06e44eb5d81765f964a012e7 100644 --- a/src/cmd/pprof/pprof.go +++ b/src/cmd/pprof/pprof.go @@ -232,9 +232,9 @@ func (f *file) Name() string { return f.name } -func (f *file) Base() uint64 { - // No support for shared libraries. - return 0 +func (f *file) ObjAddr(addr uint64) (uint64, error) { + // No support for shared libraries, so translation is a no-op. + return addr, nil } func (f *file) BuildID() string { diff --git a/src/cmd/pprof/pprof_test.go b/src/cmd/pprof/pprof_test.go new file mode 100644 index 0000000000000000000000000000000000000000..11e251bfde2654e0e015d645ef2251dc6d2679d5 --- /dev/null +++ b/src/cmd/pprof/pprof_test.go @@ -0,0 +1,127 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "internal/testenv" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" +) + +var tmp, pprofExe string // populated by buildPprof + +func TestMain(m *testing.M) { + if !testenv.HasGoBuild() { + return + } + + var exitcode int + if err := buildPprof(); err == nil { + exitcode = m.Run() + } else { + fmt.Println(err) + exitcode = 1 + } + os.RemoveAll(tmp) + os.Exit(exitcode) +} + +func buildPprof() error { + var err error + tmp, err = os.MkdirTemp("", "TestPprof") + if err != nil { + return fmt.Errorf("TempDir failed: %v", err) + } + + pprofExe = filepath.Join(tmp, "testpprof.exe") + gotool, err := testenv.GoTool() + if err != nil { + return err + } + out, err := exec.Command(gotool, "build", "-o", pprofExe, "cmd/pprof").CombinedOutput() + if err != nil { + os.RemoveAll(tmp) + return fmt.Errorf("go build -o %v cmd/pprof: %v\n%s", pprofExe, err, string(out)) + } + + return nil +} + +// See also runtime/pprof.cpuProfilingBroken. +func mustHaveCPUProfiling(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("skipping on %s, unimplemented", runtime.GOOS) + case "aix": + t.Skipf("skipping on %s, issue 45170", runtime.GOOS) + case "ios", "dragonfly", "netbsd", "illumos", "solaris": + t.Skipf("skipping on %s, issue 13841", runtime.GOOS) + case "openbsd": + if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + t.Skipf("skipping on %s/%s, issue 13841", runtime.GOOS, runtime.GOARCH) + } + } +} + +func mustHaveDisasm(t *testing.T) { + switch runtime.GOARCH { + case "mips", "mipsle", "mips64", "mips64le": + t.Skipf("skipping on %s, issue 12559", runtime.GOARCH) + case "riscv64": + t.Skipf("skipping on %s, issue 36738", runtime.GOARCH) + case "s390x": + t.Skipf("skipping on %s, issue 15255", runtime.GOARCH) + } + + // Skip PIE platforms, pprof can't disassemble PIE. + if runtime.GOOS == "windows" { + t.Skipf("skipping on %s, issue 46639", runtime.GOOS) + } + if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { + t.Skipf("skipping on %s/%s, issue 46639", runtime.GOOS, runtime.GOARCH) + } +} + +// TestDisasm verifies that cmd/pprof can successfully disassemble functions. +// +// This is a regression test for issue 46636. +func TestDisasm(t *testing.T) { + mustHaveCPUProfiling(t) + mustHaveDisasm(t) + testenv.MustHaveGoBuild(t) + + tmpdir := t.TempDir() + cpuExe := filepath.Join(tmpdir, "cpu.exe") + cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", cpuExe, "cpu.go") + cmd.Dir = "testdata/" + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("build failed: %v\n%s", err, out) + } + + profile := filepath.Join(tmpdir, "cpu.pprof") + cmd = exec.Command(cpuExe, "-output", profile) + out, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("cpu failed: %v\n%s", err, out) + } + + cmd = exec.Command(pprofExe, "-disasm", "main.main", cpuExe, profile) + out, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("pprof failed: %v\n%s", err, out) + } + + sout := string(out) + want := "ROUTINE ======================== main.main" + if !strings.Contains(sout, want) { + t.Errorf("pprof disasm got %s want contains %q", sout, want) + } +} diff --git a/src/cmd/pprof/readlineui.go b/src/cmd/pprof/readlineui.go index 0c9fafdad75456aea5f0cc4a111b83be260253a2..f46e934e0f3cf81d687799fd6d41bf621ef4d7f7 100644 --- a/src/cmd/pprof/readlineui.go +++ b/src/cmd/pprof/readlineui.go @@ -5,6 +5,7 @@ // This file contains a driver.UI implementation // that provides the readline functionality if possible. +//go:build (darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows) && !appengine && !android // +build darwin dragonfly freebsd linux netbsd openbsd solaris windows // +build !appengine // +build !android @@ -18,7 +19,7 @@ import ( "strings" "github.com/google/pprof/driver" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) func init() { @@ -26,11 +27,11 @@ func init() { } // readlineUI implements driver.UI interface using the -// golang.org/x/crypto/ssh/terminal package. +// golang.org/x/term package. // The upstream pprof command implements the same functionality // using the github.com/chzyer/readline package. type readlineUI struct { - term *terminal.Terminal + term *term.Terminal } func newReadlineUI() driver.UI { @@ -38,19 +39,19 @@ func newReadlineUI() driver.UI { if v := strings.ToLower(os.Getenv("TERM")); v == "" || v == "dumb" { return nil } - // test if we can use terminal.ReadLine + // test if we can use term.ReadLine // that assumes operation in the raw mode. - oldState, err := terminal.MakeRaw(0) + oldState, err := term.MakeRaw(0) if err != nil { return nil } - terminal.Restore(0, oldState) + term.Restore(0, oldState) rw := struct { io.Reader io.Writer }{os.Stdin, os.Stderr} - return &readlineUI{term: terminal.NewTerminal(rw, "")} + return &readlineUI{term: term.NewTerminal(rw, "")} } // Read returns a line of text (a command) read from the user. @@ -60,8 +61,8 @@ func (r *readlineUI) ReadLine(prompt string) (string, error) { // skip error checking because we tested it // when creating this readlineUI initially. - oldState, _ := terminal.MakeRaw(0) - defer terminal.Restore(0, oldState) + oldState, _ := term.MakeRaw(0) + defer term.Restore(0, oldState) s, err := r.term.ReadLine() return s, err @@ -105,7 +106,7 @@ func colorize(msg string) string { // interactive terminal (as opposed to being redirected to a file). func (r *readlineUI) IsTerminal() bool { const stdout = 1 - return terminal.IsTerminal(stdout) + return term.IsTerminal(stdout) } // WantBrowser indicates whether browser should be opened with the -http option. diff --git a/src/cmd/pprof/testdata/cpu.go b/src/cmd/pprof/testdata/cpu.go new file mode 100644 index 0000000000000000000000000000000000000000..5b682870db8f69ad5ee1ef539c8dcb505c596e58 --- /dev/null +++ b/src/cmd/pprof/testdata/cpu.go @@ -0,0 +1,41 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "fmt" + "os" + "runtime/pprof" + "time" +) + +var output = flag.String("output", "", "pprof profile output file") + +func main() { + flag.Parse() + if *output == "" { + fmt.Fprintf(os.Stderr, "usage: %s -output file.pprof\n", os.Args[0]) + os.Exit(2) + } + + f, err := os.Create(*output) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + defer f.Close() + + if err := pprof.StartCPUProfile(f); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + defer pprof.StopCPUProfile() + + // Spin for long enough to collect some samples. + start := time.Now() + for time.Since(start) < time.Second { + } +} diff --git a/src/cmd/trace/annotations_test.go b/src/cmd/trace/annotations_test.go index 9c2d027366846ac9697bf75611cfb83efe8146ae..acd5693c7d5fea053190a80e9b7f174b21d60463 100644 --- a/src/cmd/trace/annotations_test.go +++ b/src/cmd/trace/annotations_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package main diff --git a/src/cmd/trace/mmu.go b/src/cmd/trace/mmu.go index b92fac652cce82f07577dcf4133dc094194c8de2..1d1fd2ea94737d3e95c0e700d120f134115330ee 100644 --- a/src/cmd/trace/mmu.go +++ b/src/cmd/trace/mmu.go @@ -283,7 +283,7 @@ var templMMU = ` .done(function(worst) { details.text('Lowest mutator utilization in ' + niceDuration(windowNS) + ' windows:'); for (var i = 0; i < worst.length; i++) { - details.append($('
    ')); + details.append($('
    ')); var text = worst[i].MutatorUtil.toFixed(3) + ' at time ' + niceDuration(worst[i].Time); details.append($('').text(text).attr('href', worst[i].URL)); } @@ -328,27 +328,27 @@ var templMMU = `
    Loading plot...

    - View
    + View
    - ?Consider whole system utilization. For example, if one of four procs is available to the mutator, mutator utilization will be 0.25. This is the standard definition of an MMU.
    + ?Consider whole system utilization. For example, if one of four procs is available to the mutator, mutator utilization will be 0.25. This is the standard definition of an MMU.
    - ?Consider per-goroutine utilization. When even one goroutine is interrupted by GC, mutator utilization is 0.
    + ?Consider per-goroutine utilization. When even one goroutine is interrupted by GC, mutator utilization is 0.

    - Include
    + Include
    - ?Stop-the-world stops all goroutines simultaneously.
    + ?Stop-the-world stops all goroutines simultaneously.
    - ?Background workers are GC-specific goroutines. 25% of the CPU is dedicated to background workers during GC.
    + ?Background workers are GC-specific goroutines. 25% of the CPU is dedicated to background workers during GC.
    - ?Mark assists are performed by allocation to prevent the mutator from outpacing GC.
    + ?Mark assists are performed by allocation to prevent the mutator from outpacing GC.
    - ?Sweep reclaims unused memory between GCs. (Enabling this may be very slow.).
    + ?Sweep reclaims unused memory between GCs. (Enabling this may be very slow.).

    - Display
    + Display
    - ?Display percentile mutator utilization in addition to minimum. E.g., p99 MU drops the worst 1% of windows.
    + ?Display percentile mutator utilization in addition to minimum. E.g., p99 MU drops the worst 1% of windows.

    diff --git a/src/cmd/trace/trace.go b/src/cmd/trace/trace.go index 30c80f0e047a75721eea76360066aba46bd386e0..ca10736c32030b4ce24fb2578c50028cfdca4035 100644 --- a/src/cmd/trace/trace.go +++ b/src/cmd/trace/trace.go @@ -627,7 +627,7 @@ func generateTrace(params *traceParams, consumer traceConsumer) error { } case trace.EvHeapAlloc: ctx.heapStats.heapAlloc = ev.Args[0] - case trace.EvNextGC: + case trace.EvHeapGoal: ctx.heapStats.nextGC = ev.Args[0] } if setGStateErr != nil { diff --git a/src/cmd/trace/trace_test.go b/src/cmd/trace/trace_test.go index ea0cc6f88004349b678bf594b194746f811f937d..2b1a68d7f3d796ec7e454db3694caf1616783744 100644 --- a/src/cmd/trace/trace_test.go +++ b/src/cmd/trace/trace_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package main diff --git a/src/cmd/trace/trace_unix_test.go b/src/cmd/trace/trace_unix_test.go index c569b40bb244cb37150eab0f124a2902abfa6b8f..8dc56a8c7be96c3fc93341b09c25eafd13ab875d 100644 --- a/src/cmd/trace/trace_unix_test.go +++ b/src/cmd/trace/trace_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris // +build darwin dragonfly freebsd linux netbsd openbsd solaris package main diff --git a/src/cmd/vendor/github.com/google/pprof/driver/driver.go b/src/cmd/vendor/github.com/google/pprof/driver/driver.go index e65bc2f417d813da273db45361ca668edeaa10cf..fc05f919baf7ae940be7b8c36a094b8810ee8be1 100644 --- a/src/cmd/vendor/github.com/google/pprof/driver/driver.go +++ b/src/cmd/vendor/github.com/google/pprof/driver/driver.go @@ -159,8 +159,8 @@ type ObjFile interface { // Name returns the underlying file name, if available. Name() string - // Base returns the base address to use when looking up symbols in the file. - Base() uint64 + // ObjAddr returns the objdump address corresponding to a runtime address. + ObjAddr(addr uint64) (uint64, error) // BuildID returns the GNU build ID of the file, or an empty string. BuildID() string diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner.go index c0661bf4aa9a3c640f6e3046b41075fffb340096..0c702398d3a23fe9768cdbbf6e4b672af24c76d2 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner.go @@ -70,7 +70,11 @@ func (a *addr2LinerJob) write(s string) error { } func (a *addr2LinerJob) readLine() (string, error) { - return a.out.ReadString('\n') + s, err := a.out.ReadString('\n') + if err != nil { + return "", err + } + return strings.TrimSpace(s), nil } // close releases any resources used by the addr2liner object. @@ -115,19 +119,11 @@ func newAddr2Liner(cmd, file string, base uint64) (*addr2Liner, error) { return a, nil } -func (d *addr2Liner) readString() (string, error) { - s, err := d.rw.readLine() - if err != nil { - return "", err - } - return strings.TrimSpace(s), nil -} - // readFrame parses the addr2line output for a single address. It // returns a populated plugin.Frame and whether it has reached the end of the // data. func (d *addr2Liner) readFrame() (plugin.Frame, bool) { - funcname, err := d.readString() + funcname, err := d.rw.readLine() if err != nil { return plugin.Frame{}, true } @@ -135,12 +131,12 @@ func (d *addr2Liner) readFrame() (plugin.Frame, bool) { // If addr2line returns a hex address we can assume it is the // sentinel. Read and ignore next two lines of output from // addr2line - d.readString() - d.readString() + d.rw.readLine() + d.rw.readLine() return plugin.Frame{}, true } - fileline, err := d.readString() + fileline, err := d.rw.readLine() if err != nil { return plugin.Frame{}, true } @@ -186,7 +182,7 @@ func (d *addr2Liner) rawAddrInfo(addr uint64) ([]plugin.Frame, error) { return nil, err } - resp, err := d.readString() + resp, err := d.rw.readLine() if err != nil { return nil, err } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go index 68fa5593ad1c80f01267472c325c444e0bc656d8..24c48e649b8875ff0dd0f7fb753a4a8098456911 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go @@ -43,15 +43,21 @@ type llvmSymbolizerJob struct { cmd *exec.Cmd in io.WriteCloser out *bufio.Reader + // llvm-symbolizer requires the symbol type, CODE or DATA, for symbolization. + symType string } func (a *llvmSymbolizerJob) write(s string) error { - _, err := fmt.Fprint(a.in, s+"\n") + _, err := fmt.Fprintln(a.in, a.symType, s) return err } func (a *llvmSymbolizerJob) readLine() (string, error) { - return a.out.ReadString('\n') + s, err := a.out.ReadString('\n') + if err != nil { + return "", err + } + return strings.TrimSpace(s), nil } // close releases any resources used by the llvmSymbolizer object. @@ -64,13 +70,17 @@ func (a *llvmSymbolizerJob) close() { // information about the given executable file. If file is a shared // library, base should be the address at which it was mapped in the // program under consideration. -func newLLVMSymbolizer(cmd, file string, base uint64) (*llvmSymbolizer, error) { +func newLLVMSymbolizer(cmd, file string, base uint64, isData bool) (*llvmSymbolizer, error) { if cmd == "" { cmd = defaultLLVMSymbolizer } j := &llvmSymbolizerJob{ - cmd: exec.Command(cmd, "-inlining", "-demangle=false"), + cmd: exec.Command(cmd, "-inlining", "-demangle=false"), + symType: "CODE", + } + if isData { + j.symType = "DATA" } var err error @@ -97,19 +107,11 @@ func newLLVMSymbolizer(cmd, file string, base uint64) (*llvmSymbolizer, error) { return a, nil } -func (d *llvmSymbolizer) readString() (string, error) { - s, err := d.rw.readLine() - if err != nil { - return "", err - } - return strings.TrimSpace(s), nil -} - // readFrame parses the llvm-symbolizer output for a single address. It // returns a populated plugin.Frame and whether it has reached the end of the // data. func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) { - funcname, err := d.readString() + funcname, err := d.rw.readLine() if err != nil { return plugin.Frame{}, true } @@ -121,13 +123,17 @@ func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) { funcname = "" } - fileline, err := d.readString() + fileline, err := d.rw.readLine() if err != nil { return plugin.Frame{Func: funcname}, true } linenumber := 0 - if fileline == "??:0" { + // The llvm-symbolizer outputs the ::. + // When it cannot identify the source code location, it outputs "??:0:0". + // Older versions output just the filename and line number, so we check for + // both conditions here. + if fileline == "??:0" || fileline == "??:0:0" { fileline = "" } else { switch split := strings.Split(fileline, ":"); len(split) { diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_nm.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_nm.go index 1987bd3dabab36b58837b0b795a988b0d737bc5e..8e0ccc728d5fde14501690555fda6374bbd07361 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_nm.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_nm.go @@ -29,27 +29,42 @@ const ( defaultNM = "nm" ) -// addr2LinerNM is a connection to an nm command for obtaining address +// addr2LinerNM is a connection to an nm command for obtaining symbol // information from a binary. type addr2LinerNM struct { - m []symbolInfo // Sorted list of addresses from binary. + m []symbolInfo // Sorted list of symbol addresses from binary. } type symbolInfo struct { address uint64 + size uint64 name string + symType string } -// newAddr2LinerNM starts the given nm command reporting information about the -// given executable file. If file is a shared library, base should be -// the address at which it was mapped in the program under -// consideration. +// isData returns if the symbol has a known data object symbol type. +func (s *symbolInfo) isData() bool { + // The following symbol types are taken from https://linux.die.net/man/1/nm: + // Lowercase letter means local symbol, uppercase denotes a global symbol. + // - b or B: the symbol is in the uninitialized data section, e.g. .bss; + // - d or D: the symbol is in the initialized data section; + // - r or R: the symbol is in a read only data section; + // - v or V: the symbol is a weak object; + // - W: the symbol is a weak symbol that has not been specifically tagged as a + // weak object symbol. Experiments with some binaries, showed these to be + // mostly data objects. + return strings.ContainsAny(s.symType, "bBdDrRvVW") +} + +// newAddr2LinerNM starts the given nm command reporting information about the +// given executable file. If file is a shared library, base should be the +// address at which it was mapped in the program under consideration. func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) { if cmd == "" { cmd = defaultNM } var b bytes.Buffer - c := exec.Command(cmd, "-n", file) + c := exec.Command(cmd, "--numeric-sort", "--print-size", "--format=posix", file) c.Stdout = &b if err := c.Run(); err != nil { return nil, err @@ -74,17 +89,23 @@ func parseAddr2LinerNM(base uint64, nm io.Reader) (*addr2LinerNM, error) { return nil, err } line = strings.TrimSpace(line) - fields := strings.SplitN(line, " ", 3) - if len(fields) != 3 { + fields := strings.Split(line, " ") + if len(fields) != 4 { + continue + } + address, err := strconv.ParseUint(fields[2], 16, 64) + if err != nil { continue } - address, err := strconv.ParseUint(fields[0], 16, 64) + size, err := strconv.ParseUint(fields[3], 16, 64) if err != nil { continue } a.m = append(a.m, symbolInfo{ address: address + base, - name: fields[2], + size: size, + name: fields[0], + symType: fields[1], }) } @@ -94,7 +115,7 @@ func parseAddr2LinerNM(base uint64, nm io.Reader) (*addr2LinerNM, error) { // addrInfo returns the stack frame information for a specific program // address. It returns nil if the address could not be identified. func (a *addr2LinerNM) addrInfo(addr uint64) ([]plugin.Frame, error) { - if len(a.m) == 0 || addr < a.m[0].address || addr > a.m[len(a.m)-1].address { + if len(a.m) == 0 || addr < a.m[0].address || addr >= (a.m[len(a.m)-1].address+a.m[len(a.m)-1].size) { return nil, nil } @@ -113,12 +134,11 @@ func (a *addr2LinerNM) addrInfo(addr uint64) ([]plugin.Frame, error) { } } - // Address is between a.m[low] and a.m[high]. - // Pick low, as it represents [low, high). - f := []plugin.Frame{ - { - Func: a.m[low].name, - }, + // Address is between a.m[low] and a.m[high]. Pick low, as it represents + // [low, high). For data symbols, we use a strict check that the address is in + // the [start, start + size) range of a.m[low]. + if a.m[low].isData() && addr >= (a.m[low].address+a.m[low].size) { + return nil, nil } - return f, nil + return []plugin.Frame{{Func: a.m[low].name}}, nil } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go index 4b67cc4ab05314f76ad6145c0fdf7c483825837f..5ed8a1f9f1e3823c884a45c3bdb78055f86184dd 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go @@ -18,6 +18,7 @@ package binutils import ( "debug/elf" "debug/macho" + "debug/pe" "encoding/binary" "errors" "fmt" @@ -41,7 +42,12 @@ type Binutils struct { rep *binrep } -var objdumpLLVMVerRE = regexp.MustCompile(`LLVM version (?:(\d*)\.(\d*)\.(\d*)|.*(trunk).*)`) +var ( + objdumpLLVMVerRE = regexp.MustCompile(`LLVM version (?:(\d*)\.(\d*)\.(\d*)|.*(trunk).*)`) + + // Defined for testing + elfOpen = elf.Open +) // binrep is an immutable representation for Binutils. It is atomically // replaced on every mutation to provide thread-safe access. @@ -255,7 +261,7 @@ func (bu *Binutils) Disasm(file string, start, end uint64, intelSyntax bool) ([] if !b.objdumpFound { return nil, errors.New("cannot disasm: no objdump tool available") } - args := []string{"--disassemble-all", "--demangle", "--no-show-raw-insn", + args := []string{"--disassemble", "--demangle", "--no-show-raw-insn", "--line-numbers", fmt.Sprintf("--start-address=%#x", start), fmt.Sprintf("--stop-address=%#x", end)} @@ -337,6 +343,15 @@ func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFi return f, nil } + peMagic := string(header[:2]) + if peMagic == "MZ" { + f, err := b.openPE(name, start, limit, offset) + if err != nil { + return nil, fmt.Errorf("error reading PE file %s: %v", name, err) + } + return f, nil + } + return nil, fmt.Errorf("unrecognized binary format: %s", name) } @@ -411,14 +426,23 @@ func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.Obj } func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) { - ef, err := elf.Open(name) + ef, err := elfOpen(name) if err != nil { return nil, fmt.Errorf("error parsing %s: %v", name, err) } defer ef.Close() - var stextOffset *uint64 - var pageAligned = func(addr uint64) bool { return addr%4096 == 0 } + buildID := "" + if f, err := os.Open(name); err == nil { + if id, err := elfexec.GetBuildID(f); err == nil { + buildID = fmt.Sprintf("%x", id) + } + } + + var ( + stextOffset *uint64 + pageAligned = func(addr uint64) bool { return addr%4096 == 0 } + ) if strings.Contains(name, "vmlinux") || !pageAligned(start) || !pageAligned(limit) || !pageAligned(offset) { // Reading all Symbols is expensive, and we only rarely need it so // we don't want to do it every time. But if _stext happens to be @@ -440,37 +464,171 @@ func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFi } } - base, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset) - if err != nil { + // Check that we can compute a base for the binary. This may not be the + // correct base value, so we don't save it. We delay computing the actual base + // value until we have a sample address for this mapping, so that we can + // correctly identify the associated program segment that is needed to compute + // the base. + if _, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset); err != nil { return nil, fmt.Errorf("could not identify base for %s: %v", name, err) } - buildID := "" - if f, err := os.Open(name); err == nil { - if id, err := elfexec.GetBuildID(f); err == nil { - buildID = fmt.Sprintf("%x", id) - } + if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) { + return &fileNM{file: file{ + b: b, + name: name, + buildID: buildID, + m: &elfMapping{start: start, limit: limit, offset: offset, stextOffset: stextOffset}, + }}, nil + } + return &fileAddr2Line{file: file{ + b: b, + name: name, + buildID: buildID, + m: &elfMapping{start: start, limit: limit, offset: offset, stextOffset: stextOffset}, + }}, nil +} + +func (b *binrep) openPE(name string, start, limit, offset uint64) (plugin.ObjFile, error) { + pf, err := pe.Open(name) + if err != nil { + return nil, fmt.Errorf("error parsing %s: %v", name, err) + } + defer pf.Close() + + var imageBase uint64 + switch h := pf.OptionalHeader.(type) { + case *pe.OptionalHeader32: + imageBase = uint64(h.ImageBase) + case *pe.OptionalHeader64: + imageBase = uint64(h.ImageBase) + default: + return nil, fmt.Errorf("unknown OptionalHeader %T", pf.OptionalHeader) + } + + var base uint64 + if start > 0 { + base = start - imageBase } if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) { - return &fileNM{file: file{b, name, base, buildID}}, nil + return &fileNM{file: file{b: b, name: name, base: base}}, nil } - return &fileAddr2Line{file: file{b, name, base, buildID}}, nil + return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil +} + +// elfMapping stores the parameters of a runtime mapping that are needed to +// identify the ELF segment associated with a mapping. +type elfMapping struct { + // Runtime mapping parameters. + start, limit, offset uint64 + // Offset of _stext symbol. Only defined for kernel images, nil otherwise. + stextOffset *uint64 } // file implements the binutils.ObjFile interface. type file struct { b *binrep name string - base uint64 buildID string + + baseOnce sync.Once // Ensures the base, baseErr and isData are computed once. + base uint64 + baseErr error // Any eventual error while computing the base. + isData bool + // Mapping information. Relevant only for ELF files, nil otherwise. + m *elfMapping +} + +// computeBase computes the relocation base for the given binary file only if +// the elfMapping field is set. It populates the base and isData fields and +// returns an error. +func (f *file) computeBase(addr uint64) error { + if f == nil || f.m == nil { + return nil + } + if addr < f.m.start || addr >= f.m.limit { + return fmt.Errorf("specified address %x is outside the mapping range [%x, %x] for file %q", addr, f.m.start, f.m.limit, f.name) + } + ef, err := elfOpen(f.name) + if err != nil { + return fmt.Errorf("error parsing %s: %v", f.name, err) + } + defer ef.Close() + + var ph *elf.ProgHeader + // For user space executables, find the actual program segment that is + // associated with the given mapping. Skip this search if limit <= start. + // We cannot use just a check on the start address of the mapping to tell if + // it's a kernel / .ko module mapping, because with quipper address remapping + // enabled, the address would be in the lower half of the address space. + if f.m.stextOffset == nil && f.m.start < f.m.limit && f.m.limit < (uint64(1)<<63) { + // Get all program headers associated with the mapping. + headers, hasLoadables := elfexec.ProgramHeadersForMapping(ef, f.m.offset, f.m.limit-f.m.start) + + // Some ELF files don't contain any loadable program segments, e.g. .ko + // kernel modules. It's not an error to have no header in such cases. + if hasLoadables { + ph, err = matchUniqueHeader(headers, addr-f.m.start+f.m.offset) + if err != nil { + return fmt.Errorf("failed to find program header for file %q, ELF mapping %#v, address %x: %v", f.name, *f.m, addr, err) + } + } + } else { + // For the kernel, find the program segment that includes the .text section. + ph = elfexec.FindTextProgHeader(ef) + } + + base, err := elfexec.GetBase(&ef.FileHeader, ph, f.m.stextOffset, f.m.start, f.m.limit, f.m.offset) + if err != nil { + return err + } + f.base = base + f.isData = ph != nil && ph.Flags&elf.PF_X == 0 + return nil +} + +// matchUniqueHeader attempts to identify a unique header from the given list, +// using the given file offset to disambiguate between multiple segments. It +// returns an error if the header list is empty or if it cannot identify a +// unique header. +func matchUniqueHeader(headers []*elf.ProgHeader, fileOffset uint64) (*elf.ProgHeader, error) { + if len(headers) == 0 { + return nil, errors.New("no program header matches mapping info") + } + if len(headers) == 1 { + // Don't use the file offset if we already have a single header. + return headers[0], nil + } + // We have multiple input segments. Attempt to identify a unique one + // based on the given file offset. + var ph *elf.ProgHeader + for _, h := range headers { + if fileOffset >= h.Off && fileOffset < h.Off+h.Memsz { + if ph != nil { + // Assuming no other bugs, this can only happen if we have two or + // more small program segments that fit on the same page, and a + // segment other than the last one includes uninitialized data. + return nil, fmt.Errorf("found second program header (%#v) that matches file offset %x, first program header is %#v. Does first program segment contain uninitialized data?", *h, fileOffset, *ph) + } + ph = h + } + } + if ph == nil { + return nil, fmt.Errorf("no program header matches file offset %x", fileOffset) + } + return ph, nil } func (f *file) Name() string { return f.name } -func (f *file) Base() uint64 { - return f.base +func (f *file) ObjAddr(addr uint64) (uint64, error) { + f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) }) + if f.baseErr != nil { + return 0, f.baseErr + } + return addr - f.base, nil } func (f *file) BuildID() string { @@ -478,7 +636,11 @@ func (f *file) BuildID() string { } func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) { - return []plugin.Frame{}, nil + f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) }) + if f.baseErr != nil { + return nil, f.baseErr + } + return nil, nil } func (f *file) Close() error { @@ -505,6 +667,10 @@ type fileNM struct { } func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) { + f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) }) + if f.baseErr != nil { + return nil, f.baseErr + } if f.addr2linernm == nil { addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base) if err != nil { @@ -524,9 +690,14 @@ type fileAddr2Line struct { file addr2liner *addr2Liner llvmSymbolizer *llvmSymbolizer + isData bool } func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) { + f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) }) + if f.baseErr != nil { + return nil, f.baseErr + } f.once.Do(f.init) if f.llvmSymbolizer != nil { return f.llvmSymbolizer.addrInfo(addr) @@ -538,7 +709,7 @@ func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) { } func (f *fileAddr2Line) init() { - if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base); err == nil { + if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base, f.isData); err == nil { f.llvmSymbolizer = llvmSymbolizer return } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go index d0be614bdc4186e4569ba3e45e5f59dfc9b0eb87..e64adf58cd6bd4e3698b0181f870894e40e2a66a 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go @@ -19,6 +19,7 @@ import ( "io" "regexp" "strconv" + "strings" "github.com/google/pprof/internal/plugin" "github.com/ianlancetaylor/demangle" @@ -121,6 +122,7 @@ func disassemble(asm []byte) ([]plugin.Inst, error) { break } } + input = strings.TrimSpace(input) if fields := objdumpAsmOutputRE.FindStringSubmatch(input); len(fields) == 3 { if address, err := strconv.ParseUint(fields[1], 16, 64); err == nil { @@ -167,6 +169,7 @@ func nextSymbol(buf *bytes.Buffer) (uint64, string, error) { return 0, "", err } } + line = strings.TrimSpace(line) if fields := nmOutputRE.FindStringSubmatch(line); len(fields) == 4 { if address, err := strconv.ParseUint(fields[1], 16, 64); err == nil { diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go index 878f2e1ead156a4cda6c9ad68f9a0f91e9ef921e..3967a12d45affbb2ec4c818da69f7fa881382130 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go @@ -163,7 +163,7 @@ func applyCommandOverrides(cmd string, outputFormat int, cfg config) config { trim := cfg.Trim switch cmd { - case "disasm", "weblist": + case "disasm": trim = false cfg.Granularity = "addresses" // Force the 'noinlines' mode so that source locations for a given address @@ -172,6 +172,10 @@ func applyCommandOverrides(cmd string, outputFormat int, cfg config) config { // This is because the merge is done by address and in case of an inlined // stack each of the inlined entries is a separate callgraph node. cfg.NoInlines = true + case "weblist": + trim = false + cfg.Granularity = "addresses" + cfg.NoInlines = false // Need inline info to support call expansion case "peek": trim = false case "list": diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go index 4f7610c7e5475693886c1eabb40c85577af7f23e..b8e8b50b94d63b9eb85ff381f7029c76523ce871 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go @@ -62,6 +62,7 @@ a { .header .title h1 { font-size: 1.75em; margin-right: 1rem; + margin-bottom: 4px; } .header .title a { color: #212121; diff --git a/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go b/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go index d520765cc9100d763040fd5f3932debe37c7838a..2638b2db2d9a95a5a74eb104a389127e59f1c97e 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go @@ -283,3 +283,72 @@ func FindTextProgHeader(f *elf.File) *elf.ProgHeader { } return nil } + +// ProgramHeadersForMapping returns the loadable program segment headers that +// are fully contained in the runtime mapping with file offset pgoff and memory +// size memsz, and if the binary includes any loadable segments. +func ProgramHeadersForMapping(f *elf.File, pgoff, memsz uint64) ([]*elf.ProgHeader, bool) { + const ( + // pageSize defines the virtual memory page size used by the loader. This + // value is dependent on the memory management unit of the CPU. The page + // size is 4KB virtually on all the architectures that we care about, so we + // define this metric as a constant. If we encounter architectures where + // page sie is not 4KB, we must try to guess the page size on the system + // where the profile was collected, possibly using the architecture + // specified in the ELF file header. + pageSize = 4096 + pageOffsetMask = pageSize - 1 + pageMask = ^uint64(pageOffsetMask) + ) + var headers []*elf.ProgHeader + hasLoadables := false + for _, p := range f.Progs { + // The segment must be fully included in the mapping. + if p.Type == elf.PT_LOAD && pgoff <= p.Off && p.Off+p.Memsz <= pgoff+memsz { + alignedOffset := uint64(0) + if p.Off > (p.Vaddr & pageOffsetMask) { + alignedOffset = p.Off - (p.Vaddr & pageOffsetMask) + } + if alignedOffset <= pgoff { + headers = append(headers, &p.ProgHeader) + } + } + if p.Type == elf.PT_LOAD { + hasLoadables = true + } + } + if len(headers) < 2 { + return headers, hasLoadables + } + + // If we have more than one matching segments, try a strict check on the + // segment memory size. We use a heuristic to compute the minimum mapping size + // required for a segment, assuming mappings are page aligned. + // The memory size based heuristic makes sense only if the mapping size is a + // multiple of page size. + if memsz%pageSize != 0 { + return headers, hasLoadables + } + + // Return all found headers if we cannot narrow the selection to a single + // program segment. + var ph *elf.ProgHeader + for _, h := range headers { + wantSize := (h.Vaddr+h.Memsz+pageSize-1)&pageMask - (h.Vaddr & pageMask) + if wantSize != memsz { + continue + } + if ph != nil { + // Found a second program header matching, so return all previously + // identified headers. + return headers, hasLoadables + } + ph = h + } + if ph == nil { + // No matching header for the strict check. Return all previously identified + // headers. + return headers, hasLoadables + } + return []*elf.ProgHeader{ph}, hasLoadables +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/graph/dotgraph.go b/src/cmd/vendor/github.com/google/pprof/internal/graph/dotgraph.go index 8cb87da9af93b6496b461a808d13b44b0efcda14..800867524848d41e6adf7c902402a30b2fc71c30 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/graph/dotgraph.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/graph/dotgraph.go @@ -322,8 +322,8 @@ func (b *builder) addEdge(edge *Edge, from, to int, hasNodelets bool) { } // dotColor returns a color for the given score (between -1.0 and -// 1.0), with -1.0 colored red, 0.0 colored grey, and 1.0 colored -// green. If isBackground is true, then a light (low-saturation) +// 1.0), with -1.0 colored green, 0.0 colored grey, and 1.0 colored +// red. If isBackground is true, then a light (low-saturation) // color is returned (suitable for use as a background color); // otherwise, a darker color is returned (suitable for use as a // foreground color). diff --git a/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go b/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go index e95b261bc2507257eab7362ee758923570c04ed5..53325740a3ed8438fa5eb5d29681ab997e34da69 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go @@ -111,8 +111,9 @@ func compatibleValueTypes(v1, v2 *profile.ValueType) bool { } return v1.Unit == v2.Unit || - (isTimeUnit(v1.Unit) && isTimeUnit(v2.Unit)) || - (isMemoryUnit(v1.Unit) && isMemoryUnit(v2.Unit)) + (timeUnits.sniffUnit(v1.Unit) != nil && timeUnits.sniffUnit(v2.Unit) != nil) || + (memoryUnits.sniffUnit(v1.Unit) != nil && memoryUnits.sniffUnit(v2.Unit) != nil) || + (gcuUnits.sniffUnit(v1.Unit) != nil && gcuUnits.sniffUnit(v2.Unit) != nil) } // Scale a measurement from an unit to a different unit and returns @@ -124,12 +125,15 @@ func Scale(value int64, fromUnit, toUnit string) (float64, string) { v, u := Scale(-value, fromUnit, toUnit) return -v, u } - if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok { + if m, u, ok := memoryUnits.convertUnit(value, fromUnit, toUnit); ok { return m, u } - if t, u, ok := timeLabel(value, fromUnit, toUnit); ok { + if t, u, ok := timeUnits.convertUnit(value, fromUnit, toUnit); ok { return t, u } + if g, u, ok := gcuUnits.convertUnit(value, fromUnit, toUnit); ok { + return g, u + } // Skip non-interesting units. switch toUnit { case "count", "sample", "unit", "minimum", "auto": @@ -172,157 +176,121 @@ func Percentage(value, total int64) string { } } -// isMemoryUnit returns whether a name is recognized as a memory size -// unit. -func isMemoryUnit(unit string) bool { - switch strings.TrimSuffix(strings.ToLower(unit), "s") { - case "byte", "b", "kilobyte", "kb", "megabyte", "mb", "gigabyte", "gb": - return true - } - return false +// unit includes a list of aliases representing a specific unit and a factor +// which one can multiple a value in the specified unit by to get the value +// in terms of the base unit. +type unit struct { + canonicalName string + aliases []string + factor float64 } -func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) { - fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s") - toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s") - - switch fromUnit { - case "byte", "b": - case "kb", "kbyte", "kilobyte": - value *= 1024 - case "mb", "mbyte", "megabyte": - value *= 1024 * 1024 - case "gb", "gbyte", "gigabyte": - value *= 1024 * 1024 * 1024 - case "tb", "tbyte", "terabyte": - value *= 1024 * 1024 * 1024 * 1024 - case "pb", "pbyte", "petabyte": - value *= 1024 * 1024 * 1024 * 1024 * 1024 - default: - return 0, "", false - } +// unitType includes a list of units that are within the same category (i.e. +// memory or time units) and a default unit to use for this type of unit. +type unitType struct { + defaultUnit unit + units []unit +} - if toUnit == "minimum" || toUnit == "auto" { - switch { - case value < 1024: - toUnit = "b" - case value < 1024*1024: - toUnit = "kb" - case value < 1024*1024*1024: - toUnit = "mb" - case value < 1024*1024*1024*1024: - toUnit = "gb" - case value < 1024*1024*1024*1024*1024: - toUnit = "tb" - default: - toUnit = "pb" +// findByAlias returns the unit associated with the specified alias. It returns +// nil if the unit with such alias is not found. +func (ut unitType) findByAlias(alias string) *unit { + for _, u := range ut.units { + for _, a := range u.aliases { + if alias == a { + return &u + } } } - - var output float64 - switch toUnit { - default: - output, toUnit = float64(value), "B" - case "kb", "kbyte", "kilobyte": - output, toUnit = float64(value)/1024, "kB" - case "mb", "mbyte", "megabyte": - output, toUnit = float64(value)/(1024*1024), "MB" - case "gb", "gbyte", "gigabyte": - output, toUnit = float64(value)/(1024*1024*1024), "GB" - case "tb", "tbyte", "terabyte": - output, toUnit = float64(value)/(1024*1024*1024*1024), "TB" - case "pb", "pbyte", "petabyte": - output, toUnit = float64(value)/(1024*1024*1024*1024*1024), "PB" - } - return output, toUnit, true + return nil } -// isTimeUnit returns whether a name is recognized as a time unit. -func isTimeUnit(unit string) bool { +// sniffUnit simpifies the input alias and returns the unit associated with the +// specified alias. It returns nil if the unit with such alias is not found. +func (ut unitType) sniffUnit(unit string) *unit { unit = strings.ToLower(unit) if len(unit) > 2 { unit = strings.TrimSuffix(unit, "s") } - - switch unit { - case "nanosecond", "ns", "microsecond", "millisecond", "ms", "s", "second", "sec", "hr", "day", "week", "year": - return true - } - return false + return ut.findByAlias(unit) } -func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) { - fromUnit = strings.ToLower(fromUnit) - if len(fromUnit) > 2 { - fromUnit = strings.TrimSuffix(fromUnit, "s") +// autoScale takes in the value with units of the base unit and returns +// that value scaled to a reasonable unit if a reasonable unit is +// found. +func (ut unitType) autoScale(value float64) (float64, string, bool) { + var f float64 + var unit string + for _, u := range ut.units { + if u.factor >= f && (value/u.factor) >= 1.0 { + f = u.factor + unit = u.canonicalName + } } - - toUnit = strings.ToLower(toUnit) - if len(toUnit) > 2 { - toUnit = strings.TrimSuffix(toUnit, "s") + if f == 0 { + return 0, "", false } + return value / f, unit, true +} - var d time.Duration - switch fromUnit { - case "nanosecond", "ns": - d = time.Duration(value) * time.Nanosecond - case "microsecond": - d = time.Duration(value) * time.Microsecond - case "millisecond", "ms": - d = time.Duration(value) * time.Millisecond - case "second", "sec", "s": - d = time.Duration(value) * time.Second - case "cycle": - return float64(value), "", true - default: +// convertUnit converts a value from the fromUnit to the toUnit, autoscaling +// the value if the toUnit is "minimum" or "auto". If the fromUnit is not +// included in the unitType, then a false boolean will be returned. If the +// toUnit is not in the unitType, the value will be returned in terms of the +// default unitType. +func (ut unitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (float64, string, bool) { + fromUnit := ut.sniffUnit(fromUnitStr) + if fromUnit == nil { return 0, "", false } - - if toUnit == "minimum" || toUnit == "auto" { - switch { - case d < 1*time.Microsecond: - toUnit = "ns" - case d < 1*time.Millisecond: - toUnit = "us" - case d < 1*time.Second: - toUnit = "ms" - case d < 1*time.Minute: - toUnit = "sec" - case d < 1*time.Hour: - toUnit = "min" - case d < 24*time.Hour: - toUnit = "hour" - case d < 15*24*time.Hour: - toUnit = "day" - case d < 120*24*time.Hour: - toUnit = "week" - default: - toUnit = "year" + v := float64(value) * fromUnit.factor + if toUnitStr == "minimum" || toUnitStr == "auto" { + if v, u, ok := ut.autoScale(v); ok { + return v, u, true } + return v / ut.defaultUnit.factor, ut.defaultUnit.canonicalName, true } - - var output float64 - dd := float64(d) - switch toUnit { - case "ns", "nanosecond": - output, toUnit = dd/float64(time.Nanosecond), "ns" - case "us", "microsecond": - output, toUnit = dd/float64(time.Microsecond), "us" - case "ms", "millisecond": - output, toUnit = dd/float64(time.Millisecond), "ms" - case "min", "minute": - output, toUnit = dd/float64(time.Minute), "mins" - case "hour", "hr": - output, toUnit = dd/float64(time.Hour), "hrs" - case "day": - output, toUnit = dd/float64(24*time.Hour), "days" - case "week", "wk": - output, toUnit = dd/float64(7*24*time.Hour), "wks" - case "year", "yr": - output, toUnit = dd/float64(365*24*time.Hour), "yrs" - default: - // "sec", "second", "s" handled by default case. - output, toUnit = dd/float64(time.Second), "s" + toUnit := ut.sniffUnit(toUnitStr) + if toUnit == nil { + return v / ut.defaultUnit.factor, ut.defaultUnit.canonicalName, true } - return output, toUnit, true + return v / toUnit.factor, toUnit.canonicalName, true +} + +var memoryUnits = unitType{ + units: []unit{ + {"B", []string{"b", "byte"}, 1}, + {"kB", []string{"kb", "kbyte", "kilobyte"}, float64(1 << 10)}, + {"MB", []string{"mb", "mbyte", "megabyte"}, float64(1 << 20)}, + {"GB", []string{"gb", "gbyte", "gigabyte"}, float64(1 << 30)}, + {"TB", []string{"tb", "tbyte", "terabyte"}, float64(1 << 40)}, + {"PB", []string{"pb", "pbyte", "petabyte"}, float64(1 << 50)}, + }, + defaultUnit: unit{"B", []string{"b", "byte"}, 1}, +} + +var timeUnits = unitType{ + units: []unit{ + {"ns", []string{"ns", "nanosecond"}, float64(time.Nanosecond)}, + {"us", []string{"μs", "us", "microsecond"}, float64(time.Microsecond)}, + {"ms", []string{"ms", "millisecond"}, float64(time.Millisecond)}, + {"s", []string{"s", "sec", "second"}, float64(time.Second)}, + {"hrs", []string{"hour", "hr"}, float64(time.Hour)}, + }, + defaultUnit: unit{"s", []string{}, float64(time.Second)}, +} + +var gcuUnits = unitType{ + units: []unit{ + {"n*GCU", []string{"nanogcu"}, 1e-9}, + {"u*GCU", []string{"microgcu"}, 1e-6}, + {"m*GCU", []string{"milligcu"}, 1e-3}, + {"GCU", []string{"gcu"}, 1}, + {"k*GCU", []string{"kilogcu"}, 1e3}, + {"M*GCU", []string{"megagcu"}, 1e6}, + {"G*GCU", []string{"gigagcu"}, 1e9}, + {"T*GCU", []string{"teragcu"}, 1e12}, + {"P*GCU", []string{"petagcu"}, 1e15}, + }, + defaultUnit: unit{"GCU", []string{}, 1.0}, } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go b/src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go index 3a8d0af7305fe7879fa9f8c2722ed35777780531..a57a0b20a96256f6d887105f76dc7e55a7c51f0e 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go @@ -131,8 +131,9 @@ type ObjFile interface { // Name returns the underlyinf file name, if available Name() string - // Base returns the base address to use when looking up symbols in the file. - Base() uint64 + // ObjAddr returns the objdump (linker) address corresponding to a runtime + // address, and an error. + ObjAddr(addr uint64) (uint64, error) // BuildID returns the GNU build ID of the file, or an empty string. BuildID() string diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/report.go b/src/cmd/vendor/github.com/google/pprof/internal/report/report.go index bc5685d61e1b16eec1c4516198b518126092634a..4a86554880132d11fa44384b1c95bfc88326d343 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/report.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/report.go @@ -445,7 +445,7 @@ func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) e return err } - ns := annotateAssembly(insts, sns, s.base) + ns := annotateAssembly(insts, sns, s.file) fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0]) for _, name := range s.sym.Name[1:] { @@ -534,7 +534,6 @@ func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regex addr = *address } msyms, err := f.Symbols(rx, addr) - base := f.Base() f.Close() if err != nil { continue @@ -543,7 +542,6 @@ func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regex objSyms = append(objSyms, &objSymbol{ sym: ms, - base: base, file: f, }, ) @@ -558,7 +556,6 @@ func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regex // added to correspond to sample addresses type objSymbol struct { sym *plugin.Sym - base uint64 file plugin.ObjFile } @@ -578,8 +575,7 @@ func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.N for _, s := range symbols { // Gather samples for this symbol. for _, n := range ns { - address := n.Info.Address - s.base - if address >= s.sym.Start && address < s.sym.End { + if address, err := s.file.ObjAddr(n.Info.Address); err == nil && address >= s.sym.Start && address < s.sym.End { symNodes[s] = append(symNodes[s], n) } } @@ -621,7 +617,7 @@ func (a *assemblyInstruction) cumValue() int64 { // annotateAssembly annotates a set of assembly instructions with a // set of samples. It returns a set of nodes to display. base is an // offset to adjust the sample addresses. -func annotateAssembly(insts []plugin.Inst, samples graph.Nodes, base uint64) []assemblyInstruction { +func annotateAssembly(insts []plugin.Inst, samples graph.Nodes, file plugin.ObjFile) []assemblyInstruction { // Add end marker to simplify printing loop. insts = append(insts, plugin.Inst{ Addr: ^uint64(0), @@ -645,7 +641,10 @@ func annotateAssembly(insts []plugin.Inst, samples graph.Nodes, base uint64) []a // Sum all the samples until the next instruction (to account // for samples attributed to the middle of an instruction). - for next := insts[ix+1].Addr; s < len(samples) && samples[s].Info.Address-base < next; s++ { + for next := insts[ix+1].Addr; s < len(samples); s++ { + if addr, err := file.ObjAddr(samples[s].Info.Address); err != nil || addr >= next { + break + } sample := samples[s] n.flatDiv += sample.FlatDiv n.flat += sample.Flat diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/source.go b/src/cmd/vendor/github.com/google/pprof/internal/report/source.go index b48053543909af86de3ee57aa6464348fb54b645..54245e5f9ea6a0f2848f6a0b4a063a52bffd03e3 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/source.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/source.go @@ -24,12 +24,15 @@ import ( "io" "os" "path/filepath" + "regexp" + "sort" "strconv" "strings" "github.com/google/pprof/internal/graph" "github.com/google/pprof/internal/measurement" "github.com/google/pprof/internal/plugin" + "github.com/google/pprof/profile" ) // printSource prints an annotated source listing, include all @@ -126,191 +129,620 @@ func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error { return nil } +// sourcePrinter holds state needed for generating source+asm HTML listing. +type sourcePrinter struct { + reader *sourceReader + synth *synthCode + objectTool plugin.ObjTool + objects map[string]plugin.ObjFile // Opened object files + sym *regexp.Regexp // May be nil + files map[string]*sourceFile // Set of files to print. + insts map[uint64]instructionInfo // Instructions of interest (keyed by address). + + // Set of function names that we are interested in (because they had + // a sample and match sym). + interest map[string]bool + + // Mapping from system function names to printable names. + prettyNames map[string]string +} + +// addrInfo holds information for an address we are interested in. +type addrInfo struct { + loc *profile.Location // Always non-nil + obj plugin.ObjFile // May be nil +} + +// instructionInfo holds collected information for an instruction. +type instructionInfo struct { + objAddr uint64 // Address in object file (with base subtracted out) + length int // Instruction length in bytes + disasm string // Disassembly of instruction + file string // For top-level function in which instruction occurs + line int // For top-level function in which instruction occurs + flat, cum int64 // Samples to report (divisor already applied) +} + +// sourceFile contains collected information for files we will print. +type sourceFile struct { + fname string + cum int64 + flat int64 + lines map[int][]sourceInst // Instructions to show per line + funcName map[int]string // Function name per line +} + +// sourceInst holds information for an instruction to be displayed. +type sourceInst struct { + addr uint64 + stack []callID // Inlined call-stack +} + +// sourceFunction contains information for a contiguous range of lines per function we +// will print. +type sourceFunction struct { + name string + begin, end int // Line numbers (end is not included in the range) + flat, cum int64 +} + +// addressRange is a range of addresses plus the object file that contains it. +type addressRange struct { + begin, end uint64 + obj plugin.ObjFile + mapping *profile.Mapping + score int64 // Used to order ranges for processing +} + // PrintWebList prints annotated source listing of rpt to w. +// rpt.prof should contain inlined call info. func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) error { - o := rpt.options - g := rpt.newGraph(nil) + sourcePath := rpt.options.SourcePath + if sourcePath == "" { + wd, err := os.Getwd() + if err != nil { + return fmt.Errorf("could not stat current dir: %v", err) + } + sourcePath = wd + } + sp := newSourcePrinter(rpt, obj, sourcePath) + sp.print(w, maxFiles, rpt) + sp.close() + return nil +} + +func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourcePrinter { + sp := &sourcePrinter{ + reader: newSourceReader(sourcePath, rpt.options.TrimPath), + synth: newSynthCode(rpt.prof.Mapping), + objectTool: obj, + objects: map[string]plugin.ObjFile{}, + sym: rpt.options.Symbol, + files: map[string]*sourceFile{}, + insts: map[uint64]instructionInfo{}, + prettyNames: map[string]string{}, + interest: map[string]bool{}, + } // If the regexp source can be parsed as an address, also match // functions that land on that address. var address *uint64 - if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil { - address = &hex + if sp.sym != nil { + if hex, err := strconv.ParseUint(sp.sym.String(), 0, 64); err == nil { + address = &hex + } } - sourcePath := o.SourcePath - if sourcePath == "" { - wd, err := os.Getwd() - if err != nil { - return fmt.Errorf("could not stat current dir: %v", err) + addrs := map[uint64]addrInfo{} + flat := map[uint64]int64{} + cum := map[uint64]int64{} + + // Record an interest in the function corresponding to lines[index]. + markInterest := func(addr uint64, loc *profile.Location, index int) { + fn := loc.Line[index] + if fn.Function == nil { + return + } + sp.interest[fn.Function.Name] = true + sp.interest[fn.Function.SystemName] = true + if _, ok := addrs[addr]; !ok { + addrs[addr] = addrInfo{loc, sp.objectFile(loc.Mapping)} } - sourcePath = wd } - reader := newSourceReader(sourcePath, o.TrimPath) - type fileFunction struct { - fileName, functionName string + // See if sp.sym matches line. + matches := func(line profile.Line) bool { + if line.Function == nil { + return false + } + return sp.sym.MatchString(line.Function.Name) || + sp.sym.MatchString(line.Function.SystemName) || + sp.sym.MatchString(line.Function.Filename) } - // Extract interesting symbols from binary files in the profile and - // classify samples per symbol. - symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj) - symNodes := nodesPerSymbol(g.Nodes, symbols) + // Extract sample counts and compute set of interesting functions. + for _, sample := range rpt.prof.Sample { + value := rpt.options.SampleValue(sample.Value) + if rpt.options.SampleMeanDivisor != nil { + div := rpt.options.SampleMeanDivisor(sample.Value) + if div != 0 { + value /= div + } + } + + // Find call-sites matching sym. + for i := len(sample.Location) - 1; i >= 0; i-- { + loc := sample.Location[i] + for _, line := range loc.Line { + if line.Function == nil { + continue + } + sp.prettyNames[line.Function.SystemName] = line.Function.Name + } + + addr := loc.Address + if addr == 0 { + // Some profiles are missing valid addresses. + addr = sp.synth.address(loc) + } + + cum[addr] += value + if i == 0 { + flat[addr] += value + } - // Identify sources associated to a symbol by examining - // symbol samples. Classify samples per source file. - fileNodes := make(map[fileFunction]graph.Nodes) - if len(symNodes) == 0 { - for _, n := range g.Nodes { - if n.Info.File == "" || !o.Symbol.MatchString(n.Info.Name) { + if sp.sym == nil || (address != nil && addr == *address) { + // Interested in top-level entry of stack. + if len(loc.Line) > 0 { + markInterest(addr, loc, len(loc.Line)-1) + } continue } - ff := fileFunction{n.Info.File, n.Info.Name} - fileNodes[ff] = append(fileNodes[ff], n) - } - } else { - for _, nodes := range symNodes { - for _, n := range nodes { - if n.Info.File != "" { - ff := fileFunction{n.Info.File, n.Info.Name} - fileNodes[ff] = append(fileNodes[ff], n) + + // Seach in inlined stack for a match. + matchFile := (loc.Mapping != nil && sp.sym.MatchString(loc.Mapping.File)) + for j, line := range loc.Line { + if (j == 0 && matchFile) || matches(line) { + markInterest(addr, loc, j) } } } } - if len(fileNodes) == 0 { - return fmt.Errorf("no source information for %s", o.Symbol.String()) + sp.expandAddresses(rpt, addrs, flat) + sp.initSamples(flat, cum) + return sp +} + +func (sp *sourcePrinter) close() { + for _, objFile := range sp.objects { + if objFile != nil { + objFile.Close() + } } +} + +func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]addrInfo, flat map[uint64]int64) { + // We found interesting addresses (ones with non-zero samples) above. + // Get covering address ranges and disassemble the ranges. + ranges, unprocessed := sp.splitIntoRanges(rpt.prof, addrs, flat) + sp.handleUnprocessed(addrs, unprocessed) - sourceFiles := make(graph.Nodes, 0, len(fileNodes)) - for _, nodes := range fileNodes { - sNode := *nodes[0] - sNode.Flat, sNode.Cum = nodes.Sum() - sourceFiles = append(sourceFiles, &sNode) + // Trim ranges if there are too many. + const maxRanges = 25 + sort.Slice(ranges, func(i, j int) bool { + return ranges[i].score > ranges[j].score + }) + if len(ranges) > maxRanges { + ranges = ranges[:maxRanges] } - // Limit number of files printed? - if maxFiles < 0 { - sourceFiles.Sort(graph.FileOrder) - } else { - sourceFiles.Sort(graph.FlatNameOrder) - if maxFiles < len(sourceFiles) { - sourceFiles = sourceFiles[:maxFiles] + for _, r := range ranges { + objBegin, err := r.obj.ObjAddr(r.begin) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range start %x: %v\n", r.begin, err) + continue + } + objEnd, err := r.obj.ObjAddr(r.end) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range end %x: %v\n", r.end, err) + continue + } + base := r.begin - objBegin + insts, err := sp.objectTool.Disasm(r.mapping.File, objBegin, objEnd, rpt.options.IntelSyntax) + if err != nil { + // TODO(sanjay): Report that the covered addresses are missing. + continue + } + + var lastFrames []plugin.Frame + var lastAddr, maxAddr uint64 + for i, inst := range insts { + addr := inst.Addr + base + + // Guard against duplicate output from Disasm. + if addr <= maxAddr { + continue + } + maxAddr = addr + + length := 1 + if i+1 < len(insts) && insts[i+1].Addr > inst.Addr { + // Extend to next instruction. + length = int(insts[i+1].Addr - inst.Addr) + } + + // Get inlined-call-stack for address. + frames, err := r.obj.SourceLine(addr) + if err != nil { + // Construct a frame from disassembler output. + frames = []plugin.Frame{{Func: inst.Function, File: inst.File, Line: inst.Line}} + } + + x := instructionInfo{objAddr: inst.Addr, length: length, disasm: inst.Text} + if len(frames) > 0 { + // We could consider using the outer-most caller's source + // location so we give the some hint as to where the + // inlining happened that led to this instruction. So for + // example, suppose we have the following (inlined) call + // chains for this instruction: + // F1->G->H + // F2->G->H + // We could tag the instructions from the first call with + // F1 and instructions from the second call with F2. But + // that leads to a somewhat confusing display. So for now, + // we stick with just the inner-most location (i.e., H). + // In the future we will consider changing the display to + // make caller info more visible. + index := 0 // Inner-most frame + x.file = frames[index].File + x.line = frames[index].Line + } + sp.insts[addr] = x + + // We sometimes get instructions with a zero reported line number. + // Make such instructions have the same line info as the preceding + // instruction, if an earlier instruction is found close enough. + const neighborhood = 32 + if len(frames) > 0 && frames[0].Line != 0 { + lastFrames = frames + lastAddr = addr + } else if (addr-lastAddr <= neighborhood) && lastFrames != nil { + frames = lastFrames + } + + sp.addStack(addr, frames) } } +} - // Print each file associated with this function. - for _, n := range sourceFiles { - ff := fileFunction{n.Info.File, n.Info.Name} - fns := fileNodes[ff] +func (sp *sourcePrinter) addStack(addr uint64, frames []plugin.Frame) { + // See if the stack contains a function we are interested in. + for i, f := range frames { + if !sp.interest[f.Func] { + continue + } - asm := assemblyPerSourceLine(symbols, fns, ff.fileName, obj, o.IntelSyntax) - start, end := sourceCoordinates(asm) + // Record sub-stack under frame's file/line. + fname := canonicalizeFileName(f.File) + file := sp.files[fname] + if file == nil { + file = &sourceFile{ + fname: fname, + lines: map[int][]sourceInst{}, + funcName: map[int]string{}, + } + sp.files[fname] = file + } + callees := frames[:i] + stack := make([]callID, 0, len(callees)) + for j := len(callees) - 1; j >= 0; j-- { // Reverse so caller is first + stack = append(stack, callID{ + file: callees[j].File, + line: callees[j].Line, + }) + } + file.lines[f.Line] = append(file.lines[f.Line], sourceInst{addr, stack}) - fnodes, path, err := getSourceFromFile(ff.fileName, reader, fns, start, end) - if err != nil { - fnodes, path = getMissingFunctionSource(ff.fileName, asm, start, end) + // Remember the first function name encountered per source line + // and assume that that line belongs to that function. + if _, ok := file.funcName[f.Line]; !ok { + file.funcName[f.Line] = f.Func } + } +} + +// synthAsm is the special disassembler value used for instructions without an object file. +const synthAsm = "" - printFunctionHeader(w, ff.functionName, path, n.Flat, n.Cum, rpt) - for _, fn := range fnodes { - printFunctionSourceLine(w, fn, asm[fn.Info.Lineno], reader, rpt) +// handleUnprocessed handles addresses that were skipped by splitIntoRanges because they +// did not belong to a known object file. +func (sp *sourcePrinter) handleUnprocessed(addrs map[uint64]addrInfo, unprocessed []uint64) { + // makeFrames synthesizes a []plugin.Frame list for the specified address. + // The result will typically have length 1, but may be longer if address corresponds + // to inlined calls. + makeFrames := func(addr uint64) []plugin.Frame { + loc := addrs[addr].loc + stack := make([]plugin.Frame, 0, len(loc.Line)) + for _, line := range loc.Line { + fn := line.Function + if fn == nil { + continue + } + stack = append(stack, plugin.Frame{ + Func: fn.Name, + File: fn.Filename, + Line: int(line.Line), + }) } - printFunctionClosing(w) + return stack + } + + for _, addr := range unprocessed { + frames := makeFrames(addr) + x := instructionInfo{ + objAddr: addr, + length: 1, + disasm: synthAsm, + } + if len(frames) > 0 { + x.file = frames[0].File + x.line = frames[0].Line + } + sp.insts[addr] = x + + sp.addStack(addr, frames) } - return nil } -// sourceCoordinates returns the lowest and highest line numbers from -// a set of assembly statements. -func sourceCoordinates(asm map[int][]assemblyInstruction) (start, end int) { - for l := range asm { - if start == 0 || l < start { - start = l +// splitIntoRanges converts the set of addresses we are interested in into a set of address +// ranges to disassemble. It also returns the set of addresses found that did not have an +// associated object file and were therefore not added to an address range. +func (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, addrMap map[uint64]addrInfo, flat map[uint64]int64) ([]addressRange, []uint64) { + // Partition addresses into two sets: ones with a known object file, and ones without. + var addrs, unprocessed []uint64 + for addr, info := range addrMap { + if info.obj != nil { + addrs = append(addrs, addr) + } else { + unprocessed = append(unprocessed, addr) + } + } + sort.Slice(addrs, func(i, j int) bool { return addrs[i] < addrs[j] }) + + const expand = 500 // How much to expand range to pick up nearby addresses. + var result []addressRange + for i, n := 0, len(addrs); i < n; { + begin, end := addrs[i], addrs[i] + sum := flat[begin] + i++ + + info := addrMap[begin] + m := info.loc.Mapping + obj := info.obj // Non-nil because of the partitioning done above. + + // Find following addresses that are close enough to addrs[i]. + for i < n && addrs[i] <= end+2*expand && addrs[i] < m.Limit { + // When we expand ranges by "expand" on either side, the ranges + // for addrs[i] and addrs[i-1] will merge. + end = addrs[i] + sum += flat[end] + i++ } - if end == 0 || l > end { - end = l + if m.Start-begin >= expand { + begin -= expand + } else { + begin = m.Start + } + if m.Limit-end >= expand { + end += expand + } else { + end = m.Limit } + + result = append(result, addressRange{begin, end, obj, m, sum}) } - return start, end + return result, unprocessed } -// assemblyPerSourceLine disassembles the binary containing a symbol -// and classifies the assembly instructions according to its -// corresponding source line, annotating them with a set of samples. -func assemblyPerSourceLine(objSyms []*objSymbol, rs graph.Nodes, src string, obj plugin.ObjTool, intelSyntax bool) map[int][]assemblyInstruction { - assembly := make(map[int][]assemblyInstruction) - // Identify symbol to use for this collection of samples. - o := findMatchingSymbol(objSyms, rs) - if o == nil { - return assembly +func (sp *sourcePrinter) initSamples(flat, cum map[uint64]int64) { + for addr, inst := range sp.insts { + // Move all samples that were assigned to the middle of an instruction to the + // beginning of that instruction. This takes care of samples that were recorded + // against pc+1. + instEnd := addr + uint64(inst.length) + for p := addr; p < instEnd; p++ { + inst.flat += flat[p] + inst.cum += cum[p] + } + sp.insts[addr] = inst } +} - // Extract assembly for matched symbol - insts, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End, intelSyntax) - if err != nil { - return assembly - } - - srcBase := filepath.Base(src) - anodes := annotateAssembly(insts, rs, o.base) - var lineno = 0 - var prevline = 0 - for _, an := range anodes { - // Do not rely solely on the line number produced by Disasm - // since it is not what we want in the presence of inlining. - // - // E.g., suppose we are printing source code for F and this - // instruction is from H where F called G called H and both - // of those calls were inlined. We want to use the line - // number from F, not from H (which is what Disasm gives us). - // - // So find the outer-most linenumber in the source file. - found := false - if frames, err := o.file.SourceLine(an.address + o.base); err == nil { - for i := len(frames) - 1; i >= 0; i-- { - if filepath.Base(frames[i].File) == srcBase { - for j := i - 1; j >= 0; j-- { - an.inlineCalls = append(an.inlineCalls, callID{frames[j].File, frames[j].Line}) - } - lineno = frames[i].Line - found = true - break +func (sp *sourcePrinter) print(w io.Writer, maxFiles int, rpt *Report) { + // Finalize per-file counts. + for _, file := range sp.files { + seen := map[uint64]bool{} + for _, line := range file.lines { + for _, x := range line { + if seen[x.addr] { + // Same address can be displayed multiple times in a file + // (e.g., if we show multiple inlined functions). + // Avoid double-counting samples in this case. + continue } + seen[x.addr] = true + inst := sp.insts[x.addr] + file.cum += inst.cum + file.flat += inst.flat } } - if !found && filepath.Base(an.file) == srcBase { - lineno = an.line + } + + // Get sorted list of files to print. + var files []*sourceFile + for _, f := range sp.files { + files = append(files, f) + } + order := func(i, j int) bool { return files[i].flat > files[j].flat } + if maxFiles < 0 { + // Order by name for compatibility with old code. + order = func(i, j int) bool { return files[i].fname < files[j].fname } + maxFiles = len(files) + } + sort.Slice(files, order) + for i, f := range files { + if i < maxFiles { + sp.printFile(w, f, rpt) + } + } +} + +func (sp *sourcePrinter) printFile(w io.Writer, f *sourceFile, rpt *Report) { + for _, fn := range sp.functions(f) { + if fn.cum == 0 { + continue } + printFunctionHeader(w, fn.name, f.fname, fn.flat, fn.cum, rpt) + var asm []assemblyInstruction + for l := fn.begin; l < fn.end; l++ { + lineContents, ok := sp.reader.line(f.fname, l) + if !ok { + if len(f.lines[l]) == 0 { + // Outside of range of valid lines and nothing to print. + continue + } + if l == 0 { + // Line number 0 shows up if line number is not known. + lineContents = "" + } else { + // Past end of file, but have data to print. + lineContents = "???" + } + } - if lineno != 0 { - if lineno != prevline { - // This instruction starts a new block - // of contiguous instructions on this line. - an.startsBlock = true + // Make list of assembly instructions. + asm = asm[:0] + var flatSum, cumSum int64 + var lastAddr uint64 + for _, inst := range f.lines[l] { + addr := inst.addr + x := sp.insts[addr] + flatSum += x.flat + cumSum += x.cum + startsBlock := (addr != lastAddr+uint64(sp.insts[lastAddr].length)) + lastAddr = addr + + // divisors already applied, so leave flatDiv,cumDiv as 0 + asm = append(asm, assemblyInstruction{ + address: x.objAddr, + instruction: x.disasm, + function: fn.name, + file: x.file, + line: x.line, + flat: x.flat, + cum: x.cum, + startsBlock: startsBlock, + inlineCalls: inst.stack, + }) } - prevline = lineno - assembly[lineno] = append(assembly[lineno], an) + + printFunctionSourceLine(w, l, flatSum, cumSum, lineContents, asm, sp.reader, rpt) } + printFunctionClosing(w) } - - return assembly } -// findMatchingSymbol looks for the symbol that corresponds to a set -// of samples, by comparing their addresses. -func findMatchingSymbol(objSyms []*objSymbol, ns graph.Nodes) *objSymbol { - for _, n := range ns { - for _, o := range objSyms { - if filepath.Base(o.sym.File) == filepath.Base(n.Info.Objfile) && - o.sym.Start <= n.Info.Address-o.base && - n.Info.Address-o.base <= o.sym.End { - return o +// functions splits apart the lines to show in a file into a list of per-function ranges. +func (sp *sourcePrinter) functions(f *sourceFile) []sourceFunction { + var funcs []sourceFunction + + // Get interesting lines in sorted order. + lines := make([]int, 0, len(f.lines)) + for l := range f.lines { + lines = append(lines, l) + } + sort.Ints(lines) + + // Merge adjacent lines that are in same function and not too far apart. + const mergeLimit = 20 + for _, l := range lines { + name := f.funcName[l] + if pretty, ok := sp.prettyNames[name]; ok { + // Use demangled name if available. + name = pretty + } + + fn := sourceFunction{name: name, begin: l, end: l + 1} + for _, x := range f.lines[l] { + inst := sp.insts[x.addr] + fn.flat += inst.flat + fn.cum += inst.cum + } + + // See if we should merge into preceding function. + if len(funcs) > 0 { + last := funcs[len(funcs)-1] + if l-last.end < mergeLimit && last.name == name { + last.end = l + 1 + last.flat += fn.flat + last.cum += fn.cum + funcs[len(funcs)-1] = last + continue } } + + // Add new function. + funcs = append(funcs, fn) } - return nil + + // Expand function boundaries to show neighborhood. + const expand = 5 + for i, f := range funcs { + if i == 0 { + // Extend backwards, stopping at line number 1, but do not disturb 0 + // since that is a special line number that can show up when addr2line + // cannot determine the real line number. + if f.begin > expand { + f.begin -= expand + } else if f.begin > 1 { + f.begin = 1 + } + } else { + // Find gap from predecessor and divide between predecessor and f. + halfGap := (f.begin - funcs[i-1].end) / 2 + if halfGap > expand { + halfGap = expand + } + funcs[i-1].end += halfGap + f.begin -= halfGap + } + funcs[i] = f + } + + // Also extend the ending point of the last function. + if len(funcs) > 0 { + funcs[len(funcs)-1].end += expand + } + + return funcs +} + +// objectFile return the object for the specified mapping, opening it if necessary. +// It returns nil on error. +func (sp *sourcePrinter) objectFile(m *profile.Mapping) plugin.ObjFile { + if m == nil { + return nil + } + if object, ok := sp.objects[m.File]; ok { + return object // May be nil if we detected an error earlier. + } + object, err := sp.objectTool.Open(m.File, m.Start, m.Limit, m.Offset) + if err != nil { + object = nil + } + sp.objects[m.File] = object // Cache even on error. + return object } // printHeader prints the page header for a weblist report. @@ -348,22 +780,39 @@ func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, } // printFunctionSourceLine prints a source line and the corresponding assembly. -func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) { +func printFunctionSourceLine(w io.Writer, lineNo int, flat, cum int64, lineContents string, + assembly []assemblyInstruction, reader *sourceReader, rpt *Report) { if len(assembly) == 0 { fmt.Fprintf(w, " %6d %10s %10s %8s %s \n", - fn.Info.Lineno, - valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), - "", template.HTMLEscapeString(fn.Info.Name)) + lineNo, + valueOrDot(flat, rpt), valueOrDot(cum, rpt), + "", template.HTMLEscapeString(lineContents)) return } + nestedInfo := false + cl := "deadsrc" + for _, an := range assembly { + if len(an.inlineCalls) > 0 || an.instruction != synthAsm { + nestedInfo = true + cl = "livesrc" + } + } + fmt.Fprintf(w, - " %6d %10s %10s %8s %s ", - fn.Info.Lineno, - valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), - "", template.HTMLEscapeString(fn.Info.Name)) - srcIndent := indentation(fn.Info.Name) + " %6d %10s %10s %8s %s ", + lineNo, cl, + valueOrDot(flat, rpt), valueOrDot(cum, rpt), + "", template.HTMLEscapeString(lineContents)) + if nestedInfo { + srcIndent := indentation(lineContents) + printNested(w, srcIndent, assembly, reader, rpt) + } + fmt.Fprintln(w) +} + +func printNested(w io.Writer, srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) { fmt.Fprint(w, "") var curCalls []callID for i, an := range assembly { @@ -374,15 +823,9 @@ func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyIns var fileline string if an.file != "" { - fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.file), an.line) + fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(filepath.Base(an.file)), an.line) } flat, cum := an.flat, an.cum - if an.flatDiv != 0 { - flat = flat / an.flatDiv - } - if an.cumDiv != 0 { - cum = cum / an.cumDiv - } // Print inlined call context. for j, c := range an.inlineCalls { @@ -398,17 +841,23 @@ func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyIns text := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline) fmt.Fprintf(w, " %8s %10s %10s %8s %s %s:%d\n", "", "", "", "", - template.HTMLEscapeString(fmt.Sprintf("%-80s", text)), + template.HTMLEscapeString(rightPad(text, 80)), template.HTMLEscapeString(filepath.Base(c.file)), c.line) } curCalls = an.inlineCalls + if an.instruction == synthAsm { + continue + } text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction fmt.Fprintf(w, " %8s %10s %10s %8x: %s %s\n", "", valueOrDot(flat, rpt), valueOrDot(cum, rpt), an.address, - template.HTMLEscapeString(fmt.Sprintf("%-80s", text)), - template.HTMLEscapeString(fileline)) + template.HTMLEscapeString(rightPad(text, 80)), + // fileline should not be escaped since it was formed by appending + // line number (just digits) to an escaped file name. Escaping here + // would cause double-escaping of file name. + fileline) } - fmt.Fprintln(w, "") + fmt.Fprint(w, "") } // printFunctionClosing prints the end of a function in a weblist report. @@ -482,36 +931,6 @@ func getSourceFromFile(file string, reader *sourceReader, fns graph.Nodes, start return src, file, nil } -// getMissingFunctionSource creates a dummy function body to point to -// the source file and annotates it with the samples in asm. -func getMissingFunctionSource(filename string, asm map[int][]assemblyInstruction, start, end int) (graph.Nodes, string) { - var fnodes graph.Nodes - for i := start; i <= end; i++ { - insts := asm[i] - if len(insts) == 0 { - continue - } - var group assemblyInstruction - for _, insn := range insts { - group.flat += insn.flat - group.cum += insn.cum - group.flatDiv += insn.flatDiv - group.cumDiv += insn.cumDiv - } - flat := group.flatValue() - cum := group.cumValue() - fnodes = append(fnodes, &graph.Node{ - Info: graph.NodeInfo{ - Name: "???", - Lineno: i, - }, - Flat: flat, - Cum: cum, - }) - } - return fnodes, filename -} - // sourceReader provides access to source code with caching of file contents. type sourceReader struct { // searchPath is a filepath.ListSeparator-separated list of directories where @@ -543,6 +962,7 @@ func (reader *sourceReader) fileError(path string) error { return reader.errors[path] } +// line returns the line numbered "lineno" in path, or _,false if lineno is out of range. func (reader *sourceReader) line(path string, lineno int) (string, bool) { lines, ok := reader.files[path] if !ok { @@ -651,3 +1071,37 @@ func indentation(line string) int { } return column } + +// rightPad pads the input with spaces on the right-hand-side to make it have +// at least width n. It treats tabs as enough spaces that lead to the next +// 8-aligned tab-stop. +func rightPad(s string, n int) string { + var str strings.Builder + + // Convert tabs to spaces as we go so padding works regardless of what prefix + // is placed before the result. + column := 0 + for _, c := range s { + column++ + if c == '\t' { + str.WriteRune(' ') + for column%8 != 0 { + column++ + str.WriteRune(' ') + } + } else { + str.WriteRune(c) + } + } + for column < n { + column++ + str.WriteRune(' ') + } + return str.String() +} + +func canonicalizeFileName(fname string) string { + fname = strings.TrimPrefix(fname, "/proc/self/cwd/") + fname = strings.TrimPrefix(fname, "./") + return filepath.Clean(fname) +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go b/src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go index 02a6d772487108f1b959e92a634c5b6352e3f08d..17c9f6eb947c512a0a8f253cfa00f4b1eed94512 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go @@ -25,12 +25,11 @@ func AddSourceTemplates(t *template.Template) { } const weblistPageCSS = `